Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ebbc90ef6 | ||
|
|
42abbfa8ad | ||
|
|
50f7f71393 | ||
|
|
e416327fbf | ||
|
|
6ed698e0a0 | ||
|
|
c370e089ef | ||
|
|
fa3e11216e | ||
|
|
a1756715ef | ||
|
|
e633b63a9b | ||
|
|
6d52913c32 | ||
|
|
0ba662c650 | ||
|
|
19711c3a33 | ||
|
|
497af0e475 | ||
|
|
2a57987aea | ||
|
|
74ffbd87d7 | ||
|
|
46e28aa73f | ||
|
|
329209fa88 | ||
|
|
5f768c7732 | ||
|
|
1144fab54d | ||
|
|
e76c0a0977 |
@@ -17,13 +17,21 @@ cmd/vpnapp/resource.syso
|
||||
navis-update.bat
|
||||
Navis.exe.new
|
||||
Navis-pending.exe
|
||||
EvilFox.exe.new
|
||||
EvilFox-pending.exe
|
||||
*.bak
|
||||
Navis.exe.bak
|
||||
EvilFox.exe.bak
|
||||
|
||||
# MSIX staging / local packages
|
||||
packaging/msix/_staging/
|
||||
packaging/msix/_staging_*/
|
||||
packaging/msix/Assets/
|
||||
*.msix
|
||||
*.msixbundle
|
||||
dist/microsoft-store/*.msix
|
||||
dist/microsoft-store/*.msixbundle
|
||||
dist/microsoft-store/_bundle_input/
|
||||
|
||||
# Android
|
||||
android/.gradle/
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# Navis
|
||||
# EvilFox
|
||||
|
||||
Клиент [NaiveProxy](https://github.com/klzgrad/naiveproxy) + [Hysteria 2](https://github.com/apernet/hysteria) + [AmneziaWG 2.0](https://docs.amnezia.org/documentation/amnezia-wg/) + [Xray](https://github.com/XTLS/Xray-core) (VLESS / VMess / Trojan) с профилями, пингом, URL-подпиской / **Remnawave API** и автообновлением.
|
||||
Клиент [NaiveProxy](https://github.com/klzgrad/naiveproxy) + [Hysteria 2](https://github.com/apernet/hysteria) + [AmneziaWG 2.0](https://docs.amnezia.org/documentation/amnezia-wg/) + [Xray](https://github.com/XTLS/Xray-core) (VLESS / VMess / Trojan) с профилями, пингом, централизованной выдачей конфигов (Remnawave) и автообновлением.
|
||||
|
||||
> Ранее приложение называлось **Navis**. С 4.0.0 бинарник — `EvilFox.exe`. Каталог релиза `dist/navis-release/` и URL-путь сохранены, чтобы клиенты 3.x продолжали обновляться; в той же папке лежит и `Navis.exe` (копия `EvilFox.exe`) как совместимый алиас.
|
||||
|
||||
## Платформы
|
||||
|
||||
| OS | Артефакты | UI |
|
||||
|----|----------|----|
|
||||
| Windows amd64 | `Navis.exe` | GUI (WebView2) |
|
||||
| Windows amd64 | `EvilFox.exe` (+ `Navis.exe` alias) | GUI (WebView2) |
|
||||
| macOS universal | `Navis.dmg` · `Navis.app.zip` | **GUI** (как Windows) |
|
||||
| macOS arm64 / amd64 | `Navis` · DMG · `Navis-cli` | GUI + CLI |
|
||||
| Android | `Navis.apk` | GUI (VpnService) |
|
||||
@@ -230,29 +232,6 @@ build.bat
|
||||
|
||||
Токены хранятся только локально в `configs/config.json` (не коммитьте).
|
||||
|
||||
### Выдача доступа из приложения (admin API)
|
||||
|
||||
Приложение умеет само создавать пользователей в панели Remnawave и сразу импортировать их конфиги (панель «Выдать доступ» в интерфейсе). Для этого нужен **отдельный** файл с админ-ключом:
|
||||
|
||||
1. Скопируйте `configs/remnawave-api.example.json` → `configs/remnawave-api.json`.
|
||||
2. Вставьте `panel_url` и `api_token` (панель → API Tokens). `caddy_api_key` — только если панель за Caddy-auth.
|
||||
3. Перезапустите Navis — появится панель «Выдать доступ».
|
||||
|
||||
Файл читается **в рантайме** из папки `configs` рядом с `config.json` — пересборка (repack) не требуется, достаточно положить файл и перезапустить приложение.
|
||||
|
||||
Тариф по умолчанию (настраивается в блоке `provision`):
|
||||
|
||||
| Поле | Значение по умолчанию |
|
||||
|------|----------------------|
|
||||
| `traffic_gb` | 50 ГБ |
|
||||
| `days` | 30 дней (expireAt = сейчас + days) |
|
||||
| `strategy` | `MONTH` (сброс трафика; также `NO_RESET` / `DAY` / `WEEK`) |
|
||||
| `hwid_device_limit` | 0 = без лимита устройств |
|
||||
|
||||
Логика: `GET /api/users/by-username/{username}` — если пользователь есть, берётся его `subscriptionUrl`; иначе `POST /api/users` с тарифом выше, затем подписка импортируется в приложение автоматически.
|
||||
|
||||
`configs/remnawave-api.json` в `.gitignore` — **никогда не коммитьте** реальный ключ, в репозитории только `remnawave-api.example.json`.
|
||||
|
||||
## Сборка macOS (кросс с Windows / Linux)
|
||||
|
||||
```bat
|
||||
|
||||
@@ -0,0 +1,650 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru" data-lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>EvilFox — Apps</title>
|
||||
<meta name="description" content="EvilFox — приложения для безопасного и удобного интернета. EvilFox VPN и другие продукты." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@500;700;800&family=Manrope:wght@400;600;700&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg0: #061018;
|
||||
--bg1: #0a1c24;
|
||||
--ink: #e8f4f7;
|
||||
--muted: #8aa8b3;
|
||||
--accent: #22d3ee;
|
||||
--accent-deep: #0e7490;
|
||||
--glow: rgba(34, 211, 238, 0.35);
|
||||
--line: rgba(232, 244, 247, 0.12);
|
||||
--panel: rgba(10, 28, 36, 0.55);
|
||||
--ease: cubic-bezier(.22, 1, .36, 1);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: Manrope, system-ui, sans-serif;
|
||||
color: var(--ink);
|
||||
background: var(--bg0);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Full-bleed atmospheric plane */
|
||||
.stage {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
isolation: isolate;
|
||||
background:
|
||||
radial-gradient(1200px 700px at 78% 18%, rgba(14, 116, 144, 0.45) 0%, transparent 58%),
|
||||
radial-gradient(900px 600px at 12% 80%, rgba(8, 90, 110, 0.35) 0%, transparent 55%),
|
||||
linear-gradient(165deg, #041015 0%, #0a1c24 48%, #07141a 100%);
|
||||
}
|
||||
.stage::before {
|
||||
content: "";
|
||||
position: absolute; inset: 0; z-index: -1; pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(rgba(232,244,247,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(232,244,247,0.03) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(ellipse 80% 70% at 50% 40%, #000 20%, transparent 75%);
|
||||
animation: gridDrift 28s linear infinite;
|
||||
}
|
||||
@keyframes gridDrift {
|
||||
from { transform: translateY(0); }
|
||||
to { transform: translateY(64px); }
|
||||
}
|
||||
.orb {
|
||||
position: absolute; border-radius: 50%; filter: blur(40px); pointer-events: none; z-index: -1;
|
||||
animation: float 12s var(--ease) infinite alternate;
|
||||
}
|
||||
.orb-a { width: 380px; height: 380px; top: -8%; right: 8%; background: var(--glow); }
|
||||
.orb-b { width: 260px; height: 260px; bottom: 12%; left: 6%; background: rgba(14,116,144,.4); animation-delay: -4s; }
|
||||
@keyframes float {
|
||||
from { transform: translate3d(0,0,0) scale(1); }
|
||||
to { transform: translate3d(12px, -22px, 0) scale(1.06); }
|
||||
}
|
||||
|
||||
.top {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 16px; padding: 22px clamp(20px, 4vw, 48px);
|
||||
max-width: 1200px; margin: 0 auto;
|
||||
}
|
||||
.logo {
|
||||
display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit;
|
||||
}
|
||||
.mark {
|
||||
width: 42px; height: 42px; border-radius: 13px;
|
||||
display: grid; place-items: center; font-size: 1.35rem;
|
||||
background: linear-gradient(145deg, #0e7490, #22d3ee);
|
||||
box-shadow: 0 12px 28px rgba(8, 145, 178, 0.35);
|
||||
}
|
||||
.word {
|
||||
font-family: Outfit, sans-serif; font-weight: 800; font-size: 1.35rem;
|
||||
letter-spacing: -0.045em; line-height: 1;
|
||||
}
|
||||
.lang {
|
||||
display: inline-flex; padding: 4px; border-radius: 999px;
|
||||
border: 1px solid var(--line); background: rgba(0,0,0,.25);
|
||||
}
|
||||
.lang button {
|
||||
border: 0; background: transparent; color: var(--muted);
|
||||
font: inherit; font-weight: 700; font-size: .8rem;
|
||||
padding: 8px 14px; border-radius: 999px; cursor: pointer;
|
||||
transition: background .2s, color .2s;
|
||||
}
|
||||
.lang button[aria-pressed="true"] {
|
||||
background: rgba(34, 211, 238, 0.18); color: var(--accent);
|
||||
}
|
||||
|
||||
/* Hero: brand-first, one composition */
|
||||
.hero {
|
||||
max-width: 1200px; margin: 0 auto;
|
||||
padding: clamp(20px, 4vh, 48px) clamp(20px, 4vw, 48px) clamp(40px, 7vh, 72px);
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 0.9fr;
|
||||
gap: clamp(28px, 5vw, 64px);
|
||||
align-items: center;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.hero { grid-template-columns: 1fr; align-items: start; }
|
||||
.visual { order: -1; min-height: 280px; }
|
||||
}
|
||||
.eyebrow {
|
||||
display: inline-block; font-size: .75rem; font-weight: 700; letter-spacing: .12em;
|
||||
text-transform: uppercase; color: var(--accent); margin: 0 0 14px;
|
||||
opacity: 0; animation: rise .7s var(--ease) .05s forwards;
|
||||
}
|
||||
.brand-title {
|
||||
font-family: Outfit, sans-serif;
|
||||
font-size: clamp(3.2rem, 8vw, 5.6rem);
|
||||
font-weight: 800; letter-spacing: -0.06em; line-height: .92;
|
||||
margin: 0 0 18px;
|
||||
opacity: 0; animation: rise .8s var(--ease) .12s forwards;
|
||||
}
|
||||
.lead {
|
||||
max-width: 42ch; font-size: clamp(1.05rem, 2vw, 1.22rem);
|
||||
color: var(--muted); margin: 0 0 14px; font-weight: 500;
|
||||
opacity: 0; animation: rise .8s var(--ease) .22s forwards;
|
||||
}
|
||||
.desc {
|
||||
max-width: 48ch; margin: 0 0 22px; color: rgba(232, 244, 247, 0.88);
|
||||
font-size: .98rem; line-height: 1.55;
|
||||
opacity: 0; animation: rise .8s var(--ease) .26s forwards;
|
||||
}
|
||||
.points {
|
||||
list-style: none; margin: 0 0 26px; padding: 0;
|
||||
display: grid; gap: 10px;
|
||||
opacity: 0; animation: rise .8s var(--ease) .3s forwards;
|
||||
}
|
||||
.points li {
|
||||
display: flex; gap: 10px; align-items: flex-start;
|
||||
color: var(--muted); font-size: .92rem; line-height: 1.45;
|
||||
}
|
||||
.points li::before {
|
||||
content: ""; flex: 0 0 8px; height: 8px; margin-top: .45em;
|
||||
border-radius: 50%; background: var(--accent);
|
||||
box-shadow: 0 0 0 4px rgba(34,211,238,.15);
|
||||
}
|
||||
.cta-row {
|
||||
display: flex; flex-wrap: wrap; gap: 12px;
|
||||
opacity: 0; animation: rise .8s var(--ease) .36s forwards;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
||||
text-decoration: none; font-weight: 700; font-size: .95rem;
|
||||
padding: 14px 22px; border-radius: 14px; border: 1px solid transparent;
|
||||
transition: transform .2s var(--ease), box-shadow .2s, background .2s;
|
||||
}
|
||||
.btn:hover { transform: translateY(-2px); }
|
||||
.btn-primary {
|
||||
color: #042028; background: linear-gradient(135deg, #67e8f9, #22d3ee);
|
||||
box-shadow: 0 14px 36px rgba(34, 211, 238, 0.28);
|
||||
}
|
||||
.btn-ghost {
|
||||
color: var(--ink); border-color: var(--line); background: rgba(255,255,255,.04);
|
||||
}
|
||||
@keyframes rise {
|
||||
from { opacity: 0; transform: translateY(18px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.visual {
|
||||
position: relative;
|
||||
border-radius: 28px;
|
||||
min-height: 420px;
|
||||
background:
|
||||
linear-gradient(160deg, rgba(34,211,238,.12), transparent 45%),
|
||||
linear-gradient(20deg, rgba(14,116,144,.2), transparent 40%),
|
||||
var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
backdrop-filter: blur(16px);
|
||||
overflow: hidden;
|
||||
opacity: 0; animation: rise .9s var(--ease) .18s forwards;
|
||||
}
|
||||
.visual-inner {
|
||||
position: absolute; inset: 0;
|
||||
display: grid; place-items: center;
|
||||
padding: 32px;
|
||||
}
|
||||
.device {
|
||||
width: min(100%, 340px);
|
||||
aspect-ratio: 10 / 14;
|
||||
border-radius: 22px;
|
||||
background: linear-gradient(180deg, #0c222c, #07151c);
|
||||
border: 1px solid rgba(34,211,238,.25);
|
||||
box-shadow: 0 30px 60px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.06);
|
||||
padding: 22px 18px;
|
||||
display: flex; flex-direction: column; gap: 16px;
|
||||
animation: deviceIn 1.1s var(--ease) .35s both;
|
||||
}
|
||||
@keyframes deviceIn {
|
||||
from { opacity: 0; transform: translateY(28px) scale(.96); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
.device-top { display: flex; align-items: center; gap: 10px; }
|
||||
.device-dot {
|
||||
width: 36px; height: 36px; border-radius: 11px;
|
||||
background: linear-gradient(145deg, #0e7490, #22d3ee);
|
||||
display: grid; place-items: center;
|
||||
}
|
||||
.device-name { font-family: Outfit, sans-serif; font-weight: 700; letter-spacing: -.02em; }
|
||||
.device-sub { font-size: .72rem; color: var(--muted); }
|
||||
.power {
|
||||
margin: 12px auto;
|
||||
width: 110px; height: 110px; border-radius: 50%;
|
||||
background: radial-gradient(circle at 35% 30%, #67e8f9, #0e7490 70%);
|
||||
box-shadow: 0 0 0 10px rgba(34,211,238,.12), 0 20px 40px rgba(14,116,144,.35);
|
||||
display: grid; place-items: center;
|
||||
animation: pulse 2.8s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 10px rgba(34,211,238,.12), 0 20px 40px rgba(14,116,144,.35); }
|
||||
50% { box-shadow: 0 0 0 16px rgba(34,211,238,.2), 0 24px 48px rgba(14,116,144,.45); }
|
||||
}
|
||||
.power span { font-size: 2rem; line-height: 1; filter: drop-shadow(0 2px 4px rgba(0,0,0,.3)); }
|
||||
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; }
|
||||
.chip {
|
||||
font-size: .7rem; font-weight: 700; color: var(--accent);
|
||||
border: 1px solid rgba(34,211,238,.25); border-radius: 999px;
|
||||
padding: 6px 10px; background: rgba(34,211,238,.08);
|
||||
}
|
||||
|
||||
/* Apps catalog — one section, one job */
|
||||
.apps {
|
||||
max-width: 1200px; margin: 0 auto;
|
||||
padding: 0 clamp(20px, 4vw, 48px) clamp(72px, 10vh, 120px);
|
||||
}
|
||||
.apps-head {
|
||||
margin-bottom: 28px;
|
||||
opacity: 0; animation: rise .7s var(--ease) .4s forwards;
|
||||
}
|
||||
.apps-head h2 {
|
||||
font-family: Outfit, sans-serif; font-size: clamp(1.6rem, 3vw, 2.1rem);
|
||||
letter-spacing: -.03em; margin: 0 0 8px;
|
||||
}
|
||||
.apps-head p { margin: 0; color: var(--muted); max-width: 48ch; }
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
.app-tile {
|
||||
display: flex; flex-direction: column; gap: 14px;
|
||||
padding: 22px; border-radius: 22px;
|
||||
border: 1px solid var(--line);
|
||||
background: linear-gradient(165deg, rgba(34,211,238,.08), rgba(255,255,255,.02));
|
||||
text-decoration: none; color: inherit;
|
||||
transition: transform .25s var(--ease), border-color .25s, box-shadow .25s;
|
||||
opacity: 0; animation: rise .7s var(--ease) forwards;
|
||||
}
|
||||
.app-tile:nth-child(1) { animation-delay: .45s; }
|
||||
.app-tile:nth-child(2) { animation-delay: .55s; }
|
||||
.app-tile:nth-child(3) { animation-delay: .65s; }
|
||||
.app-tile:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: rgba(34,211,238,.35);
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,.25);
|
||||
}
|
||||
.app-tile.coming {
|
||||
opacity: .55; filter: grayscale(.2);
|
||||
pointer-events: none;
|
||||
animation-name: rise; animation-fill-mode: forwards;
|
||||
}
|
||||
.app-tile.coming:nth-child(2) { animation-delay: .55s; }
|
||||
.app-tile.coming:nth-child(3) { animation-delay: .65s; }
|
||||
.app-ico {
|
||||
width: 52px; height: 52px; border-radius: 16px;
|
||||
display: grid; place-items: center; font-size: 1.5rem;
|
||||
background: linear-gradient(145deg, #0e7490, #22d3ee);
|
||||
}
|
||||
.app-ico.muted { background: linear-gradient(145deg, #1e293b, #334155); }
|
||||
.app-name { font-family: Outfit, sans-serif; font-weight: 700; font-size: 1.2rem; letter-spacing: -.02em; margin: 0; }
|
||||
.app-desc { margin: 0; color: var(--muted); font-size: .92rem; flex: 1; }
|
||||
.app-meta {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
||||
font-size: .78rem; font-weight: 700;
|
||||
}
|
||||
.tag {
|
||||
color: var(--accent); background: rgba(34,211,238,.1);
|
||||
padding: 5px 10px; border-radius: 999px;
|
||||
}
|
||||
.tag.soon { color: var(--muted); background: rgba(255,255,255,.06); }
|
||||
.go { color: var(--accent); }
|
||||
|
||||
.faq {
|
||||
max-width: 1200px; margin: 0 auto 28px;
|
||||
padding: 0 clamp(20px, 4vw, 48px);
|
||||
}
|
||||
.faq-head { margin-bottom: 18px; }
|
||||
.faq-head h2 {
|
||||
font-family: Outfit, sans-serif; font-size: clamp(1.45rem, 2.6vw, 1.9rem);
|
||||
letter-spacing: -.03em; margin: 0 0 8px;
|
||||
}
|
||||
.faq-head p { margin: 0; color: var(--muted); max-width: 52ch; }
|
||||
.faq-list { display: grid; gap: 10px; }
|
||||
.faq-item {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
background: rgba(255,255,255,.02);
|
||||
overflow: hidden;
|
||||
}
|
||||
.faq-item summary {
|
||||
list-style: none; cursor: pointer;
|
||||
padding: 16px 18px;
|
||||
font-family: Outfit, sans-serif; font-weight: 700; font-size: 1.02rem;
|
||||
letter-spacing: -.01em;
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||||
}
|
||||
.faq-item summary::-webkit-details-marker { display: none; }
|
||||
.faq-item summary::after {
|
||||
content: "+"; color: var(--accent); font-weight: 800; font-size: 1.2rem; line-height: 1;
|
||||
transition: transform .2s var(--ease);
|
||||
}
|
||||
.faq-item[open] summary::after { content: "−"; }
|
||||
.faq-item .faq-a {
|
||||
margin: 0; padding: 0 18px 16px;
|
||||
color: var(--muted); font-size: .95rem; line-height: 1.55;
|
||||
}
|
||||
.faq-item[open] { border-color: rgba(34,211,238,.28); background: rgba(34,211,238,.05); }
|
||||
|
||||
.contact {
|
||||
max-width: 1200px; margin: 0 auto 28px;
|
||||
padding: 0 clamp(20px, 4vw, 48px);
|
||||
}
|
||||
.contact-panel {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 22px;
|
||||
padding: 22px 24px;
|
||||
background: linear-gradient(165deg, rgba(34,211,238,.08), rgba(255,255,255,.02));
|
||||
}
|
||||
.contact-panel h2 {
|
||||
font-family: Outfit, sans-serif; font-size: 1.35rem;
|
||||
letter-spacing: -.02em; margin: 0 0 8px;
|
||||
}
|
||||
.contact-panel > p { margin: 0 0 14px; color: var(--muted); font-size: .95rem; }
|
||||
.mails { display: flex; flex-wrap: wrap; gap: 10px 18px; }
|
||||
.mails a {
|
||||
color: var(--accent); font-weight: 700; text-decoration: none;
|
||||
border-bottom: 1px solid rgba(34,211,238,.35);
|
||||
}
|
||||
.mails a:hover { border-bottom-color: var(--accent); }
|
||||
.mails .label { color: var(--muted); font-size: .8rem; font-weight: 700; display: block; margin-bottom: 2px; }
|
||||
.mail-item { min-width: 200px; }
|
||||
|
||||
.legal {
|
||||
max-width: 1200px; margin: 0 auto;
|
||||
padding: 0 clamp(20px, 4vw, 48px) 48px;
|
||||
display: flex; flex-wrap: wrap; gap: 14px 22px;
|
||||
font-size: .85rem; color: var(--muted);
|
||||
}
|
||||
.legal a { color: var(--accent); font-weight: 700; text-decoration: none; }
|
||||
.legal a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="stage">
|
||||
<div class="orb orb-a" aria-hidden="true"></div>
|
||||
<div class="orb orb-b" aria-hidden="true"></div>
|
||||
|
||||
<header class="top">
|
||||
<a class="logo" href="index.html">
|
||||
<div class="mark" aria-hidden="true">🦊</div>
|
||||
<div class="word">EvilFox</div>
|
||||
</a>
|
||||
<div class="lang" role="group" aria-label="Language">
|
||||
<button type="button" data-set-lang="ru" aria-pressed="true">RU</button>
|
||||
<button type="button" data-set-lang="en" aria-pressed="false">EN</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="eyebrow" data-i18n="eyebrow">Студия приложений</p>
|
||||
<h1 class="brand-title">EvilFox</h1>
|
||||
<p class="lead" data-i18n="lead">Приложения для безопасного и спокойного интернета. Начните с EvilFox VPN — дальше появятся новые продукты.</p>
|
||||
<p class="desc" data-i18n="desc">EvilFox — это локальный клиент: вы сами добавляете конфигурации и URL подключения. Шифрование, выбор серверов, Kill Switch, светлая и тёмная темы, русский и английский интерфейс. Сборка для Windows, в том числе Microsoft Store.</p>
|
||||
<ul class="points">
|
||||
<li data-i18n="point1">Современные протоколы и группировка серверов по странам</li>
|
||||
<li data-i18n="point2">Режимы полной защиты и системного прокси</li>
|
||||
<li data-i18n="point3">Данные профилей хранятся на устройстве — без обязательной регистрации</li>
|
||||
</ul>
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-primary" href="#apps" data-i18n="cta_apps">Смотреть приложения</a>
|
||||
<a class="btn btn-ghost" href="#faq" data-i18n="cta_faq">FAQ</a>
|
||||
<a class="btn btn-ghost" href="#contact" data-i18n="cta_contact">Связаться с нами</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="visual" aria-hidden="true">
|
||||
<div class="visual-inner">
|
||||
<div class="device">
|
||||
<div class="device-top">
|
||||
<div class="device-dot">🦊</div>
|
||||
<div>
|
||||
<div class="device-name">EvilFox VPN</div>
|
||||
<div class="device-sub" data-i18n="device_sub">Защита · Windows</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="power"><span>⏻</span></div>
|
||||
<div class="chip-row">
|
||||
<span class="chip" data-i18n="chip1">Шифрование</span>
|
||||
<span class="chip" data-i18n="chip2">Kill Switch</span>
|
||||
<span class="chip" data-i18n="chip3">RU / EN</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="apps" id="apps">
|
||||
<div class="apps-head">
|
||||
<h2 data-i18n="apps_title">Наши приложения</h2>
|
||||
<p data-i18n="apps_lead">Каталог продуктов EvilFox. Сейчас доступен VPN-клиент; другие приложения — скоро.</p>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<a class="app-tile" href="https://evilfox.win/">
|
||||
<div class="app-ico">🦊</div>
|
||||
<h3 class="app-name">EvilFox VPN</h3>
|
||||
<p class="app-desc" data-i18n="vpn_desc">Клиент для защищённого доступа в интернет на Windows: современные протоколы, флаги серверов, Kill Switch, светлая и тёмная темы, русский и английский.</p>
|
||||
<div class="app-meta">
|
||||
<span class="tag" data-i18n="tag_available">Доступно</span>
|
||||
<span class="go" data-i18n="open">Открыть →</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="app-tile coming" aria-disabled="true">
|
||||
<div class="app-ico muted">◇</div>
|
||||
<h3 class="app-name" data-i18n="soon1_name">Скоро</h3>
|
||||
<p class="app-desc" data-i18n="soon1_desc">Новое приложение вне VPN — в разработке. Следите за обновлениями на сайте.</p>
|
||||
<div class="app-meta">
|
||||
<span class="tag soon" data-i18n="tag_soon">В планах</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-tile coming" aria-disabled="true">
|
||||
<div class="app-ico muted">◇</div>
|
||||
<h3 class="app-name" data-i18n="soon2_name">Ещё проекты</h3>
|
||||
<p class="app-desc" data-i18n="soon2_desc">Инструменты для работы и повседневных задач — появятся здесь позже.</p>
|
||||
<div class="app-meta">
|
||||
<span class="tag soon" data-i18n="tag_soon">В планах</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="faq" id="faq" aria-labelledby="faq-title">
|
||||
<div class="faq-head">
|
||||
<h2 id="faq-title" data-i18n="faq_title">Частые вопросы</h2>
|
||||
<p data-i18n="faq_lead">Кратко о клиенте, данных и поддержке. Если ответа нет — напишите нам.</p>
|
||||
</div>
|
||||
<div class="faq-list">
|
||||
<details class="faq-item">
|
||||
<summary data-i18n="faq_q1">Что такое EvilFox?</summary>
|
||||
<p class="faq-a" data-i18n="faq_a1">EvilFox — Windows-клиент для защищённого доступа в интернет. Вы подключаетесь к серверам по своим конфигурациям; мы публикуем приложение и обновления.</p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary data-i18n="faq_q2">Нужна ли регистрация или аккаунт?</summary>
|
||||
<p class="faq-a" data-i18n="faq_a2">Нет обязательного аккаунта у издателя. Профили и настройки хранятся локально. URL и ключи подключения вы получаете у своего провайдера конфигурации.</p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary data-i18n="faq_q3">Собираете ли вы мой трафик?</summary>
|
||||
<p class="faq-a" data-i18n="faq_a3">Клиент работает на устройстве. EvilFox как издатель не ведёт централизованный сбор содержимого вашего трафика для рекламы. При подключении трафик идёт через серверы из вашей конфигурации — их политика может отличаться.</p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary data-i18n="faq_q4">На каких платформах доступно приложение?</summary>
|
||||
<p class="faq-a" data-i18n="faq_a4">Основная платформа — Windows (включая Microsoft Store). Следите за каталогом на сайте: появятся и другие продукты EvilFox.</p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary data-i18n="faq_q5">Что делает Kill Switch?</summary>
|
||||
<p class="faq-a" data-i18n="faq_a5">Kill Switch помогает ограничить незащищённый доступ в сеть, если защищённое соединение обрывается. Включайте в настройках приложения, если нужна такая защита.</p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary data-i18n="faq_q6">Как связаться с поддержкой?</summary>
|
||||
<p class="faq-a" data-i18n="faq_a6">Напишите на animegold3@gmail.com или резервную admin@evilfox.win. Также есть Telegram: t.me/evilfox100Bot.</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="contact" id="contact" aria-labelledby="contact-title">
|
||||
<div class="contact-panel">
|
||||
<h2 id="contact-title" data-i18n="contact_title">Связаться с нами</h2>
|
||||
<p data-i18n="contact_lead">Напишите на почту — ответим по вопросам приложений и поддержки.</p>
|
||||
<div class="mails">
|
||||
<div class="mail-item">
|
||||
<span class="label" data-i18n="mail_main">Основная почта</span>
|
||||
<a href="mailto:animegold3@gmail.com">animegold3@gmail.com</a>
|
||||
</div>
|
||||
<div class="mail-item">
|
||||
<span class="label" data-i18n="mail_backup">Резервная почта</span>
|
||||
<a href="mailto:admin@evilfox.win">admin@evilfox.win</a>
|
||||
</div>
|
||||
<div class="mail-item">
|
||||
<span class="label" data-i18n="mail_tg">Telegram</span>
|
||||
<a href="https://t.me/evilfox100Bot">t.me/evilfox100Bot</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="legal">
|
||||
<span>© 2026 EvilFox</span>
|
||||
<a href="#faq" data-i18n="faq_link">FAQ</a>
|
||||
<a href="privacy-ru.html" data-i18n-href-ru="privacy-ru.html" data-i18n-href-en="privacy-en.html" data-i18n="privacy">Конфиденциальность</a>
|
||||
<a href="terms-ru.html" data-i18n-href-ru="terms-ru.html" data-i18n-href-en="terms-en.html" data-i18n="terms">Соглашение</a>
|
||||
<a href="#contact" data-i18n="support">Связаться</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const I18N = {
|
||||
ru: {
|
||||
eyebrow: "Студия приложений",
|
||||
lead: "Приложения для безопасного и спокойного интернета. Начните с EvilFox VPN — дальше появятся новые продукты.",
|
||||
desc: "EvilFox — это локальный клиент: вы сами добавляете конфигурации и URL подключения. Шифрование, выбор серверов, Kill Switch, светлая и тёмная темы, русский и английский интерфейс. Сборка для Windows, в том числе Microsoft Store.",
|
||||
point1: "Современные протоколы и группировка серверов по странам",
|
||||
point2: "Режимы полной защиты и системного прокси",
|
||||
point3: "Данные профилей хранятся на устройстве — без обязательной регистрации",
|
||||
cta_apps: "Смотреть приложения",
|
||||
cta_faq: "FAQ",
|
||||
cta_contact: "Связаться с нами",
|
||||
device_sub: "Защита · Windows",
|
||||
chip1: "Шифрование",
|
||||
chip2: "Kill Switch",
|
||||
chip3: "RU / EN",
|
||||
apps_title: "Наши приложения",
|
||||
apps_lead: "Каталог продуктов EvilFox. Сейчас доступен VPN-клиент; другие приложения — скоро.",
|
||||
vpn_desc: "Клиент для защищённого доступа в интернет на Windows: современные протоколы, флаги серверов, Kill Switch, светлая и тёмная темы, русский и английский.",
|
||||
tag_available: "Доступно",
|
||||
open: "Открыть →",
|
||||
soon1_name: "Скоро",
|
||||
soon1_desc: "Новое приложение вне VPN — в разработке. Следите за обновлениями на сайте.",
|
||||
soon2_name: "Ещё проекты",
|
||||
soon2_desc: "Инструменты для работы и повседневных задач — появятся здесь позже.",
|
||||
tag_soon: "В планах",
|
||||
faq_title: "Частые вопросы",
|
||||
faq_lead: "Кратко о клиенте, данных и поддержке. Если ответа нет — напишите нам.",
|
||||
faq_link: "FAQ",
|
||||
faq_q1: "Что такое EvilFox?",
|
||||
faq_a1: "EvilFox — Windows-клиент для защищённого доступа в интернет. Вы подключаетесь к серверам по своим конфигурациям; мы публикуем приложение и обновления.",
|
||||
faq_q2: "Нужна ли регистрация или аккаунт?",
|
||||
faq_a2: "Нет обязательного аккаунта у издателя. Профили и настройки хранятся локально. URL и ключи подключения вы получаете у своего провайдера конфигурации.",
|
||||
faq_q3: "Собираете ли вы мой трафик?",
|
||||
faq_a3: "Клиент работает на устройстве. EvilFox как издатель не ведёт централизованный сбор содержимого вашего трафика для рекламы. При подключении трафик идёт через серверы из вашей конфигурации — их политика может отличаться.",
|
||||
faq_q4: "На каких платформах доступно приложение?",
|
||||
faq_a4: "Основная платформа — Windows (включая Microsoft Store). Следите за каталогом на сайте: появятся и другие продукты EvilFox.",
|
||||
faq_q5: "Что делает Kill Switch?",
|
||||
faq_a5: "Kill Switch помогает ограничить незащищённый доступ в сеть, если защищённое соединение обрывается. Включайте в настройках приложения, если нужна такая защита.",
|
||||
faq_q6: "Как связаться с поддержкой?",
|
||||
faq_a6: "Напишите на animegold3@gmail.com или резервную admin@evilfox.win. Также есть Telegram: t.me/evilfox100Bot.",
|
||||
privacy: "Конфиденциальность",
|
||||
terms: "Соглашение",
|
||||
support: "Связаться",
|
||||
contact_title: "Связаться с нами",
|
||||
contact_lead: "Напишите на почту — ответим по вопросам приложений и поддержки.",
|
||||
mail_main: "Основная почта",
|
||||
mail_backup: "Резервная почта",
|
||||
mail_tg: "Telegram"
|
||||
},
|
||||
en: {
|
||||
eyebrow: "App studio",
|
||||
lead: "Apps for a safer, calmer internet. Start with EvilFox VPN — more products are on the way.",
|
||||
desc: "EvilFox is a local client: you add your own configs and connection URLs. Encryption, server picker, Kill Switch, light and dark themes, Russian and English UI. Built for Windows, including the Microsoft Store.",
|
||||
point1: "Modern protocols and servers grouped by country",
|
||||
point2: "Full protection and system proxy modes",
|
||||
point3: "Profiles stay on your device — no mandatory publisher account",
|
||||
cta_apps: "Browse apps",
|
||||
cta_faq: "FAQ",
|
||||
cta_contact: "Contact us",
|
||||
device_sub: "Protection · Windows",
|
||||
chip1: "Encryption",
|
||||
chip2: "Kill Switch",
|
||||
chip3: "RU / EN",
|
||||
apps_title: "Our apps",
|
||||
apps_lead: "The EvilFox catalog. VPN is available now; more apps are coming.",
|
||||
vpn_desc: "A Windows client for private internet access: modern protocols, country flags, Kill Switch, light and dark themes, Russian and English.",
|
||||
tag_available: "Available",
|
||||
open: "Open →",
|
||||
soon1_name: "Coming soon",
|
||||
soon1_desc: "A new non-VPN app is in the works. Watch the site for updates.",
|
||||
soon2_name: "More projects",
|
||||
soon2_desc: "Tools for work and everyday tasks will appear here later.",
|
||||
tag_soon: "Planned",
|
||||
faq_title: "FAQ",
|
||||
faq_lead: "Short answers about the client, data, and support. Still stuck? Contact us.",
|
||||
faq_link: "FAQ",
|
||||
faq_q1: "What is EvilFox?",
|
||||
faq_a1: "EvilFox is a Windows client for private internet access. You connect using your own configurations; we publish the app and updates.",
|
||||
faq_q2: "Do I need to register or create an account?",
|
||||
faq_a2: "No publisher account is required. Profiles and settings stay local. Connection URLs and keys come from your configuration provider.",
|
||||
faq_q3: "Do you collect my traffic?",
|
||||
faq_a3: "The client runs on your device. As publisher, EvilFox does not centrally collect your traffic contents for advertising. When connected, traffic goes through servers in your config — their policies may differ.",
|
||||
faq_q4: "Which platforms are supported?",
|
||||
faq_a4: "The main platform is Windows (including Microsoft Store). Watch the site catalog for more EvilFox products.",
|
||||
faq_q5: "What does Kill Switch do?",
|
||||
faq_a5: "Kill Switch helps limit unprotected network access if the protected connection drops. Turn it on in app settings when you want that safeguard.",
|
||||
faq_q6: "How do I contact support?",
|
||||
faq_a6: "Email animegold3@gmail.com or backup admin@evilfox.win. Telegram: t.me/evilfox100Bot.",
|
||||
privacy: "Privacy",
|
||||
terms: "Terms",
|
||||
support: "Contact",
|
||||
contact_title: "Contact us",
|
||||
contact_lead: "Email us about apps and support — we will get back to you.",
|
||||
mail_main: "Primary email",
|
||||
mail_backup: "Backup email",
|
||||
mail_tg: "Telegram"
|
||||
}
|
||||
};
|
||||
|
||||
function setLang(lang) {
|
||||
const pack = I18N[lang] || I18N.ru;
|
||||
document.documentElement.lang = lang;
|
||||
document.documentElement.dataset.lang = lang;
|
||||
document.querySelectorAll("[data-i18n]").forEach((el) => {
|
||||
const key = el.getAttribute("data-i18n");
|
||||
if (pack[key] != null) el.textContent = pack[key];
|
||||
});
|
||||
document.querySelectorAll("[data-i18n-href-ru]").forEach((el) => {
|
||||
el.setAttribute("href", lang === "en" ? el.getAttribute("data-i18n-href-en") : el.getAttribute("data-i18n-href-ru"));
|
||||
});
|
||||
document.querySelectorAll(".lang button").forEach((btn) => {
|
||||
btn.setAttribute("aria-pressed", btn.getAttribute("data-set-lang") === lang ? "true" : "false");
|
||||
});
|
||||
try { localStorage.setItem("evilfox.site.lang", lang); } catch (_) {}
|
||||
document.title = lang === "en" ? "EvilFox — Apps" : "EvilFox — Приложения";
|
||||
}
|
||||
|
||||
document.querySelectorAll(".lang button").forEach((btn) => {
|
||||
btn.addEventListener("click", () => setLang(btn.getAttribute("data-set-lang")));
|
||||
});
|
||||
|
||||
let initial = "ru";
|
||||
try {
|
||||
const saved = localStorage.getItem("evilfox.site.lang");
|
||||
if (saved === "en" || saved === "ru") initial = saved;
|
||||
else if (navigator.language && navigator.language.toLowerCase().startsWith("en")) initial = "en";
|
||||
} catch (_) {}
|
||||
setLang(initial);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Privacy Policy — EvilFox</title>
|
||||
<meta name="description" content="Detailed EvilFox Privacy Policy (US, EU, Microsoft Store)." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&family=Outfit:wght@700;800&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4fafb; --ink: #0f172a; --muted: #64748b; --card: #fff;
|
||||
--line: rgba(15, 23, 42, 0.1); --accent: #0e7490;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0; font-family: Manrope, system-ui, sans-serif; color: var(--ink);
|
||||
background:
|
||||
radial-gradient(900px 420px at 10% -10%, rgba(34, 211, 238, 0.18), transparent 55%),
|
||||
radial-gradient(700px 360px at 100% 0%, rgba(14, 116, 144, 0.12), transparent 50%),
|
||||
var(--bg);
|
||||
line-height: 1.65;
|
||||
}
|
||||
.wrap { max-width: 860px; margin: 0 auto; padding: 32px 20px 72px; }
|
||||
header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
|
||||
.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
|
||||
.logo {
|
||||
width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center;
|
||||
background: linear-gradient(145deg, #0e7490, #22d3ee);
|
||||
box-shadow: 0 10px 24px rgba(8, 145, 178, 0.28); font-size: 1.4rem;
|
||||
}
|
||||
.brand h1 { font-family: Outfit, sans-serif; font-size: 1.45rem; font-weight: 800; letter-spacing: -0.04em; margin: 0; }
|
||||
.brand span { display: block; font-size: .78rem; color: var(--muted); font-weight: 600; }
|
||||
.langs { display: flex; gap: 8px; }
|
||||
.langs a {
|
||||
text-decoration: none; font-weight: 700; font-size: .85rem; padding: 8px 14px;
|
||||
border-radius: 999px; border: 1px solid var(--line); color: var(--muted); background: var(--card);
|
||||
}
|
||||
.langs a.active, .langs a:hover { color: #fff; background: var(--accent); border-color: var(--accent); }
|
||||
.card {
|
||||
background: var(--card); border: 1px solid var(--line); border-radius: 20px;
|
||||
padding: 28px 26px; box-shadow: 0 18px 40px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
h2 { font-family: Outfit, sans-serif; font-size: 1.65rem; letter-spacing: -0.03em; margin: 0 0 8px; }
|
||||
.meta { color: var(--muted); font-size: .92rem; margin: 0 0 18px; }
|
||||
.toc { background: rgba(14,116,144,.06); border-radius: 14px; padding: 14px 18px; margin: 0 0 24px; font-size: .9rem; }
|
||||
.toc a { color: var(--accent); font-weight: 600; text-decoration: none; }
|
||||
.toc ol { margin: 8px 0 0; padding-left: 1.25rem; }
|
||||
.toc li { margin: 4px 0; }
|
||||
h3 { font-size: 1.08rem; margin: 26px 0 10px; color: var(--accent); scroll-margin-top: 16px; }
|
||||
h4 { font-size: .98rem; margin: 16px 0 8px; }
|
||||
p, li { margin: 0 0 12px; }
|
||||
ul, ol { padding-left: 1.25rem; margin: 0 0 14px; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: .9rem; margin: 0 0 16px; }
|
||||
th, td { border: 1px solid var(--line); padding: 10px 12px; text-align: left; vertical-align: top; }
|
||||
th { background: rgba(14,116,144,.08); }
|
||||
.note { font-size: .9rem; color: var(--muted); border-left: 3px solid var(--accent); padding-left: 12px; margin: 12px 0 16px; }
|
||||
footer { margin-top: 28px; font-size: .88rem; color: var(--muted); display: flex; flex-wrap: wrap; gap: 12px 20px; }
|
||||
footer a, .card a { color: var(--accent); font-weight: 700; text-decoration: none; }
|
||||
footer a:hover, .card a:hover { text-decoration: underline; }
|
||||
.nav-docs { margin-top: 22px; font-size: .9rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<a class="brand" href="https://evilfox.win/">
|
||||
<div class="logo" aria-hidden="true">🦊</div>
|
||||
<div>
|
||||
<h1>EvilFox</h1>
|
||||
<span>evilfox.win</span>
|
||||
</div>
|
||||
</a>
|
||||
<nav class="langs" aria-label="Language">
|
||||
<a href="privacy-ru.html" lang="ru">Русский</a>
|
||||
<a class="active" href="privacy-en.html" lang="en">English</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<article class="card">
|
||||
<h2>Privacy Policy</h2>
|
||||
<p class="meta">
|
||||
Publisher / controller (for on-device data): <strong>EvilFox</strong><br />
|
||||
Effective date: <strong>August 2, 2026</strong> · Document version: 1.0<br />
|
||||
Applies to the EvilFox Windows app (including the Microsoft Store build) and related pages on evilfox.win.
|
||||
</p>
|
||||
|
||||
<nav class="toc" aria-label="Table of contents">
|
||||
<strong>Contents</strong>
|
||||
<ol>
|
||||
<li><a href="#s1">Who we are and what this Policy covers</a></li>
|
||||
<li><a href="#s2">Scope and roles</a></li>
|
||||
<li><a href="#s3">What data is processed</a></li>
|
||||
<li><a href="#s4">What we do not collect centrally</a></li>
|
||||
<li><a href="#s5">Purposes and legal bases</a></li>
|
||||
<li><a href="#s6">Network traffic and third-party servers</a></li>
|
||||
<li><a href="#s7">App updates</a></li>
|
||||
<li><a href="#s8">External links and third-party services</a></li>
|
||||
<li><a href="#s9">Retention, deletion, and security</a></li>
|
||||
<li><a href="#s10">Transfers and international aspects</a></li>
|
||||
<li><a href="#s11">Your rights (EU / GDPR)</a></li>
|
||||
<li><a href="#s12">California (CCPA/CPRA) and US users</a></li>
|
||||
<li><a href="#s13">Children</a></li>
|
||||
<li><a href="#s14">Microsoft Store</a></li>
|
||||
<li><a href="#s15">Changes to this Policy</a></li>
|
||||
<li><a href="#s16">Contact</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h3 id="s1">1. Who we are and what this Policy covers</h3>
|
||||
<p>EvilFox is a local Windows client for protected network connectivity (proxy mode and full-protection / TUN mode, modern protocols). You add your own configurations and connection URLs. The Microsoft Store build has no in-app purchases.</p>
|
||||
<p>This Privacy Policy explains what data may be processed when you use the App, for what purposes, how long it is kept, what rights you have, and how to contact us. We aim to meet Microsoft Store transparency expectations and the expectations of users in the United States and the European Union / EEA / United Kingdom.</p>
|
||||
<p class="note">In short: most data stays on your device. We do not sell personal data and do not build an advertising profile from the contents of your traffic.</p>
|
||||
|
||||
<h3 id="s2">2. Scope and roles</h3>
|
||||
<h4>2.1. What is covered</h4>
|
||||
<ul>
|
||||
<li>the EvilFox application (Windows, including MSIX / Microsoft Store);</li>
|
||||
<li>policy and terms pages on evilfox.win;</li>
|
||||
<li>support requests sent to the contacts listed below.</li>
|
||||
</ul>
|
||||
<h4>2.2. Roles</h4>
|
||||
<ul>
|
||||
<li><strong>EvilFox (client publisher)</strong> — controller for data the App stores and processes <em>locally on your device</em> to run the client.</li>
|
||||
<li><strong>Server operator / configuration provider</strong> — a separate party (you or your provider). When traffic goes through servers you choose, processing on those servers is governed by their policies. EvilFox as client publisher is not necessarily the operator of those servers.</li>
|
||||
<li><strong>Microsoft</strong> — if you install or update from the Microsoft Store, Microsoft’s policies apply to Store distribution, Microsoft accounts, and Store payments (where applicable).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="s3">3. What data is processed</h3>
|
||||
<h4>3.1. Data on your device (local)</h4>
|
||||
<p>The App may store on your device (config files, app data folders, WebView2 localStorage and similar OS storage):</p>
|
||||
<table>
|
||||
<thead><tr><th>Category</th><th>Examples</th><th>Why</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Connection configurations</td><td>Configuration URLs, server profiles, protocol parameters you enter or import</td><td>Connecting and choosing a server</td></tr>
|
||||
<tr><td>App settings</td><td>Theme (light/dark), language (RU/EN), proxy or full-protection mode, Kill Switch, auto best server</td><td>Remembering your preferences</td></tr>
|
||||
<tr><td>Session technical data</td><td>Connection status, selected profile, ping, local session/traffic stats (when available)</td><td>Status and statistics UI</td></tr>
|
||||
<tr><td>OS / WebView2 service data</td><td>WebView2 cache, on-device error logs</td><td>Stable UI operation</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>3.2. Data during network use</h4>
|
||||
<ul>
|
||||
<li><strong>With servers you choose:</strong> technical parameters needed by the protocol are exchanged (addresses, keys/tokens from your configuration, tunnel control traffic). Your user traffic is processed along the path through the selected server — see Section 6.</li>
|
||||
<li><strong>Update checks (non-Store builds or when you initiate a check):</strong> a request to an update feed (e.g. hosting on git.de4ima.uk) may include standard HTTP request metadata (IP address as seen by the host, User-Agent, time).</li>
|
||||
<li><strong>When you open external links:</strong> the browser or OS sends data to that site/messenger under their rules.</li>
|
||||
</ul>
|
||||
<h4>3.3. Data you may send to support</h4>
|
||||
<p>If you contact us (email / Telegram / website), you may voluntarily provide an address, problem description, or screenshots. We use them only to respond and improve the product.</p>
|
||||
|
||||
<h3 id="s4">4. What we do not collect centrally</h3>
|
||||
<p>As the client publisher, EvilFox does <strong>not</strong> operate a mandatory central collection and does not sell:</p>
|
||||
<ul>
|
||||
<li>a cloud account registry of EvilFox client users for ordinary app operation;</li>
|
||||
<li>payment cards or in-app purchase details (the Store build has no in-app purchases; Store purchases, if any, are handled by Microsoft);</li>
|
||||
<li>browsing history, contacts, microphone/camera by default;</li>
|
||||
<li>the contents of your internet traffic for advertising or data resale.</li>
|
||||
</ul>
|
||||
<p>We do <strong>not sell</strong> personal data.</p>
|
||||
|
||||
<h3 id="s5">5. Purposes and legal bases</h3>
|
||||
<table>
|
||||
<thead><tr><th>Purpose</th><th>Legal basis (GDPR / equivalents)</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Providing client features (connect, settings, UI)</td><td>Performance of a contract / user request for the client service</td></tr>
|
||||
<tr><td>Security and stability (local logs, preventing failures)</td><td>Legitimate interests / necessity to operate the product</td></tr>
|
||||
<tr><td>Update checks (user-initiated or build policy)</td><td>Legitimate interests / user action</td></tr>
|
||||
<tr><td>Responding to support requests</td><td>Legitimate interests / responding to the data subject</td></tr>
|
||||
<tr><td>Legal compliance and protecting rights</td><td>Legal obligation / legitimate interests</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="s6">6. Network traffic and third-party servers</h3>
|
||||
<p>After you connect, traffic is routed according to your configuration. The node operator may process IP addresses, session metadata, and (depending on protocol and settings) traffic contents. EvilFox does not control every third-party server’s privacy practice.</p>
|
||||
<p><strong>Recommendation:</strong> use only trusted configurations; read your server provider’s privacy policy and terms.</p>
|
||||
<p><strong>Full-protection (TUN) mode and Kill Switch:</strong> these features change on-device network routing to protect the connection and reduce leaks if the tunnel drops. They operate locally on your system.</p>
|
||||
|
||||
<h3 id="s7">7. App updates</h3>
|
||||
<ul>
|
||||
<li><strong>Microsoft Store:</strong> updates are normally delivered by the Store; in-app self-update in the MSIX build is disabled or limited.</li>
|
||||
<li><strong>Other channels (sideload):</strong> when you choose, the client may fetch an update manifest from a publisher URL (including on git.de4ima.uk). The hosting server may see IP and technical request headers.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="s8">8. External links and third-party services</h3>
|
||||
<p>The App may open, after you choose to click:</p>
|
||||
<ul>
|
||||
<li><a href="https://evilfox.win/">https://evilfox.win/</a> — publisher website;</li>
|
||||
<li><a href="https://t.me/evilfox100Bot">https://t.me/evilfox100Bot</a> — Telegram support (Telegram’s policies apply).</li>
|
||||
</ul>
|
||||
<p>The device also uses OS components and Microsoft Edge WebView2 — Microsoft’s policies apply to those components.</p>
|
||||
|
||||
<h3 id="s9">9. Retention, deletion, and security</h3>
|
||||
<h4>9.1. Retention</h4>
|
||||
<p>Local data remains while you use the App, or until you delete profiles, clear app data, or uninstall EvilFox. Support correspondence is kept as long as needed to resolve the request (typically no longer than needed for the conversation and basic support records).</p>
|
||||
<h4>9.2. How to delete data</h4>
|
||||
<ul>
|
||||
<li>delete configurations/profiles in the App;</li>
|
||||
<li>uninstall EvilFox in Windows;</li>
|
||||
<li>if needed, manually clear remaining app data folders.</li>
|
||||
</ul>
|
||||
<p>There is no central EvilFox cloud account for restoring local profiles — we cannot restore device-deleted data from our servers.</p>
|
||||
<h4>9.3. Security</h4>
|
||||
<p>We apply reasonable organizational and technical measures within the client and OS. No method is perfectly secure. You are responsible for keeping configuration URLs/keys confidential on the device and for physical access to the computer.</p>
|
||||
|
||||
<h3 id="s10">10. Transfers and international aspects</h3>
|
||||
<p>The client runs locally. Data may be processed in other countries when:</p>
|
||||
<ul>
|
||||
<li>you connect to servers abroad (via your configuration);</li>
|
||||
<li>you open foreign websites/services;</li>
|
||||
<li>an update request is handled by hosting infrastructure outside your country.</li>
|
||||
</ul>
|
||||
<p>For users in the EU/EEA/UK: such transfers result from your choice of configuration and services. Where required, server operators should provide appropriate safeguards (e.g. Standard Contractual Clauses) — ask your configuration provider.</p>
|
||||
|
||||
<h3 id="s11">11. Your rights (EU / GDPR and equivalents)</h3>
|
||||
<p>Depending on applicable law, you may have rights to:</p>
|
||||
<ul>
|
||||
<li>access and obtain a copy of data;</li>
|
||||
<li>rectify inaccurate data;</li>
|
||||
<li>erasure (“right to be forgotten”) — including by uninstalling and clearing local data;</li>
|
||||
<li>restriction of processing and objection to processing based on legitimate interests;</li>
|
||||
<li>data portability — to the extent data is stored in a structured form on the device (export configurations if available);</li>
|
||||
<li>lodge a complaint with a supervisory authority where you live or work in the EU/EEA/UK.</li>
|
||||
</ul>
|
||||
<p>Because most processing is on-device, most rights are exercised locally. For questions about this Policy, contact us below. We will respond within a reasonable time.</p>
|
||||
|
||||
<h3 id="s12">12. California (CCPA/CPRA) and US users</h3>
|
||||
<p>We do <strong>not sell</strong> and do <strong>not “share”</strong> personal information for cross-context behavioral advertising under CCPA/CPRA. Categories of data are described in Section 3. Sources are mainly you (configuration input) and the device. We do not use sensitive personal information to infer consumer characteristics for advertising.</p>
|
||||
<p>Rights to know, delete, and correct regarding local client data are exercised on the device (uninstall / clear data). Requests related to support correspondence can be sent via Section 16.</p>
|
||||
<p>We do not discriminate against you for exercising privacy rights.</p>
|
||||
|
||||
<h3 id="s13">13. Children</h3>
|
||||
<p>The App is not directed to children under <strong>13</strong>. In the EU/EEA, where local law sets a higher digital consent age (often up to <strong>16</strong>), do not use the App below that age without parental/guardian consent where required. We do not knowingly collect children’s data centrally. If you believe a child sent us data via support, contact us — we will delete the request where appropriate.</p>
|
||||
|
||||
<h3 id="s14">14. Microsoft Store</h3>
|
||||
<p>When distributed through the Microsoft Store, Microsoft may process install, update, review, and (for paid products) payment data under its own policies. Capabilities such as runFullTrust / classic desktop may require Store disclosure and approval of restricted capabilities.</p>
|
||||
|
||||
<h3 id="s15">15. Changes to this Policy</h3>
|
||||
<p>We may update this Policy. The current version is published on the website (this page) and/or in the App Help section. Material changes are reflected by the date at the top where practical. Continued use of the App after an update means you have an opportunity to review the new text.</p>
|
||||
|
||||
<h3 id="s16">16. Contact</h3>
|
||||
<p>
|
||||
<strong>EvilFox</strong><br />
|
||||
Website: <a href="https://evilfox.win/">https://evilfox.win/</a><br />
|
||||
Primary email: <a href="mailto:animegold3@gmail.com">animegold3@gmail.com</a><br />
|
||||
Backup email: <a href="mailto:admin@evilfox.win">admin@evilfox.win</a><br />
|
||||
Telegram: <a href="https://t.me/evilfox100Bot">https://t.me/evilfox100Bot</a><br />
|
||||
Terms of Use: <a href="terms-en.html">terms-en.html</a>
|
||||
</p>
|
||||
|
||||
<p class="nav-docs">Русский: <a href="privacy-ru.html">Политика конфиденциальности</a></p>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
<span>© 2026 EvilFox</span>
|
||||
<a href="terms-en.html">Terms of Use</a>
|
||||
<a href="https://evilfox.win/">Website</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Политика конфиденциальности — EvilFox</title>
|
||||
<meta name="description" content="Подробная политика конфиденциальности приложения EvilFox (США, ЕС, Microsoft Store)." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&family=Outfit:wght@700;800&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4fafb; --ink: #0f172a; --muted: #64748b; --card: #fff;
|
||||
--line: rgba(15, 23, 42, 0.1); --accent: #0e7490;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0; font-family: Manrope, system-ui, sans-serif; color: var(--ink);
|
||||
background:
|
||||
radial-gradient(900px 420px at 10% -10%, rgba(34, 211, 238, 0.18), transparent 55%),
|
||||
radial-gradient(700px 360px at 100% 0%, rgba(14, 116, 144, 0.12), transparent 50%),
|
||||
var(--bg);
|
||||
line-height: 1.65;
|
||||
}
|
||||
.wrap { max-width: 860px; margin: 0 auto; padding: 32px 20px 72px; }
|
||||
header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
|
||||
.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
|
||||
.logo {
|
||||
width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center;
|
||||
background: linear-gradient(145deg, #0e7490, #22d3ee);
|
||||
box-shadow: 0 10px 24px rgba(8, 145, 178, 0.28); font-size: 1.4rem;
|
||||
}
|
||||
.brand h1 { font-family: Outfit, sans-serif; font-size: 1.45rem; font-weight: 800; letter-spacing: -0.04em; margin: 0; }
|
||||
.brand span { display: block; font-size: .78rem; color: var(--muted); font-weight: 600; }
|
||||
.langs { display: flex; gap: 8px; }
|
||||
.langs a {
|
||||
text-decoration: none; font-weight: 700; font-size: .85rem; padding: 8px 14px;
|
||||
border-radius: 999px; border: 1px solid var(--line); color: var(--muted); background: var(--card);
|
||||
}
|
||||
.langs a.active, .langs a:hover { color: #fff; background: var(--accent); border-color: var(--accent); }
|
||||
.card {
|
||||
background: var(--card); border: 1px solid var(--line); border-radius: 20px;
|
||||
padding: 28px 26px; box-shadow: 0 18px 40px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
h2 { font-family: Outfit, sans-serif; font-size: 1.65rem; letter-spacing: -0.03em; margin: 0 0 8px; }
|
||||
.meta { color: var(--muted); font-size: .92rem; margin: 0 0 18px; }
|
||||
.toc { background: rgba(14,116,144,.06); border-radius: 14px; padding: 14px 18px; margin: 0 0 24px; font-size: .9rem; }
|
||||
.toc a { color: var(--accent); font-weight: 600; text-decoration: none; }
|
||||
.toc ol { margin: 8px 0 0; padding-left: 1.25rem; }
|
||||
.toc li { margin: 4px 0; }
|
||||
h3 { font-size: 1.08rem; margin: 26px 0 10px; color: var(--accent); scroll-margin-top: 16px; }
|
||||
h4 { font-size: .98rem; margin: 16px 0 8px; }
|
||||
p, li { margin: 0 0 12px; }
|
||||
ul, ol { padding-left: 1.25rem; margin: 0 0 14px; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: .9rem; margin: 0 0 16px; }
|
||||
th, td { border: 1px solid var(--line); padding: 10px 12px; text-align: left; vertical-align: top; }
|
||||
th { background: rgba(14,116,144,.08); }
|
||||
.note { font-size: .9rem; color: var(--muted); border-left: 3px solid var(--accent); padding-left: 12px; margin: 12px 0 16px; }
|
||||
footer { margin-top: 28px; font-size: .88rem; color: var(--muted); display: flex; flex-wrap: wrap; gap: 12px 20px; }
|
||||
footer a, .card a { color: var(--accent); font-weight: 700; text-decoration: none; }
|
||||
footer a:hover, .card a:hover { text-decoration: underline; }
|
||||
.nav-docs { margin-top: 22px; font-size: .9rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<a class="brand" href="https://evilfox.win/">
|
||||
<div class="logo" aria-hidden="true">🦊</div>
|
||||
<div>
|
||||
<h1>EvilFox</h1>
|
||||
<span>evilfox.win</span>
|
||||
</div>
|
||||
</a>
|
||||
<nav class="langs" aria-label="Язык">
|
||||
<a class="active" href="privacy-ru.html" lang="ru">Русский</a>
|
||||
<a href="privacy-en.html" lang="en">English</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<article class="card">
|
||||
<h2>Политика конфиденциальности</h2>
|
||||
<p class="meta">
|
||||
Издатель / контролёр (для данных на устройстве): <strong>EvilFox</strong><br />
|
||||
Дата вступления в силу: <strong>2 августа 2026 г.</strong> · Версия документа: 1.0<br />
|
||||
Применяется к приложению EvilFox для Windows (включая сборку Microsoft Store) и связанным страницам на evilfox.win.
|
||||
</p>
|
||||
|
||||
<nav class="toc" aria-label="Содержание">
|
||||
<strong>Содержание</strong>
|
||||
<ol>
|
||||
<li><a href="#s1">Кто мы и о чём эта политика</a></li>
|
||||
<li><a href="#s2">Область применения и роли</a></li>
|
||||
<li><a href="#s3">Какие данные обрабатываются</a></li>
|
||||
<li><a href="#s4">Что мы не собираем централизованно</a></li>
|
||||
<li><a href="#s5">Цели и правовые основания</a></li>
|
||||
<li><a href="#s6">Сетевой трафик и сторонние серверы</a></li>
|
||||
<li><a href="#s7">Обновления приложения</a></li>
|
||||
<li><a href="#s8">Внешние ссылки и сторонние сервисы</a></li>
|
||||
<li><a href="#s9">Хранение, удаление и безопасность</a></li>
|
||||
<li><a href="#s10">Передачи данных и международные аспекты</a></li>
|
||||
<li><a href="#s11">Ваши права (ЕС / GDPR)</a></li>
|
||||
<li><a href="#s12">Калифорния (CCPA/CPRA) и США</a></li>
|
||||
<li><a href="#s13">Дети</a></li>
|
||||
<li><a href="#s14">Microsoft Store</a></li>
|
||||
<li><a href="#s15">Изменения политики</a></li>
|
||||
<li><a href="#s16">Контакты</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h3 id="s1">1. Кто мы и о чём эта политика</h3>
|
||||
<p>EvilFox — локальный клиент для защищённого сетевого подключения на Windows (режимы прокси и полной защиты / TUN, поддержка современных протоколов). Вы самостоятельно добавляете конфигурации и URL подключения. В сборке Microsoft Store нет встроенных покупок внутри приложения.</p>
|
||||
<p>Настоящая Политика объясняет, какие данные могут обрабатываться при использовании Приложения, для каких целей, как долго они хранятся, какие у вас есть права и как с нами связаться. Мы стремимся соответствовать требованиям прозрачности Microsoft Store, а также ожиданиям пользователей в США и Европейском союзе / ЕЭЗ / Великобритании.</p>
|
||||
<p class="note">Кратко: большинство данных остаётся на вашем устройстве. Мы не продаём персональные данные и не ведём рекламный профиль на основе содержимого вашего трафика.</p>
|
||||
|
||||
<h3 id="s2">2. Область применения и роли</h3>
|
||||
<h4>2.1. Что покрывается</h4>
|
||||
<ul>
|
||||
<li>приложение EvilFox (Windows, в том числе MSIX / Microsoft Store);</li>
|
||||
<li>страницы политики и соглашения на сайте evilfox.win;</li>
|
||||
<li>обращения в поддержку через указанные контакты.</li>
|
||||
</ul>
|
||||
<h4>2.2. Роли</h4>
|
||||
<ul>
|
||||
<li><strong>EvilFox (издатель клиента)</strong> — контролёр в отношении данных, которые Приложение хранит и обрабатывает <em>локально на устройстве</em> для работы клиента.</li>
|
||||
<li><strong>Оператор серверов / провайдер конфигурации</strong> — отдельное лицо (вы сами или ваш поставщик). Когда трафик проходит через выбранные вами серверы, обработка на стороне этих серверов регулируется их политиками. EvilFox как издатель клиента не обязательно является оператором этих серверов.</li>
|
||||
<li><strong>Microsoft</strong> — при установке и обновлении из Microsoft Store действуют политики Microsoft в части Store, учётной записи Microsoft и платёжных операций Store (если применимо).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="s3">3. Какие данные обрабатываются</h3>
|
||||
<h4>3.1. Данные на вашем устройстве (локально)</h4>
|
||||
<p>Приложение может сохранять на устройстве (файлы конфигурации, каталог данных приложения, localStorage WebView2 и аналогичные хранилища ОС):</p>
|
||||
<table>
|
||||
<thead><tr><th>Категория</th><th>Примеры</th><th>Зачем</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Конфигурации подключения</td><td>URL конфигурации, профили серверов, параметры протоколов, которые вы вводите или импортируете</td><td>Подключение и выбор сервера</td></tr>
|
||||
<tr><td>Настройки приложения</td><td>Тема (светлая/тёмная), язык (RU/EN), режим прокси или полной защиты, Kill Switch, автовыбор лучшего сервера</td><td>Сохранение ваших предпочтений</td></tr>
|
||||
<tr><td>Технические сведения о сессии</td><td>Статус подключения, выбранный профиль, пинг, локальная статистика сессий/трафика (если доступна)</td><td>Отображение статуса и статистики</td></tr>
|
||||
<tr><td>Служебные файлы ОС/WebView2</td><td>Кэш WebView2, журналы ошибок на устройстве</td><td>Стабильная работа интерфейса</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>3.2. Данные при сетевом взаимодействии</h4>
|
||||
<ul>
|
||||
<li><strong>С выбранными вами серверами:</strong> для установления защищённого соединения передаются технические параметры, необходимые протоколу (адреса, ключи/токены из вашей конфигурации, служебный трафик туннеля). Содержимое вашего пользовательского трафика обрабатывается на пути через выбранный сервер — см. раздел 6.</li>
|
||||
<li><strong>При проверке обновлений (не Store-сборка или по вашей инициативе):</strong> запрос к ленте обновлений (например, хостинг git.de4ima.uk) может включать стандартные технические данные HTTP-запроса (IP-адрес на стороне сервера хостинга, User-Agent, время запроса).</li>
|
||||
<li><strong>При открытии внешних ссылок:</strong> браузер или система передаёт данные соответствующему сайту/мессенджеру по их правилам.</li>
|
||||
</ul>
|
||||
<h4>3.3. Данные, которые вы можете прислать в поддержку</h4>
|
||||
<p>Если вы пишете нам (почта / Telegram / сайт), вы можете добровольно указать адрес, описание проблемы, скриншоты. Мы используем их только для ответа на обращение и улучшения продукта.</p>
|
||||
|
||||
<h3 id="s4">4. Что мы не собираем централизованно</h3>
|
||||
<p>EvilFox как издатель клиента <strong>не ведёт</strong> обязательный централизованный сбор и не продаёт:</p>
|
||||
<ul>
|
||||
<li>реестр аккаунтов пользователей клиента «в облаке EvilFox» для обычной работы приложения;</li>
|
||||
<li>платёжные карты и данные покупок внутри приложения (в Store-сборке покупок в приложении нет; покупки Store, если есть, обрабатывает Microsoft);</li>
|
||||
<li>историю браузера, контакты, микрофон/камеру по умолчанию;</li>
|
||||
<li>содержимое вашего интернет-трафика для рекламы или перепродажи данных.</li>
|
||||
</ul>
|
||||
<p>Мы <strong>не продаём</strong> персональные данные.</p>
|
||||
|
||||
<h3 id="s5">5. Цели и правовые основания</h3>
|
||||
<table>
|
||||
<thead><tr><th>Цель</th><th>Основание (GDPR / аналоги)</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Предоставление функций клиента (подключение, настройки, интерфейс)</td><td>Исполнение договора / запрос пользователя на предоставление сервиса клиента</td></tr>
|
||||
<tr><td>Безопасность и стабильность (локальные логи, предотвращение сбоев)</td><td>Законный интерес / необходимость для работы продукта</td></tr>
|
||||
<tr><td>Проверка обновлений (по действию пользователя или политике сборки)</td><td>Законный интерес / согласие-действие пользователя</td></tr>
|
||||
<tr><td>Ответы на обращения в поддержку</td><td>Законный интерес / исполнение запроса субъекта</td></tr>
|
||||
<tr><td>Соблюдение закона и защита прав</td><td>Правовая обязанность / законный интерес</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="s6">6. Сетевой трафик и сторонние серверы</h3>
|
||||
<p>После подключения ваш трафик направляется согласно выбранной конфигурации. Оператор узла может обрабатывать IP-адреса, метаданные сессии и (в зависимости от протокола и настроек) содержимое трафика. EvilFox не контролирует политику каждого стороннего сервера.</p>
|
||||
<p><strong>Рекомендация:</strong> используйте только доверенные конфигурации; читайте политику конфиденциальности и условия вашего провайдера серверов.</p>
|
||||
<p><strong>Режим полной защиты (TUN) и Kill Switch:</strong> эти функции изменяют сетевую маршрутизацию на устройстве для защиты соединения и снижения утечек при обрыве. Они работают локально на вашей системе.</p>
|
||||
|
||||
<h3 id="s7">7. Обновления приложения</h3>
|
||||
<ul>
|
||||
<li><strong>Microsoft Store:</strong> обновления обычно доставляет Store; встроенное самообновление в MSIX-сборке отключено или ограничено.</li>
|
||||
<li><strong>Иные каналы (sideload):</strong> по вашей инициативе клиент может запросить манифест обновлений с указанного издателем URL (в т.ч. на git.de4ima.uk). Сервер хостинга может видеть IP и технические заголовки запроса.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="s8">8. Внешние ссылки и сторонние сервисы</h3>
|
||||
<p>Приложение может открывать по вашему действию:</p>
|
||||
<ul>
|
||||
<li><a href="https://evilfox.win/">https://evilfox.win/</a> — сайт издателя;</li>
|
||||
<li><a href="https://t.me/evilfox100Bot">https://t.me/evilfox100Bot</a> — поддержка в Telegram (действует политика Telegram).</li>
|
||||
</ul>
|
||||
<p>Также на устройстве используются компоненты ОС и Microsoft Edge WebView2 — к ним применяются политики Microsoft.</p>
|
||||
|
||||
<h3 id="s9">9. Хранение, удаление и безопасность</h3>
|
||||
<h4>9.1. Срок хранения</h4>
|
||||
<p>Локальные данные хранятся, пока вы пользуетесь Приложением, либо до удаления профилей, очистки данных приложения или удаления EvilFox. Данные обращений в поддержку храним столько, сколько нужно для решения вопроса (как правило, не дольше, чем требуется для переписки и учёта обращений).</p>
|
||||
<h4>9.2. Как удалить данные</h4>
|
||||
<ul>
|
||||
<li>удалить конфигурации/профили в приложении;</li>
|
||||
<li>удалить приложение EvilFox в Windows;</li>
|
||||
<li>при необходимости очистить остаточные папки данных приложения вручную.</li>
|
||||
</ul>
|
||||
<p>Центрального «облачного аккаунта EvilFox» для восстановления локальных профилей нет — удалённое с устройства мы не можем вернуть с наших серверов.</p>
|
||||
<h4>9.3. Безопасность</h4>
|
||||
<p>Мы применяем разумные организационные и технические меры в рамках клиентского приложения и ОС. Абсолютной безопасности не существует. Вы отвечаете за сохранность URL, ключей и файлов конфигурации на устройстве и за физический доступ к компьютеру.</p>
|
||||
|
||||
<h3 id="s10">10. Передачи данных и международные аспекты</h3>
|
||||
<p>Клиент работает локально. Передача данных в другие страны может происходить, когда:</p>
|
||||
<ul>
|
||||
<li>вы подключаетесь к серверам за рубежом (по вашей конфигурации);</li>
|
||||
<li>вы открываете зарубежные сайты/сервисы;</li>
|
||||
<li>запрос обновлений обрабатывается инфраструктурой хостинга за пределами вашей страны.</li>
|
||||
</ul>
|
||||
<p>Для пользователей в ЕС/ЕЭЗ/Великобритании: такие передачи связаны с вашим выбором конфигурации и сервисов. При необходимости операторы серверов должны обеспечивать надлежащие гарантии (например, стандартные договорные условия) — уточняйте у провайдера конфигурации.</p>
|
||||
|
||||
<h3 id="s11">11. Ваши права (ЕС / GDPR и аналоги)</h3>
|
||||
<p>В зависимости от применимого права вы можете иметь права на:</p>
|
||||
<ul>
|
||||
<li>доступ к данным и получение копии;</li>
|
||||
<li>исправление неточных данных;</li>
|
||||
<li>удаление («право на забвение») — в т.ч. путём удаления приложения и локальных данных;</li>
|
||||
<li>ограничение обработки и возражение против обработки на основе законного интереса;</li>
|
||||
<li>переносимость данных — в той мере, в какой данные хранятся в структурированном виде на устройстве (экспорт конфигураций, если доступен);</li>
|
||||
<li>жалобу в надзорный орган по месту жительства/работы в ЕС/ЕЭЗ/Великобритании.</li>
|
||||
</ul>
|
||||
<p>Поскольку основная обработка идёт на устройстве, большинство прав реализуется вами локально. По вопросам, связанным с этой Политикой, пишите на контакты ниже. Мы ответим в разумный срок.</p>
|
||||
|
||||
<h3 id="s12">12. Калифорния (CCPA/CPRA) и пользователи в США</h3>
|
||||
<p>Мы <strong>не продаём</strong> и <strong>не «делимся»</strong> персональной информацией для контекстной кросс-рекламы в смысле CCPA/CPRA. Категории данных описаны в разделе 3. Источники — в основном вы (ввод конфигураций) и устройство. Мы не используем чувствительные данные для вывода характеристик потребителя в рекламных целях.</p>
|
||||
<p>Права на знание, удаление и исправление в отношении локальных данных клиента осуществляются на устройстве (удаление приложения/данных). Запросы, связанные с перепиской поддержки, можно направить через контакты в разделе 16.</p>
|
||||
<p>Мы не осуществляем дискриминацию за реализацию конфиденциальных прав.</p>
|
||||
|
||||
<h3 id="s13">13. Дети</h3>
|
||||
<p>Приложение не предназначено для детей младше <strong>13 лет</strong>. В ЕС/ЕЭЗ, где местное право устанавливает более высокий возраст цифрового согласия (часто до <strong>16 лет</strong>), не используйте Приложение ниже этого возраста без согласия родителя или опекуна, если оно требуется. Мы сознательно не собираем данные детей централизованно. Если вы считаете, что ребёнок передал нам данные через поддержку, свяжитесь с нами — мы удалим обращение, где это уместно.</p>
|
||||
|
||||
<h3 id="s14">14. Microsoft Store</h3>
|
||||
<p>При распространении через Microsoft Store корпорация Microsoft может обрабатывать данные установки, обновлений, отзывов и (при платных продуктах) платежей согласно своим политикам. Для возможностей вроде runFullTrust / классического рабочего стола могут применяться требования Store к прозрачности и разрешению ограниченных возможностей.</p>
|
||||
|
||||
<h3 id="s15">15. Изменения политики</h3>
|
||||
<p>Мы можем обновлять Политику. Актуальная версия публикуется на сайте (эта страница) и/или в разделе «Помощь» приложения. Существенные изменения по возможности отражаются датой в начале документа. Продолжение использования Приложения после обновления означает, что вы можете ознакомиться с новой редакцией.</p>
|
||||
|
||||
<h3 id="s16">16. Контакты</h3>
|
||||
<p>
|
||||
<strong>EvilFox</strong><br />
|
||||
Сайт: <a href="https://evilfox.win/">https://evilfox.win/</a><br />
|
||||
Основная почта: <a href="mailto:animegold3@gmail.com">animegold3@gmail.com</a><br />
|
||||
Резервная почта: <a href="mailto:admin@evilfox.win">admin@evilfox.win</a><br />
|
||||
Telegram: <a href="https://t.me/evilfox100Bot">https://t.me/evilfox100Bot</a><br />
|
||||
Пользовательское соглашение: <a href="terms-ru.html">terms-ru.html</a>
|
||||
</p>
|
||||
|
||||
<p class="nav-docs">English: <a href="privacy-en.html">Privacy Policy</a></p>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
<span>© 2026 EvilFox</span>
|
||||
<a href="terms-ru.html">Пользовательское соглашение</a>
|
||||
<a href="https://evilfox.win/">Сайт</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="refresh" content="0; url=privacy-ru.html" />
|
||||
<title>EvilFox — Privacy</title>
|
||||
<link rel="canonical" href="privacy-ru.html" />
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="privacy-ru.html">Политика конфиденциальности (RU)</a> · <a href="privacy-en.html">Privacy Policy (EN)</a></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Terms of Use — EvilFox</title>
|
||||
<meta name="description" content="EvilFox Terms of Use (User Agreement)." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&family=Outfit:wght@700;800&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4fafb;
|
||||
--ink: #0f172a;
|
||||
--muted: #64748b;
|
||||
--card: #ffffff;
|
||||
--line: rgba(15, 23, 42, 0.1);
|
||||
--accent: #0e7490;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Manrope, system-ui, sans-serif;
|
||||
color: var(--ink);
|
||||
background:
|
||||
radial-gradient(900px 420px at 10% -10%, rgba(34, 211, 238, 0.18), transparent 55%),
|
||||
radial-gradient(700px 360px at 100% 0%, rgba(14, 116, 144, 0.12), transparent 50%),
|
||||
var(--bg);
|
||||
line-height: 1.65;
|
||||
}
|
||||
.wrap { max-width: 820px; margin: 0 auto; padding: 32px 20px 64px; }
|
||||
header {
|
||||
display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
|
||||
gap: 16px; margin-bottom: 28px;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
|
||||
.logo {
|
||||
width: 44px; height: 44px; border-radius: 12px;
|
||||
display: grid; place-items: center;
|
||||
background: linear-gradient(145deg, #0e7490, #22d3ee);
|
||||
box-shadow: 0 10px 24px rgba(8, 145, 178, 0.28);
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.brand h1 {
|
||||
font-family: Outfit, sans-serif; font-size: 1.45rem; font-weight: 800;
|
||||
letter-spacing: -0.04em; margin: 0;
|
||||
}
|
||||
.brand span { display: block; font-size: .78rem; color: var(--muted); font-weight: 600; }
|
||||
.langs { display: flex; gap: 8px; }
|
||||
.langs a {
|
||||
text-decoration: none; font-weight: 700; font-size: .85rem;
|
||||
padding: 8px 14px; border-radius: 999px; border: 1px solid var(--line);
|
||||
color: var(--muted); background: var(--card);
|
||||
}
|
||||
.langs a.active, .langs a:hover {
|
||||
color: #fff; background: var(--accent); border-color: var(--accent);
|
||||
}
|
||||
.card {
|
||||
background: var(--card); border: 1px solid var(--line); border-radius: 20px;
|
||||
padding: 28px 26px; box-shadow: 0 18px 40px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
h2 {
|
||||
font-family: Outfit, sans-serif; font-size: 1.65rem; letter-spacing: -0.03em;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.meta { color: var(--muted); font-size: .92rem; margin: 0 0 24px; }
|
||||
h3 { font-size: 1.05rem; margin: 22px 0 8px; color: var(--accent); }
|
||||
p { margin: 0 0 12px; }
|
||||
footer {
|
||||
margin-top: 28px; font-size: .88rem; color: var(--muted);
|
||||
display: flex; flex-wrap: wrap; gap: 12px 20px;
|
||||
}
|
||||
footer a { color: var(--accent); font-weight: 700; text-decoration: none; }
|
||||
footer a:hover { text-decoration: underline; }
|
||||
.nav-docs { margin-top: 18px; font-size: .9rem; }
|
||||
.nav-docs a { color: var(--accent); font-weight: 700; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<a class="brand" href="https://evilfox.win/">
|
||||
<div class="logo" aria-hidden="true">🦊</div>
|
||||
<div>
|
||||
<h1>EvilFox</h1>
|
||||
<span>evilfox.win</span>
|
||||
</div>
|
||||
</a>
|
||||
<nav class="langs" aria-label="Language">
|
||||
<a href="terms-ru.html" lang="ru">Русский</a>
|
||||
<a class="active" href="terms-en.html" lang="en">English</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<article class="card">
|
||||
<h2>Terms of Use (User Agreement)</h2>
|
||||
<p class="meta">Publisher: EvilFox · Effective date: August 2, 2026</p>
|
||||
|
||||
<h3>1. Acceptance</h3>
|
||||
<p>By installing or using the EvilFox application (the “App”), you confirm that you have read and accept this User Agreement (Terms of Use). If you do not agree, do not install or use the App.</p>
|
||||
|
||||
<h3>2. License</h3>
|
||||
<p>You receive a limited, non-exclusive, non-transferable license to use the EvilFox client on your devices for personal or internal use, subject to these Terms and applicable law, including Microsoft Store rules if the App is installed from the Microsoft Store.</p>
|
||||
|
||||
<h3>3. Acceptable use</h3>
|
||||
<p>Use EvilFox only for lawful purposes. You must not violate the laws of your country or third-party rights, or attempt to disrupt the App or others’ systems. You are responsible for how you use the client and the servers you choose.</p>
|
||||
|
||||
<h3>4. User-supplied configurations</h3>
|
||||
<p>You provide connection URLs and parameters. EvilFox does not guarantee availability, quality, or legality of third-party servers. Review your configuration provider’s terms. Configurations are not part of a Microsoft Store purchase (where applicable).</p>
|
||||
|
||||
<h3>5. No warranty</h3>
|
||||
<p>The App is provided “as is” and “as available,” without warranties of any kind (express or implied, including merchantability and fitness for a particular purpose), to the fullest extent permitted by law.</p>
|
||||
|
||||
<h3>6. Limitation of liability</h3>
|
||||
<p>To the maximum extent permitted by law, EvilFox is not liable for indirect, incidental, special, or consequential damages, lost profits, data loss, or connectivity interruptions arising from use of the client or third-party servers. Mandatory consumer rights (including in the EU) remain unaffected.</p>
|
||||
|
||||
<h3>7. Updates</h3>
|
||||
<p>Microsoft Store builds update through the Store. Other (sideload) builds may offer updates via the publisher’s update feed. Features may change in new versions.</p>
|
||||
|
||||
<h3>8. Termination</h3>
|
||||
<p>You may stop using the App by uninstalling it. We may discontinue support for particular builds. If you breach these Terms, your right to use the App ends.</p>
|
||||
|
||||
<h3>9. Governing law</h3>
|
||||
<p>These Terms are interpreted under the laws of your country of residence unless mandatory rules require otherwise. EU consumers keep mandatory rights under the law of their country of residence.</p>
|
||||
|
||||
<h3>10. Contact</h3>
|
||||
<p>
|
||||
EvilFox · <a href="https://evilfox.win/">https://evilfox.win/</a><br />
|
||||
Primary: <a href="mailto:animegold3@gmail.com">animegold3@gmail.com</a> ·
|
||||
Backup: <a href="mailto:admin@evilfox.win">admin@evilfox.win</a> ·
|
||||
Telegram: <a href="https://t.me/evilfox100Bot">https://t.me/evilfox100Bot</a>
|
||||
</p>
|
||||
|
||||
<p class="nav-docs">See also: <a href="privacy-en.html">Privacy Policy</a></p>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
<span>© 2026 EvilFox</span>
|
||||
<a href="terms-ru.html">Русская версия</a>
|
||||
<a href="https://evilfox.win/">Website</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Пользовательское соглашение — EvilFox</title>
|
||||
<meta name="description" content="Пользовательское соглашение (условия использования) приложения EvilFox." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&family=Outfit:wght@700;800&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4fafb;
|
||||
--ink: #0f172a;
|
||||
--muted: #64748b;
|
||||
--card: #ffffff;
|
||||
--line: rgba(15, 23, 42, 0.1);
|
||||
--accent: #0e7490;
|
||||
--accent-soft: rgba(14, 116, 144, 0.1);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Manrope, system-ui, sans-serif;
|
||||
color: var(--ink);
|
||||
background:
|
||||
radial-gradient(900px 420px at 10% -10%, rgba(34, 211, 238, 0.18), transparent 55%),
|
||||
radial-gradient(700px 360px at 100% 0%, rgba(14, 116, 144, 0.12), transparent 50%),
|
||||
var(--bg);
|
||||
line-height: 1.65;
|
||||
}
|
||||
.wrap { max-width: 820px; margin: 0 auto; padding: 32px 20px 64px; }
|
||||
header {
|
||||
display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
|
||||
gap: 16px; margin-bottom: 28px;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
|
||||
.logo {
|
||||
width: 44px; height: 44px; border-radius: 12px;
|
||||
display: grid; place-items: center;
|
||||
background: linear-gradient(145deg, #0e7490, #22d3ee);
|
||||
box-shadow: 0 10px 24px rgba(8, 145, 178, 0.28);
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.brand h1 {
|
||||
font-family: Outfit, sans-serif; font-size: 1.45rem; font-weight: 800;
|
||||
letter-spacing: -0.04em; margin: 0;
|
||||
}
|
||||
.brand span { display: block; font-size: .78rem; color: var(--muted); font-weight: 600; }
|
||||
.langs { display: flex; gap: 8px; }
|
||||
.langs a {
|
||||
text-decoration: none; font-weight: 700; font-size: .85rem;
|
||||
padding: 8px 14px; border-radius: 999px; border: 1px solid var(--line);
|
||||
color: var(--muted); background: var(--card);
|
||||
}
|
||||
.langs a.active, .langs a:hover {
|
||||
color: #fff; background: var(--accent); border-color: var(--accent);
|
||||
}
|
||||
.card {
|
||||
background: var(--card); border: 1px solid var(--line); border-radius: 20px;
|
||||
padding: 28px 26px; box-shadow: 0 18px 40px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
h2 {
|
||||
font-family: Outfit, sans-serif; font-size: 1.65rem; letter-spacing: -0.03em;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.meta { color: var(--muted); font-size: .92rem; margin: 0 0 24px; }
|
||||
h3 { font-size: 1.05rem; margin: 22px 0 8px; color: var(--accent); }
|
||||
p, li { margin: 0 0 12px; }
|
||||
ul { padding-left: 1.2rem; margin: 0 0 12px; }
|
||||
footer {
|
||||
margin-top: 28px; font-size: .88rem; color: var(--muted);
|
||||
display: flex; flex-wrap: wrap; gap: 12px 20px;
|
||||
}
|
||||
footer a { color: var(--accent); font-weight: 700; text-decoration: none; }
|
||||
footer a:hover { text-decoration: underline; }
|
||||
.nav-docs { margin-top: 18px; font-size: .9rem; }
|
||||
.nav-docs a { color: var(--accent); font-weight: 700; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<a class="brand" href="https://evilfox.win/">
|
||||
<div class="logo" aria-hidden="true">🦊</div>
|
||||
<div>
|
||||
<h1>EvilFox</h1>
|
||||
<span>evilfox.win</span>
|
||||
</div>
|
||||
</a>
|
||||
<nav class="langs" aria-label="Language">
|
||||
<a class="active" href="terms-ru.html" lang="ru">Русский</a>
|
||||
<a href="terms-en.html" lang="en">English</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<article class="card">
|
||||
<h2>Пользовательское соглашение</h2>
|
||||
<p class="meta">Издатель: EvilFox · Дата вступления в силу: 2 августа 2026 г.</p>
|
||||
|
||||
<h3>1. Принятие условий</h3>
|
||||
<p>Устанавливая или используя приложение EvilFox («Приложение»), вы подтверждаете, что прочитали и принимаете настоящее Пользовательское соглашение (условия использования). Если вы не согласны с условиями — не устанавливайте и не используйте Приложение.</p>
|
||||
|
||||
<h3>2. Лицензия</h3>
|
||||
<p>Вам предоставляется ограниченная, неисключительная, непередаваемая лицензия на использование клиента EvilFox на ваших устройствах в личных или внутренних целях в соответствии с настоящим Соглашением и применимым правом, включая правила Microsoft Store (если Приложение установлено из Microsoft Store).</p>
|
||||
|
||||
<h3>3. Допустимое использование</h3>
|
||||
<p>Используйте EvilFox только законно. Запрещены действия, нарушающие законы вашей страны или права третьих лиц, а также попытки нарушить работу Приложения или чужих систем. Вы несёте ответственность за то, как используете клиент и выбранные серверы.</p>
|
||||
|
||||
<h3>4. Конфигурации пользователя</h3>
|
||||
<p>URL и параметры подключения задаёте вы. EvilFox не гарантирует доступность, качество или законность сторонних серверов. Ознакомьтесь с условиями вашего провайдера конфигурации. Конфигурации не являются частью покупки в Microsoft Store (если применимо).</p>
|
||||
|
||||
<h3>5. Отсутствие гарантий</h3>
|
||||
<p>Приложение предоставляется «как есть» и «по мере доступности», без явных или подразумеваемых гарантий (включая товарную пригодность и пригодность для конкретной цели), в пределах, допускаемых законом.</p>
|
||||
|
||||
<h3>6. Ограничение ответственности</h3>
|
||||
<p>В максимальной степени, допускаемой законом, EvilFox не несёт ответственности за косвенные, случайные, особые убытки, упущенную выгоду, потерю данных или перебои связи, связанные с использованием клиента или сторонних серверов. Обязательные права потребителей (в том числе в ЕС) сохраняются.</p>
|
||||
|
||||
<h3>7. Обновления</h3>
|
||||
<p>Сборка из Microsoft Store обновляется через Store. Иные (sideload) сборки могут предлагать обновление через ленту обновлений издателя. Функции могут меняться в новых версиях.</p>
|
||||
|
||||
<h3>8. Прекращение</h3>
|
||||
<p>Вы можете прекратить использование, удалив Приложение. Мы можем прекратить поддержку отдельных сборок. При нарушении Соглашения право на использование Приложения прекращается.</p>
|
||||
|
||||
<h3>9. Применимое право</h3>
|
||||
<p>Настоящее Соглашение толкуется по законам страны вашего проживания, если иное не требуется императивными нормами. Для потребителей в ЕС сохраняются обязательные права по праву страны проживания.</p>
|
||||
|
||||
<h3>10. Контакты</h3>
|
||||
<p>
|
||||
EvilFox · <a href="https://evilfox.win/">https://evilfox.win/</a><br />
|
||||
Основная: <a href="mailto:animegold3@gmail.com">animegold3@gmail.com</a> ·
|
||||
Резервная: <a href="mailto:admin@evilfox.win">admin@evilfox.win</a> ·
|
||||
Telegram: <a href="https://t.me/evilfox100Bot">https://t.me/evilfox100Bot</a>
|
||||
</p>
|
||||
|
||||
<p class="nav-docs">См. также: <a href="privacy-ru.html">Политика конфиденциальности</a></p>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
<span>© 2026 EvilFox</span>
|
||||
<a href="terms-en.html">English version</a>
|
||||
<a href="https://evilfox.win/">Сайт</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="refresh" content="0; url=terms-ru.html" />
|
||||
<title>EvilFox — Terms</title>
|
||||
<link rel="canonical" href="terms-ru.html" />
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="terms-ru.html">Пользовательское соглашение (RU)</a> · <a href="terms-en.html">Terms of Use (EN)</a></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,8 +13,8 @@ android {
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
// versionCode = major*1_000_000 + minor*10_000 + patch*100 + build
|
||||
versionCode = 3_100_001
|
||||
versionName = "3.10.0+1"
|
||||
versionCode = 4_028_001
|
||||
versionName = "4.2.8+1"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
|
||||
|
||||
@@ -94,7 +94,7 @@ fun NavisScreen(
|
||||
.padding(18.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Navis", fontSize = 28.sp, fontWeight = FontWeight.Black, color = Ink)
|
||||
Text("EvilFox", fontSize = 28.sp, fontWeight = FontWeight.Black, color = Ink)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Surface(color = Soft, shape = RoundedCornerShape(999.dp)) {
|
||||
Text("2.6.1", Modifier.padding(horizontal = 8.dp, vertical = 2.dp), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = AccentDeep)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Navis</string>
|
||||
<string name="vpn_notification_title">Navis VPN</string>
|
||||
<string name="app_name">EvilFox</string>
|
||||
<string name="vpn_notification_title">EvilFox VPN</string>
|
||||
<string name="vpn_notification_connected">Подключено</string>
|
||||
<string name="vpn_notification_channel">VPN</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="3.9.0.3"
|
||||
version="4.2.8.1"
|
||||
processorArchitecture="*"
|
||||
name="EvilFox.Navis"
|
||||
type="win32"/>
|
||||
|
||||
@@ -14,22 +14,27 @@ if errorlevel 1 (
|
||||
goversioninfo -64 -icon assets\navis.ico -manifest assets\app.manifest -o cmd\vpnapp\resource.syso versioninfo.json
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo Building Navis GUI and CLI...
|
||||
go build -ldflags="-H windowsgui -s -w" -trimpath -o Navis.exe ./cmd/vpnapp
|
||||
echo Building EvilFox GUI and CLI...
|
||||
go build -ldflags="-H windowsgui -s -w" -trimpath -o EvilFox.exe ./cmd/vpnapp
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
go build -ldflags="-s -w" -trimpath -o vpnclient.exe ./cmd/vpnclient
|
||||
go build -ldflags="-s -w" -trimpath -o evilfox-cli.exe ./cmd/vpnclient
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
mkdir "dist\navis-release\windows" 2>nul
|
||||
copy /Y Navis.exe "dist\navis-release\windows\Navis.exe" >nul
|
||||
copy /Y vpnclient.exe "dist\navis-release\windows\vpnclient.exe" >nul
|
||||
copy /Y EvilFox.exe "dist\navis-release\windows\EvilFox.exe" >nul
|
||||
REM Keep Navis.exe path for existing 3.x clients that fetch the old feed URL.
|
||||
copy /Y EvilFox.exe "dist\navis-release\windows\Navis.exe" >nul
|
||||
copy /Y evilfox-cli.exe "dist\navis-release\windows\evilfox-cli.exe" >nul
|
||||
copy /Y evilfox-cli.exe "dist\navis-release\windows\vpnclient.exe" >nul
|
||||
|
||||
echo.
|
||||
echo Done:
|
||||
echo Navis.exe
|
||||
echo vpnclient.exe
|
||||
echo dist\navis-release\windows\Navis.exe
|
||||
echo EvilFox.exe
|
||||
echo evilfox-cli.exe
|
||||
echo dist\navis-release\windows\EvilFox.exe
|
||||
echo dist\navis-release\windows\Navis.exe (compat alias for 3.x updater)
|
||||
echo dist\navis-release\windows\evilfox-cli.exe
|
||||
echo dist\navis-release\windows\vpnclient.exe
|
||||
echo assets\navis.ico
|
||||
endlocal
|
||||
|
||||
@@ -71,7 +71,7 @@ func main() {
|
||||
log.Printf("open UI: %v — откройте вручную: %s", err, uiURL)
|
||||
fmt.Println(uiURL)
|
||||
} else {
|
||||
log.Printf("Navis UI: %s", uiURL)
|
||||
log.Printf("EvilFox UI: %s", uiURL)
|
||||
}
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
@@ -96,7 +96,7 @@ func openAppWindow(uiURL string) error {
|
||||
func fatalf(format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
log.Println(msg)
|
||||
_ = exec.Command("osascript", "-e", fmt.Sprintf(`display alert "Navis" message %q`, msg)).Run()
|
||||
_ = exec.Command("osascript", "-e", fmt.Sprintf(`display alert "EvilFox" message %q`, msg)).Run()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Fprintln(os.Stderr, "Navis GUI supports Windows (WebView2) and macOS.")
|
||||
fmt.Fprintln(os.Stderr, "On this OS use the CLI: ./Navis connect | install-core | check-update")
|
||||
fmt.Fprintln(os.Stderr, "EvilFox GUI supports Windows (WebView2) and macOS.")
|
||||
fmt.Fprintln(os.Stderr, "On this OS use the CLI: ./evilfox-cli connect | install-core | check-update")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/jchv/go-webview2"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"vpnclient/internal/apphost"
|
||||
"vpnclient/internal/appui"
|
||||
"vpnclient/internal/config"
|
||||
"vpnclient/internal/core"
|
||||
@@ -60,11 +61,13 @@ type uiState struct {
|
||||
Remnawave remnawave.Settings `json:"remnawave"`
|
||||
Hy2 core.Hy2Options `json:"hy2"`
|
||||
StorePackaged bool `json:"store_packaged,omitempty"`
|
||||
ProvisionReady bool `json:"provision_ready,omitempty"`
|
||||
Account *remnawave.Account `json:"account,omitempty"`
|
||||
Mode string `json:"mode"`
|
||||
VPNSupported bool `json:"vpn_mode_supported"`
|
||||
VPNActive bool `json:"vpn_active"`
|
||||
// KillSwitch — persisted setting; KillSwitchActive — tunnel died and
|
||||
// traffic is intentionally blocked until disconnect / reconnect.
|
||||
KillSwitch bool `json:"kill_switch"`
|
||||
KillSwitchActive bool `json:"kill_switch_active,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -73,6 +76,10 @@ func main() {
|
||||
}
|
||||
update.CleanupStaleDownloads()
|
||||
|
||||
// Before any HWND: declare DPI awareness so CreateWindow/SetSize sizes
|
||||
// are physical pixels and we can convert DIPs correctly.
|
||||
enableProcessDPIAware()
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
cfgPath, err := config.LocateConfig()
|
||||
@@ -111,15 +118,17 @@ func main() {
|
||||
|
||||
// Do NOT auto-download cores on startup — silent network+write looks like malware to AV.
|
||||
|
||||
// Sidebar layout (Hiddify-style) needs a wider window than the old column UI.
|
||||
// Size from monitor work area in DIPs, converted to physical pixels for
|
||||
// DPI-aware CreateWindow (fixed 920x680 physical collapses CSS <700px on HiDPI).
|
||||
initW, initH := initialWindowPhysicalSize()
|
||||
w := webview2.NewWithOptions(webview2.WebViewOptions{
|
||||
Debug: false,
|
||||
AutoFocus: true,
|
||||
DataPath: dataPath,
|
||||
WindowOptions: webview2.WindowOptions{
|
||||
Title: "Navis 2",
|
||||
Width: 920,
|
||||
Height: 680,
|
||||
Title: "EvilFox",
|
||||
Width: uint(initW),
|
||||
Height: uint(initH),
|
||||
Center: true,
|
||||
IconId: 1,
|
||||
},
|
||||
@@ -134,7 +143,7 @@ func main() {
|
||||
}()
|
||||
|
||||
applyWindowIcon(w.Window())
|
||||
w.SetSize(920, 680, webview2.HintNone)
|
||||
applyAdaptiveWindowSize(w)
|
||||
|
||||
mustBind(w, "getState", a.getState)
|
||||
mustBind(w, "connect", a.connect)
|
||||
@@ -147,20 +156,19 @@ func main() {
|
||||
mustBind(w, "installCore", a.installCore)
|
||||
mustBind(w, "openURL", openURL)
|
||||
mustBind(w, "pingServers", a.pingServers)
|
||||
mustBind(w, "pingServer", a.pingServer)
|
||||
mustBind(w, "pingBest", a.pingBest)
|
||||
mustBind(w, "setKillSwitch", a.setKillSwitch)
|
||||
mustBind(w, "checkUpdate", a.checkUpdate)
|
||||
mustBind(w, "applyUpdate", a.applyUpdate)
|
||||
mustBind(w, "saveHy2", a.saveHy2)
|
||||
mustBind(w, "importSubscription", a.importSubscription)
|
||||
mustBind(w, "saveRemnawave", a.saveRemnawave)
|
||||
mustBind(w, "importRemnawave", a.importRemnawave)
|
||||
mustBind(w, "provisionUser", a.provisionUser)
|
||||
mustBind(w, "renewAccount", a.renewAccount)
|
||||
mustBind(w, "setMode", a.setMode)
|
||||
mustBind(w, "getLogs", a.getLogs)
|
||||
|
||||
go a.autoCheckUpdate()
|
||||
mgr.StartAutoRenew(6 * time.Hour)
|
||||
|
||||
w.SetHtml(appui.IndexHTML)
|
||||
w.Run()
|
||||
@@ -229,11 +237,11 @@ func (a *app) getState() (uiState, error) {
|
||||
Remnawave: a.mgr.RemnawaveSettings(),
|
||||
Hy2: a.mgr.ActiveHy2Options(),
|
||||
StorePackaged: update.IsStorePackaged(),
|
||||
ProvisionReady: a.mgr.ProvisionConfigured(),
|
||||
Account: a.mgr.Account(),
|
||||
Mode: a.mgr.Mode(),
|
||||
VPNSupported: a.mgr.VPNModeSupported(),
|
||||
VPNActive: st.VPNActive,
|
||||
KillSwitch: cfg.KillSwitch,
|
||||
KillSwitchActive: st.KillSwitchActive,
|
||||
}
|
||||
if out.Protocol == "" {
|
||||
if p, err := cfg.ActiveProfile(); err == nil {
|
||||
@@ -357,14 +365,6 @@ func (a *app) importRemnawave(s remnawave.Settings) (core.ImportResult, error) {
|
||||
return a.mgr.ImportRemnawave(s)
|
||||
}
|
||||
|
||||
func (a *app) provisionUser(username string) (core.ProvisionOutcome, error) {
|
||||
return a.mgr.ProvisionAccess(username)
|
||||
}
|
||||
|
||||
func (a *app) renewAccount() (core.ProvisionOutcome, error) {
|
||||
return a.mgr.RenewAccount()
|
||||
}
|
||||
|
||||
func (a *app) setMode(mode string) error {
|
||||
return a.mgr.SetMode(strings.TrimSpace(mode))
|
||||
}
|
||||
@@ -394,6 +394,50 @@ func (a *app) pingServers() ([]netcheck.Result, error) {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// pingServer measures latency of one server (📊 button on a server row)
|
||||
// and merges the result into the cached ping list.
|
||||
func (a *app) pingServer(name string) (netcheck.Result, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
a.mu.Lock()
|
||||
list := a.mgr.Profiles()
|
||||
a.mu.Unlock()
|
||||
var target *netcheck.Target
|
||||
for _, p := range list {
|
||||
if p.Name == name {
|
||||
target = &netcheck.Target{Name: p.Name, Protocol: config.Protocol(p.Protocol), Proxy: p.Proxy}
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == nil {
|
||||
return netcheck.Result{}, fmt.Errorf("сервер %q не найден", name)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
out := netcheck.PingAll(ctx, []netcheck.Target{*target})
|
||||
if len(out) == 0 {
|
||||
return netcheck.Result{}, fmt.Errorf("не удалось измерить пинг")
|
||||
}
|
||||
res := out[0]
|
||||
a.mu.Lock()
|
||||
replaced := false
|
||||
for i := range a.pings {
|
||||
if a.pings[i].Name == res.Name {
|
||||
a.pings[i] = res
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
a.pings = append(a.pings, res)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a *app) setKillSwitch(enabled bool) error {
|
||||
return a.mgr.SetKillSwitch(enabled)
|
||||
}
|
||||
|
||||
// pingBest measures all nodes, activates the fastest OK one, and optionally connects.
|
||||
func (a *app) pingBest(autoConnect bool) (pingBestResult, error) {
|
||||
pings, err := a.runPings()
|
||||
@@ -503,12 +547,12 @@ func (a *app) applyUpdate() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
_ = a.mgr.Disconnect()
|
||||
// Hard-exit so Windows unlocks Navis.exe; do not wait on webview.Terminate (can hang).
|
||||
// Hard-exit so Windows unlocks EvilFox.exe; do not wait on webview.Terminate (can hang).
|
||||
go func() {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
}()
|
||||
return "Устанавливается v" + latest + "… Navis перезапустится.", nil
|
||||
return "Устанавливается v" + latest + "… EvilFox перезапустится.", nil
|
||||
}
|
||||
|
||||
func (a *app) autoCheckUpdate() {
|
||||
@@ -531,21 +575,12 @@ func (a *app) autoCheckUpdate() {
|
||||
}
|
||||
|
||||
func openURL(raw string) error {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return fmt.Errorf("пустая ссылка")
|
||||
// Shared allowlist: shop + «Полезные ссылки» hosts only.
|
||||
normalized, err := apphost.LinkAllowed(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lower := strings.ToLower(raw)
|
||||
switch {
|
||||
case lower == "https://evilfox.win/" || lower == "https://evilfox.win" ||
|
||||
lower == "http://evilfox.win/" || lower == "http://evilfox.win":
|
||||
raw = "https://evilfox.win/"
|
||||
case strings.HasPrefix(lower, "https://evilfox.win/") || strings.HasPrefix(lower, "http://evilfox.win/"):
|
||||
// allow shop deep-links on evilfox.win only
|
||||
default:
|
||||
return fmt.Errorf("разрешена только ссылка evilfox.win")
|
||||
}
|
||||
return shellOpen(raw)
|
||||
return shellOpen(normalized)
|
||||
}
|
||||
|
||||
func shellOpen(raw string) error {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func messageBox(text string) {
|
||||
user32 := syscall.NewLazyDLL("user32.dll")
|
||||
proc := user32.NewProc("MessageBoxW")
|
||||
title, _ := syscall.UTF16PtrFromString("Navis")
|
||||
title, _ := syscall.UTF16PtrFromString("EvilFox")
|
||||
body, _ := syscall.UTF16PtrFromString(text)
|
||||
proc.Call(0, uintptr(unsafe.Pointer(body)), uintptr(unsafe.Pointer(title)), 0x10) // MB_ICONERROR
|
||||
}
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/jchv/go-webview2"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = (DPI_AWARENESS_CONTEXT)-4
|
||||
const dpiAwarenessContextPerMonitorAwareV2 = ^uintptr(3)
|
||||
|
||||
const (
|
||||
spiGetWorkArea = 0x0030
|
||||
monitorDefaultToNearest = 2
|
||||
)
|
||||
|
||||
type monitorInfo struct {
|
||||
CbSize uint32
|
||||
RcMonitor windows.Rect
|
||||
RcWork windows.Rect
|
||||
DwFlags uint32
|
||||
}
|
||||
|
||||
// enableProcessDPIAware opts into per-monitor DPI before any HWND is created.
|
||||
// The embedded app.manifest already declares PerMonitorV2; this is a belt-and-
|
||||
// suspenders call for builds that lose the manifest resource.
|
||||
func enableProcessDPIAware() {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("SetProcessDpiAwarenessContext"); p.Find() == nil {
|
||||
_, _, _ = p.Call(dpiAwarenessContextPerMonitorAwareV2)
|
||||
return
|
||||
}
|
||||
if p := user32.NewProc("SetProcessDPIAware"); p.Find() == nil {
|
||||
_, _, _ = p.Call()
|
||||
}
|
||||
}
|
||||
|
||||
func systemDPI() int {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("GetDpiForSystem"); p.Find() == nil {
|
||||
if r, _, _ := p.Call(); r >= 72 && r <= 480 {
|
||||
return int(r)
|
||||
}
|
||||
}
|
||||
return 96
|
||||
}
|
||||
|
||||
func windowDPI(hwnd uintptr) int {
|
||||
if hwnd != 0 {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("GetDpiForWindow"); p.Find() == nil {
|
||||
if r, _, _ := p.Call(hwnd); r >= 72 && r <= 480 {
|
||||
return int(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return systemDPI()
|
||||
}
|
||||
|
||||
func dipsToPhysical(dips, dpi int) int {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
return (dips*dpi + 48) / 96
|
||||
}
|
||||
|
||||
func physicalToDIPs(px, dpi int) int {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
return (px*96 + dpi/2) / dpi
|
||||
}
|
||||
|
||||
func primaryWorkAreaPhysical() (w, h int, ok bool) {
|
||||
var r windows.Rect
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
p := user32.NewProc("SystemParametersInfoW")
|
||||
ret, _, _ := p.Call(spiGetWorkArea, 0, uintptr(unsafe.Pointer(&r)), 0)
|
||||
if ret == 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
return int(r.Right - r.Left), int(r.Bottom - r.Top), true
|
||||
}
|
||||
|
||||
func monitorWorkAreaPhysical(hwnd uintptr) (w, h int, ok bool) {
|
||||
if hwnd == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
fromWin := user32.NewProc("MonitorFromWindow")
|
||||
getInfo := user32.NewProc("GetMonitorInfoW")
|
||||
if fromWin.Find() != nil || getInfo.Find() != nil {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
hmon, _, _ := fromWin.Call(hwnd, monitorDefaultToNearest)
|
||||
if hmon == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
var mi monitorInfo
|
||||
mi.CbSize = uint32(unsafe.Sizeof(mi))
|
||||
ret, _, _ := getInfo.Call(hmon, uintptr(unsafe.Pointer(&mi)))
|
||||
if ret == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
return int(mi.RcWork.Right - mi.RcWork.Left), int(mi.RcWork.Bottom - mi.RcWork.Top), true
|
||||
}
|
||||
|
||||
// computeClientSizeDIPs picks a comfortable client size in DIPs from the
|
||||
// monitor work area (also expressed as physical px + dpi).
|
||||
// Target: ~min(1100, workW*0.72) x min(780, workH*0.78), floored near
|
||||
// 960x680 when the screen allows, always capped with margins.
|
||||
func computeClientSizeDIPs(workWPhys, workHPhys, dpi int) (w, h int) {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
workW := physicalToDIPs(workWPhys, dpi)
|
||||
workH := physicalToDIPs(workHPhys, dpi)
|
||||
if workW < 320 {
|
||||
workW = 320
|
||||
}
|
||||
if workH < 240 {
|
||||
workH = 240
|
||||
}
|
||||
|
||||
const marginX, marginY = 64, 64
|
||||
maxW := workW - marginX
|
||||
maxH := workH - marginY
|
||||
if maxW < 480 {
|
||||
maxW = workW
|
||||
}
|
||||
if maxH < 360 {
|
||||
maxH = workH
|
||||
}
|
||||
if maxW < 480 {
|
||||
maxW = 480
|
||||
}
|
||||
if maxH < 360 {
|
||||
maxH = 360
|
||||
}
|
||||
|
||||
prefW := int(float64(workW) * 0.72)
|
||||
if prefW > 1100 {
|
||||
prefW = 1100
|
||||
}
|
||||
prefH := int(float64(workH) * 0.78)
|
||||
if prefH > 780 {
|
||||
prefH = 780
|
||||
}
|
||||
|
||||
if prefW < 960 && maxW >= 960 {
|
||||
prefW = 960
|
||||
}
|
||||
if prefH < 680 && maxH >= 680 {
|
||||
prefH = 680
|
||||
}
|
||||
|
||||
if prefW > maxW {
|
||||
prefW = maxW
|
||||
}
|
||||
if prefH > maxH {
|
||||
prefH = maxH
|
||||
}
|
||||
if prefW < 640 && maxW >= 640 {
|
||||
prefW = 640
|
||||
}
|
||||
if prefH < 480 && maxH >= 480 {
|
||||
prefH = 480
|
||||
}
|
||||
return prefW, prefH
|
||||
}
|
||||
|
||||
// initialWindowPhysicalSize estimates CreateWindow size before an HWND exists
|
||||
// (primary work area + system DPI). go-webview2 treats Width/Height as physical
|
||||
// pixels when the process is DPI-aware.
|
||||
func initialWindowPhysicalSize() (w, h int) {
|
||||
dpi := systemDPI()
|
||||
ww, wh, ok := primaryWorkAreaPhysical()
|
||||
if !ok || ww <= 0 || wh <= 0 {
|
||||
return dipsToPhysical(960, dpi), dipsToPhysical(680, dpi)
|
||||
}
|
||||
dipW, dipH := computeClientSizeDIPs(ww, wh, dpi)
|
||||
return dipsToPhysical(dipW, dpi), dipsToPhysical(dipH, dpi)
|
||||
}
|
||||
|
||||
// applyAdaptiveWindowSize sets HintMin (~720x520 DIPs) then the preferred
|
||||
// client size from the window's monitor work area, both in physical pixels.
|
||||
func applyAdaptiveWindowSize(w webview2.WebView) {
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
hwnd := uintptr(w.Window())
|
||||
dpi := windowDPI(hwnd)
|
||||
ww, wh, ok := monitorWorkAreaPhysical(hwnd)
|
||||
if !ok || ww <= 0 || wh <= 0 {
|
||||
ww, wh, _ = primaryWorkAreaPhysical()
|
||||
}
|
||||
dipW, dipH := computeClientSizeDIPs(ww, wh, dpi)
|
||||
physW := dipsToPhysical(dipW, dpi)
|
||||
physH := dipsToPhysical(dipH, dpi)
|
||||
|
||||
minW := dipsToPhysical(720, dpi)
|
||||
minH := dipsToPhysical(520, dpi)
|
||||
if minW > physW {
|
||||
minW = physW
|
||||
}
|
||||
if minH > physH {
|
||||
minH = physH
|
||||
}
|
||||
|
||||
w.SetSize(minW, minH, webview2.HintMin)
|
||||
w.SetSize(physW, physH, webview2.HintNone)
|
||||
}
|
||||
@@ -59,16 +59,16 @@ func main() {
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprintf(os.Stderr, `Navis VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan) — Windows / macOS
|
||||
fmt.Fprintf(os.Stderr, `EvilFox VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan) — Windows / macOS
|
||||
|
||||
Usage:
|
||||
navis init [-config configs/config.json]
|
||||
navis install-core [-config configs/config.json]
|
||||
navis set-proxy [-config configs/config.json] <share-or-proxy-uri>
|
||||
navis connect [-config configs/config.json] [-profile name] [-no-sysproxy] [-probe]
|
||||
navis probe [-config configs/config.json] [-profile name] [-url URL]
|
||||
navis check-update
|
||||
navis apply-update
|
||||
evilfox-cli init [-config configs/config.json]
|
||||
evilfox-cli install-core [-config configs/config.json]
|
||||
evilfox-cli set-proxy [-config configs/config.json] <share-or-proxy-uri>
|
||||
evilfox-cli connect [-config configs/config.json] [-profile name] [-no-sysproxy] [-probe]
|
||||
evilfox-cli probe [-config configs/config.json] [-profile name] [-url URL]
|
||||
evilfox-cli check-update
|
||||
evilfox-cli apply-update
|
||||
|
||||
Share links: naive+https://… hysteria2://… vless://… vmess://… trojan://… awg .conf
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"panel_url": "https://panel.example.com",
|
||||
"api_token": "PASTE_API_TOKEN_HERE",
|
||||
"caddy_api_key": "",
|
||||
"provision": {
|
||||
"traffic_gb": 50,
|
||||
"days": 30,
|
||||
"strategy": "MONTH",
|
||||
"hwid_device_limit": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
In addition to the standard Microsoft Store / app license terms:
|
||||
|
||||
1. EvilFox is a client for protected network connectivity. Server configurations and connection URLs are supplied by you and are not part of any Store purchase (where applicable).
|
||||
|
||||
2. You are responsible for using the app and chosen servers lawfully in your jurisdiction.
|
||||
|
||||
3. The publisher does not warrant uninterrupted operation of third-party servers, speed, or availability of specific nodes.
|
||||
|
||||
4. The app may store settings and configurations locally on your device. The Privacy Policy and full Terms of Use are available in the app Help section and at https://evilfox.win/
|
||||
|
||||
5. Updates delivered via the Microsoft Store replace in-app self-update in Store builds.
|
||||
|
||||
6. The runFullTrust / classic desktop capability is required for the client to function (WebView2, network modes).
|
||||
|
||||
Contact: https://evilfox.win/ · https://t.me/evilfox100Bot
|
||||
@@ -0,0 +1,15 @@
|
||||
Дополнительно к стандартным условиям лицензии Microsoft Store / приложения:
|
||||
|
||||
1. EvilFox — клиент для защищённого сетевого подключения. Конфигурации серверов и URL подключения предоставляются вами и не являются частью покупки в Store (если применимо).
|
||||
|
||||
2. Вы несёте ответственность за законность использования приложения и выбранных серверов в вашей юрисдикции.
|
||||
|
||||
3. Издатель не гарантирует бесперебойную работу сторонних серверов, скорость или доступность конкретных узлов.
|
||||
|
||||
4. Приложение может хранить настройки и конфигурации локально на устройстве. Политика конфиденциальности и полные условия использования доступны в разделе «Помощь» приложения и на https://evilfox.win/
|
||||
|
||||
5. Обновления через Microsoft Store заменяют самообновление приложения в сборке Store.
|
||||
|
||||
6. Возможность runFullTrust / классическое настольное приложение требуется для работы клиента (WebView2, сетевые режимы).
|
||||
|
||||
Контакты: https://evilfox.win/ · https://t.me/evilfox100Bot
|
||||
@@ -0,0 +1 @@
|
||||
© 2026 EvilFox. All rights reserved. EvilFox, the EvilFox logo, and related marks are trademarks or registered trademarks of EvilFox. Other product and company names mentioned in the app or product listing may be trademarks of their respective owners.
|
||||
@@ -0,0 +1 @@
|
||||
© 2026 EvilFox. Все права защищены. EvilFox, логотип EvilFox и связанные знаки являются товарными знаками или зарегистрированными товарными знаками EvilFox. Прочие названия продуктов и компаний, упомянутые в приложении или на странице продукта, могут быть товарными знаками соответствующих владельцев.
|
||||
@@ -0,0 +1,57 @@
|
||||
EvilFox Microsoft Store package folder
|
||||
Version: 4.2.9.0
|
||||
Generated: 2026-08-02 15:53:56
|
||||
|
||||
=== Partner Center upload (EN) ===
|
||||
1. Sign in to Partner Center with the same Microsoft Store developer account
|
||||
you use for Visual Studio packaging / Store association (same tenant).
|
||||
2. Open the EvilFox product -> Packages (Windows 10 / 11).
|
||||
3. Prefer uploading EvilFox_4.2.9.0.msixbundle if present.
|
||||
Otherwise upload each EvilFox_4.2.9.0_<arch>.msix (x64 + arm64).
|
||||
4. Device family: check Windows 10 / 11 Desktop ONLY
|
||||
(do NOT select Xbox, HoloLens, Surface Hub, IoT, Team, etc.).
|
||||
5. Arm64 package covers Windows on ARM. Do not ship x86 / AArch32;
|
||||
Microsoft is dropping AArch32 on future WoA.
|
||||
6. Before final Store signing, replace Identity Name/Publisher with the
|
||||
exact values from Partner Center (or let Partner Center re-sign).
|
||||
7. Privacy policy URL on https://evilfox.win is required for Store listing.
|
||||
|
||||
=== Partner Center (RU) ===
|
||||
1. Войдите в Partner Center тем же аккаунтом разработчика Store,
|
||||
что и в Visual Studio (ассоциация приложения / упаковка).
|
||||
2. Загрузите EvilFox_4.2.9.0.msixbundle, если есть; иначе оба .msix
|
||||
(x64 и arm64).
|
||||
3. Семейство устройств: только Windows 10/11 Desktop
|
||||
(без Xbox / HoloLens / IoT и т.п.).
|
||||
4. Arm64 = Windows on ARM; x86/AArch32 не публикуем.
|
||||
5. Name/Publisher в манифесте должны совпадать с Partner Center
|
||||
(или Store переподпишет пакет).
|
||||
6. Нужен URL политики конфиденциальности на evilfox.win.
|
||||
|
||||
=== Artifacts ===
|
||||
Bundle: D:\vpn navi\dist\microsoft-store\EvilFox_4.2.9.0.msixbundle
|
||||
MSIX: D:\vpn navi\dist\microsoft-store\EvilFox_4.2.9.0_x64.msix
|
||||
MSIX: D:\vpn navi\dist\microsoft-store\EvilFox_4.2.9.0_arm64.msix
|
||||
Staging x64: D:\vpn navi\packaging\msix\_staging_x64
|
||||
Staging arm64: D:\vpn navi\packaging\msix\_staging_arm64
|
||||
|
||||
=== MakeAppx ===
|
||||
Found: C:\Users\orohi\AppData\Local\Microsoft\WinGet\Packages\Microsoft.MSIX-Toolkit_Microsoft.Winget.Source_8wekyb3d8bbwe\MSIX-Toolkit.x64\makeappx.exe
|
||||
|
||||
=== wintun.dll ===
|
||||
wintun.dll was NOT found under assets/ or dist/. Packaging continued.
|
||||
VPN (TUN) mode needs wintun.dll next to EvilFox.exe.
|
||||
Store users can still use proxy mode without it.
|
||||
Add arch-specific wintun.dll later under assets/wintun/<arch>/ if needed.
|
||||
|
||||
=== Listing assets ===
|
||||
listing\store.csv - Partner Center bulk listing fields
|
||||
listing\screen\ - screenshots for Store listing
|
||||
|
||||
=== Arm64 build note ===
|
||||
Cross-build (CGO_ENABLED=0):
|
||||
set GOOS=windows
|
||||
set GOARCH=arm64
|
||||
rem temporarily move cmd\vpnapp\resource.syso (amd64 from goversioninfo -64)
|
||||
go build -ldflags="-H windowsgui -s -w" -trimpath -o dist\navis-release\windows\arm64\EvilFox.exe ./cmd/vpnapp
|
||||
rem restore resource.syso
|
||||
@@ -0,0 +1,10 @@
|
||||
EvilFox 4.2.9 — a clear client for private internet access.
|
||||
|
||||
What's new:
|
||||
• Add configuration: AWG / AWG 2.0 (.conf, JSON, awg://) and Naive proxy — not only http(s) subscription URLs
|
||||
• Clash wireguard / naive bodies when fetched from a config URL
|
||||
• Support contacts and FAQ on the website
|
||||
• Modern UI: Protection, My servers, Settings, Statistics, Help
|
||||
• Light/dark themes, Russian and English
|
||||
• Country flags, server grouping, Kill Switch, auto-select by ping
|
||||
• Privacy Policy and Terms in Help
|
||||
@@ -0,0 +1,10 @@
|
||||
EvilFox 4.2.9 — удобный клиент для безопасного доступа в интернет.
|
||||
|
||||
Что нового:
|
||||
• Добавление конфигурации: AWG / AWG 2.0 (.conf, JSON, awg://) и Naive-прокси — не только http(s) URL подписки
|
||||
• Поддержка Clash wireguard / naive в теле конфигурации по URL
|
||||
• Контакты поддержки и FAQ на сайте
|
||||
• Современный интерфейс: Защита, Мои серверы, Настройки, Статистика, Помощь
|
||||
• Светлая/тёмная тема, русский и английский
|
||||
• Флаги стран, группировка серверов, Kill Switch, автовыбор по пингу
|
||||
• Политика конфиденциальности и условия в разделе Помощь
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 166 KiB |
@@ -0,0 +1,548 @@
|
||||
Field,ID,Type (Тип),default,en
|
||||
Description,2,Текст,"EvilFox — приложение для безопасного доступа в интернет на Windows.
|
||||
|
||||
Защитите данные в публичных Wi‑Fi сетях: EvilFox шифрует трафик, скрывает ваш реальный IP-адрес и помогает предотвратить утечки DNS. Современный интерфейс на русском и английском, светлая и тёмная темы, удобный выбор серверов и понятный статус подключения.
|
||||
|
||||
Возможности:
|
||||
• Шифрование трафика с поддержкой современных протоколов
|
||||
• Режим прокси и полный режим защиты (TUN) для всего трафика приложений
|
||||
• Защита от утечек (Kill Switch) при обрыве соединения
|
||||
• Автовыбор сервера с наименьшим пингом
|
||||
• Список серверов с флагами стран и группировкой по хосту
|
||||
• Добавление собственной конфигурации по URL
|
||||
• Мониторинг использования трафика и статистика сессий
|
||||
• Интерфейс на русском и английском языках
|
||||
|
||||
EvilFox подходит для повседневной работы, путешествий и защиты приватности. Конфигурацию подключения вы добавляете самостоятельно; покупки внутри приложения не требуются.
|
||||
|
||||
Сайт: https://evilfox.win/
|
||||
Поддержка: https://t.me/evilfox100Bot","EvilFox is a Windows app for private, secure internet access.
|
||||
|
||||
Protect your data on public Wi‑Fi: EvilFox encrypts your traffic, hides your real IP address, and helps prevent DNS leaks. A modern interface in English and Russian, light and dark themes, an easy server list, and a clear connection status.
|
||||
|
||||
Features:
|
||||
• Traffic encryption with modern protocol support
|
||||
• Proxy mode and full protection mode (TUN) for all app traffic
|
||||
• Leak protection (Kill Switch) if the connection drops
|
||||
• Auto-select the server with the lowest ping
|
||||
• Server list with country flags and host grouping
|
||||
• Add your own configuration via URL
|
||||
• Traffic usage monitoring and session stats
|
||||
• English and Russian interface
|
||||
|
||||
EvilFox is built for everyday browsing, travel, and privacy. You add your own connection configuration; no in-app purchases are required.
|
||||
|
||||
Website: https://evilfox.win/
|
||||
Support: https://t.me/evilfox100Bot"
|
||||
ReleaseNotes,3,Текст,"EvilFox 4.2 — удобный и понятный клиент для безопасного доступа в интернет.
|
||||
|
||||
Что нового для пользователей:
|
||||
• Современный интерфейс: вкладки Защита, Мои серверы, Настройки, Статистика, Помощь
|
||||
• Большая кнопка подключения и понятный статус защиты
|
||||
• Светлая тема по умолчанию и переключение на тёмную
|
||||
• Языки: русский и английский (RU | EN)
|
||||
• Флаги стран у серверов (корректно на Windows)
|
||||
• Серверы с одним IP объединяются в одну строку с доступными протоколами
|
||||
• Добавление конфигурации по URL
|
||||
• Режим прокси и полный режим защиты (TUN)
|
||||
• Kill Switch — защита от утечек при обрыве соединения
|
||||
• Автовыбор сервера с лучшим пингом
|
||||
• Статистика трафика и сессий
|
||||
• Политика конфиденциальности и условия использования в разделе Помощь
|
||||
• Адаптивное окно под любой экран и масштаб Windows","EvilFox 4.2 — a clearer, more polished client for private internet access.
|
||||
|
||||
What's new:
|
||||
• Modern UI: Protection, My servers, Settings, Statistics, Help
|
||||
• Large connect button and clear protection status
|
||||
• Light theme by default, with dark theme toggle
|
||||
• Russian and English languages (RU | EN)
|
||||
• Country flags for servers (rendered correctly on Windows)
|
||||
• Same-IP servers grouped into one row with available protocols
|
||||
• Add configuration via URL
|
||||
• Proxy mode and full protection mode (TUN)
|
||||
• Kill Switch — leak protection if the connection drops
|
||||
• Auto-select the lowest-ping server
|
||||
• Traffic and session statistics
|
||||
• Privacy Policy and Terms of Use in Help
|
||||
• Adaptive window sizing for any screen and Windows display scale"
|
||||
Title,4,Текст,EvilFox,EvilFox
|
||||
ShortTitle,5,Текст,EvilFox,EvilFox
|
||||
SortTitle,6,Текст,EvilFox,EvilFox
|
||||
VoiceTitle,7,Текст,Evil Fox,Evil Fox
|
||||
ShortDescription,8,Текст,"Безопасный доступ в интернет: шифрование трафика, скрытие IP, защита от утечек DNS. Русский и английский. Современные протоколы, Kill Switch, удобный выбор серверов.","Secure internet access: encrypt traffic, hide your IP, help prevent DNS leaks. English & Russian. Modern protocols, Kill Switch, easy server selection."
|
||||
DevStudio,9,Текст,EvilFox,EvilFox
|
||||
CopyrightTrademarkInformation,12,Текст,"© 2026 EvilFox. Все права защищены. EvilFox, логотип EvilFox и связанные знаки являются товарными знаками или зарегистрированными товарными знаками EvilFox. Прочие названия продуктов и компаний, упомянутые в приложении или на странице продукта, могут быть товарными знаками соответствующих владельцев.","© 2026 EvilFox. All rights reserved. EvilFox, the EvilFox logo, and related marks are trademarks or registered trademarks of EvilFox. Other product and company names mentioned in the app or product listing may be trademarks of their respective owners."
|
||||
AdditionalLicenseTerms,13,Текст,"Дополнительно к стандартным условиям лицензии Microsoft Store / приложения:
|
||||
|
||||
1. EvilFox — клиент для защищённого сетевого подключения. Конфигурации серверов и URL подключения предоставляются вами и не являются частью покупки в Store (если применимо).
|
||||
|
||||
2. Вы несёте ответственность за законность использования приложения и выбранных серверов в вашей юрисдикции.
|
||||
|
||||
3. Издатель не гарантирует бесперебойную работу сторонних серверов, скорость или доступность конкретных узлов.
|
||||
|
||||
4. Приложение может хранить настройки и конфигурации локально на устройстве. Политика конфиденциальности и полные условия использования доступны в разделе «Помощь» приложения и на https://evilfox.win/
|
||||
|
||||
5. Обновления через Microsoft Store заменяют самообновление приложения в сборке Store.
|
||||
|
||||
6. Возможность runFullTrust / классическое настольное приложение требуется для работы клиента (WebView2, сетевые режимы).
|
||||
|
||||
Контакты: https://evilfox.win/ · https://t.me/evilfox100Bot
|
||||
","In addition to the standard Microsoft Store / app license terms:
|
||||
|
||||
1. EvilFox is a client for protected network connectivity. Server configurations and connection URLs are supplied by you and are not part of any Store purchase (where applicable).
|
||||
|
||||
2. You are responsible for using the app and chosen servers lawfully in your jurisdiction.
|
||||
|
||||
3. The publisher does not warrant uninterrupted operation of third-party servers, speed, or availability of specific nodes.
|
||||
|
||||
4. The app may store settings and configurations locally on your device. The Privacy Policy and full Terms of Use are available in the app Help section and at https://evilfox.win/
|
||||
|
||||
5. Updates delivered via the Microsoft Store replace in-app self-update in Store builds.
|
||||
|
||||
6. The runFullTrust / classic desktop capability is required for the client to function (WebView2, network modes).
|
||||
|
||||
Contact: https://evilfox.win/ · https://t.me/evilfox100Bot
|
||||
"
|
||||
DesktopScreenshot1,100,Относительный путь (или URL-адрес файла в Центре партнеров),screen/1.ru.png,screen/7.en.png
|
||||
DesktopScreenshot2,101,Относительный путь (или URL-адрес файла в Центре партнеров),screen/2.ru.png,screen/8.en.png
|
||||
DesktopScreenshot3,102,Относительный путь (или URL-адрес файла в Центре партнеров),screen/3.ru.png,screen/9.png
|
||||
DesktopScreenshot4,103,Относительный путь (или URL-адрес файла в Центре партнеров),screen/4.ru.png,screen/10.en.png
|
||||
DesktopScreenshot5,104,Относительный путь (или URL-адрес файла в Центре партнеров),screen/5.ru.png,screen/11.en.png
|
||||
DesktopScreenshot6,105,Относительный путь (или URL-адрес файла в Центре партнеров),screen/6.ru.png,screen/12dark.png
|
||||
DesktopScreenshot7,106,Относительный путь (или URL-адрес файла в Центре партнеров),screen/12dark.png,screen/13dark.png
|
||||
DesktopScreenshot8,107,Относительный путь (или URL-адрес файла в Центре партнеров),screen/13dark.png,screen/14dark.png
|
||||
DesktopScreenshot9,108,Относительный путь (или URL-адрес файла в Центре партнеров),screen/14dark.png,screen/15dark.png
|
||||
DesktopScreenshot10,109,Относительный путь (или URL-адрес файла в Центре партнеров),screen/15dark.png,screen/16dark.png
|
||||
DesktopScreenshot11,110,Относительный путь (или URL-адрес файла в Центре партнеров),screen/16dark.png,
|
||||
DesktopScreenshot12,111,Относительный путь (или URL-адрес файла в Центре партнеров),screen/9.png,
|
||||
DesktopScreenshot13,112,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot14,113,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot15,114,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot16,115,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot17,116,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot18,117,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot19,118,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot20,119,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot21,120,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot22,121,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot23,122,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot24,123,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot25,124,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot26,125,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot27,126,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot28,127,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot29,128,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot30,129,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshotCaption1,150,Текст,Главный экран: подключение и статус защиты,Home: connect and protection status
|
||||
DesktopScreenshotCaption2,151,Текст,Список серверов с флагами и пингом,Server list with flags and ping
|
||||
DesktopScreenshotCaption3,152,Текст,"Настройки: тема, язык, Kill Switch","Settings: theme, language, Kill Switch"
|
||||
DesktopScreenshotCaption4,153,Текст,Статистика использования,Usage statistics
|
||||
DesktopScreenshotCaption5,154,Текст,Помощь и полезные ссылки,Statistics and help
|
||||
DesktopScreenshotCaption6,155,Текст,Добавление конфигурации,Dark theme: home
|
||||
DesktopScreenshotCaption7,156,Текст,Тёмная тема: главный экран,Dark theme: servers
|
||||
DesktopScreenshotCaption8,157,Текст,Тёмная тема: серверы,Dark theme: settings
|
||||
DesktopScreenshotCaption9,158,Текст,Тёмная тема: настройки,Dark theme: statistics
|
||||
DesktopScreenshotCaption10,159,Текст,Тёмная тема: статистика,Dark theme: help
|
||||
DesktopScreenshotCaption11,160,Текст,Тёмная тема: помощь,Dark theme: help
|
||||
DesktopScreenshotCaption12,161,Текст,Общий вид приложения,App overview
|
||||
DesktopScreenshotCaption13,162,Текст,,
|
||||
DesktopScreenshotCaption14,163,Текст,,
|
||||
DesktopScreenshotCaption15,164,Текст,,
|
||||
DesktopScreenshotCaption16,165,Текст,,
|
||||
DesktopScreenshotCaption17,166,Текст,,
|
||||
DesktopScreenshotCaption18,167,Текст,,
|
||||
DesktopScreenshotCaption19,168,Текст,,
|
||||
DesktopScreenshotCaption20,169,Текст,,
|
||||
DesktopScreenshotCaption21,170,Текст,,
|
||||
DesktopScreenshotCaption22,171,Текст,,
|
||||
DesktopScreenshotCaption23,172,Текст,,
|
||||
DesktopScreenshotCaption24,173,Текст,,
|
||||
DesktopScreenshotCaption25,174,Текст,,
|
||||
DesktopScreenshotCaption26,175,Текст,,
|
||||
DesktopScreenshotCaption27,176,Текст,,
|
||||
DesktopScreenshotCaption28,177,Текст,,
|
||||
DesktopScreenshotCaption29,178,Текст,,
|
||||
DesktopScreenshotCaption30,179,Текст,,
|
||||
MobileScreenshot1,200,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot2,201,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot3,202,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot4,203,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot5,204,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot6,205,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot7,206,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot8,207,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot9,208,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot10,209,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot11,210,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot12,211,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot13,212,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot14,213,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot15,214,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot16,215,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot17,216,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot18,217,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot19,218,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot20,219,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot21,220,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot22,221,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot23,222,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot24,223,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot25,224,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot26,225,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot27,226,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot28,227,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot29,228,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot30,229,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshotCaption1,250,Текст,,
|
||||
MobileScreenshotCaption2,251,Текст,,
|
||||
MobileScreenshotCaption3,252,Текст,,
|
||||
MobileScreenshotCaption4,253,Текст,,
|
||||
MobileScreenshotCaption5,254,Текст,,
|
||||
MobileScreenshotCaption6,255,Текст,,
|
||||
MobileScreenshotCaption7,256,Текст,,
|
||||
MobileScreenshotCaption8,257,Текст,,
|
||||
MobileScreenshotCaption9,258,Текст,,
|
||||
MobileScreenshotCaption10,259,Текст,,
|
||||
MobileScreenshotCaption11,260,Текст,,
|
||||
MobileScreenshotCaption12,261,Текст,,
|
||||
MobileScreenshotCaption13,262,Текст,,
|
||||
MobileScreenshotCaption14,263,Текст,,
|
||||
MobileScreenshotCaption15,264,Текст,,
|
||||
MobileScreenshotCaption16,265,Текст,,
|
||||
MobileScreenshotCaption17,266,Текст,,
|
||||
MobileScreenshotCaption18,267,Текст,,
|
||||
MobileScreenshotCaption19,268,Текст,,
|
||||
MobileScreenshotCaption20,269,Текст,,
|
||||
MobileScreenshotCaption21,270,Текст,,
|
||||
MobileScreenshotCaption22,271,Текст,,
|
||||
MobileScreenshotCaption23,272,Текст,,
|
||||
MobileScreenshotCaption24,273,Текст,,
|
||||
MobileScreenshotCaption25,274,Текст,,
|
||||
MobileScreenshotCaption26,275,Текст,,
|
||||
MobileScreenshotCaption27,276,Текст,,
|
||||
MobileScreenshotCaption28,277,Текст,,
|
||||
MobileScreenshotCaption29,278,Текст,,
|
||||
MobileScreenshotCaption30,279,Текст,,
|
||||
XboxScreenshot1,300,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot2,301,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot3,302,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot4,303,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot5,304,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot6,305,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot7,306,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot8,307,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot9,308,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot10,309,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot11,310,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot12,311,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot13,312,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot14,313,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot15,314,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot16,315,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot17,316,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot18,317,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot19,318,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot20,319,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot21,320,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot22,321,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot23,322,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot24,323,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot25,324,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot26,325,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot27,326,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot28,327,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot29,328,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot30,329,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshotCaption1,350,Текст,,
|
||||
XboxScreenshotCaption2,351,Текст,,
|
||||
XboxScreenshotCaption3,352,Текст,,
|
||||
XboxScreenshotCaption4,353,Текст,,
|
||||
XboxScreenshotCaption5,354,Текст,,
|
||||
XboxScreenshotCaption6,355,Текст,,
|
||||
XboxScreenshotCaption7,356,Текст,,
|
||||
XboxScreenshotCaption8,357,Текст,,
|
||||
XboxScreenshotCaption9,358,Текст,,
|
||||
XboxScreenshotCaption10,359,Текст,,
|
||||
XboxScreenshotCaption11,360,Текст,,
|
||||
XboxScreenshotCaption12,361,Текст,,
|
||||
XboxScreenshotCaption13,362,Текст,,
|
||||
XboxScreenshotCaption14,363,Текст,,
|
||||
XboxScreenshotCaption15,364,Текст,,
|
||||
XboxScreenshotCaption16,365,Текст,,
|
||||
XboxScreenshotCaption17,366,Текст,,
|
||||
XboxScreenshotCaption18,367,Текст,,
|
||||
XboxScreenshotCaption19,368,Текст,,
|
||||
XboxScreenshotCaption20,369,Текст,,
|
||||
XboxScreenshotCaption21,370,Текст,,
|
||||
XboxScreenshotCaption22,371,Текст,,
|
||||
XboxScreenshotCaption23,372,Текст,,
|
||||
XboxScreenshotCaption24,373,Текст,,
|
||||
XboxScreenshotCaption25,374,Текст,,
|
||||
XboxScreenshotCaption26,375,Текст,,
|
||||
XboxScreenshotCaption27,376,Текст,,
|
||||
XboxScreenshotCaption28,377,Текст,,
|
||||
XboxScreenshotCaption29,378,Текст,,
|
||||
XboxScreenshotCaption30,379,Текст,,
|
||||
HolographicScreenshot1,400,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot2,401,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot3,402,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot4,403,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot5,404,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot6,405,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot7,406,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot8,407,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot9,408,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot10,409,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot11,410,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot12,411,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot13,412,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot14,413,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot15,414,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot16,415,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot17,416,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot18,417,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot19,418,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot20,419,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot21,420,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot22,421,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot23,422,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot24,423,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot25,424,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot26,425,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot27,426,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot28,427,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot29,428,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot30,429,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshotCaption1,450,Текст,,
|
||||
HolographicScreenshotCaption2,451,Текст,,
|
||||
HolographicScreenshotCaption3,452,Текст,,
|
||||
HolographicScreenshotCaption4,453,Текст,,
|
||||
HolographicScreenshotCaption5,454,Текст,,
|
||||
HolographicScreenshotCaption6,455,Текст,,
|
||||
HolographicScreenshotCaption7,456,Текст,,
|
||||
HolographicScreenshotCaption8,457,Текст,,
|
||||
HolographicScreenshotCaption9,458,Текст,,
|
||||
HolographicScreenshotCaption10,459,Текст,,
|
||||
HolographicScreenshotCaption11,460,Текст,,
|
||||
HolographicScreenshotCaption12,461,Текст,,
|
||||
HolographicScreenshotCaption13,462,Текст,,
|
||||
HolographicScreenshotCaption14,463,Текст,,
|
||||
HolographicScreenshotCaption15,464,Текст,,
|
||||
HolographicScreenshotCaption16,465,Текст,,
|
||||
HolographicScreenshotCaption17,466,Текст,,
|
||||
HolographicScreenshotCaption18,467,Текст,,
|
||||
HolographicScreenshotCaption19,468,Текст,,
|
||||
HolographicScreenshotCaption20,469,Текст,,
|
||||
HolographicScreenshotCaption21,470,Текст,,
|
||||
HolographicScreenshotCaption22,471,Текст,,
|
||||
HolographicScreenshotCaption23,472,Текст,,
|
||||
HolographicScreenshotCaption24,473,Текст,,
|
||||
HolographicScreenshotCaption25,474,Текст,,
|
||||
HolographicScreenshotCaption26,475,Текст,,
|
||||
HolographicScreenshotCaption27,476,Текст,,
|
||||
HolographicScreenshotCaption28,477,Текст,,
|
||||
HolographicScreenshotCaption29,478,Текст,,
|
||||
HolographicScreenshotCaption30,479,Текст,,
|
||||
SurfaceHubScreenshot11,510,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot12,511,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot13,512,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot14,513,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot15,514,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot16,515,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot17,516,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot18,517,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot19,518,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot20,519,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot21,520,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot22,521,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot23,522,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot24,523,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot25,524,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot26,525,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot27,526,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot28,527,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot29,528,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot30,529,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshotCaption11,560,Текст,,
|
||||
SurfaceHubScreenshotCaption12,561,Текст,,
|
||||
SurfaceHubScreenshotCaption13,562,Текст,,
|
||||
SurfaceHubScreenshotCaption14,563,Текст,,
|
||||
SurfaceHubScreenshotCaption15,564,Текст,,
|
||||
SurfaceHubScreenshotCaption16,565,Текст,,
|
||||
SurfaceHubScreenshotCaption17,566,Текст,,
|
||||
SurfaceHubScreenshotCaption18,567,Текст,,
|
||||
SurfaceHubScreenshotCaption19,568,Текст,,
|
||||
SurfaceHubScreenshotCaption20,569,Текст,,
|
||||
SurfaceHubScreenshotCaption21,570,Текст,,
|
||||
SurfaceHubScreenshotCaption22,571,Текст,,
|
||||
SurfaceHubScreenshotCaption23,572,Текст,,
|
||||
SurfaceHubScreenshotCaption24,573,Текст,,
|
||||
SurfaceHubScreenshotCaption25,574,Текст,,
|
||||
SurfaceHubScreenshotCaption26,575,Текст,,
|
||||
SurfaceHubScreenshotCaption27,576,Текст,,
|
||||
SurfaceHubScreenshotCaption28,577,Текст,,
|
||||
SurfaceHubScreenshotCaption29,578,Текст,,
|
||||
SurfaceHubScreenshotCaption30,579,Текст,,
|
||||
StoreLogo720x1080,600,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
StoreLogo1080x1080,601,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
StoreLogo300x300,602,Относительный путь (или URL-адрес файла в Центре партнеров),screen/1.ru.300x300.png,screen/1.ru.300x300.png
|
||||
OverrideLogosForWin10,603,True/False,,False
|
||||
StoreLogoOverride150x150,604,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
StoreLogoOverride71x71,605,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
PromoImage1920x1080,606,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
PromoImage2400x1200,607,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxBrandedKeyArt584x800,608,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxTitledHero1920x1080,609,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxFeaturedPromo1080x1080,610,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
OptionalPromo358x358,611,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
OptionalPromo1000x800,612,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
OptionalPromo414x180,613,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Feature1,700,Текст,Шифрование трафика и скрытие IP-адреса,Encrypt traffic and hide your IP address
|
||||
Feature2,701,Текст,Режим прокси и полный режим защиты (TUN),Proxy mode and full protection mode (TUN)
|
||||
Feature3,702,Текст,Kill Switch — блокировка трафика при обрыве VPN,Kill Switch blocks traffic if VPN drops
|
||||
Feature4,703,Текст,Автовыбор сервера с лучшим пингом,Auto-select the lowest-ping server
|
||||
Feature5,704,Текст,Серверы с флагами стран и группировкой протоколов,Servers with country flags and protocol grouping
|
||||
Feature6,705,Текст,Добавление конфигурации по URL,Add configuration via URL
|
||||
Feature7,706,Текст,Статистика трафика и сессий,Traffic and session statistics
|
||||
Feature8,707,Текст,Интерфейс на русском и английском,English and Russian interface
|
||||
Feature9,708,Текст,Светлая и тёмная темы оформления,Light and dark themes
|
||||
Feature10,709,Текст,Защита от утечек DNS,DNS leak protection
|
||||
Feature11,710,Текст,,
|
||||
Feature12,711,Текст,,
|
||||
Feature13,712,Текст,,
|
||||
Feature14,713,Текст,,
|
||||
Feature15,714,Текст,,
|
||||
Feature16,715,Текст,,
|
||||
Feature17,716,Текст,,
|
||||
Feature18,717,Текст,,
|
||||
Feature19,718,Текст,,
|
||||
Feature20,719,Текст,,
|
||||
MinimumHardwareReq1,800,Текст,Windows 10 version 1809 or later (x64),Windows 10 version 1809 or later (x64)
|
||||
MinimumHardwareReq2,801,Текст,Internet connection,Internet connection
|
||||
MinimumHardwareReq3,802,Текст,100 MB free disk space,100 MB free disk space
|
||||
MinimumHardwareReq4,803,Текст,,
|
||||
MinimumHardwareReq5,804,Текст,,
|
||||
MinimumHardwareReq6,805,Текст,,
|
||||
MinimumHardwareReq7,806,Текст,,
|
||||
MinimumHardwareReq8,807,Текст,,
|
||||
MinimumHardwareReq9,808,Текст,,
|
||||
MinimumHardwareReq10,809,Текст,,
|
||||
MinimumHardwareReq11,810,Текст,,
|
||||
RecommendedHardwareReq1,850,Текст,Windows 11 (x64),Windows 11 (x64)
|
||||
RecommendedHardwareReq2,851,Текст,Stable broadband connection,Stable broadband connection
|
||||
RecommendedHardwareReq3,852,Текст,,
|
||||
RecommendedHardwareReq4,853,Текст,,
|
||||
RecommendedHardwareReq5,854,Текст,,
|
||||
RecommendedHardwareReq6,855,Текст,,
|
||||
RecommendedHardwareReq7,856,Текст,,
|
||||
RecommendedHardwareReq8,857,Текст,,
|
||||
RecommendedHardwareReq9,858,Текст,,
|
||||
RecommendedHardwareReq10,859,Текст,,
|
||||
RecommendedHardwareReq11,860,Текст,,
|
||||
SearchTerm1,900,Текст,VPN,VPN
|
||||
SearchTerm2,901,Текст,privacy,privacy
|
||||
SearchTerm3,902,Текст,secure internet,secure internet
|
||||
SearchTerm4,903,Текст,encrypt traffic,encrypt traffic
|
||||
SearchTerm5,904,Текст,EvilFox,EvilFox
|
||||
SearchTerm6,905,Текст,proxy client,proxy client
|
||||
SearchTerm7,906,Текст,kill switch,kill switch
|
||||
TrailerToPlayAtTopOfListing,999,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer1,1000,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer2,1001,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer3,1002,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer4,1003,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer5,1004,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer6,1005,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer7,1006,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer8,1007,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer9,1008,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer10,1009,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer11,1010,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer12,1011,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer13,1012,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer14,1013,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer15,1014,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerTitle1,1020,Текст,,
|
||||
TrailerTitle2,1021,Текст,,
|
||||
TrailerTitle3,1022,Текст,,
|
||||
TrailerTitle4,1023,Текст,,
|
||||
TrailerTitle5,1024,Текст,,
|
||||
TrailerTitle6,1025,Текст,,
|
||||
TrailerTitle7,1026,Текст,,
|
||||
TrailerTitle8,1027,Текст,,
|
||||
TrailerTitle9,1028,Текст,,
|
||||
TrailerTitle10,1029,Текст,,
|
||||
TrailerTitle11,1030,Текст,,
|
||||
TrailerTitle12,1031,Текст,,
|
||||
TrailerTitle13,1032,Текст,,
|
||||
TrailerTitle14,1033,Текст,,
|
||||
TrailerTitle15,1034,Текст,,
|
||||
TrailerThumbnail1,1040,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail2,1041,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail3,1042,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail4,1043,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail5,1044,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail6,1045,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail7,1046,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail8,1047,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail9,1048,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail10,1049,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail11,1050,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail12,1051,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail13,1052,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail14,1053,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail15,1054,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption1,1055,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption2,1056,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption3,1057,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption4,1058,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption5,1059,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption6,1060,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption7,1061,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption8,1062,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption9,1063,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption10,1064,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption11,1065,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption12,1066,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption13,1067,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption14,1068,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption15,1069,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription1,1070,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription2,1071,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription3,1072,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription4,1073,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription5,1074,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription6,1075,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription7,1076,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription8,1077,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription9,1078,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription10,1079,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription11,1080,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription12,1081,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription13,1082,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription14,1083,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription15,1084,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
|
@@ -1,48 +1,48 @@
|
||||
{
|
||||
"version": "3.10.0",
|
||||
"notes": "3.10.0+1: новый интерфейс с боковым меню (Главная / Профили / Настройки / Логи / О программе), аккаунт с продлением 50 ГБ / +30 дней и автопродлением, режим VPN на Windows (TUN, весь трафик, нужны права администратора и wintun.dll). Windows 3.10.0.1.",
|
||||
"version": "4.2.8",
|
||||
"notes": "4.2.8: Store-ready Privacy Policy and Terms of Use on Help (RU/EN); docs for website hosting",
|
||||
"platform": "windows-amd64",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/windows/Navis.exe",
|
||||
"sha256": "2d1face047cf2fa8e6479c2e078495fff6233c28baa4d4c7cab0cef39d065d30",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "9b5cc04093561a6a6d7b97baf8f8cc9498dd863dfeb0bc3c92446c3fdb79f7c2",
|
||||
"mandatory": false,
|
||||
"platforms": {
|
||||
"windows-amd64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/windows/Navis.exe",
|
||||
"sha256": "2d1face047cf2fa8e6479c2e078495fff6233c28baa4d4c7cab0cef39d065d30",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis",
|
||||
"sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d",
|
||||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis.app.zip"
|
||||
},
|
||||
"darwin-amd64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis",
|
||||
"sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3",
|
||||
"os": "darwin",
|
||||
"arch": "amd64",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis.app.zip"
|
||||
},
|
||||
"darwin-universal": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis",
|
||||
"sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2",
|
||||
"os": "darwin",
|
||||
"arch": "universal",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis.app.zip"
|
||||
},
|
||||
"android": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/android/Navis.apk",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk",
|
||||
"sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf",
|
||||
"os": "android",
|
||||
"arch": "universal"
|
||||
},
|
||||
"darwin-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis",
|
||||
"sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3",
|
||||
"os": "darwin",
|
||||
"arch": "amd64",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis",
|
||||
"sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d",
|
||||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip"
|
||||
},
|
||||
"darwin-universal": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis",
|
||||
"sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2",
|
||||
"os": "darwin",
|
||||
"arch": "universal",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip"
|
||||
},
|
||||
"windows-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "9b5cc04093561a6a6d7b97baf8f8cc9498dd863dfeb0bc3c92446c3fdb79f7c2",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,54 @@
|
||||
{
|
||||
"version": "3.10.0",
|
||||
"notes": "3.10.0+1: новый интерфейс с боковым меню (Главная / Профили / Настройки / Логи / О программе), аккаунт с продлением 50 ГБ / +30 дней и автопродлением, режим VPN на Windows (TUN, весь трафик, нужны права администратора и wintun.dll). Windows 3.10.0.1.",
|
||||
"version": "4.2.9",
|
||||
"notes": "4.2.9: Add config accepts AWG 2.0 (.conf/JSON/awg://) and Naive share links (not only http(s) subscription URLs); Clash wireguard/naive; site contacts/FAQ",
|
||||
"platform": "windows-amd64",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/windows/Navis.exe",
|
||||
"sha256": "2d1face047cf2fa8e6479c2e078495fff6233c28baa4d4c7cab0cef39d065d30",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "4e6de7e107a560cd5c24b83fc1064c50145cdee5c36049e8ff274fa0f8c542ea",
|
||||
"mandatory": false,
|
||||
"platforms": {
|
||||
"windows-amd64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/windows/Navis.exe",
|
||||
"sha256": "2d1face047cf2fa8e6479c2e078495fff6233c28baa4d4c7cab0cef39d065d30",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis",
|
||||
"sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d",
|
||||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis.app.zip"
|
||||
},
|
||||
"darwin-amd64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis",
|
||||
"sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3",
|
||||
"os": "darwin",
|
||||
"arch": "amd64",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis.app.zip"
|
||||
},
|
||||
"darwin-universal": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis",
|
||||
"sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2",
|
||||
"os": "darwin",
|
||||
"arch": "universal",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis.app.zip"
|
||||
},
|
||||
"android": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/android/Navis.apk",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk",
|
||||
"sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf",
|
||||
"os": "android",
|
||||
"arch": "universal"
|
||||
},
|
||||
"darwin-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis",
|
||||
"sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3",
|
||||
"os": "darwin",
|
||||
"arch": "amd64",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis",
|
||||
"sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d",
|
||||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip"
|
||||
},
|
||||
"darwin-universal": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis",
|
||||
"sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2",
|
||||
"os": "darwin",
|
||||
"arch": "universal",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip"
|
||||
},
|
||||
"windows-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "4e6de7e107a560cd5c24b83fc1064c50145cdee5c36049e8ff274fa0f8c542ea",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
},
|
||||
"windows-arm64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/arm64/EvilFox.exe",
|
||||
"sha256": "6d09911f4cf49747d0f71e1626eebb4c256884425a6f810f63121ae4ce8ea7e2",
|
||||
"os": "windows",
|
||||
"arch": "arm64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# EvilFox Privacy Policy
|
||||
|
||||
**Publisher:** EvilFox
|
||||
**Effective date:** August 2, 2026
|
||||
|
||||
## 1. Who we are
|
||||
|
||||
EvilFox is a local VPN/proxy client for Windows. You supply your own connection configurations and URLs. The Microsoft Store build has no in-app purchases. We do not sell personal data.
|
||||
|
||||
## 2. Data stored on your device
|
||||
|
||||
On your device the app may store:
|
||||
|
||||
- profiles and connection URLs/parameters you enter;
|
||||
- settings (mode, theme, language, Kill Switch, etc.);
|
||||
- local traffic/session statistics (localStorage / config files).
|
||||
|
||||
This data stays on the device until you delete it or uninstall the app.
|
||||
|
||||
## 3. What we do not collect
|
||||
|
||||
As the client publisher, EvilFox does not operate a central service that collects accounts, payment details, contacts, browsing history, or your traffic contents for advertising or sale. We do not sell personal data.
|
||||
|
||||
## 4. Network traffic and third-party servers
|
||||
|
||||
When connected, traffic goes through servers specified in *your* configuration. The operator of those servers is your config provider (you or your provider), not necessarily EvilFox. Their privacy practices may differ — review your provider’s terms.
|
||||
|
||||
## 5. Update checks
|
||||
|
||||
When you choose to check/apply updates, the app may contact an update feed such as `https://git.de4ima.uk/…/update.json` (git hosting). Microsoft Store builds are normally updated via the Store; in-app update checks may be limited.
|
||||
|
||||
## 6. External links
|
||||
|
||||
`https://evilfox.win/` and Telegram `https://t.me/evilfox100Bot` open only after you tap them. Those services’ own policies then apply.
|
||||
|
||||
## 7. How data is used
|
||||
|
||||
Local data is used to run the client: connect, pick servers, show stats and settings. Network requests are for connecting to servers you choose and (optionally) checking for updates.
|
||||
|
||||
## 8. Retention and deletion
|
||||
|
||||
Data remains on-device until you uninstall, clear app data, or delete profiles. We cannot restore deleted local data from our servers because the client has no central user account store.
|
||||
|
||||
## 9. Security
|
||||
|
||||
We use reasonable measures appropriate for on-device settings within OS capabilities. No method is perfectly secure; you are responsible for keeping configuration URLs/keys confidential.
|
||||
|
||||
## 10. Children’s privacy
|
||||
|
||||
The app is not directed to children under 13 (in the EU, 16 where local law so requires). Do not use it below that age without parental/guardian consent where required.
|
||||
|
||||
## 11. International transfers (EU)
|
||||
|
||||
The client runs locally. If you connect to servers or open websites outside your country, data may be processed in other jurisdictions by those operators — as a result of your config and link choices.
|
||||
|
||||
## 12. Your rights
|
||||
|
||||
You can view and delete local data in the app or by uninstalling EvilFox / clearing app data. For questions about this policy, contact us below.
|
||||
|
||||
**California (CCPA):** we do not sell or “share” personal information for cross-context behavioral advertising. Access/deletion is exercised locally on the device.
|
||||
|
||||
## 13. EU GDPR summary (EEA/UK)
|
||||
|
||||
**Controller** for on-device client data: EvilFox (app publisher).
|
||||
**Legal bases:** performance of the contract to provide client features, and legitimate interests in operating and updating the app.
|
||||
Processing on third-party VPN/proxy servers is the responsibility of those server operators.
|
||||
|
||||
## 14. Changes
|
||||
|
||||
We may update this policy. The current version is in the app (Help) and on the website. Continued use after an update means you have had the chance to review the new text.
|
||||
|
||||
## 15. Contact
|
||||
|
||||
EvilFox · https://evilfox.win/
|
||||
Primary: animegold3@gmail.com · Backup: admin@evilfox.win · Telegram: https://t.me/evilfox100Bot
|
||||
@@ -0,0 +1,75 @@
|
||||
# Политика конфиденциальности EvilFox
|
||||
|
||||
**Издатель:** EvilFox
|
||||
**Дата вступления в силу:** 2 августа 2026 г.
|
||||
|
||||
## 1. Кто мы
|
||||
|
||||
EvilFox — локальный VPN/прокси-клиент для Windows. Вы сами добавляете конфигурации и URL подключения. В сборке Microsoft Store нет встроенных покупок. Мы не продаём персональные данные.
|
||||
|
||||
## 2. Какие данные хранятся локально
|
||||
|
||||
На вашем устройстве приложение может сохранять:
|
||||
|
||||
- профили и URL/параметры подключения, которые вы вводите;
|
||||
- настройки (режим, тема, язык, Kill Switch и т.п.);
|
||||
- локальную статистику трафика и сессий (localStorage / файлы конфигурации).
|
||||
|
||||
Эти данные остаются на устройстве, пока вы их не удалите или не удалите приложение.
|
||||
|
||||
## 3. Что мы не собираем
|
||||
|
||||
EvilFox как издатель клиента не ведёт централизованный сбор аккаунтов, платёжных данных, контактов, истории браузера или содержимого вашего трафика для рекламы или продажи. Мы не продаём персональные данные.
|
||||
|
||||
## 4. Сетевой трафик и сторонние серверы
|
||||
|
||||
При подключении трафик идёт через серверы, указанные в *вашей* конфигурации. Оператором этих серверов является поставщик конфигурации (вы или ваш провайдер), а не обязательно EvilFox. Их политика конфиденциальности может отличаться — ознакомьтесь с условиями вашего провайдера.
|
||||
|
||||
## 5. Проверка обновлений
|
||||
|
||||
По вашей инициативе (кнопка проверки/обновления) приложение может обратиться к ленте обновлений, например `https://git.de4ima.uk/…/update.json` (хостинг git). Сборка из Microsoft Store обычно обновляется через Store; встроенная проверка может быть ограничена.
|
||||
|
||||
## 6. Внешние ссылки
|
||||
|
||||
Сайт `https://evilfox.win/` и Telegram `https://t.me/evilfox100Bot` открываются только после вашего нажатия. Тогда действуют правила соответствующих сервисов.
|
||||
|
||||
## 7. Как используются данные
|
||||
|
||||
Локальные данные нужны для работы клиента: подключение, выбор сервера, отображение статистики и настроек. Сетевые обращения — для соединения с выбранными вами серверами и (по желанию) проверки обновлений.
|
||||
|
||||
## 8. Хранение и удаление
|
||||
|
||||
Данные хранятся на устройстве до удаления приложения, очистки данных приложения или ручного удаления профилей. Удалённые локальные данные мы не можем восстановить с наших серверов, потому что центрального профиля пользователя у клиента нет.
|
||||
|
||||
## 9. Безопасность
|
||||
|
||||
Мы применяем разумные меры защиты локальных настроек в рамках возможностей ОС. Абсолютной безопасности не существует; ответственность за конфиденциальность URL/ключей в конфигурациях лежит на вас.
|
||||
|
||||
## 10. Дети
|
||||
|
||||
Приложение не предназначено для детей младше 13 лет (в ЕС — ориентир 16 лет, если применимо местное право). Не используйте приложение, если вам меньше указанного возраста, без согласия родителя/опекуна где это требуется.
|
||||
|
||||
## 11. Международная передача (ЕС)
|
||||
|
||||
Клиент работает локально. Если вы подключаетесь к серверам или открываете сайты за пределами своей страны, данные могут обрабатываться в других юрисдикциях операторами этих сервисов. Это следует из вашего выбора конфигурации и внешних ссылок.
|
||||
|
||||
## 12. Ваши права
|
||||
|
||||
Вы можете просмотреть и удалить локальные данные в приложении или удалив EvilFox / очистив данные приложения. Для вопросов по этой политике свяжитесь с нами (ниже).
|
||||
|
||||
**Калифорния (CCPA):** мы не продаём и не «делимся» персональными данными в смысле CCPA для перекрёстной рекламы. Права на доступ/удаление реализуются локально на устройстве.
|
||||
|
||||
## 13. Кратко о GDPR (ЕЭЗ/Великобритания)
|
||||
|
||||
**Контролёр** в отношении локальных данных клиента: EvilFox (издатель приложения).
|
||||
**Правовые основания:** исполнение договора на предоставление функций клиента и законный интерес в обеспечении работы и обновлений.
|
||||
Обработка на сторонних VPN/прокси-серверах — ответственность оператора тех серверов.
|
||||
|
||||
## 14. Изменения
|
||||
|
||||
Мы можем обновлять эту политику. Актуальная версия доступна в приложении (Помощь) и на сайте. Продолжение использования после обновления означает ознакомление с новой редакцией.
|
||||
|
||||
## 15. Контакты
|
||||
|
||||
EvilFox · https://evilfox.win/
|
||||
Основная: animegold3@gmail.com · Резервная: admin@evilfox.win · Telegram: https://t.me/evilfox100Bot
|
||||
@@ -0,0 +1,45 @@
|
||||
# EvilFox Terms of Use
|
||||
|
||||
**Publisher:** EvilFox
|
||||
**Effective date:** August 2, 2026
|
||||
|
||||
## 1. Acceptance
|
||||
|
||||
By installing or using EvilFox, you agree to these Terms. If you do not agree, do not use the app.
|
||||
|
||||
## 2. License
|
||||
|
||||
You receive a limited, non-exclusive, non-transferable license to use the EvilFox client on your devices for personal or internal use, subject to these Terms and applicable law (including Microsoft Store rules if installed from the Store).
|
||||
|
||||
## 3. Acceptable use
|
||||
|
||||
Use EvilFox only for lawful purposes. You must not violate the laws of your country or third-party rights, or attempt to disrupt the app or others’ systems. You are responsible for how you use the client and the servers you choose.
|
||||
|
||||
## 4. User-supplied configurations
|
||||
|
||||
You provide connection URLs and parameters. EvilFox does not guarantee availability, quality, or legality of third-party servers. Review your configuration provider’s terms.
|
||||
|
||||
## 5. No warranty
|
||||
|
||||
The app is provided “as is” and “as available,” without warranties of any kind (express or implied, including merchantability and fitness for a particular purpose), to the fullest extent permitted by law.
|
||||
|
||||
## 6. Limitation of liability
|
||||
|
||||
To the maximum extent permitted by law, EvilFox is not liable for indirect, incidental, special, or consequential damages, lost profits, data loss, or connectivity interruptions arising from use of the client or third-party servers. Mandatory consumer rights (including in the EU) remain unaffected.
|
||||
|
||||
## 7. Updates
|
||||
|
||||
Microsoft Store builds update through the Store. Other (sideload) builds may offer updates via the feed on git.de4ima.uk. Features may change in new versions.
|
||||
|
||||
## 8. Termination
|
||||
|
||||
You may stop using the app by uninstalling it. We may discontinue support for particular builds. If you breach these Terms, your right to use the app ends.
|
||||
|
||||
## 9. Governing law
|
||||
|
||||
These Terms are interpreted under the laws of your country of residence unless mandatory rules require otherwise. EU consumers keep mandatory rights under the law of their country of residence.
|
||||
|
||||
## 10. Contact
|
||||
|
||||
EvilFox · https://evilfox.win/
|
||||
Primary: animegold3@gmail.com · Backup: admin@evilfox.win · Telegram: https://t.me/evilfox100Bot
|
||||
@@ -0,0 +1,45 @@
|
||||
# Условия использования EvilFox
|
||||
|
||||
**Издатель:** EvilFox
|
||||
**Дата вступления в силу:** 2 августа 2026 г.
|
||||
|
||||
## 1. Принятие условий
|
||||
|
||||
Устанавливая или используя EvilFox, вы соглашаетесь с этими Условиями. Если не согласны — не используйте приложение.
|
||||
|
||||
## 2. Лицензия
|
||||
|
||||
Вам предоставляется ограниченная, неисключительная, непередаваемая лицензия на использование клиента EvilFox на ваших устройствах в личных или внутренних целях в соответствии с этими Условиями и применимым правом (включая правила Microsoft Store, если приложение установлено из Store).
|
||||
|
||||
## 3. Допустимое использование
|
||||
|
||||
Используйте EvilFox только законно. Запрещены действия, нарушающие законы вашей страны или права третьих лиц, а также попытки нарушить работу приложения или чужих систем. Вы несёте ответственность за то, как используете клиент и выбранные серверы.
|
||||
|
||||
## 4. Конфигурации пользователя
|
||||
|
||||
URL и параметры подключения задаёте вы. EvilFox не гарантирует доступность, качество или законность сторонних серверов. Проверьте условия вашего провайдера конфигурации.
|
||||
|
||||
## 5. Нет гарантий
|
||||
|
||||
Приложение предоставляется «как есть» и «по мере доступности», без явных или подразумеваемых гарантий (включая товарную пригодность и пригодность для конкретной цели), в пределах, допускаемых законом.
|
||||
|
||||
## 6. Ограничение ответственности
|
||||
|
||||
В максимальной степени, допускаемой законом, EvilFox не несёт ответственности за косвенные, случайные, особые убытки, упущенную выгоду, потерю данных или перебои связи, связанные с использованием клиента или сторонних серверов. Обязательные права потребителей (в т.ч. в ЕС) сохраняются.
|
||||
|
||||
## 7. Обновления
|
||||
|
||||
Сборка из Microsoft Store обновляется через Store. Другие (sideload) сборки могут предлагать обновление через ленту на git.de4ima.uk. Мы можем менять функции в новых версиях.
|
||||
|
||||
## 8. Прекращение
|
||||
|
||||
Вы можете прекратить использование, удалив приложение. Мы можем прекратить поддержку отдельных сборок. При нарушении Условий право на использование прекращается.
|
||||
|
||||
## 9. Применимое право
|
||||
|
||||
Эти Условия толкуются по законам страны вашего проживания, если иное не требуется императивными нормами. Для потребителей в ЕС сохраняются обязательные права по праву страны проживания.
|
||||
|
||||
## 10. Контакты
|
||||
|
||||
EvilFox · https://evilfox.win/
|
||||
Основная: animegold3@gmail.com · Резервная: admin@evilfox.win · Telegram: https://t.me/evilfox100Bot
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -58,11 +59,13 @@ type UIState struct {
|
||||
Remnawave remnawave.Settings `json:"remnawave"`
|
||||
Hy2 core.Hy2Options `json:"hy2"`
|
||||
StorePackaged bool `json:"store_packaged,omitempty"`
|
||||
ProvisionReady bool `json:"provision_ready,omitempty"`
|
||||
Account *remnawave.Account `json:"account,omitempty"`
|
||||
Mode string `json:"mode"`
|
||||
VPNSupported bool `json:"vpn_mode_supported"`
|
||||
VPNActive bool `json:"vpn_active"`
|
||||
// KillSwitch is the persisted setting; KillSwitchActive means the tunnel
|
||||
// died unexpectedly and traffic is blocked until disconnect/reconnect.
|
||||
KillSwitch bool `json:"kill_switch"`
|
||||
KillSwitchActive bool `json:"kill_switch_active,omitempty"`
|
||||
}
|
||||
|
||||
type PingBestResult struct {
|
||||
@@ -74,8 +77,6 @@ type PingBestResult struct {
|
||||
}
|
||||
|
||||
func New(mgr *core.Manager, cfgPath string, logBuf *bytes.Buffer) *App {
|
||||
// Monthly plan upkeep: check at start and then periodically.
|
||||
mgr.StartAutoRenew(6 * time.Hour)
|
||||
return &App{
|
||||
Mgr: mgr,
|
||||
CfgPath: cfgPath,
|
||||
@@ -144,11 +145,11 @@ func (a *App) GetState() (UIState, error) {
|
||||
Remnawave: a.Mgr.RemnawaveSettings(),
|
||||
Hy2: a.Mgr.ActiveHy2Options(),
|
||||
StorePackaged: update.IsStorePackaged(),
|
||||
ProvisionReady: a.Mgr.ProvisionConfigured(),
|
||||
Account: a.Mgr.Account(),
|
||||
Mode: a.Mgr.Mode(),
|
||||
VPNSupported: a.Mgr.VPNModeSupported(),
|
||||
VPNActive: st.VPNActive,
|
||||
KillSwitch: cfg.KillSwitch,
|
||||
KillSwitchActive: st.KillSwitchActive,
|
||||
}
|
||||
if out.Protocol == "" {
|
||||
if p, err := cfg.ActiveProfile(); err == nil {
|
||||
@@ -265,14 +266,6 @@ func (a *App) ImportRemnawave(s remnawave.Settings) (core.ImportResult, error) {
|
||||
return a.Mgr.ImportRemnawave(s)
|
||||
}
|
||||
|
||||
func (a *App) ProvisionUser(username string) (core.ProvisionOutcome, error) {
|
||||
return a.Mgr.ProvisionAccess(username)
|
||||
}
|
||||
|
||||
func (a *App) RenewAccount() (core.ProvisionOutcome, error) {
|
||||
return a.Mgr.RenewAccount()
|
||||
}
|
||||
|
||||
func (a *App) SetMode(mode string) error {
|
||||
return a.Mgr.SetMode(strings.TrimSpace(mode))
|
||||
}
|
||||
@@ -294,6 +287,51 @@ func (a *App) PingServers() ([]netcheck.Result, error) {
|
||||
return a.runPings()
|
||||
}
|
||||
|
||||
// PingServer measures latency of a single server (📊 button on a server row)
|
||||
// and merges the result into the cached ping list.
|
||||
func (a *App) PingServer(name string) (netcheck.Result, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
a.mu.Lock()
|
||||
list := a.Mgr.Profiles()
|
||||
a.mu.Unlock()
|
||||
var target *netcheck.Target
|
||||
for _, p := range list {
|
||||
if p.Name == name {
|
||||
target = &netcheck.Target{Name: p.Name, Protocol: config.Protocol(p.Protocol), Proxy: p.Proxy}
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == nil {
|
||||
return netcheck.Result{}, fmt.Errorf("сервер %q не найден", name)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
out := netcheck.PingAll(ctx, []netcheck.Target{*target})
|
||||
if len(out) == 0 {
|
||||
return netcheck.Result{}, fmt.Errorf("не удалось измерить пинг")
|
||||
}
|
||||
res := out[0]
|
||||
a.mu.Lock()
|
||||
replaced := false
|
||||
for i := range a.Pings {
|
||||
if a.Pings[i].Name == res.Name {
|
||||
a.Pings[i] = res
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
a.Pings = append(a.Pings, res)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SetKillSwitch persists the leak-protection setting.
|
||||
func (a *App) SetKillSwitch(enabled bool) error {
|
||||
return a.Mgr.SetKillSwitch(enabled)
|
||||
}
|
||||
|
||||
func (a *App) PingBest(autoConnect bool) (PingBestResult, error) {
|
||||
pings, err := a.runPings()
|
||||
out := PingBestResult{Pings: pings}
|
||||
@@ -410,7 +448,7 @@ func (a *App) ApplyUpdate() (string, error) {
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
return "Устанавливается v" + latest + "… Navis перезапустится.", nil
|
||||
return "Устанавливается v" + latest + "… EvilFox перезапустится.", nil
|
||||
}
|
||||
|
||||
func (a *App) AutoCheckUpdate() {
|
||||
@@ -431,22 +469,61 @@ func (a *App) AutoCheckUpdate() {
|
||||
_, _ = a.CheckUpdate()
|
||||
}
|
||||
|
||||
func (a *App) OpenShopURL(raw string) error {
|
||||
// allowedLinkHosts are the only hosts the UI may open in the default browser
|
||||
// (shop / «Полезные ссылки» in index.html).
|
||||
var allowedLinkHosts = map[string]bool{
|
||||
"evilfox.win": true,
|
||||
"t.me": true,
|
||||
}
|
||||
|
||||
// allowedMailto are support addresses the UI may open via mailto:.
|
||||
var allowedMailto = map[string]bool{
|
||||
"animegold3@gmail.com": true,
|
||||
"admin@evilfox.win": true,
|
||||
}
|
||||
|
||||
// LinkAllowed validates that raw is an http(s) URL pointing at one of the
|
||||
// allowed hosts, or a mailto: address from the allowlist; returns the normalized URL.
|
||||
func LinkAllowed(raw string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return fmt.Errorf("пустая ссылка")
|
||||
return "", fmt.Errorf("пустая ссылка")
|
||||
}
|
||||
lower := strings.ToLower(raw)
|
||||
switch {
|
||||
case lower == "https://evilfox.win/" || lower == "https://evilfox.win" ||
|
||||
lower == "http://evilfox.win/" || lower == "http://evilfox.win":
|
||||
raw = "https://evilfox.win/"
|
||||
case strings.HasPrefix(lower, "https://evilfox.win/") || strings.HasPrefix(lower, "http://evilfox.win/"):
|
||||
default:
|
||||
return fmt.Errorf("разрешена только ссылка evilfox.win")
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("некорректная ссылка")
|
||||
}
|
||||
if strings.EqualFold(u.Scheme, "mailto") {
|
||||
addr := strings.TrimSpace(u.Opaque)
|
||||
if addr == "" {
|
||||
addr = strings.TrimSpace(u.Path)
|
||||
}
|
||||
addr = strings.TrimPrefix(addr, "//")
|
||||
if i := strings.IndexAny(addr, "?#"); i >= 0 {
|
||||
addr = addr[:i]
|
||||
}
|
||||
addr = strings.ToLower(addr)
|
||||
if !allowedMailto[addr] {
|
||||
return "", fmt.Errorf("почта %s не разрешена", addr)
|
||||
}
|
||||
return "mailto:" + addr, nil
|
||||
}
|
||||
if u.Scheme != "https" && u.Scheme != "http" {
|
||||
return "", fmt.Errorf("разрешены только http(s) и mailto ссылки")
|
||||
}
|
||||
if !allowedLinkHosts[strings.ToLower(u.Hostname())] {
|
||||
return "", fmt.Errorf("ссылка на %s не разрешена", u.Hostname())
|
||||
}
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func (a *App) OpenShopURL(raw string) error {
|
||||
normalized, err := LinkAllowed(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.OpenURL != nil {
|
||||
return a.OpenURL(raw)
|
||||
return a.OpenURL(normalized)
|
||||
}
|
||||
return fmt.Errorf("openURL не настроен")
|
||||
}
|
||||
@@ -522,6 +599,10 @@ func (a *App) dispatch(name string, args []json.RawMessage) (any, error) {
|
||||
return nil, a.OpenShopURL(arg(args, 0, ""))
|
||||
case "pingServers":
|
||||
return a.PingServers()
|
||||
case "pingServer":
|
||||
return a.PingServer(arg(args, 0, ""))
|
||||
case "setKillSwitch":
|
||||
return nil, a.SetKillSwitch(arg(args, 0, false))
|
||||
case "pingBest":
|
||||
return a.PingBest(arg(args, 0, false))
|
||||
case "checkUpdate":
|
||||
@@ -548,10 +629,6 @@ func (a *App) dispatch(name string, args []json.RawMessage) (any, error) {
|
||||
_ = json.Unmarshal(args[0], &s)
|
||||
}
|
||||
return a.ImportRemnawave(s)
|
||||
case "provisionUser":
|
||||
return a.ProvisionUser(arg(args, 0, ""))
|
||||
case "renewAccount":
|
||||
return a.RenewAccount()
|
||||
case "setMode":
|
||||
return nil, a.SetMode(arg(args, 0, ""))
|
||||
case "getLogs":
|
||||
|
||||
@@ -31,6 +31,12 @@ type Config struct {
|
||||
// system proxy) or "vpn" (Windows: TUN device, ALL traffic incl. apps/games).
|
||||
Mode string `json:"mode,omitempty"`
|
||||
|
||||
// KillSwitch keeps leak protection engaged when the tunnel dies
|
||||
// unexpectedly: in VPN mode the TUN blackhole routes stay up, in proxy
|
||||
// mode the system proxy keeps pointing at the dead local port, so traffic
|
||||
// is blocked until the user disconnects manually or reconnects.
|
||||
KillSwitch bool `json:"kill_switch,omitempty"`
|
||||
|
||||
// BinDir is where protocol binaries (naive.exe / hysteria.exe) are stored.
|
||||
BinDir string `json:"bin_dir,omitempty"`
|
||||
|
||||
|
||||
@@ -25,5 +25,8 @@ type Status struct {
|
||||
SOCKSProxy string `json:"socks_proxy,omitempty"`
|
||||
SystemProxy bool `json:"system_proxy"`
|
||||
VPNActive bool `json:"vpn_active,omitempty"`
|
||||
// KillSwitchActive: the tunnel died unexpectedly and traffic is being
|
||||
// blocked on purpose until manual disconnect / reconnect.
|
||||
KillSwitchActive bool `json:"kill_switch_active,omitempty"`
|
||||
Extra map[string]string `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
|
||||
"vpnclient/internal/config"
|
||||
"vpnclient/internal/linknorm"
|
||||
"vpnclient/internal/netcheck"
|
||||
"vpnclient/internal/protocols/awg"
|
||||
"vpnclient/internal/protocols/hysteria2"
|
||||
"vpnclient/internal/protocols/naive"
|
||||
"vpnclient/internal/protocols/xray"
|
||||
"vpnclient/internal/netcheck"
|
||||
"vpnclient/internal/remnawave"
|
||||
"vpnclient/internal/subscription"
|
||||
"vpnclient/internal/sysproxy"
|
||||
@@ -37,12 +37,14 @@ type Manager struct {
|
||||
stderr io.Writer
|
||||
binDir string
|
||||
|
||||
// account is the locally remembered Remnawave user (configs/account.json).
|
||||
account *remnawave.Account
|
||||
// vpn is the active VPN-mode (TUN) session, nil in proxy mode.
|
||||
vpn *vpnmode.Session
|
||||
// renewMu serializes manual + automatic renewals.
|
||||
renewMu sync.Mutex
|
||||
|
||||
// watchGen invalidates engine watchdogs from previous connects.
|
||||
watchGen uint64
|
||||
// ksEngaged is true after an unexpected engine death with kill switch on:
|
||||
// blackhole routes / dead system proxy are intentionally left in place.
|
||||
ksEngaged bool
|
||||
}
|
||||
|
||||
func NewManager(cfgPath string, cfg *config.Config, stderr io.Writer) (*Manager, error) {
|
||||
@@ -53,18 +55,12 @@ func NewManager(cfgPath string, cfg *config.Config, stderr io.Writer) (*Manager,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acc, err := remnawave.LoadAccount(remnawave.AccountPath(cfgPath))
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "account.json: %v\n", err)
|
||||
acc = nil
|
||||
}
|
||||
return &Manager{
|
||||
cfgPath: cfgPath,
|
||||
cfg: cfg,
|
||||
sys: sysproxy.New(),
|
||||
stderr: stderr,
|
||||
binDir: binDir,
|
||||
account: acc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -76,6 +72,18 @@ func (m *Manager) Connect(ctx context.Context, profileName string) error {
|
||||
return fmt.Errorf("already connected; disconnect first")
|
||||
}
|
||||
|
||||
// Reconnect releases a previously engaged kill switch (blackhole state).
|
||||
if m.ksEngaged {
|
||||
if m.vpn != nil {
|
||||
_ = m.vpn.Close()
|
||||
m.vpn = nil
|
||||
}
|
||||
if m.sys.Enabled() {
|
||||
_ = m.sys.Disable()
|
||||
}
|
||||
m.ksEngaged = false
|
||||
}
|
||||
|
||||
if profileName != "" {
|
||||
m.cfg.Active = profileName
|
||||
}
|
||||
@@ -145,9 +153,70 @@ func (m *Manager) Connect(ctx context.Context, profileName string) error {
|
||||
|
||||
m.engine = engine
|
||||
m.profile = profile
|
||||
m.watchGen++
|
||||
go m.watchEngine(m.watchGen, engine)
|
||||
return nil
|
||||
}
|
||||
|
||||
// watchEngine polls the engine and reacts to an UNEXPECTED death (crash of the
|
||||
// core process). A normal Disconnect bumps watchGen first, so the watcher just
|
||||
// exits. With kill switch enabled the blackhole state is kept, otherwise the
|
||||
// network is restored (system proxy off, TUN routes removed).
|
||||
func (m *Manager) watchEngine(gen uint64, engine Engine) {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
m.mu.Lock()
|
||||
if m.watchGen != gen || m.engine != engine {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
if engine.Running() {
|
||||
m.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
// Unexpected death: engine stopped without Disconnect.
|
||||
m.engine = nil
|
||||
m.profile = nil
|
||||
ks := m.cfg.KillSwitch
|
||||
vpn := m.vpn
|
||||
sysOn := m.sys.Enabled()
|
||||
if ks && (vpn != nil || sysOn) {
|
||||
// Keep TUN blackhole routes / dead system proxy: traffic is
|
||||
// blocked until the user disconnects manually or reconnects.
|
||||
m.ksEngaged = true
|
||||
m.mu.Unlock()
|
||||
fmt.Fprintf(m.stderr, "kill switch: соединение прервано — трафик заблокирован до ручного отключения или переподключения\n")
|
||||
return
|
||||
}
|
||||
m.vpn = nil
|
||||
m.mu.Unlock()
|
||||
if vpn != nil {
|
||||
_ = vpn.Close()
|
||||
}
|
||||
if sysOn {
|
||||
_ = m.sys.Disable()
|
||||
}
|
||||
fmt.Fprintf(m.stderr, "соединение прервано — сетевые настройки восстановлены\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// KillSwitchEnabled returns the persisted kill switch setting.
|
||||
func (m *Manager) KillSwitchEnabled() bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.cfg.KillSwitch
|
||||
}
|
||||
|
||||
// SetKillSwitch persists the kill switch setting.
|
||||
func (m *Manager) SetKillSwitch(enabled bool) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.cfg.KillSwitch = enabled
|
||||
return config.Save(m.cfgPath, *m.cfg)
|
||||
}
|
||||
|
||||
func (m *Manager) Disconnect() error {
|
||||
m.mu.Lock()
|
||||
sys := m.sys
|
||||
@@ -156,6 +225,8 @@ func (m *Manager) Disconnect() error {
|
||||
m.engine = nil
|
||||
m.profile = nil
|
||||
m.vpn = nil
|
||||
m.watchGen++ // cancel the engine watchdog — this stop is intentional
|
||||
m.ksEngaged = false
|
||||
m.mu.Unlock()
|
||||
|
||||
var first error
|
||||
@@ -191,7 +262,7 @@ func (m *Manager) Status() Status {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
st := Status{SystemProxy: m.sys.Enabled(), VPNActive: m.vpn != nil}
|
||||
st := Status{SystemProxy: m.sys.Enabled(), VPNActive: m.vpn != nil, KillSwitchActive: m.ksEngaged}
|
||||
if m.engine == nil || !m.engine.Running() {
|
||||
return st
|
||||
}
|
||||
@@ -298,10 +369,10 @@ func (m *Manager) SetMode(mode string) error {
|
||||
return fmt.Errorf("режим VPN пока доступен только на Windows")
|
||||
}
|
||||
if !vpnmode.IsElevated() {
|
||||
return fmt.Errorf("Запустите Navis от имени администратора для режима VPN")
|
||||
return fmt.Errorf("Запустите EvilFox от имени администратора для режима VPN")
|
||||
}
|
||||
if !vpnmode.WintunAvailable() {
|
||||
return fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с Navis.exe")
|
||||
return fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с EvilFox.exe")
|
||||
}
|
||||
}
|
||||
m.mu.Lock()
|
||||
@@ -561,6 +632,9 @@ type ImportResult struct {
|
||||
|
||||
func (m *Manager) ImportSubscription(rawURL string) (ImportResult, error) {
|
||||
rawURL = strings.TrimSpace(rawURL)
|
||||
if rawURL == "" {
|
||||
return ImportResult{}, fmt.Errorf("пустой URL или конфигурация")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -579,11 +653,25 @@ func (m *Manager) ImportSubscription(rawURL string) (ImportResult, error) {
|
||||
return m.applySubscription(ctx, res, used, &s)
|
||||
}
|
||||
|
||||
// Remote subscription / hosted config file (http(s) without user:pass@).
|
||||
if linknorm.LooksLikeSubscriptionURL(rawURL) {
|
||||
res, err := subscription.Fetch(ctx, rawURL)
|
||||
if err != nil {
|
||||
return ImportResult{}, err
|
||||
}
|
||||
return m.applySubscription(ctx, res, rawURL, nil)
|
||||
}
|
||||
|
||||
// Direct paste into «Добавить конфигурацию»: awg://, AWG 2.0 .conf / JSON,
|
||||
// naive share links, Clash YAML body, multi-line link lists — no HTTP fetch.
|
||||
res, err := subscription.ParseBodyDetailed(rawURL)
|
||||
if err != nil || res == nil || len(res.Items) == 0 {
|
||||
if err != nil {
|
||||
return ImportResult{}, fmt.Errorf("%w — либо вставьте http(s) URL конфигурации, либо ссылку/тело AWG, naive, hy2, vless…", err)
|
||||
}
|
||||
return ImportResult{}, fmt.Errorf("не удалось распознать конфигурацию — нужен http(s) URL либо ссылка/тело (AWG, naive, hy2, vless…)")
|
||||
}
|
||||
return m.applySubscription(ctx, res, "", nil)
|
||||
}
|
||||
|
||||
// ImportRemnawave fetches configs using saved / provided panel settings.
|
||||
@@ -618,7 +706,7 @@ func (m *Manager) applySubscription(ctx context.Context, res *subscription.Resul
|
||||
return ImportResult{Skipped: res.Skipped, Warnings: res.Warnings},
|
||||
fmt.Errorf("нет валидных серверов: %d записей пропущено (%s)", res.Skipped, strings.Join(res.Warnings, "; "))
|
||||
}
|
||||
return ImportResult{}, fmt.Errorf("в подписке нет поддерживаемых ссылок (naive / hysteria2 / awg / vless / vmess / trojan)")
|
||||
return ImportResult{}, fmt.Errorf("в конфигурации нет поддерживаемых ссылок (naive / hysteria2 / awg / vless / vmess / trojan)")
|
||||
}
|
||||
|
||||
// Subscription usage info: header data first, enriched via Remnawave API
|
||||
@@ -689,190 +777,6 @@ func (m *Manager) applySubscription(ctx context.Context, res *subscription.Resul
|
||||
return ImportResult{Imported: len(res.Items), Skipped: res.Skipped, Warnings: res.Warnings}, nil
|
||||
}
|
||||
|
||||
// ProvisionOutcome is what the UI shows after issuing access via the panel.
|
||||
type ProvisionOutcome struct {
|
||||
Username string `json:"username"`
|
||||
Created bool `json:"created"`
|
||||
SubscriptionURL string `json:"subscription_url"`
|
||||
TrafficLimitBytes int64 `json:"traffic_limit_bytes,omitempty"`
|
||||
ExpireAt int64 `json:"expire_at,omitempty"`
|
||||
Imported int `json:"imported"`
|
||||
Skipped int `json:"skipped"`
|
||||
}
|
||||
|
||||
// ProvisionConfigured reports whether configs/remnawave-api.json exists —
|
||||
// the admin "Выдать доступ" panel is shown only in that case.
|
||||
func (m *Manager) ProvisionConfigured() bool {
|
||||
st, err := os.Stat(remnawave.AdminConfigPath(m.cfgPath))
|
||||
return err == nil && !st.IsDir()
|
||||
}
|
||||
|
||||
// Account returns a copy of the remembered panel account (nil when absent).
|
||||
func (m *Manager) Account() *remnawave.Account {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.account == nil {
|
||||
return nil
|
||||
}
|
||||
cp := *m.account
|
||||
return &cp
|
||||
}
|
||||
|
||||
func (m *Manager) saveAccount(a *remnawave.Account) {
|
||||
m.mu.Lock()
|
||||
m.account = a
|
||||
m.mu.Unlock()
|
||||
if err := remnawave.SaveAccount(remnawave.AccountPath(m.cfgPath), a); err != nil {
|
||||
fmt.Fprintf(m.stderr, "account.json: сохранение не удалось: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ProvisionAccess creates (or finds) a panel user via the Remnawave admin API,
|
||||
// remembers it locally and imports its subscription configs into the app.
|
||||
// Once an account is remembered, creating another one is refused —
|
||||
// paid subscription flow will replace free provisioning later.
|
||||
func (m *Manager) ProvisionAccess(username string) (ProvisionOutcome, error) {
|
||||
if acc := m.Account(); acc != nil {
|
||||
return ProvisionOutcome{}, fmt.Errorf("аккаунт уже привязан (%s) — новый создавать нельзя, используйте «Продлить»", acc.Username)
|
||||
}
|
||||
cfg, err := remnawave.LoadAdminConfig(remnawave.AdminConfigPath(m.cfgPath))
|
||||
if err != nil {
|
||||
return ProvisionOutcome{}, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
res, err := remnawave.ProvisionUser(ctx, cfg, username)
|
||||
if err != nil {
|
||||
return ProvisionOutcome{}, err
|
||||
}
|
||||
out := ProvisionOutcome{
|
||||
Username: res.Username,
|
||||
Created: res.Created,
|
||||
SubscriptionURL: res.SubscriptionURL,
|
||||
TrafficLimitBytes: res.TrafficLimitBytes,
|
||||
ExpireAt: res.ExpireAt,
|
||||
}
|
||||
subURL := res.SubscriptionURL
|
||||
if subURL == "" && res.ShortUUID != "" {
|
||||
subURL = remnawave.PublicSubURL(cfg.PanelURL, res.ShortUUID)
|
||||
out.SubscriptionURL = subURL
|
||||
}
|
||||
if subURL == "" {
|
||||
return out, fmt.Errorf("панель не вернула subscription URL для %s", res.Username)
|
||||
}
|
||||
|
||||
// Remember the account — from now on only renewals of this user.
|
||||
m.saveAccount(&remnawave.Account{
|
||||
Username: res.Username,
|
||||
UUID: res.UUID,
|
||||
ShortUUID: res.ShortUUID,
|
||||
SubscriptionURL: subURL,
|
||||
TrafficLimitBytes: res.TrafficLimitBytes,
|
||||
ExpireAt: res.ExpireAt,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
|
||||
imp, err := m.ImportSubscription(subURL)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("доступ выдан (%s), но импорт конфигов не удался: %w", res.Username, err)
|
||||
}
|
||||
out.Imported = imp.Imported
|
||||
out.Skipped = imp.Skipped
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// RenewAccount extends the remembered user: resets traffic to the plan
|
||||
// (50 GB default) and pushes expiry +30 days. Never creates users.
|
||||
func (m *Manager) RenewAccount() (ProvisionOutcome, error) {
|
||||
m.renewMu.Lock()
|
||||
defer m.renewMu.Unlock()
|
||||
|
||||
acc := m.Account()
|
||||
if acc == nil {
|
||||
return ProvisionOutcome{}, fmt.Errorf("нет привязанного аккаунта — сначала выдайте доступ")
|
||||
}
|
||||
cfg, err := remnawave.LoadAdminConfig(remnawave.AdminConfigPath(m.cfgPath))
|
||||
if err != nil {
|
||||
return ProvisionOutcome{}, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
res, err := remnawave.RenewUser(ctx, cfg, acc.UUID, acc.ExpireAt)
|
||||
if err != nil {
|
||||
return ProvisionOutcome{}, err
|
||||
}
|
||||
|
||||
acc.LastRenewedAt = time.Now().Unix()
|
||||
if res.ExpireAt > 0 {
|
||||
acc.ExpireAt = res.ExpireAt
|
||||
}
|
||||
if res.TrafficLimitBytes > 0 {
|
||||
acc.TrafficLimitBytes = res.TrafficLimitBytes
|
||||
}
|
||||
if res.SubscriptionURL != "" {
|
||||
acc.SubscriptionURL = res.SubscriptionURL
|
||||
}
|
||||
m.saveAccount(acc)
|
||||
|
||||
out := ProvisionOutcome{
|
||||
Username: acc.Username,
|
||||
SubscriptionURL: acc.SubscriptionURL,
|
||||
TrafficLimitBytes: acc.TrafficLimitBytes,
|
||||
ExpireAt: acc.ExpireAt,
|
||||
}
|
||||
// Refresh configs + usage info; renewal itself already succeeded.
|
||||
if acc.SubscriptionURL != "" {
|
||||
if imp, err := m.ImportSubscription(acc.SubscriptionURL); err == nil {
|
||||
out.Imported = imp.Imported
|
||||
out.Skipped = imp.Skipped
|
||||
} else {
|
||||
fmt.Fprintf(m.stderr, "renew: обновление подписки после продления: %v\n", err)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(m.stderr, "renew: аккаунт %s продлён до %s\n", acc.Username,
|
||||
time.Unix(acc.ExpireAt, 0).Format("2006-01-02"))
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// StartAutoRenew launches the monthly auto-renew loop: checks on start and
|
||||
// then every interval (6h default); renews when expiry is near/past or the
|
||||
// traffic is exhausted. LastRenewedAt guards against renew loops.
|
||||
func (m *Manager) StartAutoRenew(interval time.Duration) {
|
||||
if interval <= 0 {
|
||||
interval = 6 * time.Hour
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(15 * time.Second) // let the app start up first
|
||||
for {
|
||||
m.maybeAutoRenew()
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *Manager) maybeAutoRenew() {
|
||||
acc := m.Account()
|
||||
if acc == nil || !m.ProvisionConfigured() {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
// Anti-loop: never auto-renew more often than every 12 hours.
|
||||
if acc.LastRenewedAt > 0 && now.Sub(time.Unix(acc.LastRenewedAt, 0)) < 12*time.Hour {
|
||||
return
|
||||
}
|
||||
var used int64
|
||||
if si := m.SubscriptionInfo(); si != nil {
|
||||
used = si.Upload + si.Download
|
||||
}
|
||||
if !acc.NeedsRenewal(used, now) {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(m.stderr, "auto-renew: аккаунт %s требует продления (истекает/трафик исчерпан) — продлеваю…\n", acc.Username)
|
||||
if _, err := m.RenewAccount(); err != nil {
|
||||
fmt.Fprintf(m.stderr, "auto-renew: не удалось: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func buildSubInfo(info *subscription.Info) *config.SubscriptionInfo {
|
||||
if info == nil {
|
||||
return nil
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -89,16 +88,59 @@ func TestSaveProfileKeepsNaiveLink(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionConfigured(t *testing.T) {
|
||||
// «Добавить конфигурацию» must accept pasted AWG 2.0 .conf / naive links,
|
||||
// not only http(s) subscription URLs (regression: "нужен http(s) URL").
|
||||
func TestImportSubscriptionDirectAWGAndNaive(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
if m.ProvisionConfigured() {
|
||||
t.Fatal("should be false without remnawave-api.json")
|
||||
awgBody := `[Interface]
|
||||
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
Address = 10.8.0.2/32
|
||||
Jc = 4
|
||||
Jmin = 40
|
||||
Jmax = 70
|
||||
H1 = 1
|
||||
H2 = 2
|
||||
H3 = 3
|
||||
H4 = 4
|
||||
|
||||
[Peer]
|
||||
PublicKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
|
||||
Endpoint = 203.0.113.10:51820
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
`
|
||||
r, err := m.ImportSubscription(awgBody)
|
||||
if err != nil {
|
||||
t.Fatalf("AWG paste: %v", err)
|
||||
}
|
||||
path := filepath.Join(filepath.Dir(m.ConfigPath()), "remnawave-api.json")
|
||||
if err := os.WriteFile(path, []byte(`{}`), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
if r.Imported < 1 {
|
||||
t.Fatalf("AWG imported=%d", r.Imported)
|
||||
}
|
||||
if !m.ProvisionConfigured() {
|
||||
t.Fatal("should be true with remnawave-api.json present")
|
||||
foundAWG := false
|
||||
for _, p := range m.Profiles() {
|
||||
if config.Protocol(p.Protocol) == config.ProtocolAWG && strings.Contains(p.Proxy, "203.0.113.10:51820") {
|
||||
foundAWG = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundAWG {
|
||||
t.Fatal("AWG profile missing")
|
||||
}
|
||||
|
||||
r2, err := m.ImportSubscription("https://user:secret@naive.example.com:443#NV")
|
||||
if err != nil {
|
||||
t.Fatalf("naive paste: %v", err)
|
||||
}
|
||||
if r2.Imported < 1 {
|
||||
t.Fatalf("naive imported=%d", r2.Imported)
|
||||
}
|
||||
foundNaive := false
|
||||
for _, p := range m.Profiles() {
|
||||
if config.Protocol(p.Protocol) == config.ProtocolNaive && strings.Contains(p.Proxy, "naive.example.com") {
|
||||
foundNaive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundNaive {
|
||||
t.Fatal("naive profile missing")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,11 @@ var countryNames = []nameCode{
|
||||
{"исландия", "IS"}, {"люксембург", "LU"}, {"англия", "GB"},
|
||||
{"сша", "US"}, {"америка", "US"},
|
||||
|
||||
// Частые опечатки в именах серверов на панелях (наблюдались в проде).
|
||||
// Longer stems first (Contains): «румьиния» before «румьиня».
|
||||
{"румьиния", "RO"}, {"румяння", "RO"}, {"румьиня", "RO"},
|
||||
{"румыня", "RO"}, {"руминия", "RO"}, {"румыниа", "RO"},
|
||||
|
||||
// City → country hints (EN + RU).
|
||||
{"frankfurt", "DE"}, {"amsterdam", "NL"}, {"london", "GB"},
|
||||
{"paris", "FR"}, {"helsinki", "FI"}, {"stockholm", "SE"},
|
||||
|
||||
@@ -25,6 +25,15 @@ func TestFromText(t *testing.T) {
|
||||
{"🇨🇿 Czech Republic Vless", "CZ"},
|
||||
{"🇳🇴 Norway TR+WS", "NO"},
|
||||
{"🇩🇪 Germany 3 HY", "DE"},
|
||||
// Panel-side typos seen in production.
|
||||
{"Румьиня", "RO"},
|
||||
{"Румыня-2", "RO"},
|
||||
{"Румяння", "RO"},
|
||||
{"Румяння — оптимальный", "RO"},
|
||||
{"Румьиния", "RO"},
|
||||
{"Румьиния Fast", "RO"},
|
||||
{"румыниа", "RO"},
|
||||
{"Румыния", "RO"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
cc, emoji := FromText(c.in)
|
||||
|
||||
@@ -64,7 +64,7 @@ func Normalize(proto config.Protocol, raw string) (normalized string, detected c
|
||||
// URL, not a naive proxy link — callers should have routed it to the
|
||||
// subscription import flow (see LooksLikeSubscriptionURL).
|
||||
if LooksLikeSubscriptionURL(raw) {
|
||||
return "", config.ProtocolNaive, "", fmt.Errorf("это ссылка подписки (нет логина и пароля) — вставьте её в поле «URL подписки» или сохраните ещё раз: импорт запустится автоматически")
|
||||
return "", config.ProtocolNaive, "", fmt.Errorf("это URL-ссылка конфигурации (нет логина и пароля) — вставьте её в поле «Добавить конфигурацию» или сохраните ещё раз: импорт запустится автоматически")
|
||||
}
|
||||
if xray.Detect(raw) {
|
||||
u, rem, err := xray.NormalizeShareLink(raw)
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestNormalizeSubscriptionURLError(t *testing.T) {
|
||||
if strings.Contains(err.Error(), "missing username") {
|
||||
t.Fatalf("raw parser error leaked: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "подписк") {
|
||||
t.Fatalf("error should mention subscription: %v", err)
|
||||
if !strings.Contains(err.Error(), "конфигурац") {
|
||||
t.Fatalf("error should mention configuration URL: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package remnawave
|
||||
|
||||
// Local account storage: after the first successful provisioning the client
|
||||
// "remembers" the panel user in configs/account.json (never committed) and
|
||||
// from then on only renews that same user — it never creates a second one.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Account is the locally remembered panel user.
|
||||
type Account struct {
|
||||
Username string `json:"username"`
|
||||
UUID string `json:"uuid"`
|
||||
ShortUUID string `json:"short_uuid,omitempty"`
|
||||
SubscriptionURL string `json:"subscription_url,omitempty"`
|
||||
TrafficLimitBytes int64 `json:"traffic_limit_bytes,omitempty"`
|
||||
ExpireAt int64 `json:"expire_at,omitempty"` // unix seconds; 0 = unknown
|
||||
CreatedAt int64 `json:"created_at,omitempty"` // unix seconds
|
||||
LastRenewedAt int64 `json:"last_renewed_at,omitempty"` // unix seconds; anti-loop guard
|
||||
LastRenewCheckAt int64 `json:"last_renew_check,omitempty"` // unix seconds of last auto check
|
||||
}
|
||||
|
||||
// AccountPath returns configs/account.json next to the main config.json.
|
||||
func AccountPath(cfgPath string) string {
|
||||
return filepath.Join(filepath.Dir(cfgPath), "account.json")
|
||||
}
|
||||
|
||||
// LoadAccount reads the remembered account; returns (nil, nil) when absent.
|
||||
func LoadAccount(path string) (*Account, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var a Account
|
||||
if err := json.Unmarshal(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF}), &a); err != nil {
|
||||
return nil, fmt.Errorf("account.json: некорректный JSON: %w", err)
|
||||
}
|
||||
if a.Username == "" && a.UUID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
// SaveAccount persists the account with restrictive permissions.
|
||||
func SaveAccount(path string, a *Account) error {
|
||||
if a == nil {
|
||||
return fmt.Errorf("пустой аккаунт")
|
||||
}
|
||||
data, err := json.MarshalIndent(a, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, '\n')
|
||||
if dir := filepath.Dir(path); dir != "." && dir != "" {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.WriteFile(path, data, 0o600)
|
||||
}
|
||||
|
||||
// NeedsRenewal reports whether the plan should be renewed now:
|
||||
// expiry within 72h / past, or traffic exhausted (usedBytes vs limit).
|
||||
func (a *Account) NeedsRenewal(usedBytes int64, now time.Time) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
if a.ExpireAt > 0 && time.Unix(a.ExpireAt, 0).Before(now.Add(72*time.Hour)) {
|
||||
return true
|
||||
}
|
||||
if a.TrafficLimitBytes > 0 && usedBytes >= a.TrafficLimitBytes {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func Fetch(ctx context.Context, s Settings) (*subscription.Result, string, error
|
||||
if lastErr != nil {
|
||||
return nil, "", lastErr
|
||||
}
|
||||
return nil, "", fmt.Errorf("Remnawave: нет поддерживаемых ссылок (проверьте short UUID / токен / правила ответа подписки)")
|
||||
return nil, "", fmt.Errorf("Remnawave: нет поддерживаемых ссылок (проверьте short UUID / токен / правила ответа конфигурации)")
|
||||
}
|
||||
|
||||
func fetchWithToken(ctx context.Context, client *http.Client, s Settings) (*subscription.Result, string, error) {
|
||||
@@ -183,7 +183,7 @@ func doGET(ctx context.Context, client *http.Client, s Settings, endpoint string
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Navis/3.9 (Remnawave)")
|
||||
req.Header.Set("User-Agent", "EvilFox/4.0 (Remnawave)")
|
||||
req.Header.Set("Accept", "*/*")
|
||||
if auth && s.Token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+s.Token)
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
package remnawave
|
||||
|
||||
// Provisioning: the app itself can issue access via the Remnawave admin API
|
||||
// (docs: https://docs.rw / TypeScript SDK commands):
|
||||
//
|
||||
// GET {base}/api/users/by-username/{username} (GetUserByUsernameCommand)
|
||||
// POST {base}/api/users (CreateUserCommand)
|
||||
//
|
||||
// Auth: Authorization: Bearer <admin API token> (+ optional Caddy X-Api-Key).
|
||||
// Credentials live in a separate config file configs/remnawave-api.json —
|
||||
// never committed; see configs/remnawave-api.example.json.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProvisionOptions are plan defaults for newly created users.
|
||||
type ProvisionOptions struct {
|
||||
TrafficGB int64 `json:"traffic_gb"` // default 50
|
||||
Days int `json:"days"` // default 30
|
||||
Strategy string `json:"strategy"` // NO_RESET | DAY | WEEK | MONTH (default MONTH)
|
||||
HwidDeviceLimit int `json:"hwid_device_limit"` // 0 = unlimited
|
||||
}
|
||||
|
||||
// AdminConfig is loaded from configs/remnawave-api.json (admin credentials).
|
||||
type AdminConfig struct {
|
||||
PanelURL string `json:"panel_url"`
|
||||
APIToken string `json:"api_token"`
|
||||
CaddyAPIKey string `json:"caddy_api_key,omitempty"`
|
||||
Provision ProvisionOptions `json:"provision"`
|
||||
}
|
||||
|
||||
// AdminConfigPath returns the expected path of remnawave-api.json —
|
||||
// next to the main config.json (configs dir).
|
||||
func AdminConfigPath(cfgPath string) string {
|
||||
return filepath.Join(filepath.Dir(cfgPath), "remnawave-api.json")
|
||||
}
|
||||
|
||||
// LoadAdminConfig reads and validates the admin API config file.
|
||||
func LoadAdminConfig(path string) (*AdminConfig, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("нет файла %s (скопируйте remnawave-api.example.json и вставьте ключ)", filepath.Base(path))
|
||||
}
|
||||
var cfg AdminConfig
|
||||
if err := json.Unmarshal(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF}), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("remnawave-api.json: некорректный JSON: %w", err)
|
||||
}
|
||||
cfg.PanelURL = NormalizeBase(cfg.PanelURL)
|
||||
cfg.APIToken = strings.TrimSpace(cfg.APIToken)
|
||||
cfg.CaddyAPIKey = strings.TrimSpace(cfg.CaddyAPIKey)
|
||||
if cfg.PanelURL == "" || strings.Contains(cfg.PanelURL, "panel.example.com") {
|
||||
return nil, fmt.Errorf("remnawave-api.json: укажите panel_url")
|
||||
}
|
||||
if cfg.APIToken == "" || cfg.APIToken == "PASTE_API_TOKEN_HERE" {
|
||||
return nil, fmt.Errorf("remnawave-api.json: укажите api_token (панель → API Tokens)")
|
||||
}
|
||||
// Plan defaults: 50 GB / 30 days / MONTH reset.
|
||||
if cfg.Provision.TrafficGB <= 0 {
|
||||
cfg.Provision.TrafficGB = 50
|
||||
}
|
||||
if cfg.Provision.Days <= 0 {
|
||||
cfg.Provision.Days = 30
|
||||
}
|
||||
switch strings.ToUpper(strings.TrimSpace(cfg.Provision.Strategy)) {
|
||||
case "NO_RESET", "DAY", "WEEK", "MONTH":
|
||||
cfg.Provision.Strategy = strings.ToUpper(strings.TrimSpace(cfg.Provision.Strategy))
|
||||
default:
|
||||
cfg.Provision.Strategy = "MONTH"
|
||||
}
|
||||
if cfg.Provision.HwidDeviceLimit < 0 {
|
||||
cfg.Provision.HwidDeviceLimit = 0
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// ProvisionResult describes the created or found panel user.
|
||||
type ProvisionResult struct {
|
||||
Username string `json:"username"`
|
||||
UUID string `json:"uuid"`
|
||||
ShortUUID string `json:"short_uuid"`
|
||||
SubscriptionURL string `json:"subscription_url"`
|
||||
TrafficLimitBytes int64 `json:"traffic_limit_bytes"`
|
||||
ExpireAt int64 `json:"expire_at"` // unix seconds; 0 = unknown
|
||||
Created bool `json:"created"`
|
||||
}
|
||||
|
||||
var reUsername = regexp.MustCompile(`^[a-zA-Z0-9_-]{6,34}$`)
|
||||
|
||||
// NormalizeUsername sanitizes free-form input (telegram @handle, email, etc.)
|
||||
// into a Remnawave-compatible username: [a-zA-Z0-9_-], 6..34 chars.
|
||||
func NormalizeUsername(raw string) (string, error) {
|
||||
s := strings.TrimSpace(raw)
|
||||
s = strings.TrimPrefix(s, "@")
|
||||
if i := strings.IndexByte(s, '@'); i > 0 {
|
||||
s = s[:i] // email → local part
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '_', r == '-':
|
||||
b.WriteRune(r)
|
||||
case r == '.', r == ' ', r == '+':
|
||||
b.WriteByte('_')
|
||||
}
|
||||
}
|
||||
s = b.String()
|
||||
for len(s) > 0 && len(s) < 6 {
|
||||
s += "_vpn" // pad short handles ("ivan" → "ivan_vpn")
|
||||
}
|
||||
if len(s) > 34 {
|
||||
s = s[:34]
|
||||
}
|
||||
if !reUsername.MatchString(s) {
|
||||
return "", fmt.Errorf("имя пользователя: 6–34 символа, латиница/цифры/_/- (получилось %q)", s)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProvisionUser finds an existing panel user by username or creates one with
|
||||
// the configured plan (traffic_gb / days / strategy / hwid_device_limit),
|
||||
// then returns its subscription URL for import.
|
||||
func ProvisionUser(ctx context.Context, cfg *AdminConfig, rawUsername string) (*ProvisionResult, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("нет конфигурации remnawave-api")
|
||||
}
|
||||
username, err := NormalizeUsername(rawUsername)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// 1) Existing user?
|
||||
getURL := cfg.PanelURL + "/api/users/by-username/" + url.PathEscape(username)
|
||||
status, body, err := adminDo(ctx, client, cfg, http.MethodGet, getURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case status == http.StatusOK:
|
||||
res, err := parseProvisionUser(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Username = username
|
||||
return res, nil
|
||||
case status == http.StatusNotFound:
|
||||
// fall through to create
|
||||
default:
|
||||
return nil, adminHTTPError(status, body)
|
||||
}
|
||||
|
||||
// 2) Create with plan defaults.
|
||||
payload := map[string]any{
|
||||
"username": username,
|
||||
"status": "ACTIVE",
|
||||
"trafficLimitBytes": cfg.Provision.TrafficGB * 1024 * 1024 * 1024,
|
||||
"trafficLimitStrategy": cfg.Provision.Strategy,
|
||||
"expireAt": time.Now().UTC().AddDate(0, 0, cfg.Provision.Days).Format(time.RFC3339),
|
||||
}
|
||||
if cfg.Provision.HwidDeviceLimit > 0 {
|
||||
payload["hwidDeviceLimit"] = cfg.Provision.HwidDeviceLimit
|
||||
}
|
||||
raw, _ := json.Marshal(payload)
|
||||
status, body, err = adminDo(ctx, client, cfg, http.MethodPost, cfg.PanelURL+"/api/users", raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status != http.StatusOK && status != http.StatusCreated {
|
||||
return nil, adminHTTPError(status, body)
|
||||
}
|
||||
res, err := parseProvisionUser(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Username = username
|
||||
res.Created = true
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// RenewUser extends the SAME panel user: resets used traffic
|
||||
// (POST /api/users/{uuid}/actions/reset-traffic) and pushes the plan forward
|
||||
// via PATCH /api/users — trafficLimitBytes = plan (50 GB default) and
|
||||
// expireAt = max(now, current expiry) + plan days (30 default).
|
||||
// It never creates users.
|
||||
func RenewUser(ctx context.Context, cfg *AdminConfig, userUUID string, currentExpire int64) (*ProvisionResult, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("нет конфигурации remnawave-api")
|
||||
}
|
||||
userUUID = strings.TrimSpace(userUUID)
|
||||
if userUUID == "" {
|
||||
return nil, fmt.Errorf("нет UUID пользователя для продления")
|
||||
}
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// 1) Reset used traffic. Some panels return 404 for this action route —
|
||||
// tolerate it, the PATCH below still extends the plan.
|
||||
resetURL := cfg.PanelURL + "/api/users/" + url.PathEscape(userUUID) + "/actions/reset-traffic"
|
||||
status, body, err := adminDo(ctx, client, cfg, http.MethodPost, resetURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status != http.StatusOK && status != http.StatusCreated && status != http.StatusNotFound {
|
||||
return nil, fmt.Errorf("сброс трафика: %w", adminHTTPError(status, body))
|
||||
}
|
||||
|
||||
// 2) Extend expiry + (re)set the traffic limit.
|
||||
base := time.Now().UTC()
|
||||
if currentExpire > 0 {
|
||||
if cur := time.Unix(currentExpire, 0).UTC(); cur.After(base) {
|
||||
base = cur
|
||||
}
|
||||
}
|
||||
payload := map[string]any{
|
||||
"uuid": userUUID,
|
||||
"status": "ACTIVE",
|
||||
"trafficLimitBytes": cfg.Provision.TrafficGB * 1024 * 1024 * 1024,
|
||||
"expireAt": base.AddDate(0, 0, cfg.Provision.Days).Format(time.RFC3339),
|
||||
}
|
||||
raw, _ := json.Marshal(payload)
|
||||
status, body, err = adminDo(ctx, client, cfg, http.MethodPatch, cfg.PanelURL+"/api/users", raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status != http.StatusOK && status != http.StatusCreated {
|
||||
return nil, fmt.Errorf("продление: %w", adminHTTPError(status, body))
|
||||
}
|
||||
res, err := parseProvisionUser(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func adminDo(ctx context.Context, client *http.Client, cfg *AdminConfig, method, endpoint string, body []byte) (int, string, error) {
|
||||
var rd io.Reader
|
||||
if body != nil {
|
||||
rd = bytes.NewReader(body)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, method, endpoint, rd)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Navis/3.10 (Remnawave provision)")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+cfg.APIToken)
|
||||
if cfg.CaddyAPIKey != "" {
|
||||
req.Header.Set("X-Api-Key", cfg.CaddyAPIKey)
|
||||
}
|
||||
if u, err := url.Parse(endpoint); err == nil && shouldSendProxyHeaders(u) {
|
||||
req.Header.Set("X-Forwarded-Proto", u.Scheme)
|
||||
req.Header.Set("X-Forwarded-For", "127.0.0.1")
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, "", fmt.Errorf("Remnawave API недоступен: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
raw, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
|
||||
if err != nil {
|
||||
return resp.StatusCode, "", err
|
||||
}
|
||||
return resp.StatusCode, string(raw), nil
|
||||
}
|
||||
|
||||
func adminHTTPError(status int, body string) error {
|
||||
msg := strings.TrimSpace(body)
|
||||
if len(msg) > 160 {
|
||||
msg = msg[:160] + "…"
|
||||
}
|
||||
switch status {
|
||||
case http.StatusUnauthorized, http.StatusForbidden:
|
||||
return fmt.Errorf("Неверный API ключ (HTTP %d) — проверьте api_token в remnawave-api.json", status)
|
||||
case http.StatusConflict:
|
||||
return fmt.Errorf("Пользователь уже существует, но панель вернула конфликт (HTTP 409): %s", msg)
|
||||
case http.StatusBadRequest:
|
||||
return fmt.Errorf("Панель отклонила запрос (HTTP 400): %s", msg)
|
||||
default:
|
||||
return fmt.Errorf("Remnawave HTTP %d: %s", status, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func parseProvisionUser(body string) (*ProvisionResult, error) {
|
||||
var payload struct {
|
||||
Response provisionUserDTO `json:"response"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(body), &payload); err != nil || payload.Response.UUID == "" {
|
||||
// Some deployments return the DTO without the envelope.
|
||||
var direct provisionUserDTO
|
||||
if err2 := json.Unmarshal([]byte(body), &direct); err2 != nil || direct.UUID == "" {
|
||||
return nil, fmt.Errorf("Remnawave: не удалось разобрать ответ панели")
|
||||
}
|
||||
payload.Response = direct
|
||||
}
|
||||
u := payload.Response
|
||||
res := &ProvisionResult{
|
||||
UUID: u.UUID,
|
||||
ShortUUID: u.ShortUUID,
|
||||
SubscriptionURL: strings.TrimSpace(u.SubscriptionURL),
|
||||
TrafficLimitBytes: u.TrafficLimitBytes,
|
||||
}
|
||||
if u.ExpireAt != "" {
|
||||
if t, err := time.Parse(time.RFC3339, u.ExpireAt); err == nil {
|
||||
res.ExpireAt = t.Unix()
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type provisionUserDTO struct {
|
||||
UUID string `json:"uuid"`
|
||||
ShortUUID string `json:"shortUuid"`
|
||||
SubscriptionURL string `json:"subscriptionUrl"`
|
||||
TrafficLimitBytes int64 `json:"trafficLimitBytes"`
|
||||
ExpireAt string `json:"expireAt"`
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
package remnawave
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testAdminConfig(baseURL string) *AdminConfig {
|
||||
return &AdminConfig{
|
||||
PanelURL: baseURL,
|
||||
APIToken: "test-token",
|
||||
Provision: ProvisionOptions{
|
||||
TrafficGB: 50,
|
||||
Days: 30,
|
||||
Strategy: "MONTH",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionUserExisting(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer test-token" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodGet && r.URL.Path == "/api/users/by-username/ivan_petrov" {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"response": map[string]any{
|
||||
"uuid": "u-1",
|
||||
"shortUuid": "short1",
|
||||
"subscriptionUrl": "https://sub.example.com/short1",
|
||||
"trafficLimitBytes": int64(50) << 30,
|
||||
"expireAt": time.Now().Add(24 * time.Hour).UTC().Format(time.RFC3339),
|
||||
}})
|
||||
return
|
||||
}
|
||||
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
res, err := ProvisionUser(context.Background(), testAdminConfig(ts.URL), "ivan_petrov")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Created {
|
||||
t.Fatal("expected existing user, got created")
|
||||
}
|
||||
if res.SubscriptionURL != "https://sub.example.com/short1" {
|
||||
t.Fatalf("subscription url: %q", res.SubscriptionURL)
|
||||
}
|
||||
if res.Username != "ivan_petrov" {
|
||||
t.Fatalf("username: %q", res.Username)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionUserCreate(t *testing.T) {
|
||||
var created map[string]any
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/users/by-username/"):
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
case r.Method == http.MethodPost && r.URL.Path == "/api/users":
|
||||
if err := json.NewDecoder(r.Body).Decode(&created); err != nil {
|
||||
t.Errorf("decode create body: %v", err)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"response": map[string]any{
|
||||
"uuid": "u-2",
|
||||
"shortUuid": "short2",
|
||||
"subscriptionUrl": "https://sub.example.com/short2",
|
||||
"trafficLimitBytes": created["trafficLimitBytes"],
|
||||
"expireAt": created["expireAt"],
|
||||
}})
|
||||
default:
|
||||
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
res, err := ProvisionUser(context.Background(), testAdminConfig(ts.URL), "@new_client")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !res.Created {
|
||||
t.Fatal("expected created user")
|
||||
}
|
||||
if res.Username != "new_client" {
|
||||
t.Fatalf("username: %q", res.Username)
|
||||
}
|
||||
if got := created["trafficLimitBytes"].(float64); int64(got) != 50<<30 {
|
||||
t.Fatalf("trafficLimitBytes = %v, want %d (50 GB)", got, int64(50)<<30)
|
||||
}
|
||||
if created["trafficLimitStrategy"] != "MONTH" {
|
||||
t.Fatalf("strategy = %v", created["trafficLimitStrategy"])
|
||||
}
|
||||
if created["status"] != "ACTIVE" {
|
||||
t.Fatalf("status = %v", created["status"])
|
||||
}
|
||||
expireStr, _ := created["expireAt"].(string)
|
||||
expire, err := time.Parse(time.RFC3339, expireStr)
|
||||
if err != nil {
|
||||
t.Fatalf("expireAt %q: %v", expireStr, err)
|
||||
}
|
||||
days := time.Until(expire).Hours() / 24
|
||||
if days < 29 || days > 31 {
|
||||
t.Fatalf("expireAt %.1f days from now, want ~30", days)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionUserBadToken(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
_, err := ProvisionUser(context.Background(), testAdminConfig(ts.URL), "somebody1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Неверный API ключ") {
|
||||
t.Fatalf("error = %v, want «Неверный API ключ»", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeUsername(t *testing.T) {
|
||||
cases := []struct {
|
||||
in, want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"ivan_petrov", "ivan_petrov", false},
|
||||
{"@tg_handle", "tg_handle", false},
|
||||
{"user@example.com", "user_vpn", false},
|
||||
{"Иван", "", true},
|
||||
{"ab", "ab_vpn", false}, // padded to min length
|
||||
}
|
||||
for _, c := range cases {
|
||||
got, err := NormalizeUsername(c.in)
|
||||
if c.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("%q: expected error, got %q", c.in, got)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%q: %v", c.in, err)
|
||||
}
|
||||
if got != c.want {
|
||||
t.Fatalf("%q: got %q want %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAdminConfig(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "remnawave-api.json")
|
||||
|
||||
if _, err := LoadAdminConfig(path); err == nil {
|
||||
t.Fatal("expected error for missing file")
|
||||
}
|
||||
|
||||
body := `{"panel_url":"https://panel.test/","api_token":"tok123","provision":{}}`
|
||||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg, err := LoadAdminConfig(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.PanelURL != "https://panel.test" {
|
||||
t.Fatalf("panel url: %q", cfg.PanelURL)
|
||||
}
|
||||
if cfg.Provision.TrafficGB != 50 || cfg.Provision.Days != 30 || cfg.Provision.Strategy != "MONTH" {
|
||||
t.Fatalf("defaults not applied: %+v", cfg.Provision)
|
||||
}
|
||||
|
||||
// Placeholder token must be rejected.
|
||||
body = `{"panel_url":"https://panel.test","api_token":"PASTE_API_TOKEN_HERE"}`
|
||||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := LoadAdminConfig(path); err == nil {
|
||||
t.Fatal("expected error for placeholder token")
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
|
||||
"vpnclient/internal/config"
|
||||
"vpnclient/internal/linknorm"
|
||||
"vpnclient/internal/protocols/awg"
|
||||
"vpnclient/internal/protocols/hysteria2"
|
||||
"vpnclient/internal/protocols/naive"
|
||||
)
|
||||
@@ -46,7 +48,7 @@ type Result struct {
|
||||
const maxWarnings = 8
|
||||
|
||||
var (
|
||||
reShareLine = regexp.MustCompile(`(?i)((?:hysteria2|hy2|vless|vmess|trojan|awg|amneziawg|wireguard|naive\+https|naive\+quic|naive|quic|https|http)://[^\s"'<>]+)`)
|
||||
reShareLine = regexp.MustCompile(`(?i)((?:hysteria2|hy2|vless|vmess|trojan|awg|amneziawg|wireguard|vpn|naive\+https|naive\+quic|naive|quic|https|http)://[^\s"'<>]+)`)
|
||||
reClashHY2 = regexp.MustCompile(`(?is)type:\s*hysteria2\b.*?server:\s*(\S+).*?port:\s*(\d+).*?(?:password|auth):\s*["']?([^\s"']+)`)
|
||||
// reURILine matches a standalone "scheme://..." line — only such lines are
|
||||
// counted as skipped entries (YAML/HTML noise is ignored silently).
|
||||
@@ -57,7 +59,7 @@ var (
|
||||
// for a generic UA, but return a Clash YAML config for Clash-like UAs — that
|
||||
// YAML loses vless/trojan entries for us, so try the plain UA first.
|
||||
var userAgents = []string{
|
||||
"Navis/3.9 (+https://evilfox.win)",
|
||||
"EvilFox/4.0 (+https://evilfox.win)",
|
||||
"ClashMeta/1.18.0",
|
||||
}
|
||||
|
||||
@@ -67,11 +69,11 @@ var userAgents = []string{
|
||||
func Fetch(ctx context.Context, rawURL string) (*Result, error) {
|
||||
rawURL = strings.TrimSpace(rawURL)
|
||||
if rawURL == "" {
|
||||
return nil, fmt.Errorf("пустой URL подписки")
|
||||
return nil, fmt.Errorf("пустой URL конфигурации")
|
||||
}
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
|
||||
return nil, fmt.Errorf("нужен http(s) URL подписки")
|
||||
return nil, fmt.Errorf("нужен http(s) URL конфигурации")
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -104,7 +106,7 @@ func Fetch(ctx context.Context, rawURL string) (*Result, error) {
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
return nil, fmt.Errorf("в подписке нет поддерживаемых ссылок")
|
||||
return nil, fmt.Errorf("в конфигурации нет поддерживаемых ссылок")
|
||||
}
|
||||
best.Info = info
|
||||
return best, nil
|
||||
@@ -120,11 +122,11 @@ func download(ctx context.Context, rawURL, userAgent string) (string, http.Heade
|
||||
client := &http.Client{Timeout: 45 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("не удалось скачать подписку: %w", err)
|
||||
return "", nil, fmt.Errorf("не удалось загрузить конфигурацию: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", nil, fmt.Errorf("подписка HTTP %d", resp.StatusCode)
|
||||
return "", nil, fmt.Errorf("конфигурация недоступна (HTTP %d)", resp.StatusCode)
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20))
|
||||
if err != nil {
|
||||
@@ -183,7 +185,7 @@ func ParseBody(body string) ([]Item, error) {
|
||||
func ParseBodyDetailed(body string) (*Result, error) {
|
||||
body = strings.TrimSpace(body)
|
||||
if body == "" {
|
||||
return &Result{}, fmt.Errorf("пустая подписка")
|
||||
return &Result{}, fmt.Errorf("пустая конфигурация")
|
||||
}
|
||||
|
||||
candidates := []string{body}
|
||||
@@ -212,7 +214,7 @@ func ParseBodyDetailed(body string) (*Result, error) {
|
||||
return best, fmt.Errorf("нет поддерживаемых ссылок: %d записей пропущено (%s)",
|
||||
best.Skipped, strings.Join(best.Warnings, "; "))
|
||||
}
|
||||
return best, fmt.Errorf("в подписке нет поддерживаемых ссылок (naive / hysteria2 / awg / vless / vmess / trojan)")
|
||||
return best, fmt.Errorf("в конфигурации нет поддерживаемых ссылок (naive / hysteria2 / awg / vless / vmess / trojan)")
|
||||
}
|
||||
|
||||
func parseText(text string) *Result {
|
||||
@@ -222,35 +224,38 @@ func parseText(text string) *Result {
|
||||
|
||||
// countSkips is true only for raw subscription lines; URIs extracted from
|
||||
// YAML/HTML/JSON noise are best-effort and never counted as "skipped".
|
||||
process := func(line string, countSkips bool) {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
process := func(raw string, countSkips bool) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" || strings.HasPrefix(raw, "#") {
|
||||
return
|
||||
}
|
||||
line = strings.Trim(line, `"'`)
|
||||
looksURI := reURILine.MatchString(line)
|
||||
// Only strip surrounding quotes on single-line share links.
|
||||
if !strings.Contains(raw, "\n") {
|
||||
raw = strings.Trim(raw, `"'`)
|
||||
}
|
||||
looksURI := reURILine.MatchString(raw)
|
||||
skip := func(reason string) {
|
||||
if !countSkips || !looksURI {
|
||||
return
|
||||
}
|
||||
res.Skipped++
|
||||
if len(res.Warnings) < maxWarnings {
|
||||
res.Warnings = append(res.Warnings, shortenLink(line)+" — "+reason)
|
||||
res.Warnings = append(res.Warnings, shortenLink(raw)+" — "+reason)
|
||||
}
|
||||
}
|
||||
|
||||
proto := config.DetectProtocol(line)
|
||||
proto := config.DetectProtocol(raw)
|
||||
if proto == "" {
|
||||
skip("неподдерживаемая схема")
|
||||
return
|
||||
}
|
||||
// Bare https:// without credentials (common noise in HTML/YAML,
|
||||
// or a subscription URL pasted among the links) is not a proxy link.
|
||||
if proto == config.ProtocolNaive && !naiveLinkHasAuth(line) && !strings.HasPrefix(strings.ToLower(line), "naive+") {
|
||||
if proto == config.ProtocolNaive && !naiveLinkHasAuth(raw) && !strings.HasPrefix(strings.ToLower(raw), "naive+") {
|
||||
skip("нет логина и пароля в ссылке")
|
||||
return
|
||||
}
|
||||
normalized, detected, remark, err := linknorm.Normalize(proto, line)
|
||||
normalized, detected, remark, err := linknorm.Normalize(proto, raw)
|
||||
if err != nil {
|
||||
skip(err.Error())
|
||||
return
|
||||
@@ -263,6 +268,12 @@ func parseText(text string) *Result {
|
||||
if name == "" {
|
||||
name = defaultName(detected, normalized)
|
||||
}
|
||||
// Clash wireguard INI may carry "# name: …" — prefer that display name.
|
||||
if detected == config.ProtocolAWG {
|
||||
if n := awgCommentName(raw); n != "" {
|
||||
name = n
|
||||
}
|
||||
}
|
||||
base := name
|
||||
for n := 2; ; n++ {
|
||||
if _, ok := seen[name]; !ok {
|
||||
@@ -274,6 +285,15 @@ func parseText(text string) *Result {
|
||||
res.Items = append(res.Items, Item{Name: name, Protocol: detected, URI: normalized})
|
||||
}
|
||||
|
||||
// Whole-body AWG/WireGuard .conf, Amnezia JSON, vpn:// — hosted as a single
|
||||
// HTTP(S) config file (line-oriented parsing cannot reconstruct these).
|
||||
if awg.Detect(text) {
|
||||
process(text, false)
|
||||
if len(res.Items) > 0 && !looksLikeShareList(text) {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range splitLines(text) {
|
||||
process(line, true)
|
||||
}
|
||||
@@ -281,12 +301,39 @@ func parseText(text string) *Result {
|
||||
for _, m := range reShareLine.FindAllString(text, -1) {
|
||||
process(strings.TrimRight(m, ".,;)]}\"'"), false)
|
||||
}
|
||||
for _, block := range extractClashHY2(text) {
|
||||
for _, block := range extractClashProxies(text) {
|
||||
process(block, false)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// looksLikeShareList reports bodies that also contain line-oriented share links
|
||||
// (mixed subscription + AWG is rare, but do not early-return on those).
|
||||
func looksLikeShareList(text string) bool {
|
||||
lower := strings.ToLower(text)
|
||||
for _, p := range []string{"vless://", "vmess://", "trojan://", "hysteria2://", "hy2://", "naive+", "ss://"} {
|
||||
if strings.Contains(lower, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func awgCommentName(raw string) string {
|
||||
for _, line := range splitLines(raw) {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
rest := strings.TrimSpace(strings.TrimPrefix(line, "#"))
|
||||
lower := strings.ToLower(rest)
|
||||
if strings.HasPrefix(lower, "name:") {
|
||||
return strings.TrimSpace(rest[len("name:"):])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// shortenLink trims a link for warning messages, hiding query/credentials noise.
|
||||
func shortenLink(line string) string {
|
||||
if i := strings.IndexAny(line, "?#"); i >= 0 {
|
||||
@@ -312,20 +359,44 @@ func naiveLinkHasAuth(line string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func extractClashHY2(text string) []string {
|
||||
// Very small helper for Clash YAML hysteria2 blocks when share URI is absent.
|
||||
// extractClashProxies builds share links from Clash YAML proxy blocks when the
|
||||
// panel does not embed native share URIs (hysteria2, wireguard/AWG, naive).
|
||||
func extractClashProxies(text string) []string {
|
||||
lower := strings.ToLower(text)
|
||||
if !strings.Contains(lower, "type:") || !strings.Contains(lower, "hysteria2") {
|
||||
if !strings.Contains(lower, "type:") {
|
||||
return nil
|
||||
}
|
||||
need := strings.Contains(lower, "hysteria2") ||
|
||||
strings.Contains(lower, "wireguard") ||
|
||||
strings.Contains(lower, "type: naive") ||
|
||||
strings.Contains(lower, "type:naive")
|
||||
if !need {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
// Split roughly by list items.
|
||||
chunks := splitClashItems(text)
|
||||
for _, chunk := range chunks {
|
||||
for _, chunk := range splitClashItems(text) {
|
||||
cl := strings.ToLower(chunk)
|
||||
if !strings.Contains(cl, "type:") || !strings.Contains(cl, "hysteria2") {
|
||||
continue
|
||||
typ := strings.ToLower(yamlField(chunk, "type"))
|
||||
switch {
|
||||
case typ == "hysteria2" || (typ == "" && strings.Contains(cl, "hysteria2")):
|
||||
if link := clashHY2Link(chunk); link != "" {
|
||||
out = append(out, link)
|
||||
}
|
||||
case typ == "wireguard":
|
||||
if link := clashWireguardLink(chunk); link != "" {
|
||||
out = append(out, link)
|
||||
}
|
||||
case typ == "naive":
|
||||
if link := clashNaiveLink(chunk); link != "" {
|
||||
out = append(out, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = reClashHY2 // kept for possible future tightening
|
||||
return out
|
||||
}
|
||||
|
||||
func clashHY2Link(chunk string) string {
|
||||
server := yamlField(chunk, "server")
|
||||
port := yamlField(chunk, "port")
|
||||
pass := yamlField(chunk, "password")
|
||||
@@ -333,7 +404,7 @@ func extractClashHY2(text string) []string {
|
||||
pass = yamlField(chunk, "auth")
|
||||
}
|
||||
if server == "" || port == "" || pass == "" {
|
||||
continue
|
||||
return ""
|
||||
}
|
||||
name := yamlField(chunk, "name")
|
||||
sni := yamlField(chunk, "sni")
|
||||
@@ -362,10 +433,117 @@ func extractClashHY2(text string) []string {
|
||||
if name != "" {
|
||||
link += "#" + url.PathEscape(name)
|
||||
}
|
||||
out = append(out, link)
|
||||
return link
|
||||
}
|
||||
|
||||
func clashWireguardLink(chunk string) string {
|
||||
priv := firstYAML(chunk, "private-key", "private_key")
|
||||
pub := firstYAML(chunk, "public-key", "public_key")
|
||||
server := yamlField(chunk, "server")
|
||||
port := yamlField(chunk, "port")
|
||||
ip := firstYAML(chunk, "ip", "address")
|
||||
if priv == "" || pub == "" || server == "" || port == "" {
|
||||
return ""
|
||||
}
|
||||
_ = reClashHY2 // kept for possible future tightening
|
||||
return out
|
||||
name := yamlField(chunk, "name")
|
||||
psk := firstYAML(chunk, "pre-shared-key", "preshared-key", "presharedkey")
|
||||
allowed := stripYAMLList(firstYAML(chunk, "allowed-ips", "allowed_ips"))
|
||||
if allowed == "" {
|
||||
allowed = "0.0.0.0/0"
|
||||
}
|
||||
dns := stripYAMLList(yamlField(chunk, "dns"))
|
||||
mtu := yamlField(chunk, "mtu")
|
||||
keepalive := firstYAML(chunk, "persistent-keepalive", "keepalive")
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("[Interface]\n")
|
||||
b.WriteString("PrivateKey = " + priv + "\n")
|
||||
if ip != "" {
|
||||
b.WriteString("Address = " + ip + "\n")
|
||||
}
|
||||
if dns != "" {
|
||||
b.WriteString("DNS = " + dns + "\n")
|
||||
}
|
||||
if mtu != "" {
|
||||
b.WriteString("MTU = " + mtu + "\n")
|
||||
}
|
||||
// AmneziaWG / AWG 2.0 obfuscation (Clash Meta / Amnezia exports).
|
||||
for _, k := range []string{"jc", "jmin", "jmax", "s1", "s2", "s3", "s4", "h1", "h2", "h3", "h4", "i1", "i2", "i3", "i4", "i5"} {
|
||||
v := firstYAML(chunk, k, strings.ToUpper(k), strings.ToUpper(k[:1])+k[1:])
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
canon := strings.ToUpper(k[:1]) + k[1:]
|
||||
b.WriteString(canon + " = " + v + "\n")
|
||||
}
|
||||
b.WriteString("\n[Peer]\n")
|
||||
b.WriteString("PublicKey = " + pub + "\n")
|
||||
if psk != "" {
|
||||
b.WriteString("PresharedKey = " + psk + "\n")
|
||||
}
|
||||
b.WriteString("Endpoint = " + server + ":" + port + "\n")
|
||||
b.WriteString("AllowedIPs = " + allowed + "\n")
|
||||
if keepalive != "" {
|
||||
b.WriteString("PersistentKeepalive = " + keepalive + "\n")
|
||||
}
|
||||
if name != "" {
|
||||
b.WriteString("# name: " + name + "\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func clashNaiveLink(chunk string) string {
|
||||
server := yamlField(chunk, "server")
|
||||
port := yamlField(chunk, "port")
|
||||
user := firstYAML(chunk, "username", "user")
|
||||
pass := yamlField(chunk, "password")
|
||||
if server == "" || user == "" || pass == "" {
|
||||
return ""
|
||||
}
|
||||
if port == "" {
|
||||
port = "443"
|
||||
}
|
||||
name := yamlField(chunk, "name")
|
||||
udp := strings.ToLower(yamlField(chunk, "udp"))
|
||||
scheme := "https"
|
||||
if udp == "true" || udp == "yes" || strings.EqualFold(yamlField(chunk, "protocol"), "quic") {
|
||||
scheme = "quic"
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: scheme,
|
||||
User: url.UserPassword(user, pass),
|
||||
Host: server + ":" + port,
|
||||
}
|
||||
link := u.String()
|
||||
if name != "" {
|
||||
link += "#" + url.PathEscape(name)
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
func firstYAML(block string, keys ...string) string {
|
||||
for _, k := range keys {
|
||||
if v := yamlField(block, k); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func stripYAMLList(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
v = strings.TrimPrefix(v, "[")
|
||||
v = strings.TrimSuffix(v, "]")
|
||||
parts := strings.Split(v, ",")
|
||||
var clean []string
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
p = strings.Trim(p, `"'`)
|
||||
if p != "" {
|
||||
clean = append(clean, p)
|
||||
}
|
||||
}
|
||||
return strings.Join(clean, ", ")
|
||||
}
|
||||
|
||||
// reClashItemStart matches a mapping list item ("- name: X", " - type: y"),
|
||||
@@ -442,6 +620,17 @@ func defaultName(proto config.Protocol, uri string) string {
|
||||
if h, _, err := hysteria2.HostPort(uri); err == nil {
|
||||
host = h
|
||||
}
|
||||
case config.ProtocolAWG:
|
||||
if c, err := awg.Parse(uri); err == nil {
|
||||
if c.Name != "" {
|
||||
return c.Name
|
||||
}
|
||||
if h, _, e := net.SplitHostPort(c.Endpoint); e == nil {
|
||||
host = h
|
||||
} else {
|
||||
host = c.Endpoint
|
||||
}
|
||||
}
|
||||
default:
|
||||
if u, _, err := naive.ParseShareLink(uri); err == nil {
|
||||
if parsed, err := url.Parse(u); err == nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package subscription
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -193,3 +194,161 @@ proxies:
|
||||
t.Fatalf("uri %s", items[0].URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBodyAWGConf(t *testing.T) {
|
||||
body := `[Interface]
|
||||
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
Address = 10.8.0.2/32
|
||||
DNS = 1.1.1.1
|
||||
Jc = 4
|
||||
Jmin = 40
|
||||
Jmax = 70
|
||||
H1 = 1
|
||||
H2 = 2
|
||||
H3 = 3
|
||||
H4 = 4
|
||||
I1 = <r 128>
|
||||
|
||||
[Peer]
|
||||
PublicKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
|
||||
Endpoint = 203.0.113.10:51820
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
PersistentKeepalive = 25
|
||||
`
|
||||
items, err := ParseBody(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("got %d items: %+v", len(items), items)
|
||||
}
|
||||
if items[0].Protocol != "awg" {
|
||||
t.Fatalf("proto %s", items[0].Protocol)
|
||||
}
|
||||
if !strings.Contains(items[0].URI, "Jc = 4") || !strings.Contains(items[0].URI, "203.0.113.10:51820") {
|
||||
t.Fatalf("uri missing AWG2 fields: %s", items[0].URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBodyAmneziaJSON(t *testing.T) {
|
||||
body := `{
|
||||
"client_priv_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"server_pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=",
|
||||
"client_ip": "10.8.0.2/32",
|
||||
"hostName": "203.0.113.10",
|
||||
"port": "51820",
|
||||
"Jc": 4,
|
||||
"Jmin": 10,
|
||||
"Jmax": 50,
|
||||
"H1": "1",
|
||||
"H2": "2",
|
||||
"H3": "3",
|
||||
"H4": "4"
|
||||
}`
|
||||
items, err := ParseBody(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 1 || items[0].Protocol != "awg" {
|
||||
t.Fatalf("got %+v", items)
|
||||
}
|
||||
if !strings.Contains(items[0].URI, "203.0.113.10:51820") {
|
||||
t.Fatalf("uri %s", items[0].URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBodyClashWireguardAWG20(t *testing.T) {
|
||||
body := `proxies:
|
||||
- name: AWG-EU
|
||||
type: wireguard
|
||||
server: 203.0.113.20
|
||||
port: 51820
|
||||
ip: 10.8.0.2/32
|
||||
private-key: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
public-key: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
|
||||
allowed-ips: ['0.0.0.0/0']
|
||||
dns: [1.1.1.1]
|
||||
jc: 4
|
||||
jmin: 40
|
||||
jmax: 70
|
||||
h1: 1
|
||||
h2: 2
|
||||
h3: 3
|
||||
h4: 4
|
||||
`
|
||||
items, err := ParseBody(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("got %d: %+v", len(items), items)
|
||||
}
|
||||
if items[0].Protocol != "awg" {
|
||||
t.Fatalf("proto %s", items[0].Protocol)
|
||||
}
|
||||
if items[0].Name != "AWG-EU" {
|
||||
t.Fatalf("name %q", items[0].Name)
|
||||
}
|
||||
if !strings.Contains(items[0].URI, "Jc = 4") || !strings.Contains(items[0].URI, "203.0.113.20:51820") {
|
||||
t.Fatalf("uri %s", items[0].URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBodyClashNaive(t *testing.T) {
|
||||
body := `proxies:
|
||||
- name: Naive-HTTPS
|
||||
type: naive
|
||||
server: naive.example.com
|
||||
port: 443
|
||||
username: user
|
||||
password: secret
|
||||
- name: Naive-QUIC
|
||||
type: naive
|
||||
server: quic.example.com
|
||||
port: 443
|
||||
username: u2
|
||||
password: p2
|
||||
udp: true
|
||||
`
|
||||
items, err := ParseBody(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("got %d: %+v", len(items), items)
|
||||
}
|
||||
if items[0].Protocol != "naive" || items[1].Protocol != "naive" {
|
||||
t.Fatalf("protos %s %s", items[0].Protocol, items[1].Protocol)
|
||||
}
|
||||
if !strings.Contains(items[0].URI, "https://") || !strings.Contains(items[0].URI, "user:secret@naive.example.com") {
|
||||
t.Fatalf("https uri %s", items[0].URI)
|
||||
}
|
||||
if !strings.Contains(items[1].URI, "quic://") || !strings.Contains(items[1].URI, "u2:p2@quic.example.com") {
|
||||
t.Fatalf("quic uri %s", items[1].URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchAWGConfOverHTTP(t *testing.T) {
|
||||
conf := `[Interface]
|
||||
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
Address = 10.8.0.2/32
|
||||
Jc = 3
|
||||
|
||||
[Peer]
|
||||
PublicKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
|
||||
Endpoint = 198.51.100.7:51820
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
`
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = io.WriteString(w, conf)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
res, err := Fetch(context.Background(), srv.URL+"/awg.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res.Items) != 1 || res.Items[0].Protocol != "awg" {
|
||||
t.Fatalf("got %+v", res.Items)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
|
||||
// CurrentVersion is the product/semver used for update eligibility (feed "version").
|
||||
// Keep major.minor.patch only — no build suffix here.
|
||||
const CurrentVersion = "3.10.0"
|
||||
const CurrentVersion = "4.2.9"
|
||||
|
||||
// BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part,
|
||||
// macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build.
|
||||
const BuildNumber = 1
|
||||
|
||||
// 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.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/update.json"
|
||||
|
||||
// DisplayVersion is shown in the UI (product + build), e.g. "2.8.0+1".
|
||||
func DisplayVersion() string {
|
||||
@@ -274,7 +274,8 @@ func splitVer(v string) []int {
|
||||
|
||||
func allowedDownloadURL(u string) bool {
|
||||
u = strings.ToLower(strings.TrimSpace(u))
|
||||
return strings.HasPrefix(u, "https://git.evilfox.cc/") ||
|
||||
return strings.HasPrefix(u, "https://git.de4ima.uk/") ||
|
||||
strings.HasPrefix(u, "https://git.evilfox.cc/") ||
|
||||
strings.HasPrefix(u, "https://files.de4ima.uk/")
|
||||
}
|
||||
|
||||
@@ -286,7 +287,7 @@ func fetchManifest(ctx context.Context, manifestURL string) (Manifest, error) {
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Navis/"+DisplayVersion())
|
||||
req.Header.Set("User-Agent", "EvilFox/"+DisplayVersion())
|
||||
req.Header.Set("Accept", "application/json")
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
@@ -309,7 +310,7 @@ func downloadFile(ctx context.Context, url, dest string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Navis/"+DisplayVersion())
|
||||
req.Header.Set("User-Agent", "EvilFox/"+DisplayVersion())
|
||||
client := &http.Client{Timeout: 10 * time.Minute}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -355,7 +356,7 @@ func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha,
|
||||
return "", "", "", "", "", fmt.Errorf("уже актуальная версия %s (канал %s)", DisplayVersion(), st.Latest)
|
||||
}
|
||||
if !allowedDownloadURL(st.URL) {
|
||||
return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk")
|
||||
return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.de4ima.uk, git.evilfox.cc или files.de4ima.uk")
|
||||
}
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
|
||||
@@ -16,9 +16,12 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// finishUpdateFlag must stay "--navis-finish-update" forever: older installed
|
||||
// builds (3.x) launch the freshly downloaded binary with exactly this flag,
|
||||
// so renaming it would break their update handoff.
|
||||
const finishUpdateFlag = "--navis-finish-update"
|
||||
|
||||
// Apply downloads the new build and replaces the running Navis.exe after this process exits.
|
||||
// Apply downloads the new build and replaces the running EvilFox.exe after this process exits.
|
||||
func Apply(ctx context.Context, manifestURL string) (string, error) {
|
||||
if IsStorePackaged() {
|
||||
return "", fmt.Errorf("обновления устанавливаются через Microsoft Store")
|
||||
@@ -27,7 +30,7 @@ func Apply(ctx context.Context, manifestURL string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pending := filepath.Join(filepath.Dir(exe), "Navis-pending.exe")
|
||||
pending := filepath.Join(filepath.Dir(exe), "EvilFox-pending.exe")
|
||||
_ = os.Remove(pending)
|
||||
if err := os.Rename(tmp, pending); err != nil {
|
||||
if err2 := copyFile(tmp, pending); err2 != nil {
|
||||
@@ -58,7 +61,7 @@ func Apply(ctx context.Context, manifestURL string) (string, error) {
|
||||
return latest, nil
|
||||
}
|
||||
|
||||
// MaybeFinishUpdate handles: Navis-pending.exe --navis-finish-update <pid> <targetExe>
|
||||
// MaybeFinishUpdate handles: EvilFox-pending.exe --navis-finish-update <pid> <targetExe>
|
||||
// Replaces targetExe in-place with this binary, then relaunches it.
|
||||
func MaybeFinishUpdate(args []string) bool {
|
||||
if len(args) < 3 || args[0] != finishUpdateFlag {
|
||||
@@ -109,6 +112,7 @@ func CleanupStaleDownloads() {
|
||||
_ = os.Remove(filepath.Join(dir, base+".new"))
|
||||
_ = os.Remove(filepath.Join(dir, "Navis-update.part"))
|
||||
_ = os.Remove(filepath.Join(dir, ".navis-update-download"))
|
||||
_ = os.Remove(filepath.Join(dir, "Navis-pending.exe"))
|
||||
}
|
||||
|
||||
func replaceExecutable(src, dst string) error {
|
||||
|
||||
@@ -86,10 +86,10 @@ func Start(cfg Config) (*Session, error) {
|
||||
return nil, fmt.Errorf("режим VPN: нет локального SOCKS-прокси у текущего протокола")
|
||||
}
|
||||
if !IsElevated() {
|
||||
return nil, fmt.Errorf("Запустите Navis от имени администратора для режима VPN")
|
||||
return nil, fmt.Errorf("Запустите EvilFox от имени администратора для режима VPN")
|
||||
}
|
||||
if !WintunAvailable() {
|
||||
return nil, fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с Navis.exe")
|
||||
return nil, fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с EvilFox.exe")
|
||||
}
|
||||
if len(cfg.DNSServers) == 0 {
|
||||
cfg.DNSServers = []string{"1.1.1.1", "8.8.8.8"}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Navis MSIX package manifest (Microsoft Store / sideload).
|
||||
EvilFox MSIX package manifest (Microsoft Store / sideload).
|
||||
|
||||
WHY MSIX (not PWA):
|
||||
Navis is a Win32 + WebView2 desktop VPN/proxy client (system proxy, local cores,
|
||||
EvilFox is a Win32 + WebView2 desktop VPN/proxy client (system proxy, local cores,
|
||||
Naive/Hy2/AWG/Xray). A PWA cannot replace those capabilities. Package the existing
|
||||
GUI binary with Desktop Bridge / full-trust MSIX for Store distribution.
|
||||
|
||||
IDENTITY PLACEHOLDERS — replace before Store submission:
|
||||
Identity/@Name → Partner Center "Package/Identity/Name" (e.g. EvilFox.Navis)
|
||||
Identity/@Name → Partner Center "Package/Identity/Name" (e.g. EvilFox.EvilFox)
|
||||
Identity/@Publisher → exact Publisher CN from your Store certificate
|
||||
(e.g. CN=EvilFox LLC or CN=A1B2C3D4-…)
|
||||
Identity/@Version → Major.Minor.Build.Revision (all numeric; Revision often 0)
|
||||
@@ -30,16 +30,17 @@
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<!-- ProcessorArchitecture is substituted by scripts/pack-msix.ps1 (x64 | arm64). Do not ship x86/AArch32. -->
|
||||
<Identity
|
||||
Name="EvilFox.Navis"
|
||||
Publisher="CN=EvilFox"
|
||||
Version="3.10.0.1"
|
||||
Name="EvilFox.evilfox"
|
||||
Publisher="CN=9D58DFF7-7918-460E-845B-B3904A30562A"
|
||||
Version="4.2.9.0"
|
||||
ProcessorArchitecture="x64" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Navis</DisplayName>
|
||||
<DisplayName>EvilFox</DisplayName>
|
||||
<PublisherDisplayName>EvilFox</PublisherDisplayName>
|
||||
<Description>Navis — VPN/proxy client (Naive, Hysteria 2, AmneziaWG, Xray)</Description>
|
||||
<Description>EvilFox — VPN/proxy client (Naive, Hysteria 2, AmneziaWG, Xray)</Description>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
@@ -61,12 +62,12 @@
|
||||
|
||||
<Applications>
|
||||
<Application
|
||||
Id="Navis"
|
||||
Executable="Navis.exe"
|
||||
Id="EvilFox"
|
||||
Executable="EvilFox.exe"
|
||||
EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="Navis"
|
||||
Description="Navis VPN client"
|
||||
DisplayName="EvilFox"
|
||||
Description="EvilFox VPN client"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
@@ -74,7 +75,7 @@
|
||||
Wide310x150Logo="Assets\Wide310x150Logo.png"
|
||||
Square71x71Logo="Assets\Square71x71Logo.png"
|
||||
Square310x310Logo="Assets\Square310x310Logo.png"
|
||||
ShortName="Navis" />
|
||||
ShortName="EvilFox" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#0c1613" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
|
||||
@@ -1,13 +1,37 @@
|
||||
# MSIX packaging for Navis (Microsoft Store)
|
||||
# MSIX packaging for EvilFox (Microsoft Store)
|
||||
|
||||
Navis ships as a **full-trust Win32 + WebView2** app. Prefer **MSIX** over a PWA:
|
||||
EvilFox ships as a **full-trust Win32 + WebView2** app. Prefer **MSIX** over a PWA:
|
||||
system proxy and protocol cores cannot be delivered as a Store PWA alone.
|
||||
|
||||
## Multi-arch (x64 + Arm64)
|
||||
|
||||
- Device family: `Windows.Desktop` (Win10 19041+ / Win11; `MaxVersionTested` 10.0.26100.0).
|
||||
- Architectures: **x64** and **arm64** only (no x86 / AArch32).
|
||||
- Pack script writes final artifacts to **`dist\microsoft-store\`**:
|
||||
- `EvilFox_<ver>_x64.msix`
|
||||
- `EvilFox_<ver>_arm64.msix`
|
||||
- `EvilFox_<ver>.msixbundle` (when MakeAppx can bundle both)
|
||||
- `README.txt` (Partner Center upload notes, RU+EN)
|
||||
- `listing\` (store.csv + screenshots) — filled by the submission prep step
|
||||
|
||||
If MakeAppx is missing, the script still stages under `packaging/msix/_staging_<arch>` and prints exact `makeappx` commands in `README.txt`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Build Windows binaries: `build.bat` → `dist\navis-release\windows\Navis.exe`
|
||||
2. [Windows SDK](https://developer.microsoft.com/windows/downloads/windows-sdk/) with `MakeAppx.exe` (and `SignTool.exe` for sideload signing)
|
||||
3. Optional: a code-signing certificate whose **Subject CN** matches `Identity/@Publisher` in `AppxManifest.xml`
|
||||
1. Build Windows x64: `build.bat` → `dist\navis-release\windows\EvilFox.exe`
|
||||
2. Cross-build Arm64 (`CGO_ENABLED=0`). **Important:** `goversioninfo -64` produces an **amd64** `cmd/vpnapp/resource.syso` — temporarily rename it aside for the arm64 link, then restore:
|
||||
|
||||
```bat
|
||||
set GOOS=windows
|
||||
set GOARCH=arm64
|
||||
set CGO_ENABLED=0
|
||||
move cmd\vpnapp\resource.syso cmd\vpnapp\resource.syso.amd64.bak
|
||||
go build -ldflags="-H windowsgui -s -w" -trimpath -o dist\navis-release\windows\arm64\EvilFox.exe ./cmd/vpnapp
|
||||
move cmd\vpnapp\resource.syso.amd64.bak cmd\vpnapp\resource.syso
|
||||
```
|
||||
|
||||
3. [Windows SDK](https://developer.microsoft.com/windows/downloads/windows-sdk/) with `MakeAppx.exe` (and `SignTool.exe` for sideload signing)
|
||||
4. Optional: code-signing certificate whose **Subject CN** matches `Identity/@Publisher`
|
||||
|
||||
## Pack
|
||||
|
||||
@@ -15,38 +39,39 @@ From the repo root (PowerShell):
|
||||
|
||||
```powershell
|
||||
.\scripts\pack-msix.ps1
|
||||
.\scripts\pack-msix.ps1 -Arch x64 -Version 4.2.8.1
|
||||
.\scripts\pack-msix.ps1 -Arch arm64 -ExePath dist\navis-release\windows\arm64\EvilFox.exe
|
||||
.\scripts\pack-msix.ps1 -Arch all -OutDir dist\microsoft-store
|
||||
.\scripts\pack-msix.ps1 -Publisher "CN=Your Store Publisher" -Name "EvilFox.EvilFox"
|
||||
.\scripts\pack-msix.ps1 -CertPath ".\certs\evilfox-msix.pfx" -CertPassword "..."
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
```powershell
|
||||
.\scripts\pack-msix.ps1 -ExePath "dist\navis-release\windows\Navis.exe" -Version "2.9.0.1"
|
||||
.\scripts\pack-msix.ps1 -Publisher "CN=Your Store Publisher" -Name "EvilFox.Navis"
|
||||
.\scripts\pack-msix.ps1 -CertPath ".\certs\navis-msix.pfx" -CertPassword "..."
|
||||
```
|
||||
|
||||
Output: `dist\navis-release\windows\Navis_<version>.msix`
|
||||
Parameters: `-Arch x64|arm64|all`, `-OutDir`, `-Version`, `-Name`, `-Publisher`.
|
||||
|
||||
## Store submission
|
||||
|
||||
1. Create the app in [Partner Center](https://partner.microsoft.com/dashboard).
|
||||
2. Copy **Package/Identity Name** and **Publisher** into `AppxManifest.xml` (or pass `-Name` / `-Publisher` to the script).
|
||||
3. Upload the `.msix` (Partner Center can re-sign with the Store cert).
|
||||
4. Declare **runFullTrust** and network capabilities; answer Store questions about VPN/proxy use.
|
||||
5. Privacy policy URL required (document system-proxy changes and outbound tunnel traffic).
|
||||
6. **Do not enable in-app self-update** for Store builds — Navis detects MSIX package identity and hides the updater (Store updates only).
|
||||
2. Copy **Package/Identity Name** and **Publisher** into `AppxManifest.xml` (or pass `-Name` / `-Publisher`).
|
||||
3. Upload **`.msixbundle`** if available, else both **`.msix`** files. Partner Center can re-sign with the Store cert.
|
||||
4. Device family: **Windows 10/11 Desktop** only. Arm64 covers Windows on ARM.
|
||||
5. Declare **runFullTrust** and network capabilities; answer Store questions about VPN/proxy use.
|
||||
6. Privacy policy URL on [evilfox.win](https://evilfox.win) required.
|
||||
7. Sign in to Visual Studio / Partner Center with the **same Store developer account** if using VS packaging / Store association.
|
||||
8. **Do not enable in-app self-update** for Store builds — EvilFox detects MSIX package identity and hides the updater (Store updates only).
|
||||
|
||||
Optional: place arch-specific `wintun.dll` under `assets/wintun/<arch>/`. Without it, packaging still succeeds; TUN/VPN mode needs the dll next to the exe (proxy mode works without it).
|
||||
|
||||
## Sideload test
|
||||
|
||||
```powershell
|
||||
Add-AppxPackage -Path .\dist\navis-release\windows\Navis_3.9.0.1.msix
|
||||
Add-AppxPackage -Path .\dist\microsoft-store\EvilFox_4.2.8.1_x64.msix
|
||||
```
|
||||
|
||||
Unsigned packages need a trusted cert or developer mode / sideloading enabled.
|
||||
|
||||
## Remnawave notes
|
||||
|
||||
Navis can pull configs from a Remnawave panel:
|
||||
EvilFox can pull configs from a Remnawave panel:
|
||||
|
||||
1. Public subscription: paste `https://<panel>/api/sub/<shortUuid>` (or use panel URL + short UUID in the Remnawave block).
|
||||
2. Optional API token (panel → API Tokens) enables authenticated lookups:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
EvilFox Microsoft Store package folder
|
||||
Version: {{VERSION}}
|
||||
Generated: {{GENERATED}}
|
||||
|
||||
=== Partner Center upload (EN) ===
|
||||
1. Sign in to Partner Center with the same Microsoft Store developer account
|
||||
you use for Visual Studio packaging / Store association (same tenant).
|
||||
2. Open the EvilFox product -> Packages (Windows 10 / 11).
|
||||
3. Prefer uploading EvilFox_{{VERSION}}.msixbundle if present.
|
||||
Otherwise upload each EvilFox_{{VERSION}}_<arch>.msix (x64 + arm64).
|
||||
4. Device family: check Windows 10 / 11 Desktop ONLY
|
||||
(do NOT select Xbox, HoloLens, Surface Hub, IoT, Team, etc.).
|
||||
5. Arm64 package covers Windows on ARM. Do not ship x86 / AArch32;
|
||||
Microsoft is dropping AArch32 on future WoA.
|
||||
6. Before final Store signing, replace Identity Name/Publisher with the
|
||||
exact values from Partner Center (or let Partner Center re-sign).
|
||||
7. Privacy policy URL on https://evilfox.win is required for Store listing.
|
||||
|
||||
=== Partner Center (RU) ===
|
||||
1. Войдите в Partner Center тем же аккаунтом разработчика Store,
|
||||
что и в Visual Studio (ассоциация приложения / упаковка).
|
||||
2. Загрузите EvilFox_{{VERSION}}.msixbundle, если есть; иначе оба .msix
|
||||
(x64 и arm64).
|
||||
3. Семейство устройств: только Windows 10/11 Desktop
|
||||
(без Xbox / HoloLens / IoT и т.п.).
|
||||
4. Arm64 = Windows on ARM; x86/AArch32 не публикуем.
|
||||
5. Name/Publisher в манифесте должны совпадать с Partner Center
|
||||
(или Store переподпишет пакет).
|
||||
6. Нужен URL политики конфиденциальности на evilfox.win.
|
||||
|
||||
=== Artifacts ===
|
||||
{{ARTIFACTS}}
|
||||
|
||||
=== MakeAppx ===
|
||||
{{MAKEAPPX}}
|
||||
|
||||
=== wintun.dll ===
|
||||
{{WINTUN}}
|
||||
|
||||
=== Listing assets ===
|
||||
listing\store.csv - Partner Center bulk listing fields
|
||||
listing\screen\ - screenshots for Store listing
|
||||
|
||||
=== Arm64 build note ===
|
||||
Cross-build (CGO_ENABLED=0):
|
||||
set GOOS=windows
|
||||
set GOARCH=arm64
|
||||
rem temporarily move cmd\vpnapp\resource.syso (amd64 from goversioninfo -64)
|
||||
go build -ldflags="-H windowsgui -s -w" -trimpath -o dist\navis-release\windows\arm64\EvilFox.exe ./cmd/vpnapp
|
||||
rem restore resource.syso
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 166 KiB |
@@ -1,44 +1,52 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pack Navis.exe into an MSIX for Microsoft Store / sideload.
|
||||
Pack EvilFox.exe into per-arch MSIX (and optional .msixbundle) for Microsoft Store.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds Store assets, stages AppxManifest + Navis.exe, runs MakeAppx.
|
||||
Prefers MSIX over PWA: Navis needs Win32/WebView2 VPN and system-proxy access.
|
||||
Builds Store assets, stages AppxManifest + EvilFox.exe per architecture, runs MakeAppx.
|
||||
Default output: dist\microsoft-store\EvilFox_<version>_<arch>.msix
|
||||
Prefer MSIX over PWA: EvilFox needs Win32/WebView2 VPN and system-proxy access.
|
||||
|
||||
resource.syso note (arm64 cross-build): goversioninfo -64 emits an amd64 .syso.
|
||||
For GOARCH=arm64 builds, temporarily rename cmd/vpnapp/resource.syso aside, then restore.
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\pack-msix.ps1
|
||||
.\scripts\pack-msix.ps1 -Version 3.9.0.1 -Publisher "CN=EvilFox"
|
||||
.\scripts\pack-msix.ps1 -Arch x64 -Version 4.2.8.0
|
||||
.\scripts\pack-msix.ps1 -Arch arm64 -ExePath dist\navis-release\windows\arm64\EvilFox.exe
|
||||
.\scripts\pack-msix.ps1 -Arch all -OutDir dist\microsoft-store
|
||||
|
||||
Store note: Identity Version MUST end with .0 (revision). Partner Center rejects 4.2.8.1.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("x64", "arm64", "all")]
|
||||
[string]$Arch = "all",
|
||||
[string]$ExePath = "",
|
||||
[string]$OutDir = "",
|
||||
[string]$Version = "3.9.0.3",
|
||||
[string]$Name = "EvilFox.Navis",
|
||||
[string]$Publisher = "CN=EvilFox",
|
||||
[string]$DisplayName = "Navis",
|
||||
[string]$Version = "4.2.9.0",
|
||||
[string]$Name = "EvilFox.evilfox",
|
||||
[string]$Publisher = "CN=9D58DFF7-7918-460E-845B-B3904A30562A",
|
||||
[string]$DisplayName = "EvilFox",
|
||||
[string]$PublisherDisplayName = "EvilFox",
|
||||
[string]$CertPath = "",
|
||||
[string]$CertPassword = "",
|
||||
[switch]$SkipAssets
|
||||
[switch]$SkipAssets,
|
||||
[switch]$SkipBundle
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
Set-Location $Root
|
||||
|
||||
if (-not $ExePath) {
|
||||
$ExePath = Join-Path $Root "dist\navis-release\windows\Navis.exe"
|
||||
}
|
||||
if (-not $OutDir) {
|
||||
$OutDir = Join-Path $Root "dist\navis-release\windows"
|
||||
$OutDir = Join-Path $Root "dist\microsoft-store"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ExePath)) {
|
||||
throw "Navis.exe not found at '$ExePath'. Run build.bat first."
|
||||
if (-not [System.IO.Path]::IsPathRooted($OutDir)) {
|
||||
$OutDir = Join-Path $Root $OutDir
|
||||
}
|
||||
$OutDir = [System.IO.Path]::GetFullPath($OutDir)
|
||||
|
||||
function Find-SdkTool([string]$name) {
|
||||
$candidates = @()
|
||||
@@ -51,6 +59,17 @@ function Find-SdkTool([string]$name) {
|
||||
$candidates += (Join-Path $_.FullName "x86\$name")
|
||||
}
|
||||
}
|
||||
# MSIX Toolkit via winget (MakeAppx without full Windows SDK)
|
||||
$wingetPkgs = Join-Path $env:LOCALAPPDATA "Microsoft\WinGet\Packages"
|
||||
if (Test-Path $wingetPkgs) {
|
||||
Get-ChildItem -Path $wingetPkgs -Directory -Filter "Microsoft.MSIX-Toolkit*" -ErrorAction SilentlyContinue |
|
||||
ForEach-Object {
|
||||
$candidates += (Join-Path $_.FullName "MSIX-Toolkit.x64\$name")
|
||||
$candidates += (Join-Path $_.FullName "MSIX-Toolkit.x86\$name")
|
||||
Get-ChildItem -Path $_.FullName -Recurse -Filter $name -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { $candidates += $_.FullName }
|
||||
}
|
||||
}
|
||||
foreach ($p in $candidates) {
|
||||
if (Test-Path -LiteralPath $p) { return $p }
|
||||
}
|
||||
@@ -59,7 +78,128 @@ function Find-SdkTool([string]$name) {
|
||||
return $null
|
||||
}
|
||||
|
||||
function Resolve-ArchExe([string]$arch, [string]$explicit) {
|
||||
if ($explicit) { return $explicit }
|
||||
switch ($arch) {
|
||||
"x64" {
|
||||
return Join-Path $Root "dist\navis-release\windows\EvilFox.exe"
|
||||
}
|
||||
"arm64" {
|
||||
return Join-Path $Root "dist\navis-release\windows\arm64\EvilFox.exe"
|
||||
}
|
||||
default { throw "Unsupported arch: $arch" }
|
||||
}
|
||||
}
|
||||
|
||||
function Find-WintunDll([string]$arch) {
|
||||
$candidates = @()
|
||||
if ($arch -eq "arm64") {
|
||||
$candidates += @(
|
||||
(Join-Path $Root "assets\wintun\arm64\wintun.dll"),
|
||||
(Join-Path $Root "dist\navis-release\windows\arm64\wintun.dll")
|
||||
)
|
||||
} else {
|
||||
$candidates += @(
|
||||
(Join-Path $Root "assets\wintun\x64\wintun.dll"),
|
||||
(Join-Path $Root "assets\wintun\amd64\wintun.dll"),
|
||||
(Join-Path $Root "dist\navis-release\windows\wintun.dll")
|
||||
)
|
||||
}
|
||||
$candidates += @(
|
||||
(Join-Path $Root "assets\wintun\$arch\wintun.dll"),
|
||||
(Join-Path $Root "assets\wintun\wintun.dll"),
|
||||
(Join-Path $Root "wintun.dll")
|
||||
)
|
||||
foreach ($p in $candidates) {
|
||||
if (Test-Path -LiteralPath $p) { return $p }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Write-StoreReadme(
|
||||
[string]$dir,
|
||||
[string]$version,
|
||||
[string[]]$stagedArchs,
|
||||
[string[]]$msixPaths,
|
||||
[string]$bundlePath,
|
||||
[string]$makeAppxPath,
|
||||
[hashtable]$makeAppxCmds,
|
||||
[bool]$missingWintun
|
||||
) {
|
||||
$tplPath = Join-Path $Root "packaging\msix\STORE_README_TEMPLATE.txt"
|
||||
if (-not (Test-Path -LiteralPath $tplPath)) {
|
||||
throw "Missing template: $tplPath"
|
||||
}
|
||||
$text = [System.IO.File]::ReadAllText($tplPath, [System.Text.Encoding]::UTF8)
|
||||
$text = $text.Replace("{{VERSION}}", $version)
|
||||
$text = $text.Replace("{{GENERATED}}", (Get-Date -Format "yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
$artifacts = New-Object System.Collections.Generic.List[string]
|
||||
if ($bundlePath -and (Test-Path -LiteralPath $bundlePath)) {
|
||||
$artifacts.Add("Bundle: $bundlePath")
|
||||
} else {
|
||||
$artifacts.Add("Bundle: (not created)")
|
||||
}
|
||||
foreach ($p in $msixPaths) {
|
||||
if (Test-Path -LiteralPath $p) {
|
||||
$artifacts.Add("MSIX: $p")
|
||||
}
|
||||
}
|
||||
foreach ($a in $stagedArchs) {
|
||||
$stg = Join-Path $Root ("packaging\msix\_staging_" + $a)
|
||||
$artifacts.Add("Staging ${a}: $stg")
|
||||
}
|
||||
$text = $text.Replace("{{ARTIFACTS}}", ($artifacts -join "`r`n"))
|
||||
|
||||
$makeLines = New-Object System.Collections.Generic.List[string]
|
||||
if ($makeAppxPath) {
|
||||
$makeLines.Add("Found: $makeAppxPath")
|
||||
} else {
|
||||
$makeLines.Add("MakeAppx.exe NOT FOUND on this machine.")
|
||||
$makeLines.Add("Install Windows 10/11 SDK, then run:")
|
||||
foreach ($a in $stagedArchs) {
|
||||
if ($makeAppxCmds.ContainsKey($a)) {
|
||||
$makeLines.Add(" $($makeAppxCmds[$a])")
|
||||
}
|
||||
}
|
||||
if ($stagedArchs.Count -ge 2) {
|
||||
$bundleDir = Join-Path $dir "_bundle_input"
|
||||
$bundleOut = Join-Path $dir ("EvilFox_" + $version + ".msixbundle")
|
||||
$makeLines.Add(" makeappx bundle /d `"$bundleDir`" /p `"$bundleOut`" /o")
|
||||
$makeLines.Add(" (copy both .msix into _bundle_input first)")
|
||||
}
|
||||
}
|
||||
$text = $text.Replace("{{MAKEAPPX}}", ($makeLines -join "`r`n"))
|
||||
|
||||
if ($missingWintun) {
|
||||
$wintun = @(
|
||||
"wintun.dll was NOT found under assets/ or dist/. Packaging continued.",
|
||||
"VPN (TUN) mode needs wintun.dll next to EvilFox.exe.",
|
||||
"Store users can still use proxy mode without it.",
|
||||
"Add arch-specific wintun.dll later under assets/wintun/<arch>/ if needed."
|
||||
) -join "`r`n"
|
||||
} else {
|
||||
$wintun = "wintun.dll was included in staging where found for the target arch."
|
||||
}
|
||||
$text = $text.Replace("{{WINTUN}}", $wintun)
|
||||
|
||||
$utf8 = New-Object System.Text.UTF8Encoding $false
|
||||
[System.IO.File]::WriteAllText((Join-Path $dir "README.txt"), $text, $utf8)
|
||||
}
|
||||
|
||||
$MakeAppx = Find-SdkTool "makeappx.exe"
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
$archList = @()
|
||||
if ($Arch -eq "all") {
|
||||
$archList = @("x64", "arm64")
|
||||
} else {
|
||||
$archList = @($Arch)
|
||||
}
|
||||
|
||||
# When -Arch all, prefer per-arch default exe paths (ignore -ExePath).
|
||||
$explicitExe = ""
|
||||
if ($Arch -ne "all") { $explicitExe = $ExePath }
|
||||
|
||||
if (-not $SkipAssets) {
|
||||
Write-Host "Generating Store assets..."
|
||||
@@ -67,60 +207,168 @@ if (-not $SkipAssets) {
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
$Staging = Join-Path $Root "packaging\msix\_staging"
|
||||
if (Test-Path $Staging) { Remove-Item -Recurse -Force $Staging }
|
||||
New-Item -ItemType Directory -Path $Staging | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $Staging "Assets") | Out-Null
|
||||
$producedMsix = @()
|
||||
$stagedArchs = @()
|
||||
$makeAppxCmds = @{}
|
||||
$anyMissingExe = $false
|
||||
$missingWintun = $false
|
||||
$packFailed = $false
|
||||
$makeAppxMissing = -not [bool]$MakeAppx
|
||||
|
||||
$AssetsSrc = Join-Path $Root "packaging\msix\Assets"
|
||||
Copy-Item -Path (Join-Path $AssetsSrc "*") -Destination (Join-Path $Staging "Assets") -Force
|
||||
Copy-Item -LiteralPath $ExePath -Destination (Join-Path $Staging "Navis.exe") -Force
|
||||
foreach ($a in $archList) {
|
||||
$exe = Resolve-ArchExe $a $explicitExe
|
||||
if (-not (Test-Path -LiteralPath $exe)) {
|
||||
Write-Host "SKIP ${a}: EvilFox.exe not found at '$exe'" -ForegroundColor Yellow
|
||||
$anyMissingExe = $true
|
||||
continue
|
||||
}
|
||||
|
||||
$ManifestSrc = Join-Path $Root "packaging\msix\AppxManifest.xml"
|
||||
$ManifestDst = Join-Path $Staging "AppxManifest.xml"
|
||||
$manifestText = Get-Content -LiteralPath $ManifestSrc -Raw -Encoding UTF8
|
||||
# Prefer exact placeholder swaps so we never touch <?xml version=...?>.
|
||||
$manifestText = $manifestText.Replace('Name="EvilFox.Navis"', "Name=`"$Name`"")
|
||||
$manifestText = $manifestText.Replace('Publisher="CN=EvilFox"', "Publisher=`"$Publisher`"")
|
||||
$manifestText = $manifestText.Replace('Version="3.9.0.3"', "Version=`"$Version`"")
|
||||
$manifestText = $manifestText.Replace('<DisplayName>Navis</DisplayName>', "<DisplayName>$DisplayName</DisplayName>")
|
||||
$manifestText = $manifestText.Replace('<PublisherDisplayName>EvilFox</PublisherDisplayName>', "<PublisherDisplayName>$PublisherDisplayName</PublisherDisplayName>")
|
||||
$manifestText = $manifestText.Replace('DisplayName="Navis"', "DisplayName=`"$DisplayName`"")
|
||||
[System.IO.File]::WriteAllText($ManifestDst, $manifestText, (New-Object System.Text.UTF8Encoding $false))
|
||||
$Staging = Join-Path $Root ("packaging\msix\_staging_" + $a)
|
||||
if (Test-Path $Staging) { Remove-Item -Recurse -Force $Staging }
|
||||
New-Item -ItemType Directory -Path $Staging | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $Staging "Assets") | Out-Null
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
$MsixPath = Join-Path $OutDir ("Navis_" + $Version + ".msix")
|
||||
if (Test-Path $MsixPath) { Remove-Item -Force $MsixPath }
|
||||
$AssetsSrc = Join-Path $Root "packaging\msix\Assets"
|
||||
if (-not (Test-Path $AssetsSrc)) {
|
||||
throw "Store assets missing at $AssetsSrc (run without -SkipAssets)."
|
||||
}
|
||||
Copy-Item -Path (Join-Path $AssetsSrc "*") -Destination (Join-Path $Staging "Assets") -Force
|
||||
Copy-Item -LiteralPath $exe -Destination (Join-Path $Staging "EvilFox.exe") -Force
|
||||
|
||||
if (-not $MakeAppx) {
|
||||
Write-Host ""
|
||||
Write-Host "MakeAppx.exe not found - install the Windows 10/11 SDK to produce .msix." -ForegroundColor Yellow
|
||||
Write-Host "Staging is ready: $Staging"
|
||||
Write-Host "Then run:"
|
||||
Write-Host (" makeappx pack /d `"{0}`" /p `"{1}`" /o" -f $Staging, $MsixPath)
|
||||
exit 2
|
||||
}
|
||||
$wintun = Find-WintunDll $a
|
||||
if ($wintun) {
|
||||
Copy-Item -LiteralPath $wintun -Destination (Join-Path $Staging "wintun.dll") -Force
|
||||
Write-Host "Included wintun.dll for $a from $wintun"
|
||||
} else {
|
||||
$missingWintun = $true
|
||||
Write-Host "No wintun.dll for $a (proxy mode still OK; TUN needs dll next to exe)." -ForegroundColor DarkYellow
|
||||
}
|
||||
|
||||
Write-Host "Packing $MsixPath ..."
|
||||
& $MakeAppx pack /d $Staging /p $MsixPath /o
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
$ManifestSrc = Join-Path $Root "packaging\msix\AppxManifest.xml"
|
||||
$ManifestDst = Join-Path $Staging "AppxManifest.xml"
|
||||
$manifestText = Get-Content -LiteralPath $ManifestSrc -Raw -Encoding UTF8
|
||||
# Prefer exact placeholder swaps so we never touch <?xml version=...?>.
|
||||
$manifestText = $manifestText.Replace('Name="EvilFox.evilfox"', "Name=`"$Name`"")
|
||||
$manifestText = $manifestText.Replace('Name="EvilFox.EvilFox"', "Name=`"$Name`"")
|
||||
$manifestText = $manifestText.Replace('Publisher="CN=9D58DFF7-7918-460E-845B-B3904A30562A"', "Publisher=`"$Publisher`"")
|
||||
$manifestText = $manifestText.Replace('Publisher="CN=EvilFox"', "Publisher=`"$Publisher`"")
|
||||
# Store requires Identity Version revision (4th component) to be 0.
|
||||
if ($Version -notmatch '^\d+\.\d+\.\d+\.0$') {
|
||||
Write-Host "WARNING: Partner Center rejects non-zero revision; use e.g. 4.2.9.0 (got '$Version')." -ForegroundColor Yellow
|
||||
}
|
||||
# Only the package Identity Version — not TargetDeviceFamily MinVersion/MaxVersionTested.
|
||||
$manifestText = [regex]::Replace(
|
||||
$manifestText,
|
||||
'(<Identity\b[^>]*\bVersion=")[^"]+(")',
|
||||
('${1}' + $Version + '${2}')
|
||||
)
|
||||
$manifestText = $manifestText.Replace('ProcessorArchitecture="x64"', "ProcessorArchitecture=`"$a`"")
|
||||
$manifestText = $manifestText.Replace('ProcessorArchitecture="arm64"', "ProcessorArchitecture=`"$a`"")
|
||||
$manifestText = $manifestText.Replace('<DisplayName>EvilFox</DisplayName>', "<DisplayName>$DisplayName</DisplayName>")
|
||||
$manifestText = $manifestText.Replace('<PublisherDisplayName>EvilFox</PublisherDisplayName>', "<PublisherDisplayName>$PublisherDisplayName</PublisherDisplayName>")
|
||||
$manifestText = $manifestText.Replace('DisplayName="EvilFox"', "DisplayName=`"$DisplayName`"")
|
||||
[System.IO.File]::WriteAllText($ManifestDst, $manifestText, (New-Object System.Text.UTF8Encoding $false))
|
||||
|
||||
if ($CertPath) {
|
||||
$MsixPath = Join-Path $OutDir ("EvilFox_" + $Version + "_" + $a + ".msix")
|
||||
$makeAppxCmds[$a] = ("makeappx pack /d `"{0}`" /p `"{1}`" /o" -f $Staging, $MsixPath)
|
||||
$stagedArchs += $a
|
||||
|
||||
if ($makeAppxMissing) {
|
||||
Write-Host "Staged $a (MakeAppx missing): $Staging" -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
|
||||
if (Test-Path $MsixPath) { Remove-Item -Force $MsixPath }
|
||||
Write-Host "Packing $MsixPath ..."
|
||||
& $MakeAppx pack /d $Staging /p $MsixPath /o
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "MakeAppx pack failed for $a (exit $LASTEXITCODE)" -ForegroundColor Red
|
||||
$packFailed = $true
|
||||
continue
|
||||
}
|
||||
|
||||
if ($CertPath) {
|
||||
$SignTool = Find-SdkTool "signtool.exe"
|
||||
if (-not $SignTool) { throw "signtool.exe not found (needed for -CertPath)." }
|
||||
Write-Host "Signing with $CertPath ..."
|
||||
Write-Host "Signing $a with $CertPath ..."
|
||||
if ($CertPassword) {
|
||||
& $SignTool sign /fd SHA256 /a /f $CertPath /p $CertPassword $MsixPath
|
||||
} else {
|
||||
& $SignTool sign /fd SHA256 /a /f $CertPath $MsixPath
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Sign failed for $a" -ForegroundColor Red
|
||||
$packFailed = $true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
$producedMsix += $MsixPath
|
||||
Write-Host "Packed: $MsixPath"
|
||||
}
|
||||
|
||||
$BundlePath = Join-Path $OutDir ("EvilFox_" + $Version + ".msixbundle")
|
||||
if (-not $SkipBundle -and -not $makeAppxMissing -and $producedMsix.Count -ge 2) {
|
||||
$bundleInput = Join-Path $OutDir "_bundle_input"
|
||||
if (Test-Path $bundleInput) { Remove-Item -Recurse -Force $bundleInput }
|
||||
New-Item -ItemType Directory -Path $bundleInput | Out-Null
|
||||
foreach ($p in $producedMsix) {
|
||||
Copy-Item -LiteralPath $p -Destination (Join-Path $bundleInput (Split-Path $p -Leaf)) -Force
|
||||
}
|
||||
if (Test-Path $BundlePath) { Remove-Item -Force $BundlePath }
|
||||
Write-Host "Bundling $BundlePath ..."
|
||||
& $MakeAppx bundle /d $bundleInput /p $BundlePath /o
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "makeappx bundle failed (exit $LASTEXITCODE)" -ForegroundColor Yellow
|
||||
$BundlePath = ""
|
||||
} else {
|
||||
Write-Host "Bundle: $BundlePath"
|
||||
}
|
||||
} elseif ($producedMsix.Count -eq 1) {
|
||||
$BundlePath = ""
|
||||
}
|
||||
|
||||
Write-StoreReadme -dir $OutDir -version $Version -stagedArchs $stagedArchs `
|
||||
-msixPaths $producedMsix -bundlePath $BundlePath -makeAppxPath $MakeAppx `
|
||||
-makeAppxCmds $makeAppxCmds -missingWintun $missingWintun
|
||||
|
||||
# Copy Partner Center listing inputs when present in the repo.
|
||||
$listingDir = Join-Path $OutDir "listing"
|
||||
$listingScreen = Join-Path $listingDir "screen"
|
||||
New-Item -ItemType Directory -Force -Path $listingScreen | Out-Null
|
||||
$storeCsv = Join-Path $Root "store.csv"
|
||||
if (Test-Path -LiteralPath $storeCsv) {
|
||||
Copy-Item -LiteralPath $storeCsv -Destination (Join-Path $listingDir "store.csv") -Force
|
||||
}
|
||||
$screenSrc = Join-Path $Root "screen"
|
||||
if (Test-Path -LiteralPath $screenSrc) {
|
||||
Copy-Item -Path (Join-Path $screenSrc "*") -Destination $listingScreen -Force
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done:"
|
||||
Write-Host " $MsixPath"
|
||||
Write-Host " Staging: $Staging"
|
||||
Write-Host ""
|
||||
Write-Host "Store: upload MSIX in Partner Center (identity must match reservation)."
|
||||
Write-Host ("Sideload: Add-AppxPackage -Path `"{0}`"" -f $MsixPath)
|
||||
Write-Host "Store folder: $OutDir"
|
||||
Write-Host "README: $(Join-Path $OutDir 'README.txt')"
|
||||
Write-Host "Staged: $($stagedArchs -join ', ')"
|
||||
if ($producedMsix.Count -gt 0) {
|
||||
Write-Host "MSIX: $($producedMsix -join ', ')"
|
||||
}
|
||||
|
||||
# Exit non-zero only after staging both (when requested) if MakeAppx missing,
|
||||
# or if packing failed / required exes missing.
|
||||
if ($makeAppxMissing) {
|
||||
if ($Arch -eq "all" -and $stagedArchs.Count -lt 2) {
|
||||
Write-Host "MakeAppx missing and could not stage both arches." -ForegroundColor Red
|
||||
exit 2
|
||||
}
|
||||
if ($stagedArchs.Count -eq 0) {
|
||||
Write-Host "Nothing staged." -ForegroundColor Red
|
||||
exit 2
|
||||
}
|
||||
Write-Host "MakeAppx missing - staging complete; see README.txt for pack commands." -ForegroundColor Yellow
|
||||
exit 2
|
||||
}
|
||||
|
||||
if ($packFailed) { exit 1 }
|
||||
if ($anyMissingExe -and $Arch -eq "all" -and $producedMsix.Count -lt 2) { exit 1 }
|
||||
if ($producedMsix.Count -eq 0) { exit 1 }
|
||||
exit 0
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
{
|
||||
"version": "3.10.0",
|
||||
"notes": "3.10.0+1: новый интерфейс с боковым меню (Главная / Профили / Настройки / Логи / О программе), аккаунт с продлением 50 ГБ / +30 дней и автопродлением, режим VPN на Windows (TUN, весь трафик, нужны права администратора и wintun.dll). Windows 3.10.0.1.",
|
||||
"version": "4.2.8",
|
||||
"notes": "4.2.8: Store-ready Privacy Policy and Terms of Use on Help (RU/EN); docs for website hosting",
|
||||
"platform": "windows-amd64",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/windows/Navis.exe",
|
||||
"sha256": "2d1face047cf2fa8e6479c2e078495fff6233c28baa4d4c7cab0cef39d065d30",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "9b5cc04093561a6a6d7b97baf8f8cc9498dd863dfeb0bc3c92446c3fdb79f7c2",
|
||||
"mandatory": false,
|
||||
"platforms": {
|
||||
"windows-amd64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/windows/Navis.exe",
|
||||
"sha256": "2d1face047cf2fa8e6479c2e078495fff6233c28baa4d4c7cab0cef39d065d30",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis",
|
||||
"sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d",
|
||||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/arm64/Navis.app.zip"
|
||||
},
|
||||
"darwin-amd64": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis",
|
||||
"sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3",
|
||||
"os": "darwin",
|
||||
"arch": "amd64",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/amd64/Navis.app.zip"
|
||||
},
|
||||
"darwin-universal": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis",
|
||||
"sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2",
|
||||
"os": "darwin",
|
||||
"arch": "universal",
|
||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis.dmg",
|
||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/macos/universal/Navis.app.zip"
|
||||
},
|
||||
"android": {
|
||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/android/Navis.apk",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk",
|
||||
"sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf",
|
||||
"os": "android",
|
||||
"arch": "universal"
|
||||
},
|
||||
"darwin-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis",
|
||||
"sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3",
|
||||
"os": "darwin",
|
||||
"arch": "amd64",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis",
|
||||
"sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d",
|
||||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip"
|
||||
},
|
||||
"darwin-universal": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis",
|
||||
"sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2",
|
||||
"os": "darwin",
|
||||
"arch": "universal",
|
||||
"dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg",
|
||||
"zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip"
|
||||
},
|
||||
"windows-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "9b5cc04093561a6a6d7b97baf8f8cc9498dd863dfeb0bc3c92446c3fdb79f7c2",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
Field,ID,Type (Тип),default,en
|
||||
Description,2,Текст,"EvilFox — приложение для безопасного доступа в интернет на Windows.
|
||||
|
||||
Защитите данные в публичных Wi‑Fi сетях: EvilFox шифрует трафик, скрывает ваш реальный IP-адрес и помогает предотвратить утечки DNS. Современный интерфейс на русском и английском, светлая и тёмная темы, удобный выбор серверов и понятный статус подключения.
|
||||
|
||||
Возможности:
|
||||
• Шифрование трафика с поддержкой современных протоколов
|
||||
• Режим прокси и полный режим защиты (TUN) для всего трафика приложений
|
||||
• Защита от утечек (Kill Switch) при обрыве соединения
|
||||
• Автовыбор сервера с наименьшим пингом
|
||||
• Список серверов с флагами стран и группировкой по хосту
|
||||
• Добавление собственной конфигурации по URL
|
||||
• Мониторинг использования трафика и статистика сессий
|
||||
• Интерфейс на русском и английском языках
|
||||
|
||||
EvilFox подходит для повседневной работы, путешествий и защиты приватности. Конфигурацию подключения вы добавляете самостоятельно; покупки внутри приложения не требуются.
|
||||
|
||||
Сайт: https://evilfox.win/
|
||||
Поддержка: https://t.me/evilfox100Bot","EvilFox is a Windows app for private, secure internet access.
|
||||
|
||||
Protect your data on public Wi‑Fi: EvilFox encrypts your traffic, hides your real IP address, and helps prevent DNS leaks. A modern interface in English and Russian, light and dark themes, an easy server list, and a clear connection status.
|
||||
|
||||
Features:
|
||||
• Traffic encryption with modern protocol support
|
||||
• Proxy mode and full protection mode (TUN) for all app traffic
|
||||
• Leak protection (Kill Switch) if the connection drops
|
||||
• Auto-select the server with the lowest ping
|
||||
• Server list with country flags and host grouping
|
||||
• Add your own configuration via URL
|
||||
• Traffic usage monitoring and session stats
|
||||
• English and Russian interface
|
||||
|
||||
EvilFox is built for everyday browsing, travel, and privacy. You add your own connection configuration; no in-app purchases are required.
|
||||
|
||||
Website: https://evilfox.win/
|
||||
Support: https://t.me/evilfox100Bot"
|
||||
ReleaseNotes,3,Текст,"EvilFox 4.2 — удобный и понятный клиент для безопасного доступа в интернет.
|
||||
|
||||
Что нового для пользователей:
|
||||
• Современный интерфейс: вкладки Защита, Мои серверы, Настройки, Статистика, Помощь
|
||||
• Большая кнопка подключения и понятный статус защиты
|
||||
• Светлая тема по умолчанию и переключение на тёмную
|
||||
• Языки: русский и английский (RU | EN)
|
||||
• Флаги стран у серверов (корректно на Windows)
|
||||
• Серверы с одним IP объединяются в одну строку с доступными протоколами
|
||||
• Добавление конфигурации по URL
|
||||
• Режим прокси и полный режим защиты (TUN)
|
||||
• Kill Switch — защита от утечек при обрыве соединения
|
||||
• Автовыбор сервера с лучшим пингом
|
||||
• Статистика трафика и сессий
|
||||
• Политика конфиденциальности и условия использования в разделе Помощь
|
||||
• Адаптивное окно под любой экран и масштаб Windows","EvilFox 4.2 — a clearer, more polished client for private internet access.
|
||||
|
||||
What's new:
|
||||
• Modern UI: Protection, My servers, Settings, Statistics, Help
|
||||
• Large connect button and clear protection status
|
||||
• Light theme by default, with dark theme toggle
|
||||
• Russian and English languages (RU | EN)
|
||||
• Country flags for servers (rendered correctly on Windows)
|
||||
• Same-IP servers grouped into one row with available protocols
|
||||
• Add configuration via URL
|
||||
• Proxy mode and full protection mode (TUN)
|
||||
• Kill Switch — leak protection if the connection drops
|
||||
• Auto-select the lowest-ping server
|
||||
• Traffic and session statistics
|
||||
• Privacy Policy and Terms of Use in Help
|
||||
• Adaptive window sizing for any screen and Windows display scale"
|
||||
Title,4,Текст,EvilFox,EvilFox
|
||||
ShortTitle,5,Текст,EvilFox,EvilFox
|
||||
SortTitle,6,Текст,EvilFox,EvilFox
|
||||
VoiceTitle,7,Текст,Evil Fox,Evil Fox
|
||||
ShortDescription,8,Текст,"Безопасный доступ в интернет: шифрование трафика, скрытие IP, защита от утечек DNS. Русский и английский. Современные протоколы, Kill Switch, удобный выбор серверов.","Secure internet access: encrypt traffic, hide your IP, help prevent DNS leaks. English & Russian. Modern protocols, Kill Switch, easy server selection."
|
||||
DevStudio,9,Текст,EvilFox,EvilFox
|
||||
CopyrightTrademarkInformation,12,Текст,"© 2026 EvilFox. Все права защищены. EvilFox, логотип EvilFox и связанные знаки являются товарными знаками или зарегистрированными товарными знаками EvilFox. Прочие названия продуктов и компаний, упомянутые в приложении или на странице продукта, могут быть товарными знаками соответствующих владельцев.","© 2026 EvilFox. All rights reserved. EvilFox, the EvilFox logo, and related marks are trademarks or registered trademarks of EvilFox. Other product and company names mentioned in the app or product listing may be trademarks of their respective owners."
|
||||
AdditionalLicenseTerms,13,Текст,"Дополнительно к стандартным условиям лицензии Microsoft Store / приложения:
|
||||
|
||||
1. EvilFox — клиент для защищённого сетевого подключения. Конфигурации серверов и URL подключения предоставляются вами и не являются частью покупки в Store (если применимо).
|
||||
|
||||
2. Вы несёте ответственность за законность использования приложения и выбранных серверов в вашей юрисдикции.
|
||||
|
||||
3. Издатель не гарантирует бесперебойную работу сторонних серверов, скорость или доступность конкретных узлов.
|
||||
|
||||
4. Приложение может хранить настройки и конфигурации локально на устройстве. Политика конфиденциальности и полные условия использования доступны в разделе «Помощь» приложения и на https://evilfox.win/
|
||||
|
||||
5. Обновления через Microsoft Store заменяют самообновление приложения в сборке Store.
|
||||
|
||||
6. Возможность runFullTrust / классическое настольное приложение требуется для работы клиента (WebView2, сетевые режимы).
|
||||
|
||||
Контакты: https://evilfox.win/ · https://t.me/evilfox100Bot
|
||||
","In addition to the standard Microsoft Store / app license terms:
|
||||
|
||||
1. EvilFox is a client for protected network connectivity. Server configurations and connection URLs are supplied by you and are not part of any Store purchase (where applicable).
|
||||
|
||||
2. You are responsible for using the app and chosen servers lawfully in your jurisdiction.
|
||||
|
||||
3. The publisher does not warrant uninterrupted operation of third-party servers, speed, or availability of specific nodes.
|
||||
|
||||
4. The app may store settings and configurations locally on your device. The Privacy Policy and full Terms of Use are available in the app Help section and at https://evilfox.win/
|
||||
|
||||
5. Updates delivered via the Microsoft Store replace in-app self-update in Store builds.
|
||||
|
||||
6. The runFullTrust / classic desktop capability is required for the client to function (WebView2, network modes).
|
||||
|
||||
Contact: https://evilfox.win/ · https://t.me/evilfox100Bot
|
||||
"
|
||||
DesktopScreenshot1,100,Относительный путь (или URL-адрес файла в Центре партнеров),screen/1.ru.png,screen/7.en.png
|
||||
DesktopScreenshot2,101,Относительный путь (или URL-адрес файла в Центре партнеров),screen/2.ru.png,screen/8.en.png
|
||||
DesktopScreenshot3,102,Относительный путь (или URL-адрес файла в Центре партнеров),screen/3.ru.png,screen/9.png
|
||||
DesktopScreenshot4,103,Относительный путь (или URL-адрес файла в Центре партнеров),screen/4.ru.png,screen/10.en.png
|
||||
DesktopScreenshot5,104,Относительный путь (или URL-адрес файла в Центре партнеров),screen/5.ru.png,screen/11.en.png
|
||||
DesktopScreenshot6,105,Относительный путь (или URL-адрес файла в Центре партнеров),screen/6.ru.png,screen/12dark.png
|
||||
DesktopScreenshot7,106,Относительный путь (или URL-адрес файла в Центре партнеров),screen/12dark.png,screen/13dark.png
|
||||
DesktopScreenshot8,107,Относительный путь (или URL-адрес файла в Центре партнеров),screen/13dark.png,screen/14dark.png
|
||||
DesktopScreenshot9,108,Относительный путь (или URL-адрес файла в Центре партнеров),screen/14dark.png,screen/15dark.png
|
||||
DesktopScreenshot10,109,Относительный путь (или URL-адрес файла в Центре партнеров),screen/15dark.png,screen/16dark.png
|
||||
DesktopScreenshot11,110,Относительный путь (или URL-адрес файла в Центре партнеров),screen/16dark.png,
|
||||
DesktopScreenshot12,111,Относительный путь (или URL-адрес файла в Центре партнеров),screen/9.png,
|
||||
DesktopScreenshot13,112,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot14,113,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot15,114,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot16,115,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot17,116,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot18,117,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot19,118,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot20,119,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot21,120,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot22,121,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot23,122,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot24,123,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot25,124,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot26,125,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot27,126,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot28,127,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot29,128,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshot30,129,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
DesktopScreenshotCaption1,150,Текст,Главный экран: подключение и статус защиты,Home: connect and protection status
|
||||
DesktopScreenshotCaption2,151,Текст,Список серверов с флагами и пингом,Server list with flags and ping
|
||||
DesktopScreenshotCaption3,152,Текст,"Настройки: тема, язык, Kill Switch","Settings: theme, language, Kill Switch"
|
||||
DesktopScreenshotCaption4,153,Текст,Статистика использования,Usage statistics
|
||||
DesktopScreenshotCaption5,154,Текст,Помощь и полезные ссылки,Statistics and help
|
||||
DesktopScreenshotCaption6,155,Текст,Добавление конфигурации,Dark theme: home
|
||||
DesktopScreenshotCaption7,156,Текст,Тёмная тема: главный экран,Dark theme: servers
|
||||
DesktopScreenshotCaption8,157,Текст,Тёмная тема: серверы,Dark theme: settings
|
||||
DesktopScreenshotCaption9,158,Текст,Тёмная тема: настройки,Dark theme: statistics
|
||||
DesktopScreenshotCaption10,159,Текст,Тёмная тема: статистика,Dark theme: help
|
||||
DesktopScreenshotCaption11,160,Текст,Тёмная тема: помощь,Dark theme: help
|
||||
DesktopScreenshotCaption12,161,Текст,Общий вид приложения,App overview
|
||||
DesktopScreenshotCaption13,162,Текст,,
|
||||
DesktopScreenshotCaption14,163,Текст,,
|
||||
DesktopScreenshotCaption15,164,Текст,,
|
||||
DesktopScreenshotCaption16,165,Текст,,
|
||||
DesktopScreenshotCaption17,166,Текст,,
|
||||
DesktopScreenshotCaption18,167,Текст,,
|
||||
DesktopScreenshotCaption19,168,Текст,,
|
||||
DesktopScreenshotCaption20,169,Текст,,
|
||||
DesktopScreenshotCaption21,170,Текст,,
|
||||
DesktopScreenshotCaption22,171,Текст,,
|
||||
DesktopScreenshotCaption23,172,Текст,,
|
||||
DesktopScreenshotCaption24,173,Текст,,
|
||||
DesktopScreenshotCaption25,174,Текст,,
|
||||
DesktopScreenshotCaption26,175,Текст,,
|
||||
DesktopScreenshotCaption27,176,Текст,,
|
||||
DesktopScreenshotCaption28,177,Текст,,
|
||||
DesktopScreenshotCaption29,178,Текст,,
|
||||
DesktopScreenshotCaption30,179,Текст,,
|
||||
MobileScreenshot1,200,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot2,201,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot3,202,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot4,203,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot5,204,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot6,205,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot7,206,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot8,207,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot9,208,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot10,209,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot11,210,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot12,211,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot13,212,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot14,213,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot15,214,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot16,215,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot17,216,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot18,217,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot19,218,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot20,219,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot21,220,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot22,221,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot23,222,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot24,223,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot25,224,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot26,225,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot27,226,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot28,227,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot29,228,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshot30,229,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
MobileScreenshotCaption1,250,Текст,,
|
||||
MobileScreenshotCaption2,251,Текст,,
|
||||
MobileScreenshotCaption3,252,Текст,,
|
||||
MobileScreenshotCaption4,253,Текст,,
|
||||
MobileScreenshotCaption5,254,Текст,,
|
||||
MobileScreenshotCaption6,255,Текст,,
|
||||
MobileScreenshotCaption7,256,Текст,,
|
||||
MobileScreenshotCaption8,257,Текст,,
|
||||
MobileScreenshotCaption9,258,Текст,,
|
||||
MobileScreenshotCaption10,259,Текст,,
|
||||
MobileScreenshotCaption11,260,Текст,,
|
||||
MobileScreenshotCaption12,261,Текст,,
|
||||
MobileScreenshotCaption13,262,Текст,,
|
||||
MobileScreenshotCaption14,263,Текст,,
|
||||
MobileScreenshotCaption15,264,Текст,,
|
||||
MobileScreenshotCaption16,265,Текст,,
|
||||
MobileScreenshotCaption17,266,Текст,,
|
||||
MobileScreenshotCaption18,267,Текст,,
|
||||
MobileScreenshotCaption19,268,Текст,,
|
||||
MobileScreenshotCaption20,269,Текст,,
|
||||
MobileScreenshotCaption21,270,Текст,,
|
||||
MobileScreenshotCaption22,271,Текст,,
|
||||
MobileScreenshotCaption23,272,Текст,,
|
||||
MobileScreenshotCaption24,273,Текст,,
|
||||
MobileScreenshotCaption25,274,Текст,,
|
||||
MobileScreenshotCaption26,275,Текст,,
|
||||
MobileScreenshotCaption27,276,Текст,,
|
||||
MobileScreenshotCaption28,277,Текст,,
|
||||
MobileScreenshotCaption29,278,Текст,,
|
||||
MobileScreenshotCaption30,279,Текст,,
|
||||
XboxScreenshot1,300,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot2,301,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot3,302,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot4,303,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot5,304,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot6,305,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot7,306,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot8,307,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot9,308,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot10,309,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot11,310,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot12,311,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot13,312,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot14,313,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot15,314,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot16,315,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot17,316,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot18,317,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot19,318,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot20,319,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot21,320,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot22,321,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot23,322,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot24,323,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot25,324,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot26,325,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot27,326,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot28,327,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot29,328,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshot30,329,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxScreenshotCaption1,350,Текст,,
|
||||
XboxScreenshotCaption2,351,Текст,,
|
||||
XboxScreenshotCaption3,352,Текст,,
|
||||
XboxScreenshotCaption4,353,Текст,,
|
||||
XboxScreenshotCaption5,354,Текст,,
|
||||
XboxScreenshotCaption6,355,Текст,,
|
||||
XboxScreenshotCaption7,356,Текст,,
|
||||
XboxScreenshotCaption8,357,Текст,,
|
||||
XboxScreenshotCaption9,358,Текст,,
|
||||
XboxScreenshotCaption10,359,Текст,,
|
||||
XboxScreenshotCaption11,360,Текст,,
|
||||
XboxScreenshotCaption12,361,Текст,,
|
||||
XboxScreenshotCaption13,362,Текст,,
|
||||
XboxScreenshotCaption14,363,Текст,,
|
||||
XboxScreenshotCaption15,364,Текст,,
|
||||
XboxScreenshotCaption16,365,Текст,,
|
||||
XboxScreenshotCaption17,366,Текст,,
|
||||
XboxScreenshotCaption18,367,Текст,,
|
||||
XboxScreenshotCaption19,368,Текст,,
|
||||
XboxScreenshotCaption20,369,Текст,,
|
||||
XboxScreenshotCaption21,370,Текст,,
|
||||
XboxScreenshotCaption22,371,Текст,,
|
||||
XboxScreenshotCaption23,372,Текст,,
|
||||
XboxScreenshotCaption24,373,Текст,,
|
||||
XboxScreenshotCaption25,374,Текст,,
|
||||
XboxScreenshotCaption26,375,Текст,,
|
||||
XboxScreenshotCaption27,376,Текст,,
|
||||
XboxScreenshotCaption28,377,Текст,,
|
||||
XboxScreenshotCaption29,378,Текст,,
|
||||
XboxScreenshotCaption30,379,Текст,,
|
||||
HolographicScreenshot1,400,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot2,401,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot3,402,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot4,403,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot5,404,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot6,405,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot7,406,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot8,407,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot9,408,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot10,409,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot11,410,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot12,411,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot13,412,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot14,413,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot15,414,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot16,415,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot17,416,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot18,417,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot19,418,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot20,419,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot21,420,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot22,421,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot23,422,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot24,423,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot25,424,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot26,425,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot27,426,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot28,427,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot29,428,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshot30,429,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
HolographicScreenshotCaption1,450,Текст,,
|
||||
HolographicScreenshotCaption2,451,Текст,,
|
||||
HolographicScreenshotCaption3,452,Текст,,
|
||||
HolographicScreenshotCaption4,453,Текст,,
|
||||
HolographicScreenshotCaption5,454,Текст,,
|
||||
HolographicScreenshotCaption6,455,Текст,,
|
||||
HolographicScreenshotCaption7,456,Текст,,
|
||||
HolographicScreenshotCaption8,457,Текст,,
|
||||
HolographicScreenshotCaption9,458,Текст,,
|
||||
HolographicScreenshotCaption10,459,Текст,,
|
||||
HolographicScreenshotCaption11,460,Текст,,
|
||||
HolographicScreenshotCaption12,461,Текст,,
|
||||
HolographicScreenshotCaption13,462,Текст,,
|
||||
HolographicScreenshotCaption14,463,Текст,,
|
||||
HolographicScreenshotCaption15,464,Текст,,
|
||||
HolographicScreenshotCaption16,465,Текст,,
|
||||
HolographicScreenshotCaption17,466,Текст,,
|
||||
HolographicScreenshotCaption18,467,Текст,,
|
||||
HolographicScreenshotCaption19,468,Текст,,
|
||||
HolographicScreenshotCaption20,469,Текст,,
|
||||
HolographicScreenshotCaption21,470,Текст,,
|
||||
HolographicScreenshotCaption22,471,Текст,,
|
||||
HolographicScreenshotCaption23,472,Текст,,
|
||||
HolographicScreenshotCaption24,473,Текст,,
|
||||
HolographicScreenshotCaption25,474,Текст,,
|
||||
HolographicScreenshotCaption26,475,Текст,,
|
||||
HolographicScreenshotCaption27,476,Текст,,
|
||||
HolographicScreenshotCaption28,477,Текст,,
|
||||
HolographicScreenshotCaption29,478,Текст,,
|
||||
HolographicScreenshotCaption30,479,Текст,,
|
||||
SurfaceHubScreenshot11,510,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot12,511,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot13,512,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot14,513,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot15,514,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot16,515,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot17,516,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot18,517,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot19,518,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot20,519,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot21,520,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot22,521,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot23,522,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot24,523,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot25,524,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot26,525,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot27,526,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot28,527,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot29,528,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshot30,529,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
SurfaceHubScreenshotCaption11,560,Текст,,
|
||||
SurfaceHubScreenshotCaption12,561,Текст,,
|
||||
SurfaceHubScreenshotCaption13,562,Текст,,
|
||||
SurfaceHubScreenshotCaption14,563,Текст,,
|
||||
SurfaceHubScreenshotCaption15,564,Текст,,
|
||||
SurfaceHubScreenshotCaption16,565,Текст,,
|
||||
SurfaceHubScreenshotCaption17,566,Текст,,
|
||||
SurfaceHubScreenshotCaption18,567,Текст,,
|
||||
SurfaceHubScreenshotCaption19,568,Текст,,
|
||||
SurfaceHubScreenshotCaption20,569,Текст,,
|
||||
SurfaceHubScreenshotCaption21,570,Текст,,
|
||||
SurfaceHubScreenshotCaption22,571,Текст,,
|
||||
SurfaceHubScreenshotCaption23,572,Текст,,
|
||||
SurfaceHubScreenshotCaption24,573,Текст,,
|
||||
SurfaceHubScreenshotCaption25,574,Текст,,
|
||||
SurfaceHubScreenshotCaption26,575,Текст,,
|
||||
SurfaceHubScreenshotCaption27,576,Текст,,
|
||||
SurfaceHubScreenshotCaption28,577,Текст,,
|
||||
SurfaceHubScreenshotCaption29,578,Текст,,
|
||||
SurfaceHubScreenshotCaption30,579,Текст,,
|
||||
StoreLogo720x1080,600,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
StoreLogo1080x1080,601,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
StoreLogo300x300,602,Относительный путь (или URL-адрес файла в Центре партнеров),screen/1.ru.300x300.png,screen/1.ru.300x300.png
|
||||
OverrideLogosForWin10,603,True/False,,False
|
||||
StoreLogoOverride150x150,604,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
StoreLogoOverride71x71,605,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
PromoImage1920x1080,606,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
PromoImage2400x1200,607,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxBrandedKeyArt584x800,608,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxTitledHero1920x1080,609,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
XboxFeaturedPromo1080x1080,610,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
OptionalPromo358x358,611,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
OptionalPromo1000x800,612,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
OptionalPromo414x180,613,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Feature1,700,Текст,Шифрование трафика и скрытие IP-адреса,Encrypt traffic and hide your IP address
|
||||
Feature2,701,Текст,Режим прокси и полный режим защиты (TUN),Proxy mode and full protection mode (TUN)
|
||||
Feature3,702,Текст,Kill Switch — блокировка трафика при обрыве VPN,Kill Switch blocks traffic if VPN drops
|
||||
Feature4,703,Текст,Автовыбор сервера с лучшим пингом,Auto-select the lowest-ping server
|
||||
Feature5,704,Текст,Серверы с флагами стран и группировкой протоколов,Servers with country flags and protocol grouping
|
||||
Feature6,705,Текст,Добавление конфигурации по URL,Add configuration via URL
|
||||
Feature7,706,Текст,Статистика трафика и сессий,Traffic and session statistics
|
||||
Feature8,707,Текст,Интерфейс на русском и английском,English and Russian interface
|
||||
Feature9,708,Текст,Светлая и тёмная темы оформления,Light and dark themes
|
||||
Feature10,709,Текст,Защита от утечек DNS,DNS leak protection
|
||||
Feature11,710,Текст,,
|
||||
Feature12,711,Текст,,
|
||||
Feature13,712,Текст,,
|
||||
Feature14,713,Текст,,
|
||||
Feature15,714,Текст,,
|
||||
Feature16,715,Текст,,
|
||||
Feature17,716,Текст,,
|
||||
Feature18,717,Текст,,
|
||||
Feature19,718,Текст,,
|
||||
Feature20,719,Текст,,
|
||||
MinimumHardwareReq1,800,Текст,Windows 10 version 1809 or later (x64),Windows 10 version 1809 or later (x64)
|
||||
MinimumHardwareReq2,801,Текст,Internet connection,Internet connection
|
||||
MinimumHardwareReq3,802,Текст,100 MB free disk space,100 MB free disk space
|
||||
MinimumHardwareReq4,803,Текст,,
|
||||
MinimumHardwareReq5,804,Текст,,
|
||||
MinimumHardwareReq6,805,Текст,,
|
||||
MinimumHardwareReq7,806,Текст,,
|
||||
MinimumHardwareReq8,807,Текст,,
|
||||
MinimumHardwareReq9,808,Текст,,
|
||||
MinimumHardwareReq10,809,Текст,,
|
||||
MinimumHardwareReq11,810,Текст,,
|
||||
RecommendedHardwareReq1,850,Текст,Windows 11 (x64),Windows 11 (x64)
|
||||
RecommendedHardwareReq2,851,Текст,Stable broadband connection,Stable broadband connection
|
||||
RecommendedHardwareReq3,852,Текст,,
|
||||
RecommendedHardwareReq4,853,Текст,,
|
||||
RecommendedHardwareReq5,854,Текст,,
|
||||
RecommendedHardwareReq6,855,Текст,,
|
||||
RecommendedHardwareReq7,856,Текст,,
|
||||
RecommendedHardwareReq8,857,Текст,,
|
||||
RecommendedHardwareReq9,858,Текст,,
|
||||
RecommendedHardwareReq10,859,Текст,,
|
||||
RecommendedHardwareReq11,860,Текст,,
|
||||
SearchTerm1,900,Текст,VPN,VPN
|
||||
SearchTerm2,901,Текст,privacy,privacy
|
||||
SearchTerm3,902,Текст,secure internet,secure internet
|
||||
SearchTerm4,903,Текст,encrypt traffic,encrypt traffic
|
||||
SearchTerm5,904,Текст,EvilFox,EvilFox
|
||||
SearchTerm6,905,Текст,proxy client,proxy client
|
||||
SearchTerm7,906,Текст,kill switch,kill switch
|
||||
TrailerToPlayAtTopOfListing,999,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer1,1000,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer2,1001,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer3,1002,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer4,1003,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer5,1004,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer6,1005,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer7,1006,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer8,1007,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer9,1008,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer10,1009,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer11,1010,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer12,1011,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer13,1012,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer14,1013,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
Trailer15,1014,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerTitle1,1020,Текст,,
|
||||
TrailerTitle2,1021,Текст,,
|
||||
TrailerTitle3,1022,Текст,,
|
||||
TrailerTitle4,1023,Текст,,
|
||||
TrailerTitle5,1024,Текст,,
|
||||
TrailerTitle6,1025,Текст,,
|
||||
TrailerTitle7,1026,Текст,,
|
||||
TrailerTitle8,1027,Текст,,
|
||||
TrailerTitle9,1028,Текст,,
|
||||
TrailerTitle10,1029,Текст,,
|
||||
TrailerTitle11,1030,Текст,,
|
||||
TrailerTitle12,1031,Текст,,
|
||||
TrailerTitle13,1032,Текст,,
|
||||
TrailerTitle14,1033,Текст,,
|
||||
TrailerTitle15,1034,Текст,,
|
||||
TrailerThumbnail1,1040,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail2,1041,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail3,1042,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail4,1043,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail5,1044,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail6,1045,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail7,1046,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail8,1047,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail9,1048,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail10,1049,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail11,1050,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail12,1051,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail13,1052,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail14,1053,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerThumbnail15,1054,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption1,1055,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption2,1056,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption3,1057,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption4,1058,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption5,1059,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption6,1060,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption7,1061,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption8,1062,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption9,1063,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption10,1064,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption11,1065,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption12,1066,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption13,1067,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption14,1068,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerClosedCaption15,1069,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription1,1070,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription2,1071,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription3,1072,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription4,1073,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription5,1074,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription6,1075,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription7,1076,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription8,1077,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription9,1078,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription10,1079,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription11,1080,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription12,1081,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription13,1082,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription14,1083,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
TrailerAudioDescription15,1084,Относительный путь (или URL-адрес файла в Центре партнеров),,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
,,,,
|
||||
|