Release 1.7.1: download-only updates and remove netsh/startup network probes for AV FPs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -208,13 +208,17 @@ https://evilfox.win/
|
|||||||
|
|
||||||
## Антивирусы (ложные срабатывания)
|
## Антивирусы (ложные срабатывания)
|
||||||
|
|
||||||
Некоторые AV (Kaspersky, Microsoft Wacatac.B!ml, Ikarus Trojan.WinGo.Agent и др.) часто помечают **любые** Go-бинарники с сетью и сменой системного прокси.
|
Некоторые AV (Bkav, Microsoft Wacapew/Wacatac, Ikarus Trojan.WinGo.Agent, Google, Trapmine) часто помечают **любые** неподписанные Go-бинарники с сетью и сменой системного прокси.
|
||||||
|
|
||||||
В 1.7.0 уменьшили «подозрительные» паттерны:
|
В 1.7.1 дополнительно:
|
||||||
- нет скрытой загрузки cores при старте;
|
- обновление только скачивается в `Downloads` (без самозамены exe);
|
||||||
- нет `.bat`-самозамены с `del /f` + hidden detached `cmd`;
|
- нет автопроверки обновлений и тихой загрузки cores при старте;
|
||||||
- обновление через `Navis-pending.exe` + явный флаг;
|
- нет вызова `netsh winhttp`;
|
||||||
- Windows application manifest (`asInvoker`);
|
- Windows manifest + metadata EvilFox.
|
||||||
- metadata компании EvilFox / Product Navis.
|
|
||||||
|
|
||||||
Если Defender/KAV ругается: добавьте `Navis.exe` в исключения или отправьте файл как False Positive вендору. Надёжнее всего — подпись Authenticode (code signing certificate).
|
Если всё ещё ругается — отправьте файл как False Positive:
|
||||||
|
- Microsoft: https://www.microsoft.com/wdsi/filesubmission
|
||||||
|
- Google: https://safebrowsing.google.com/safebrowsing/report_error/
|
||||||
|
- VirusTotal community vote: «False positive»
|
||||||
|
|
||||||
|
Полностью убрать ML-детекты обычно можно только с **Authenticode** (code signing).
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
<assemblyIdentity
|
<assemblyIdentity
|
||||||
version="1.7.0.0"
|
version="1.7.1.0"
|
||||||
processorArchitecture="*"
|
processorArchitecture="*"
|
||||||
name="EvilFox.Navis"
|
name="EvilFox.Navis"
|
||||||
type="win32"/>
|
type="win32"/>
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ type uiState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if update.MaybeFinishUpdate(os.Args[1:]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
||||||
cfgPath, err := config.LocateConfig()
|
cfgPath, err := config.LocateConfig()
|
||||||
@@ -138,8 +134,6 @@ func main() {
|
|||||||
mustBind(w, "saveHy2", a.saveHy2)
|
mustBind(w, "saveHy2", a.saveHy2)
|
||||||
mustBind(w, "importSubscription", a.importSubscription)
|
mustBind(w, "importSubscription", a.importSubscription)
|
||||||
|
|
||||||
go a.autoCheckUpdate()
|
|
||||||
|
|
||||||
w.SetHtml(appui.IndexHTML)
|
w.SetHtml(appui.IndexHTML)
|
||||||
w.Run()
|
w.Run()
|
||||||
}
|
}
|
||||||
@@ -329,33 +323,10 @@ func (a *app) checkUpdate() (update.Status, error) {
|
|||||||
return st, nil
|
return st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) applyUpdate() error {
|
func (a *app) applyUpdate() (string, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
latest, err := update.Apply(ctx, update.DefaultManifestURL)
|
return update.Apply(ctx, update.DefaultManifestURL)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = a.mgr.Disconnect()
|
|
||||||
go func() {
|
|
||||||
time.Sleep(400 * time.Millisecond)
|
|
||||||
if a.webview != nil {
|
|
||||||
a.webview.Terminate()
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}()
|
|
||||||
_ = latest
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) autoCheckUpdate() {
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
_, _ = a.checkUpdate()
|
|
||||||
ticker := time.NewTicker(6 * time.Hour)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
_, _ = a.checkUpdate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func openURL(raw string) error {
|
func openURL(raw string) error {
|
||||||
|
|||||||
@@ -287,11 +287,11 @@ func runApplyUpdate(args []string) int {
|
|||||||
_ = args
|
_ = args
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
latest, err := update.Apply(ctx, update.DefaultManifestURL)
|
msg, err := update.Apply(ctx, update.DefaultManifestURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "apply-update: %v\n", err)
|
fmt.Fprintf(os.Stderr, "apply-update: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
fmt.Printf("updating to %s — exit the app to finish\n", latest)
|
fmt.Println(msg)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
+10
-10
@@ -1,38 +1,38 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.0",
|
"version": "1.7.1",
|
||||||
"notes": "Меньше ложных срабатываний AV: без bat-апдейтера и тихой загрузки cores; Windows manifest; metadata EvilFox",
|
"notes": "AV: обновление только в Downloads без самозамены; без netsh и автопроверки при старте",
|
||||||
"platform": "windows-amd64",
|
"platform": "windows-amd64",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
||||||
"sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d",
|
"sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b",
|
||||||
"mandatory": false,
|
"mandatory": false,
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"windows-amd64": {
|
"windows-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
||||||
"sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d",
|
"sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64"
|
"arch": "amd64"
|
||||||
},
|
},
|
||||||
"darwin-arm64": {
|
"darwin-arm64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
||||||
"sha256": "3e544461c1ce8ec3b508639cb549a759b773da38517c98c9ca634faaf09a601c",
|
"sha256": "3cb42ab226b0263c8b6566888d80c6e7a6982d8cda992d112989c7f5c8a99bab",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "arm64",
|
"arch": "arm64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
||||||
"dmg_sha256": "39e88c985867ce6ef37deb00d87e1c6669cfbc8b9f8f0589d11a3f32036a5258",
|
"dmg_sha256": "65b8fe8c0d294ee923d59125f3e39c37f7c8a201aa6fd4661d080da59928c2e4",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
||||||
"zip_sha256": "213fd7563e068f60a1ae2504c6e48171b9f9e3f2fe3c14ecad75ed809fa05d06"
|
"zip_sha256": "6685b1f8be9f8aa87613f35b8f8a86a00a6fb667c2f7bdc7116e6908c0cb1513"
|
||||||
},
|
},
|
||||||
"darwin-amd64": {
|
"darwin-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
||||||
"sha256": "f40a711108e78bfa1a944a1d4946549aba32cb5e00ef9035be352810c700807c",
|
"sha256": "7bffda217e178224f3a68fed4d5fff4e37b4705b7c8291b84f1d58151ef43b9d",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
|
||||||
"dmg_sha256": "c70fe7feb000eec34c0fe4ef2d05565dd0285c0c4f18a264ecad540d2c0878db",
|
"dmg_sha256": "959526963936ad22d3c3607fe18295018362ec0d5fedcdf7561edf5d71c3b827",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
|
||||||
"zip_sha256": "5b3e3fea5aef4ac962c18a190c1943c7cfd5357ea233abbdec1d26007e1e6eb4"
|
"zip_sha256": "079c2af3fe5de6b9e73bad15a15bddfc93970d27be68b6620b5be3729163fc15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+10
-10
@@ -1,38 +1,38 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.0",
|
"version": "1.7.1",
|
||||||
"notes": "Меньше ложных срабатываний AV: без bat-апдейтера и тихой загрузки cores; Windows manifest; metadata EvilFox",
|
"notes": "AV: обновление только в Downloads без самозамены; без netsh и автопроверки при старте",
|
||||||
"platform": "windows-amd64",
|
"platform": "windows-amd64",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
||||||
"sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d",
|
"sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b",
|
||||||
"mandatory": false,
|
"mandatory": false,
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"windows-amd64": {
|
"windows-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
||||||
"sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d",
|
"sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64"
|
"arch": "amd64"
|
||||||
},
|
},
|
||||||
"darwin-arm64": {
|
"darwin-arm64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
||||||
"sha256": "3e544461c1ce8ec3b508639cb549a759b773da38517c98c9ca634faaf09a601c",
|
"sha256": "3cb42ab226b0263c8b6566888d80c6e7a6982d8cda992d112989c7f5c8a99bab",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "arm64",
|
"arch": "arm64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
||||||
"dmg_sha256": "39e88c985867ce6ef37deb00d87e1c6669cfbc8b9f8f0589d11a3f32036a5258",
|
"dmg_sha256": "65b8fe8c0d294ee923d59125f3e39c37f7c8a201aa6fd4661d080da59928c2e4",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
||||||
"zip_sha256": "213fd7563e068f60a1ae2504c6e48171b9f9e3f2fe3c14ecad75ed809fa05d06"
|
"zip_sha256": "6685b1f8be9f8aa87613f35b8f8a86a00a6fb667c2f7bdc7116e6908c0cb1513"
|
||||||
},
|
},
|
||||||
"darwin-amd64": {
|
"darwin-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
||||||
"sha256": "f40a711108e78bfa1a944a1d4946549aba32cb5e00ef9035be352810c700807c",
|
"sha256": "7bffda217e178224f3a68fed4d5fff4e37b4705b7c8291b84f1d58151ef43b9d",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
|
||||||
"dmg_sha256": "c70fe7feb000eec34c0fe4ef2d05565dd0285c0c4f18a264ecad540d2c0878db",
|
"dmg_sha256": "959526963936ad22d3c3607fe18295018362ec0d5fedcdf7561edf5d71c3b827",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
|
||||||
"zip_sha256": "5b3e3fea5aef4ac962c18a190c1943c7cfd5357ea233abbdec1d26007e1e6eb4"
|
"zip_sha256": "079c2af3fe5de6b9e73bad15a15bddfc93970d27be68b6620b5be3729163fc15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -842,8 +842,8 @@
|
|||||||
updateBtn.addEventListener("click", () => withBusy(async () => {
|
updateBtn.addEventListener("click", () => withBusy(async () => {
|
||||||
try {
|
try {
|
||||||
setMeta("Скачивание обновления…");
|
setMeta("Скачивание обновления…");
|
||||||
await applyUpdate();
|
const msg = await applyUpdate();
|
||||||
setMeta("Обновление установлено, перезапуск…", "ok");
|
setMeta(String(msg || "Готово"), "ok");
|
||||||
} catch (e) { setMeta(String(e), "err"); }
|
} catch (e) { setMeta(String(e), "err"); }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ package sysproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
@@ -87,7 +85,7 @@ func (c *windowsController) Enable(httpHostPort string) error {
|
|||||||
if err := notifyInternetSettingsChanged(); err != nil {
|
if err := notifyInternetSettingsChanged(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_ = setWinHTTPProxy(httpHostPort)
|
// Skip netsh winhttp — spawning netsh is a common AV heuristic; WinINET covers browsers.
|
||||||
|
|
||||||
c.enabled = true
|
c.enabled = true
|
||||||
return nil
|
return nil
|
||||||
@@ -120,7 +118,6 @@ func (c *windowsController) Disable() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = notifyInternetSettingsChanged()
|
_ = notifyInternetSettingsChanged()
|
||||||
_ = resetWinHTTPProxy()
|
|
||||||
|
|
||||||
c.enabled = false
|
c.enabled = false
|
||||||
return nil
|
return nil
|
||||||
@@ -139,18 +136,3 @@ func notifyInternetSettingsChanged() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setWinHTTPProxy(httpHostPort string) error {
|
|
||||||
cmd := exec.Command("netsh", "winhttp", "set", "proxy", httpHostPort,
|
|
||||||
"bypass-list=localhost;127.*;10.*;192.168.*;<local>")
|
|
||||||
const createNoWindow = 0x08000000
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow}
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetWinHTTPProxy() error {
|
|
||||||
cmd := exec.Command("netsh", "winhttp", "reset", "proxy")
|
|
||||||
const createNoWindow = 0x08000000
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow}
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -17,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CurrentVersion is the shipped client version.
|
// CurrentVersion is the shipped client version.
|
||||||
const CurrentVersion = "1.7.0"
|
const CurrentVersion = "1.7.1"
|
||||||
|
|
||||||
// DefaultManifestURL is the update feed (hosted in the project git repo).
|
// DefaultManifestURL is the update feed (hosted in the project git repo).
|
||||||
const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json"
|
const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json"
|
||||||
@@ -226,51 +225,3 @@ func fileSHA256(path string) (string, error) {
|
|||||||
}
|
}
|
||||||
return hex.EncodeToString(h.Sum(nil)), nil
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha, exePath, tmpPath string, err error) {
|
|
||||||
st, err := Check(ctx, manifestURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", "", "", err
|
|
||||||
}
|
|
||||||
if !st.Available {
|
|
||||||
if st.Error != "" {
|
|
||||||
return "", "", "", "", "", fmt.Errorf("%s", st.Error)
|
|
||||||
}
|
|
||||||
return "", "", "", "", "", fmt.Errorf("обновление не найдено")
|
|
||||||
}
|
|
||||||
if !allowedDownloadURL(st.URL) {
|
|
||||||
return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk")
|
|
||||||
}
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", "", "", err
|
|
||||||
}
|
|
||||||
exe, err = filepath.Abs(exe)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", "", "", err
|
|
||||||
}
|
|
||||||
tmp := exe + ".new"
|
|
||||||
if err := downloadFile(ctx, st.URL, tmp); err != nil {
|
|
||||||
return "", "", "", "", "", err
|
|
||||||
}
|
|
||||||
wantSHA := st.SHA256
|
|
||||||
if wantSHA == "" {
|
|
||||||
if man, err := fetchManifest(ctx, manifestURL); err == nil {
|
|
||||||
if _, sha2, ok := man.AssetFor(PlatformKey()); ok {
|
|
||||||
wantSHA = sha2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if wantSHA != "" {
|
|
||||||
sum, err := fileSHA256(tmp)
|
|
||||||
if err != nil {
|
|
||||||
_ = os.Remove(tmp)
|
|
||||||
return "", "", "", "", "", err
|
|
||||||
}
|
|
||||||
if !strings.EqualFold(sum, wantSHA) {
|
|
||||||
_ = os.Remove(tmp)
|
|
||||||
return "", "", "", "", "", fmt.Errorf("checksum mismatch")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return st.Latest, st.URL, wantSHA, exe, tmp, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,43 +8,88 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"runtime"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apply downloads the new binary and schedules replacement after exit (macOS).
|
// Apply downloads the new binary into ~/Downloads (no silent self-replace).
|
||||||
func Apply(ctx context.Context, manifestURL string) (string, error) {
|
func Apply(ctx context.Context, manifestURL string) (string, error) {
|
||||||
latest, _, _, exe, tmp, err := prepareDownload(ctx, manifestURL)
|
st, err := Check(ctx, manifestURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
_ = os.Chmod(tmp, 0o755)
|
if !st.Available {
|
||||||
dir := filepath.Dir(exe)
|
if st.Error != "" {
|
||||||
scriptPath := filepath.Join(dir, "navis-update.sh")
|
return "", fmt.Errorf("%s", st.Error)
|
||||||
pid := os.Getpid()
|
}
|
||||||
|
return "", fmt.Errorf("обновление не найдено")
|
||||||
|
}
|
||||||
|
if !allowedDownloadURL(st.URL) {
|
||||||
|
return "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk")
|
||||||
|
}
|
||||||
|
|
||||||
script := "#!/bin/bash\n" +
|
home, err := os.UserHomeDir()
|
||||||
"set -e\n" +
|
if err != nil {
|
||||||
"EXE=" + shellQuote(exe) + "\n" +
|
|
||||||
"NEW=" + shellQuote(tmp) + "\n" +
|
|
||||||
"PID=" + strconv.Itoa(pid) + "\n" +
|
|
||||||
"while kill -0 \"$PID\" 2>/dev/null; do sleep 0.4; done\n" +
|
|
||||||
"sleep 0.5\n" +
|
|
||||||
"mv -f \"$NEW\" \"$EXE\"\n" +
|
|
||||||
"chmod +x \"$EXE\"\n" +
|
|
||||||
"rm -f " + shellQuote(scriptPath) + "\n"
|
|
||||||
|
|
||||||
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
cmd := exec.Command("/bin/bash", scriptPath)
|
dir := filepath.Join(home, "Downloads")
|
||||||
cmd.Dir = dir
|
_ = os.MkdirAll(dir, 0o755)
|
||||||
if err := cmd.Start(); err != nil {
|
dest := filepath.Join(dir, fmt.Sprintf("Navis-%s-darwin-%s", sanitizeDarwinVer(st.Latest), runtime.GOARCH))
|
||||||
return "", fmt.Errorf("start updater: %w", err)
|
tmp := dest + ".part"
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
_ = os.Remove(dest)
|
||||||
|
|
||||||
|
if err := downloadFile(ctx, st.URL, tmp); err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
return latest, nil
|
if st.SHA256 != "" {
|
||||||
|
sum, err := fileSHA256(tmp)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if sum != "" && !eqFold(sum, st.SHA256) {
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
return "", fmt.Errorf("checksum mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.Rename(tmp, dest); err != nil {
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_ = os.Chmod(dest, 0o755)
|
||||||
|
_ = exec.Command("open", "-R", dest).Start()
|
||||||
|
return fmt.Sprintf("Скачано v%s:\n%s\nЗакройте Navis и запустите новый файл.", st.Latest, dest), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shellQuote(s string) string {
|
func sanitizeDarwinVer(v string) string {
|
||||||
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
out := make([]rune, 0, len(v))
|
||||||
|
for _, r := range v {
|
||||||
|
switch {
|
||||||
|
case r >= '0' && r <= '9', r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r == '.', r == '-':
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return "update"
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eqFold(a, b string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(a); i++ {
|
||||||
|
ca, cb := a[i], b[i]
|
||||||
|
if ca >= 'A' && ca <= 'F' {
|
||||||
|
ca += 'a' - 'A'
|
||||||
|
}
|
||||||
|
if cb >= 'A' && cb <= 'F' {
|
||||||
|
cb += 'a' - 'A'
|
||||||
|
}
|
||||||
|
if ca != cb {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package update
|
|
||||||
|
|
||||||
// MaybeFinishUpdate is Windows-only.
|
|
||||||
func MaybeFinishUpdate(args []string) bool {
|
|
||||||
_ = args
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -5,116 +5,113 @@ package update
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const finishUpdateFlag = "--navis-finish-update"
|
// Apply downloads the new build into the user Downloads folder.
|
||||||
|
// It does NOT replace the running exe or spawn a self-updater (AV false-positive pattern).
|
||||||
// Apply downloads the new exe and starts it to replace the running binary after exit.
|
|
||||||
// Avoids writing .bat self-replacer scripts (common AV false-positive pattern).
|
|
||||||
func Apply(ctx context.Context, manifestURL string) (string, error) {
|
func Apply(ctx context.Context, manifestURL string) (string, error) {
|
||||||
latest, _, _, exe, tmp, err := prepareDownload(ctx, manifestURL)
|
st, err := Check(ctx, manifestURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
pending := filepath.Join(filepath.Dir(exe), "Navis-pending.exe")
|
if !st.Available {
|
||||||
_ = os.Remove(pending)
|
if st.Error != "" {
|
||||||
if err := os.Rename(tmp, pending); err != nil {
|
return "", fmt.Errorf("%s", st.Error)
|
||||||
if err2 := copyFile(tmp, pending); err2 != nil {
|
|
||||||
_ = os.Remove(tmp)
|
|
||||||
return "", err2
|
|
||||||
}
|
}
|
||||||
_ = os.Remove(tmp)
|
return "", fmt.Errorf("обновление не найдено")
|
||||||
}
|
}
|
||||||
_ = os.Chmod(pending, 0o755)
|
if !allowedDownloadURL(st.URL) {
|
||||||
|
return "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk")
|
||||||
cmd := exec.Command(pending, finishUpdateFlag, strconv.Itoa(os.Getpid()), exe)
|
|
||||||
cmd.Dir = filepath.Dir(exe)
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false}
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return "", fmt.Errorf("start updater: %w", err)
|
|
||||||
}
|
|
||||||
return latest, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaybeFinishUpdate handles: Navis-pending.exe --navis-finish-update <pid> <targetExe>
|
dir, err := downloadsDir()
|
||||||
// Returns true if this process was the updater and should exit.
|
if err != nil {
|
||||||
func MaybeFinishUpdate(args []string) bool {
|
return "", err
|
||||||
if len(args) < 3 || args[0] != finishUpdateFlag {
|
}
|
||||||
|
_ = os.MkdirAll(dir, 0o755)
|
||||||
|
dest := filepath.Join(dir, fmt.Sprintf("Navis-%s-windows-amd64.exe", sanitizeFileVer(st.Latest)))
|
||||||
|
tmp := dest + ".part"
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
_ = os.Remove(dest)
|
||||||
|
|
||||||
|
if err := downloadFile(ctx, st.URL, tmp); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if st.SHA256 != "" {
|
||||||
|
sum, err := fileSHA256(tmp)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !equalFoldHex(sum, st.SHA256) {
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
return "", fmt.Errorf("checksum mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.Rename(tmp, dest); err != nil {
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = revealInExplorer(dest)
|
||||||
|
return fmt.Sprintf("Скачано v%s:\n%s\nЗакройте Navis и запустите новый файл.", st.Latest, dest), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadsDir() (string, error) {
|
||||||
|
if u := os.Getenv("USERPROFILE"); u != "" {
|
||||||
|
return filepath.Join(u, "Downloads"), nil
|
||||||
|
}
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(home, "Downloads"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func revealInExplorer(path string) error {
|
||||||
|
path, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := exec.Command("explorer.exe", "/select,", path)
|
||||||
|
const createNoWindow = 0x08000000
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow}
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeFileVer(v string) string {
|
||||||
|
out := make([]rune, 0, len(v))
|
||||||
|
for _, r := range v {
|
||||||
|
switch {
|
||||||
|
case r >= '0' && r <= '9', r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r == '.', r == '-':
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return "update"
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func equalFoldHex(a, b string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
pid, err := strconv.Atoi(args[1])
|
for i := 0; i < len(a); i++ {
|
||||||
if err != nil || pid <= 0 {
|
ca, cb := a[i], b[i]
|
||||||
|
if ca >= 'A' && ca <= 'F' {
|
||||||
|
ca += 'a' - 'A'
|
||||||
|
}
|
||||||
|
if cb >= 'A' && cb <= 'F' {
|
||||||
|
cb += 'a' - 'A'
|
||||||
|
}
|
||||||
|
if ca != cb {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
target := args[2]
|
|
||||||
self, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
self, _ = filepath.Abs(self)
|
|
||||||
target, _ = filepath.Abs(target)
|
|
||||||
|
|
||||||
waitPIDExit(uint32(pid), 90*time.Second)
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
|
|
||||||
backup := target + ".bak"
|
|
||||||
_ = os.Remove(backup)
|
|
||||||
if err := os.Rename(target, backup); err != nil {
|
|
||||||
// Still locked — wait a bit more.
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
_ = os.Rename(target, backup)
|
|
||||||
}
|
|
||||||
if err := copyFile(self, target); err != nil {
|
|
||||||
_ = os.Rename(backup, target)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
_ = os.Chmod(target, 0o755)
|
|
||||||
_ = os.Remove(backup)
|
|
||||||
_ = os.Remove(self)
|
|
||||||
|
|
||||||
cmd := exec.Command(target)
|
|
||||||
cmd.Dir = filepath.Dir(target)
|
|
||||||
_ = cmd.Start()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitPIDExit(pid uint32, timeout time.Duration) {
|
|
||||||
const synchronize = 0x00100000
|
|
||||||
h, err := windows.OpenProcess(synchronize, false, pid)
|
|
||||||
if err != nil {
|
|
||||||
time.Sleep(1500 * time.Millisecond)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer windows.CloseHandle(h)
|
|
||||||
ms := uint32(timeout / time.Millisecond)
|
|
||||||
if ms == 0 {
|
|
||||||
ms = 1000
|
|
||||||
}
|
|
||||||
_, _ = windows.WaitForSingleObject(h, ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
if _, err := io.Copy(out, in); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return out.Close()
|
|
||||||
}
|
|
||||||
|
|||||||
+10
-10
@@ -1,38 +1,38 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.0",
|
"version": "1.7.1",
|
||||||
"notes": "Меньше ложных срабатываний AV: без bat-апдейтера и тихой загрузки cores; Windows manifest; metadata EvilFox",
|
"notes": "AV: обновление только в Downloads без самозамены; без netsh и автопроверки при старте",
|
||||||
"platform": "windows-amd64",
|
"platform": "windows-amd64",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
||||||
"sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d",
|
"sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b",
|
||||||
"mandatory": false,
|
"mandatory": false,
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"windows-amd64": {
|
"windows-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
|
||||||
"sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d",
|
"sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64"
|
"arch": "amd64"
|
||||||
},
|
},
|
||||||
"darwin-arm64": {
|
"darwin-arm64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
||||||
"sha256": "3e544461c1ce8ec3b508639cb549a759b773da38517c98c9ca634faaf09a601c",
|
"sha256": "3cb42ab226b0263c8b6566888d80c6e7a6982d8cda992d112989c7f5c8a99bab",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "arm64",
|
"arch": "arm64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
||||||
"dmg_sha256": "39e88c985867ce6ef37deb00d87e1c6669cfbc8b9f8f0589d11a3f32036a5258",
|
"dmg_sha256": "65b8fe8c0d294ee923d59125f3e39c37f7c8a201aa6fd4661d080da59928c2e4",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
||||||
"zip_sha256": "213fd7563e068f60a1ae2504c6e48171b9f9e3f2fe3c14ecad75ed809fa05d06"
|
"zip_sha256": "6685b1f8be9f8aa87613f35b8f8a86a00a6fb667c2f7bdc7116e6908c0cb1513"
|
||||||
},
|
},
|
||||||
"darwin-amd64": {
|
"darwin-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
||||||
"sha256": "f40a711108e78bfa1a944a1d4946549aba32cb5e00ef9035be352810c700807c",
|
"sha256": "7bffda217e178224f3a68fed4d5fff4e37b4705b7c8291b84f1d58151ef43b9d",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
|
||||||
"dmg_sha256": "c70fe7feb000eec34c0fe4ef2d05565dd0285c0c4f18a264ecad540d2c0878db",
|
"dmg_sha256": "959526963936ad22d3c3607fe18295018362ec0d5fedcdf7561edf5d71c3b827",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
|
||||||
"zip_sha256": "5b3e3fea5aef4ac962c18a190c1943c7cfd5357ea233abbdec1d26007e1e6eb4"
|
"zip_sha256": "079c2af3fe5de6b9e73bad15a15bddfc93970d27be68b6620b5be3729163fc15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"FixedFileInfo": {
|
"FixedFileInfo": {
|
||||||
"FileVersion": { "Major": 1, "Minor": 7, "Patch": 0, "Build": 0 },
|
"FileVersion": { "Major": 1, "Minor": 7, "Patch": 1, "Build": 0 },
|
||||||
"ProductVersion": { "Major": 1, "Minor": 7, "Patch": 0, "Build": 0 },
|
"ProductVersion": { "Major": 1, "Minor": 7, "Patch": 1, "Build": 0 },
|
||||||
"FileFlagsMask": "3f",
|
"FileFlagsMask": "3f",
|
||||||
"FileFlags": "00",
|
"FileFlags": "00",
|
||||||
"FileOS": "40004",
|
"FileOS": "40004",
|
||||||
@@ -11,12 +11,12 @@
|
|||||||
"StringFileInfo": {
|
"StringFileInfo": {
|
||||||
"CompanyName": "EvilFox",
|
"CompanyName": "EvilFox",
|
||||||
"FileDescription": "Navis VPN client (NaiveProxy / Hysteria 2)",
|
"FileDescription": "Navis VPN client (NaiveProxy / Hysteria 2)",
|
||||||
"FileVersion": "1.7.0.0",
|
"FileVersion": "1.7.1.0",
|
||||||
"InternalName": "Navis",
|
"InternalName": "Navis",
|
||||||
"LegalCopyright": "Copyright (c) EvilFox",
|
"LegalCopyright": "Copyright (c) EvilFox",
|
||||||
"OriginalFilename": "Navis.exe",
|
"OriginalFilename": "Navis.exe",
|
||||||
"ProductName": "Navis",
|
"ProductName": "Navis",
|
||||||
"ProductVersion": "1.7.0.0",
|
"ProductVersion": "1.7.1.0",
|
||||||
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
|
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
|
||||||
},
|
},
|
||||||
"VarFileInfo": {
|
"VarFileInfo": {
|
||||||
|
|||||||
Reference in New Issue
Block a user