$(DONE)
BLUE := echo -e "$(blue)
GREEN := echo -e "$(green)
YELLOW := echo -e "$(yellow)
DONE := $(reset)"
MKDIR := mkdir -p
RM := rm -rf
SEP :=/
ifeq ($(OS),Windows_NT)
ifeq ($(IS_GITHUB_ACTIONS),)
# MKDIR := -mkdir
RM := rmdir /s /q
# SEP:=\\
endif
endif
# Define sed command based on the OS
ifeq ($(OS),Windows_NT)
# Windows (Assume Git Bash or similar sed is available, or standard syntax)
SED := sed -i
else
ifeq ($(shell uname),Darwin) # macOS
SED :=sed -i ''
else # Linux
SED :=sed -i
endif
endif
BINDIR=hiddify-core$(SEP)bin
ANDROID_OUT=android$(SEP)app$(SEP)libs
IOS_OUT=ios$(SEP)Frameworks
DESKTOP_OUT=hiddify-core$(SEP)bin
GEO_ASSETS_DIR=assets$(SEP)core
CORE_PRODUCT_NAME=hiddify-core
CORE_NAME=hiddify-lib
LIB_NAME=hiddify-core
ifeq ($(CHANNEL),prod)
CORE_URL=https://github.com/hiddify/hiddify-next-core/releases/download/v$(core.version)
else
CORE_URL=https://github.com/hiddify/hiddify-next-core/releases/download/draft
endif
ifeq ($(CHANNEL),prod)
TARGET=lib/main_prod.dart
else
TARGET=lib/main.dart
endif
BUILD_ARGS=--dart-define sentry_dsn=$(SENTRY_DSN)
DISTRIBUTOR_ARGS=--skip-clean --build-target $(TARGET) --build-dart-define sentry_dsn=$(SENTRY_DSN)
get:
flutter pub get
gen:
dart run build_runner build --delete-conflicting-outputs
translate:
dart run slang
prepare:
@echo use the following commands to prepare the library for each platform:
@echo make android-prepare
@echo make windows-prepare
@echo make linux-prepare
@echo make macos-prepare
@echo make ios-prepare
common-prepare: get gen translate
windows-prepare: common-prepare windows-libs
ios-prepare: common-prepare ios-libs
cd ios; pod repo update; pod install;echo "done ios prepare"
macos-prepare: common-prepare macos-libs
linux-prepare: common-prepare linux-amd64-libs
linux-amd64-prepare: common-prepare linux-amd64-libs
linux-arm64-prepare: common-prepare linux-arm64-libs
linux-amd64-musl-prepare: common-prepare linux-amd64-musl-libs
linux-arm64-musl-prepare: common-prepare linux-arm64-musl-libs
linux-appimage-prepare:linux-prepare
linux-rpm-prepare:linux-prepare
linux-deb-prepare:linux-prepare
android-prepare:common-prepare android-libs
android-apk-prepare:android-prepare
android-aab-prepare:android-prepare
.PHONY: generate_kotlin_protos
generate_kotlin_protos:
# Run protoc to generate Kotlin files
# protoc \
# --proto_path=hiddify-core/ \
# --java_out=./android/app/src/main/java/ \
# --grpc-java_out=./android/app/src/main/java/ \
# $(shell find hiddify-core/v2 hiddify-core/extension -name "*.proto")
rsync -av --delete \
--include='*/' \
--include='*.proto' \
--exclude='*' \
hiddify-core/v2 hiddify-core/extension ./android/app/src/main/protos/
# # Find .proto files and update package declarations
# find "./android/app/src/main/java/com/hiddify/hiddify/protos" -type f -name "*.java" | while read -r proto_file; do \
# if grep -q "^package " "$$proto_file"; then \
# $(SED) 's/^package \([\w\.]*\)/package com.hiddify.hiddify.protos.\1/g' "$$proto_file"; \
# fi \
# done
generate_go_protoc:
make -C hiddify-core -f Makefile protos
echo "SED: $(SED)"
generate_dart_protoc:
mkdir -p lib/hiddifycore/generated
protoc --dart_out=grpc:lib/hiddifycore/generated --proto_path=hiddify-core/ $(shell find hiddify-core/v2 hiddify-core/extension -name "*.proto") google/protobuf/timestamp.proto ; \
.PHONY: protos
protos: generate_go_protoc generate_kotlin_protos generate_dart_protoc
macos-install-deps:
brew install create-dmg tree
npm install -g appdmg
dart pub global activate fastforge
ios-install-deps:
if [ "$(flutter)" = "true" ]; then \
curl -L -o ~/Downloads/flutter_macos_3.19.3-stable.zip https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.22.3-stable.zip; \
mkdir -p ~/develop; \
cd ~/develop; \
unzip ~/Downloads/flutter_macos_3.22.3-stable.zip; \
export PATH="$$PATH:$$HOME/develop/flutter/bin"; \
echo 'export PATH="$$PATH:$$HOME/develop/flutter/bin"' >> ~/.zshrc; \
export PATH="$PATH:$HOME/develop/flutter/bin"; \
echo 'export PATH="$PATH:$HOME/develop/flutter/bin"' >> ~/.zshrc; \
curl -sSL https://rvm.io/mpapis.asc | gpg --import -; \
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import -; \
curl -sSL https://get.rvm.io | bash -s stable; \
brew install openssl@1.1; \
PKG_CONFIG_PATH=$(brew --prefix openssl@1.1)/lib/pkgconfig rvm install 2.7.5; \
sudo gem install cocoapods -V; \
fi
brew install create-dmg tree
npm install -g appdmg
dart pub global activate fastforge
android-install-deps:
dart pub global activate fastforge
android-apk-install-deps: android-install-deps
android-aab-install-deps: android-install-deps
# loads the package list from linux_deps.list
LINUX_DEPS = $(shell grep -vE '^\s*#|^\s*$$' linux_deps.list)
# reads the Flutter version from pubspec.yaml
REQUIRED_VER = $(shell sed -n '/environment:/,/flutter:/ s/.*flutter:[[:space:]]*//p' pubspec.yaml | tr -d " '^\"")
linux-amd64-install-deps:linux-install-deps
linux-amd64-musl-install-deps:linux-install-deps
linux-arm64-install-deps:linux-install-deps
linux-arm64-musl-install-deps:linux-install-deps
linux-install-deps:
@$(BLUE)Installing Debian/Ubuntu dependencies...$(DONE)
sudo apt-get update -y
sudo apt-get install -y $(LINUX_DEPS)
# loading fuce kernel module
@$(BLUE)Loading fuce kernel module$(DONE)
sudo modprobe fuse
# tools for appimage
@$(BLUE)Installing appimagetool$(DONE)
if [ "$$(uname -m)" = "aarch64" ]; then \
wget -O /tmp/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage"; \
else \
wget -O /tmp/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"; \
fi
chmod +x /tmp/appimagetool
sudo mv /tmp/appimagetool /usr/local/bin/
# cloning flutter sdk
@$(BLUE)Cloning Flutter SDK$(DONE); \
mkdir -p ~/develop; \
cd ~/develop; \
\
if [ ! -d "flutter/.git" ]; then \
$(BLUE)Flutter not found. cloning stable channel$(DONE); \
rm -rf flutter; \
git clone https://github.com/flutter/flutter.git -b stable flutter; \
fi; \
\
git config --global --add safe.directory $$HOME/develop/flutter; \
\
export PATH="$$HOME/develop/flutter/bin:$$PATH"; \
if ! grep -q 'flutter/bin' ~/.bashrc; then \
echo 'export PATH="$$HOME/develop/flutter/bin:$$PATH"' >> ~/.bashrc; \
fi
# syncing flutter version
$(MAKE) linux-flutter-sync
# installing fastforge https://pub.dev/packages/fastforge
@$(BLUE)Installing fastforge$(DONE); \
export PATH="$$HOME/develop/flutter/bin:$$HOME/.pub-cache/bin:$$PATH"; \
if ! grep -q '.pub-cache/bin' ~/.bashrc; then \
echo 'export PATH="$$HOME/.pub-cache/bin:$$PATH"' >> ~/.bashrc; \
fi; \
dart pub global activate fastforge; \
dart pub global activate protoc_plugin; \
echo ""; \
echo "============================================================"; \
echo "NOTE: After first setup, use the following command to update the PATH"; \
echo "source ~/.bashrc"; \
echo "============================================================"
# syncing 'flutter sdk' version with pubspec.yaml flutter version
linux-flutter-sync:
@$(BLUE)Syncing Flutter version with pubspec.yaml flutter version$(DONE); \
export PATH="$$HOME/develop/flutter/bin:$$PATH"; \
$(BLUE)Downloading Flutter SDK components...$(DONE); \
flutter --version > /dev/null; \
\
$(BLUE)Checking Flutter version...$(DONE); \
CURRENT_VER=$$(flutter --version | head -n 1 | awk '{print $$2}'); \
$(BLUE)Target: $(REQUIRED_VER) | Current: $$CURRENT_VER$(DONE); \
\
if [ "$$CURRENT_VER" != "$(REQUIRED_VER)" ]; then \
$(BLUE)Version mismatch! switching to $(REQUIRED_VER)...$(DONE); \
cd ~/develop/flutter; \
git fetch --tags; \
git checkout $(REQUIRED_VER); \
$(BLUE)Switched to $(REQUIRED_VER)$(DONE); \
flutter doctor; \
else \
$(GREEN)Flutter SDK is ready.$(DONE); \
fi
windows-install-deps:
dart pub global activate fastforge
# choco install innosetup -y
gen_translations: #generating missing translations using google translate
cd .github && bash sync_translate.sh
make translate
android-release: android-apk-release android-aab-release
android-apk-release:
fastforge package \
--platform android \
--targets apk \
--skip-clean \
--build-target=$(TARGET) \
--build-target-platform=android-arm,android-arm64,android-x64 \
--build-dart-define=sentry_dsn=$(SENTRY_DSN)
ls -R build/app/outputs
android-aab-release:
fastforge package \
--platform android \
--targets aab \
--skip-clean \
--build-target=$(TARGET) \
--build-dart-define=sentry_dsn=$(SENTRY_DSN) \
--build-dart-define=release=google-play
windows-release: windows-zip-release windows-exe-release windows-msix-release
windows-zip-release:
fastforge package \
--platform windows \
--targets zip \
--skip-clean \
--build-target=$(TARGET) \
--build-dart-define=sentry_dsn=$(SENTRY_DSN) \
--build-dart-define=portable=true
@FULL_PATH=$$(ls dist/*/*.zip | head -n 1); \
ZIP_DIR=$$(dirname "$$FULL_PATH"); \
ZIP_FILE=$$(basename "$$FULL_PATH"); \
FILE_NAME=$${ZIP_FILE%.*}; \
$(YELLOW)Post-processing Windows portable$(DONE); \
cd "$$ZIP_DIR"; \
$(BLUE)Extracting and Repacking...$(DONE); \
mkdir -p Hiddify; \
unzip -q "$$ZIP_FILE" -d Hiddify/; \
rm "$$ZIP_FILE"; \
tar -a -cf "$$FILE_NAME.zip" Hiddify; \
rm -rf Hiddify; \
$(GREEN)Successful$(DONE)
windows-exe-release:
fastforge package \
--platform windows \
--targets exe \
--skip-clean \
--build-target=$(TARGET) \
--build-dart-define=sentry_dsn=$(SENTRY_DSN)
windows-msix-release:
fastforge package \
--platform windows \
--targets msix \
--skip-clean \
--build-target=$(TARGET) \
--build-dart-define=sentry_dsn=$(SENTRY_DSN)
linux-release: linux-deb-release linux-appimage-release
linux-amd64-release: linux-release
linux-arm64-release: linux-release
linux-amd64-musl-release: linux-release
linux-arm64-musl-release: linux-release
linux-deb-release:
fastforge package \
--platform linux \
--targets deb \
--skip-clean \
--build-target=$(TARGET) \
--build-dart-define=sentry_dsn=$(SENTRY_DSN)
# ==============================================================================
# REFERENCE: MANUAL LIBRARY BUNDLING (INJECTION)
# ==============================================================================
# Use this method only if you need to manually force specific shared libraries
# (e.g., libcurl.so.4) into the AppImage bundle.
#
# IMPLEMENTATION STEPS:
#
# 1. PRE-BUILD SCRIPT (Add to Makefile before build command):
# Create a temporary directory and copy the target library there.
# ---------------------------------------------------------------------------
# mkdir -p linux/bundled_libs
# cp /usr/lib/x86_64-linux-gnu/libcurl.so.4 linux/bundled_libs/
# ---------------------------------------------------------------------------
#
# 2. CMAKE CONFIGURATION (Add to linux/CMakeLists.txt):
# Instruct CMake to include the copied file in the final bundle.
# ---------------------------------------------------------------------------
# install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/bundled_libs/libcurl.so.4"
# DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
# COMPONENT Runtime)
# ---------------------------------------------------------------------------
#
# ! WARNING !
# This approach is generally DISCOURAGED. Manually bundling libraries can lead to
# "Dependency Hell," where bundled libs conflict with system libraries or have
# their own unresolved dependencies. It increases maintenance cost and may cause
# runtime instability. Use only for specific edge cases where standard linking fails.
# ==============================================================================
linux-appimage-release:
fastforge package \
--platform linux \
--targets appimage \
--skip-clean \
--build-target=$(TARGET) \
--build-dart-define=sentry_dsn=$(SENTRY_DSN)
@$(YELLOW)Post-processing AppImage$(DONE); \
$(BLUE)Extracting AppImage$(DONE); \
cd dist/* && ./*.AppImage --appimage-extract > /dev/null; \
$(BLUE)Replacing AppRun$(DONE); \
cp ../../linux/packaging/appimage/AppRun squashfs-root/AppRun; \
$(BLUE)Granting permissions$(DONE); \
chmod +x squashfs-root/AppRun; \
$(BLUE)Adding StartupWMClass to hiddify.desktop$(DONE); \
sed -i '/^\[Desktop Entry\]/a StartupWMClass=app.hiddify.com' "squashfs-root/hiddify.desktop"; \
$(BLUE)Removing old AppImage$(DONE); \
rm *.AppImage; \
$(BLUE)Deleting bundled libstdc++ to fix Arch Linux compatibility...$(DONE); \
find squashfs-root/usr/lib -name "libstdc++.so.6" -delete; \
$(BLUE)Rebuilding AppImage$(DONE); \
ARCH=x86_64 appimagetool --no-appstream squashfs-root Hiddify.AppImage > /dev/null; \
$(BLUE)Cleaning up squashfs$(DONE); \
rm -rf squashfs-root; \
$(YELLOW)Creating Portable Package$(DONE); \
PKG_DIR_NAME="hiddify-linux-appimage"; \
$(BLUE)Creating dir: $$PKG_DIR_NAME$(DONE); \
mkdir -p "$$PKG_DIR_NAME"; \
$(BLUE)Moving Hiddify.AppImage$(DONE); \
cp -p "Hiddify.AppImage" "$$PKG_DIR_NAME/Hiddify.AppImage"; \
$(BLUE)Creating Portable Home directory$(DONE); \
mkdir -p "$$PKG_DIR_NAME/Hiddify.AppImage.home"; \
$(BLUE)Compressing to .tar.gz$(DONE); \
tar -czf "$$PKG_DIR_NAME.tar.gz" -C . "$$PKG_DIR_NAME"; \
$(BLUE)Removing intermediate directory$(DONE); \
rm -rf "$$PKG_DIR_NAME"; \
$(GREEN)Successful$(DONE)
DOCKER_IMAGE_NAME := hiddify-linux-builder
DOCKER_FLUTTER_VOL := hiddify-flutter-sdk-cache
DOCKER_PUB_VOL := hiddify-pub-cache
ifeq ($(OS),Windows_NT)
FIX_OWNERSHIP := echo \"Windows detected: Skipping chown\"
else
FIX_OWNERSHIP := chown -R $(shell id -u):$(shell id -g) /host/dist_docker
endif
DOCKER_CMD := \
set -e; \
echo '** Copying source code to container...'; \
mkdir -p /app; \
cp -r /host/. /app/; \
cd /app; \
make linux-flutter-sync; \
make linux-prepare; \
echo '** Building Release (linux-release)...'; \
make linux-release; \
echo '** Copying artifacts to host...'; \
rm -rf /host/dist_docker; \
if [ -d \"dist\" ]; then \
cp -r dist /host/dist_docker; \
echo '** Fixing permissions for dist_docker...'; \
$(FIX_OWNERSHIP); \
else \
echo 'Error: dist folder not found!'; \
exit 1; \
fi;
linux-docker-release:
@$(BLUE)Cleaning main project to reduce context size$(DONE)
flutter clean
@$(BLUE)Building docker image (Cached)$(DONE)
docker build -t $(DOCKER_IMAGE_NAME) -f Dockerfile .
@$(BLUE)Ensuring cache volumes exist$(DONE)
docker volume create $(DOCKER_FLUTTER_VOL) || true
docker volume create $(DOCKER_PUB_VOL) || true
@$(YELLOW)Running build inside container$(DONE)
@docker run --rm \
-v "$(CURDIR)://host" \
-v $(DOCKER_FLUTTER_VOL)://root/develop/flutter \
-v $(DOCKER_PUB_VOL)://root/.pub-cache \
-e APPIMAGE_EXTRACT_AND_RUN=1 \
$(DOCKER_IMAGE_NAME) \
//bin/bash -c "$(DOCKER_CMD)"
@$(GREEN)Successful. Output is in 'dist_docker' folder.$(DONE)
macos-release:
fastforge package --platform macos --targets dmg,pkg $(DISTRIBUTOR_ARGS)
ios-release: #not tested
fastforge package --platform ios --targets ipa --build-export-options-plist ios/exportOptions.plist $(DISTRIBUTOR_ARGS)
android-libs:
$(MKDIR) $(ANDROID_OUT) || echo Folder already exists. Skipping...
curl -L $(CORE_URL)/$(CORE_NAME)-android.tar.gz | tar xz -C $(ANDROID_OUT)/
android-apk-libs: android-libs
android-aab-libs: android-libs
windows-libs:
$(MKDIR) $(DESKTOP_OUT) || echo Folder already exists. Skipping...
curl -L $(CORE_URL)/$(CORE_NAME)-windows-amd64.tar.gz | tar xz -C $(DESKTOP_OUT)/
ls $(DESKTOP_OUT) || dir $(DESKTOP_OUT)/
linux-amd64-libs:
mkdir -p $(DESKTOP_OUT)
curl -L $(CORE_URL)/$(CORE_NAME)-linux-amd64.tar.gz | tar xz -C $(DESKTOP_OUT)/
linux-arm64-libs:
mkdir -p $(DESKTOP_OUT)
curl -L $(CORE_URL)/$(CORE_NAME)-linux-arm64.tar.gz | tar xz -C $(DESKTOP_OUT)/
linux-amd64-musl-libs:
mkdir -p $(DESKTOP_OUT)
curl -L $(CORE_URL)/$(CORE_NAME)-linux-amd64-musl.tar.gz | tar xz -C $(DESKTOP_OUT)/
linux-arm64-musl-libs:
mkdir -p $(DESKTOP_OUT)
curl -L $(CORE_URL)/$(CORE_NAME)-linux-arm64-musl.tar.gz | tar xz -C $(DESKTOP_OUT)/
macos-libs:
mkdir -p $(DESKTOP_OUT)
curl -L $(CORE_URL)/$(CORE_NAME)-macos.tar.gz | tar xz -C $(DESKTOP_OUT)
ios-libs: #not tested
mkdir -p $(IOS_OUT)
rm -rf $(IOS_OUT)/HiddifyCore.xcframework
curl -L $(CORE_URL)/$(CORE_NAME)-ios.tar.gz | tar xz -C "$(IOS_OUT)"
get-geo-assets:
echo ""
# curl -L https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db -o $(GEO_ASSETS_DIR)/geoip.db
# curl -L https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db -o $(GEO_ASSETS_DIR)/geosite.db
build-headers:
make -C hiddify-core -f Makefile headers && mv $(BINDIR)/$(CORE_NAME)-headers.h $(BINDIR)/hiddify-core.h
build-android-libs:
make -C hiddify-core -f Makefile android
mv $(BINDIR)/$(LIB_NAME).aar $(ANDROID_OUT)/
build-windows-libs:
make -C hiddify-core -f Makefile windows-amd64
build-linux-libs:
make -C hiddify-core -f Makefile linux-amd64
build-macos-libs:
make -C hiddify-core -f Makefile macos
build-ios-libs:
rm -rf $(IOS_OUT)/HiddifyCore.xcframework
make -C hiddify-core -f Makefile ios
mv $(BINDIR)/HiddifyCore.xcframework $(IOS_OUT)/HiddifyCore.xcframework
release: # Create a new tag for release.
@CORE_VERSION=$(core.version) bash -c ".github/change_version.sh "
ios-temp-prepare:
make ios-prepare
flutter build ios-framework
cd ios
pod install
================================================
FILE: README.md
================================================
[** فارسی**](README_fa.md) / [**Русский 🇷🇺**](README_ru.md) / [**简体中文 🇨🇳**](README_cn.md) / [**日本語 🇯🇵**](README_ja.md) / [**Portugês-BR 🇧🇷**](README_br.md)
[](https://play.google.com/store/apps/details?id=app.hiddify.com) [](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/)
[](https://www.youtube.com/@hiddify)[](https://telegram.dog/hiddify)[](https://telegram.dog/hiddify_board/5)
## What is Hiddify app?
A multi-platform proxy client based on Sing-box universal proxy tool-chain. Hiddify offers a wide range of capabilities, like automatic node selection, TUN mode, remote profiles etc. Hiddify is ad-free and open-source. With support for a wide range of protocols, it provides a secure and private way for accessing free internet.
## 🚀 Main features
✈️ Multi-platform: Android, iOS, Windows, macOS and Linux
⭐ Intuitive and accessible UI
🔍 Delay based node selection
🟡 Wide range of protocols:
Vless, Vmess, Reality, TUIC, Hysteria, Wireguard, SSH etc.
🟡 Subscription link and configuration formats: Sing-box, V2ray, Clash, Clash meta
🔄 Automatic subscription update
🔎 Display profile information including remaining days and traffic usage
🛡 Open source, secure and community driven
🌙 Dark and light modes
⚙ Compatible with all proxy management panels
⭐ Appropriate configuration for Iran, China, Russia and other countries
📱 Available on official stores
## 🛍️ Get It On Stores
## 📥 Direct Download
OS
Download
iOS
Android
Windows
macOS
Linux
## ⚙️ Installation and tutorials
**Find tutorial information on our wiki page by clicking on image below.**
[](https://hiddify.com/app/)
## 🌎 Translations
You can improve existing languages or contribute new ones either by editing the JSON files in `/assets/translations` or [](https://fink.inlang.com/github.com/hiddify/hiddify-app) by using [Inlang online editor](https://fink.inlang.com/github.com/hiddify/hiddify-app).
## ✏️ Acknowledgements
We would like to express our sincere appreciation to the contributors of the following projects, whose robust foundation and innovative features have significantly enhanced the success and functionality of this project.
- [Sing-box](https://github.com/SagerNet/sing-box)
- [Sing-box for Android](https://github.com/SagerNet/sing-box-for-android)
- [Sing-box for Apple](https://github.com/SagerNet/sing-box-for-apple)
- [Clash](https://github.com/Dreamacro/clash)
- [Clash Meta](https://github.com/MetaCubeX/Clash.Meta)
- [FClash](https://github.com/Fclash/Fclash)
- [Vazirmatn Font by Saber Rastikerdar](https://github.com/rastikerdar/vazirmatn)
- [Others](./pubspec.yaml)
## 🎯 Donation and Support
The easiest way to support us is to click on the star (⭐) at the top of this page.
We also need financial support for our services. All of our activities are done voluntarily and financial support will be spent on the development of the project. You can view our support addresses [here](https://hiddify.com/donation-and-support/).
## 👩🏫 Collaboration and Contact Information
Hiddify is a community driven project. If you're interested in contributing, please read the [contribution guidelines](./CONTRIBUTING.md). We would specially appreciate any help we can get in these areas: **Flutter, Go, iOS development (Swift), Android development (Kotlin).**
[](mailto:contribute@hiddify.com)
[](https://telegram.dog/hiddify)
[](https://telegram.dog/hiddify_board)
[](https://www.youtube.com/@hiddify)
[](https://twitter.com/intent/follow?screen_name=hiddify_com)
We appreciate all people who are participating in this project. Some people here and many many more outside of Github. It means a lot to us. ♥
Made with Contrib.Rocks
================================================
FILE: README_br.md
================================================
[** فارسی**](README_fa.md) / [**English 🇺🇸**](README.md) / [**Русский 🇷🇺**](README_ru.md) / [**简体中文 🇨🇳**](README_cn.md) / [**日本語 🇯🇵**](README_ja.md)
[](https://play.google.com/store/apps/details?id=app.hiddify.com) [](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/)
[](https://www.youtube.com/@hiddify)[](https://telegram.dog/hiddify)[](https://telegram.dog/hiddify_board/5)
## O que é Hiddify app?
Um cliente de proxy multiplataforma baseado na ferramenta de proxy universal Sing-box . O Hiddify oferece uma ampla gama de recursos, como seleção automática de nós, modo TUN, perfis remotos, etc. O Hiddify é livre de anúncios e de código aberto. Com suporte para uma ampla variedade de protocolos, ele oferece uma maneira segura e privada de acessar a internet gratuitamente.
## 🚀 Principais recursos
✈️ Multiplataforma: Android, iOS, Windows, macOS e Linux
⭐ Interface intuitiva e acessível
🔍 Seleção de nós baseada em atraso
🟡 Amplas opções de protocolos:
Vless, Vmess, Reality, TUIC, Hysteria, Wireguard, SSH, etc.
🟡 Links de assinatura e formatos de configuração: Sing-box, V2ray, Clash, Clash meta
🔄 Atualização automática de assinatura
🔎 Exibição de informações do perfil, incluindo dias restantes e uso de tráfego
🛡 Código aberto, seguro e impulsionado pela comunidade
🌙 Modos escuro e claro
⚙ Compatível com todos os painéis de gerenciamento de proxy
⭐ Configuração adequada para Irã, China, Rússia e outros países
📱 Disponível nas lojas oficiais
## 🛍️ Obtenha nas lojas
## 📥 Download direto
Sistema Operacional
Download
iOS
Android
Windows
macOS
Linux
## ⚙️ Instalação e tutoriais
**Encontre informações de tutoriais em nossa página wiki clicando na imagem abaixo.**
[](https://hiddify.com/app/)
## 🌎 Tradução
Melhore os idiomas existentes ou adicione novos editando manualmente os arquivos JSON localizados em `/assets/translations` ou [](https://fink.inlang.com/github.com/hiddify/hiddify-app) usando o [editor online Inlang](https://fink.inlang.com/editor/github.com/hiddify/hiddify-app).
## ✏️ Agradecimentos
Gostaríamos de expressar nossa sincera gratidão aos contribuidores dos seguintes projetos, cuja base sólida e recursos inovadores têm melhorado significativamente o sucesso e a funcionalidade deste projeto.
- [Sing-box](https://github.com/SagerNet/sing-box)
- [Sing-box for Android](https://github.com/SagerNet/sing-box-for-android)
- [Sing-box for Apple](https://github.com/SagerNet/sing-box-for-apple)
- [Clash](https://github.com/Dreamacro/clash)
- [Clash Meta](https://github.com/MetaCubeX/Clash.Meta)
- [FClash](https://github.com/Fclash/Fclash)
- [Vazirmatn Font by Saber Rastikerdar](https://github.com/rastikerdar/vazirmatn)
- [Others](./pubspec.yaml)
## 🎯 Doação e Suporte
A maneira mais fácil de nos apoiar é clicar na estrela (⭐) no topo desta página.
Também precisamos de apoio financeiro para nossos serviços. Todas as nossas atividades são realizadas voluntariamente e o apoio financeiro será utilizado no desenvolvimento do projeto. Você pode ver nossos links de suporte [aqui](https://hiddify.com/donation-and-support/).
## 👩🏫 Colaboração e Informações de Contato
Hiddify é um projeto impulsionado pela comunidade. Se você estiver interessado em contribuir, por favor, leia as [diretrizes de contribuição](./CONTRIBUTING.md). Agradeceríamos especialmente qualquer ajuda que pudermos obter nessas áreas: **Flutter, Go, desenvolvimento iOS (Swift), desenvolvimento Android (Kotlin).**
[](mailto:contribute@hiddify.com)
[](https://telegram.dog/hiddify)
[](https://telegram.dog/hiddify_board)
[](https://www.youtube.com/@hiddify)
[](https://twitter.com/intent/follow?screen_name=hiddify_com)
Agradecemos a todas as pessoas que estão participando deste projeto. Algumas pessoas aqui e muitas outras fora do Github. Isso significa muito para nós. ♥
Feito com Contrib.Rocks
================================================
FILE: README_cn.md
================================================
[** فارسی**](README_fa.md) / [**Русский 🇷🇺**](README_ru.md) / [**日本語 🇯🇵**](README_ja.md) / [**Portugês-BR 🇧🇷**](README_br.md) / [**English 🇺🇸**](README.md)
[](https://play.google.com/store/apps/details?id=app.hiddify.com) [](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/)
[](https://www.youtube.com/@hiddify)[](https://telegram.dog/hiddify)[](https://telegram.dog/hiddify_board/5)
## Hiddify app 是什么?
一款基于 Sing-box 通用代理工具的跨平台代理客户端。Hiddify 提供了较全面的代理功能,例如自动选择节点、TUN 模式、使用远程配置文件等。Hiddify 无广告,并且代码开源。它为大家自由访问互联网提供了一个支持多种协议的、安全且私密的工具。
## 🚀 主要特性
✈️ 多平台客户端:Android、iOS、Windows、macOS 和 Linux
⭐ 简单易用的用户界面
🔍 基于延迟自动选择节点
🟡 全面的协议支持:**Vless、Vmess、Reality、TUIC、Wireguard、Hysteria、SSH**
🟡 多种订阅链接和配置文件格式支持: **Sing-box、V2ray、Clash、Clash meta**
🔄 支持自动更新订阅
🔎 可显示包含了剩余天数和流量使用情况的配置文件信息
🛡 开源、安全且由社区驱动
🌙 深色和浅色模式
⚙ 兼容所有的代理管理面板
⭐ 适用于伊朗、中国、俄罗斯或其他国家的配置
📱 官方商店有售
## 🛍️ 在商店购买
## 📥 直接下载
操作系统
下载链接
iOS
Android
Windows
macOS
Linux
## ⚙️ 安装和教程
**请单击下面的图片,在我们的维基页面上找到相关信息。**
[](https://hiddify.com/app/)
## 🌎 翻译
您可以通过手动编辑位于 `/assets/translations` 中的 JSON 文件,或使用 [Inlang 在线编辑器](https://fink.inlang.com/editor/github.com/hiddify/hiddify-app)来改进现有语言或添加新语言。
## ✏️ 致谢
我们谨向以下项目的贡献者表示诚挚的谢意,这些项目打下的坚实基础和开发的创新功能,显着增强了本项目的功能,为本项目的开发带来了成功。
- [Sing-box](https://github.com/SagerNet/sing-box)
- [Sing-box for Android](https://github.com/SagerNet/sing-box-for-android)
- [Sing-box for Apple](https://github.com/SagerNet/sing-box-for-apple)
- [Clash](https://github.com/Dreamacro/clash)
- [Clash Meta](https://github.com/MetaCubeX/Clash.Meta)
- [FClash](https://github.com/Fclash/Fclash)
- [字体 Vazirmatn Font by Saber Rastikerdar](https://github.com/rastikerdar/vazirmatn)
- [其他](./pubspec.yaml)
## 🎯 捐赠和支持
支持我们的最简单方法是单击此页面顶部的Star (⭐) 。
我们的服务也需要经济支持。我们所有的活动都是志愿性质的,经济支持将被用于项目的发展。您可以在 [这里](https://hiddify.com/donation-and-support/) 查看我们的支持地址。
## 👩🏫 合作及联系信息
Hiddify 是一个由社区驱动的项目。如果您有兴趣为本项目做出贡献,请阅读 [贡献指南](./CONTRIBUTING.md)。我们将非常感谢您,如果您能够在以下领域提供任何帮助:Flutter、Go、iOS 开发 (Swift)、Android 开发 (Kotlin)。
[](mailto:contribute@hiddify.com)
[](https://telegram.dog/hiddify)
[](https://telegram.dog/hiddify_board)
[](https://www.youtube.com/@hiddify)
[](https://twitter.com/intent/follow?screen_name=hiddify_com)
我们非常感谢所有参与此项目的朋友,包括下面列出的这些朋友,以及许许多多没有在 Github 社区的朋友。这对我们来说意义重大。♥
使用 Contrib.Rocks 制作
================================================
FILE: README_fa.md
================================================
[**🇺🇸 English**](README.md) / [**🇨🇳 简体中文**](README_cn.md) / [**🇷🇺 Русский**](README_ru.md) / [**🇯🇵 日本語**](README_ja.md) / [**🇧🇷 Portugês-BR**](README_br.md)
[](https://play.google.com/store/apps/details?id=app.hiddify.com) [](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/)
[](https://www.youtube.com/@hiddify)[](https://telegram.dog/hiddify)[](https://telegram.dog/hiddify_board/5)
## اپ هیدیفای چیست؟
یک کلاینت خودکار مالتیپلتفرم مبتنی بر [سینگباکس](https://github.com/SagerNet/sing-box) که به عنوان یک ابزار عمومی برای پروکسی عمل میکند. این برنامه طیف گستردهای از قابلیتها را ارائه میدهد مثل انتخاب خودکار نود، مود تونل، پروفایلهای ریموت و غیره. این برنامه رایگان، بدون آگهی و منبع باز است. با پشتیبانی از طیف وسیعی از پروتکلها، این اپلیکیشن یک ابزار امن و مطمئن برای دسترسی به اینترنت رایگان فراهم میکند.
## 🚀 امکانات اصلی
✈️ پشتیبانی از چند پلتفرم: اندروید، iOS، ویندوز، مک و لینوکس
⭐ استفاده بسیار آسان با رابط کاربری ساده
🔍 انتخاب خودکار بهترین سرور و کانفیگ بر اساس تاخیر
🟡 پشتیبانی از رنج وسیعی از پروتکلها
Vless, Vmess, Reality, TUIC, Hysteria, Wireguard, SSH, etc.
🟡 پشتیبانی لینکهای سابسکریپشن مختلف:
سینگباکس، V2ray، کلش، کلشمتا
🔄 آپدیت خودکار لینک سابسکریپشن و کانفیگها
🔎 نمایش اطلاعات پروفایل کاربر شامل روز و حجم باقیمانده
🛡 اپن سورس، کاملا امن و کامیونیتی محور
🌙 دارای تم دارک و لایت
⚙ سازگار با تمام پنلها
⭐ کانفیگ متناسب برای ایران، چین، روسیه و سایر کشورها
📱 انتشار در استورهای معتبر
## 🛍️ دریافت از استورها
## 📥 دانلود مستقیم
سیستم عامل
دانلود
iOS
اندروید
ویندوز
مک
لینوکس
## ⚙️ نصب و آموزش
**برای مطالعه و مشاهده همه مطالب آموزشی در مورد این برنامه، با کلیک روی تصویر زیر به صفحه ویکی پروژه مراجعه کنید.**
[](https://hiddify.com/fa/app/)
## 🌎 ترجمهها
با ویرایش دستی فایلهای JSON در assets/translations/ یا با استفاده از [](https://fink.inlang.com/github.com/hiddify/hiddify-app) [ویرایشگر آنلاین Inlang](https://fink.inlang.com/editor/github.com/hiddify/hiddify-app)، زبانهای موجود را بهبود بدهید و یا زبانهای جدید اضافه کنید.
## ✏️ سپاسگزاریها
مایلیم از دستاندرکاران پروژههای زیر صمیمانه قدردانی کنیم که پایه قوی و ویژگیهای نوآورانه آنها موفقیت و عملکرد این پروژه را به میزان قابل توجهی افزایش داده است.
- [سینگباکس](https://github.com/SagerNet/sing-box)
- [سینگباکس برای اندروید](https://github.com/SagerNet/sing-box-for-android)
- [کلش](https://github.com/Dreamacro/clash)
- [کلشمتا](https://github.com/MetaCubeX/Clash.Meta)
- [افکلش](https://github.com/Fclash/Fclash)
- [فونت وزیرمتن صابر راستیکردار](https://github.com/rastikerdar/vazirmatn)
- [سایر](./pubspec.yaml)
## 🎯 حمایت از پروژه
سادهترین راه حمایت از ما کلیک کردن روی ستاره (⭐) بالای همین صفحه است.
ما برای سرویس هایمان به کمک مالی هم نیاز داریم. تمامی فعالیتهای ما به صورت داوطلبانه انجام میشود و حمایتهای مالی صرف توسعه پروژه میشود. اطلاعات و آدرسهای حمایت از ما را در [این لینک](https://hiddify.com/fa/donation-and-support/) مشاهده فرمایید.
## 👩🏫 راههای همکاری و ارتباط با ما
هیدیفاینکست یه پروژه کامیونیتی محور است. اگر به مشارکت در این پروژه علاقه دارید، لطفا راهنمای مشارکت در این پروژه را مطالعه فرمایید. ما به صورت ویژه هر گونه کمکی در این زمینهها را ارج مینهیم: **فلاتر، Go، توسعه iOS (سوئیفت)، توسعه اندروید (کاتلین)**.
[](mailto:contribute@hiddify.com)
[](https://telegram.dog/hiddify)
[](https://telegram.dog/hiddify_board)
[](https://www.youtube.com/@hiddify)
[](https://twitter.com/intent/follow?screen_name=hiddify_com)
از همه کسانی که در این پروژه مشارکت میکنند سپاسگزاریم. بعضی از آنها اینجا هستند و خیلی های دیگه خارج از گیتهاب. همگی خیلی برای ما ارزشمندند. ♥
ساخته شده با Contrib.Rocks
================================================
FILE: README_ja.md
================================================
[** فارسی**](README_fa.md) / [**Русский 🇷🇺**](README_ru.md) / [**简体中文 🇨🇳**](README_cn.md) / [**English 🇺🇸**](README.md) / [**Portugês-BR 🇧🇷**](README_br.md)
[](https://play.google.com/store/apps/details?id=app.hiddify.com) [](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/)
[](https://www.youtube.com/@hiddify)[](https://telegram.dog/hiddify)[](https://telegram.dog/hiddify_board/5)
## Hiddify app とは?
Sing-box ユニバーサルプロキシツールチェーンに基づくマルチプラットフォームプロキシクライアントです。Hiddify は、自動ノード選択、TUN モード、リモートプロファイルなど、幅広い機能を提供します。Hiddify は無料でオープンソースです。幅広いプロトコルをサポートし、無料インターネットにアクセスするための安全でプライベートな方法を提供します。
## 🚀 主な特徴
✈️ マルチプラットフォーム: Android、iOS、Windows、macOS 及び Linux
⭐ 直感的でアクセシブルな UI
🔍 遅延に基づいたノード選択
🟡 幅広いプロトコル:
Vless、Vmess、Reality、TUIC、Hysteria、Wireguard、SSH など。
🟡 サブスクリプションのリンクと設定フォーマット
🔄 サブスクリプションの自動更新
🔎 残り日数やトラフィック使用量などのプロフィール情報を表示
🛡 オープンソース、セキュア、そしてコミュニティドリブン
🌙 ダークモードとライトモード
⚙ 全てのプロキシ管理パネルに対応
⭐ イラン、中国、ロシア、その他の国に適した構成
📱公式ストアで購入可能
## 🛍️ 店舗で入手
## 📥 直接ダウンロード
OS
ダウンロード
iOS
Android
Windows
macOS
Linux
## ⚙️ インストールとチュートリアル
**チュートリアル情報は、下の画像をクリックしてウィキのページをご覧ください。**
[](https://hiddify.com/app/)
## 🌎 翻訳
インストールとチュートリアル `/assets/translations` にある JSON ファイルを手動で編集するか、[Inlang オンラインエディタ](https://fink.inlang.com/editor/github.com/hiddify/hiddify-app)を使って、既存の言語を改良したり、新しい言語を追加したりすることができます。
## ✏️ 謝辞
堅牢な基盤と革新的な機能により、このプロジェクトの成功と機能性を大幅に向上させた、以下のプロジェクトのコントリビューターに心から感謝の意を表します。
- [Sing-box](https://github.com/SagerNet/sing-box)
- [Sing-box for Android](https://github.com/SagerNet/sing-box-for-android)
- [Sing-box for Apple](https://github.com/SagerNet/sing-box-for-apple)
- [Clash](https://github.com/Dreamacro/clash)
- [Clash Meta](https://github.com/MetaCubeX/Clash.Meta)
- [FClash](https://github.com/Fclash/Fclash)
- [Saber Rastikerdar による Vazirmatn フォント](https://github.com/rastikerdar/vazirmatn)
- [その他](./pubspec.yaml)
## 🎯 寄付とサポート
最も簡単な支援方法は、このページの上部にある star(⭐)をクリックすることです。
また、私たちのサービスには財政的な支援も必要です。私たちの活動はすべて自発的に行われており、経済的支援はプロジェクトの発展に費やされます。私たちのサポートアドレスは[こちら](https://hiddify.com/donation-and-support/)からご覧いただけます。
## 👩🏫 コラボレーションおよび連絡先
Hiddify はコミュニティドリブンのプロジェクトです。コントリビュートすることに興味がある方は、[コントリビューションガイドライン](./CONTRIBUTING.md)をお読みください。私たちは特に以下の分野において、どのような協力でもいただけるとありがたいです: **Flutter、Go、iOS開発(Swift)、Androi d開発(Kotlin)。**
[](mailto:contribute@hiddify.com)
[](https://telegram.dog/hiddify)
[](https://telegram.dog/hiddify_board)
[](https://www.youtube.com/@hiddify)
[](https://twitter.com/intent/follow?screen_name=hiddify_com)
このプロジェクトに参加してくれているすべての人に感謝しております。ここにいる人たちもいるし、GitHub の外にもたくさんいます。それは私たちにとって大きな意味があります。 ♥
Contrib.Rocks で作成。
================================================
FILE: README_ru.md
================================================
[** فارسی**](README_fa.md) / [**简体中文 🇨🇳**](README_cn.md) / [**English 🇺🇸**](README.md) / [**日本語 🇯🇵**](README_ja.md) / [**Portugês-BR 🇧🇷**](README_br.md)
[](https://play.google.com/store/apps/details?id=app.hiddify.com) [](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/releases/)[](https://github.com/hiddify/hiddify-app/)
[](https://www.youtube.com/@hiddify)[](https://telegram.dog/hiddify)[](https://telegram.dog/hiddify_board/5)
## Что такое Hiddify app?
Кроссплатформенный прокси-клиент на основе ядра [Sing-box](https://github.com/SagerNet/sing-box). Hiddify предлагает широкий спектр возможностей, таких как автоматический выбор узла, режим TUN, удалённые конфигурации (подписки). Приложение с открытым исходным кодом, без рекламы. Поддерживая широкий спектр протоколов, мы обеспечиваем безопасный и конфиденциальный доступ к свободному интернету.
## 🚀 Основные особенности
⭐ Простотой и лаконичный интерфейс
✈️ Кроссплатформенность: Android, Windows, Linux и macOS
🔍 Автоматический выбор профиля с наименьшей задержкой
🟡 Широкий выбор протоколов: Vless, Vmess, Reality, TUIC, Hysteria, Wireguard, SSH и прочие
🟡 Поддержка всех основных форматов подписки: Sing-box, V2ray, Clash, Clash meta
🔄 Автоматическое обновление подписки
🔎 Отображение информации профиля, включая оставшиеся дни и использованный трафик.
🛡 Открытый исходный код, безопасность, разрабатывается сообществом людей
🌙 Темная и светлая темы
⚙ Совместимость со всеми панелями управления прокси.
⭐ Подходящая конфигурация для Ирана, Китая, России и других стран.
📱Доступно в официальных магазинах.
## 🛍️ Приобретите в магазинах
## 📥 Прямая загрузка
Операционная система
Скачать
iOS
Android
Windows
MacOS
Linux
## ⚙️ Установка и руководства
**Соответствующую информацию можно найти на нашей вики-странице, нажав на изображение ниже.**
[](https://hiddify.com/app/)
## 🌎 Переводы
Улучшайте существующие языки или добавляйте новые, вручную редактируя JSON-файлы, расположенные в `/assets/translations`, или используя [Онлайн редактор Inlang](https://fink.inlang.com/editor/github.com/hiddify/hiddify-app)
## ✏️ Благодарности
Мы хотели бы выразить нашу искреннюю признательность участникам следующих проектов, чья прочная основа и инновационные функции значительно повысили успех и функциональность этого проекта.
- [Sing-box](https://github.com/SagerNet/sing-box)
- [Sing-box для Android](https://github.com/SagerNet/sing-box-for-android)
- [Sing-box для Apple](https://github.com/SagerNet/sing-box-for-apple)
- [Clash](https://github.com/Dreamacro/clash)
- [Clash Meta](https://github.com/MetaCubeX/Clash.Meta)
- [FClash](https://github.com/Fclash/Fclash)
- [Шрифт Vazirmatn от Saber Rastikerdar](https://github.com/rastikerdar/vazirmatn)
- [Others](./pubspec.yaml)
## 🎯 Пожертвования и поддержка
Самый простой способ поддержать нас — нажать на звездочку (⭐) вверху этой страницы.
Нам также нужна финансовая поддержка для наших сервисов. Вся наша деятельность осуществляется на добровольных началах, а финансовая поддержка будет направлена на развитие проекта. Вы можете просмотреть адреса нашей поддержки [здесь](https://hiddify.com/donation-and-support/).
## 👩🏫 Сотрудничество и контактная информация
Hiddify - это проект, развиваемый сообществом. Если вас интересует возможность внести свой вклад, пожалуйста, ознакомьтесь с [руководством](./CONTRIBUTING.md). Мы бы особенно оценили любую помощь в следующих областях: **Flutter, Go, iOS разработка (Swift), Android разработка (Kotlin).**
[](mailto:contribute@hiddify.com)
[](https://telegram.dog/hiddify)
[](https://telegram.dog/hiddify_board)
[](https://www.youtube.com/@hiddify)
[](https://twitter.com/intent/follow?screen_name=hiddify_com)
Мы ценим всех людей, которые участвуют в этом проекте. Некоторые люди здесь и многие-многие другие за пределами Github. Это очень много значит для нас. ♥
Сделано с Contrib.Rocks
================================================
FILE: analysis_options.yaml
================================================
include: package:lint/strict.yaml
analyzer:
plugins:
- custom_lint
exclude:
- "hiddify-core/**"
- "**.g.dart"
- "lib/gen/**"
errors:
invalid_annotation_target: ignore
formatter:
page_width: 120
linter:
rules:
sort_pub_dependencies: false
sort_unnamed_constructors_first: false
avoid_classes_with_only_static_members: false
custom_lint:
rules:
# Enable one rule
- provider_parameters
================================================
FILE: android/.gitignore
================================================
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
/app/libs/*
!/app/libs/.gitkeep
/app/.cxx
================================================
FILE: android/.stignore
================================================
gradle-wrapper.jar
.gradle
captures/
gradlew
gradlew.bat
local.properties
GeneratedPluginRegistrant.java
key.properties
**.keystore
**.jks
================================================
FILE: android/app/build.gradle
================================================
plugins {
id "com.android.application"
id "org.jetbrains.kotlin.android"
id "dev.flutter.flutter-gradle-plugin"
id "com.google.protobuf" version "0.9.4"
id "com.squareup.wire" version "5.3.1"
}
wire {
kotlin {
android = true
}
protoPath {
srcDir 'src/main/protos'
}
sourcePath {
srcDir 'src/main/protos/'
include '**'
}
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
boolean hasKeyStore = false
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
hasKeyStore = true
} else {
println "+++"
println "No keystore defined. The app will not be signed."
println "Create a android/key.properties file with the following properties:"
println "storePassword"
println "keyPassword"
println "keyAlias"
println "storeFile"
println "+++"
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')?: '1'
def flutterVersionName = localProperties.getProperty('flutter.versionName') ?: '1.0'
android {
namespace 'com.hiddify.hiddify'
testNamespace "test.com.hiddify.hiddify"
compileSdkVersion 36
ndkVersion "28.2.13676358"
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
applicationId "app.hiddify.com"
minSdkVersion flutter.minSdkVersion
targetSdkVersion 36
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
splits {
abi {
enable true
reset()
//noinspection ChromeOsAbiSupport
include "x86_64", "armeabi-v7a", "arm64-v8a"
universalApk true
}
}
if (hasKeyStore) {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
}
buildTypes {
release {
if (hasKeyStore) {
signingConfig signingConfigs.release
} else {
signingConfig signingConfigs.debug
}
ndk {
//noinspection ChromeOsAbiSupport
abiFilters "x86_64", "armeabi-v7a", "arm64-v8a"
debugSymbolLevel 'FULL'
}
}
}
buildFeatures {
viewBinding true
aidl true
}
}
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.versionCodeOverride = android.defaultConfig.versionCode
}
}
flutter {
source '../..'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.7'
implementation "androidx.compose.ui:ui:1.7.8"
implementation("com.squareup.wire:wire-grpc-client:5.3.1")
implementation 'io.grpc:grpc-okhttp:1.64.0'
implementation 'io.grpc:grpc-protobuf-lite:1.64.0'
implementation 'io.grpc:grpc-stub:1.64.0'
// protoPath(project(':hiddify_api'))
// implementation project(':hiddify_api')
}
================================================
FILE: android/app/libs/.gitkeep
================================================
================================================
FILE: android/app/src/debug/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/aidl/com/hiddify/hiddify/IService.aidl
================================================
package com.hiddify.hiddify;
import com.hiddify.hiddify.IServiceCallback;
interface IService {
int getStatus();
void registerCallback(in IServiceCallback callback);
oneway void unregisterCallback(in IServiceCallback callback);
}
================================================
FILE: android/app/src/main/aidl/com/hiddify/hiddify/IServiceCallback.aidl
================================================
package com.hiddify.hiddify;
interface IServiceCallback {
void onServiceStatusChanged(int status);
void onServiceAlert(int type, String message);
void onServiceWriteLog(String message);
void onServiceResetLogs(in List
messages);
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/ActiveGroupsChannel.kt
================================================
package com.hiddify.hiddify
import android.util.Log
import com.google.gson.Gson
//import com.hiddify.hiddify.utils.CommandClient
import com.hiddify.hiddify.utils.ParsedOutboundGroup
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import com.hiddify.core.libbox.OutboundGroup
import kotlinx.coroutines.CoroutineScope
//
//class ActiveGroupsChannel(private val scope: CoroutineScope) : FlutterPlugin,
// CommandClient.Handler {
// companion object {
// const val TAG = "A/ActiveGroupsChannel"
// const val CHANNEL = "com.hiddify.app/active-groups"
// val gson = Gson()
// }
//
// private val client =
// CommandClient(scope, CommandClient.ConnectionType.GroupOnly, this)
//
// private var channel: EventChannel? = null
// private var event: EventChannel.EventSink? = null
//
// override fun updateGroups(groups: List) {
// MainActivity.instance.runOnUiThread {
// val parsedGroups = groups.map { group -> ParsedOutboundGroup.fromOutbound(group) }
// event?.success(gson.toJson(parsedGroups))
// }
// }
//
// override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
// channel = EventChannel(
// flutterPluginBinding.binaryMessenger,
// CHANNEL
// )
//
// channel!!.setStreamHandler(object : EventChannel.StreamHandler {
// override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
// event = events
// Log.d(TAG, "connecting active groups command client")
// client.connect()
// }
//
// override fun onCancel(arguments: Any?) {
// event = null
// Log.d(TAG, "disconnecting active groups command client")
// client.disconnect()
// }
// })
// }
//
// override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// event = null
// client.disconnect()
// channel?.setStreamHandler(null)
// }
//}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/Application.kt
================================================
package com.hiddify.hiddify
import android.app.Application
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import android.os.PowerManager
import androidx.core.content.getSystemService
import com.hiddify.hiddify.bg.AppChangeReceiver
import go.Seq
import com.hiddify.hiddify.Application as BoxApplication
class Application : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
application = this
}
override fun onCreate() {
super.onCreate()
Seq.setContext(this)
registerReceiver(AppChangeReceiver(), IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addDataScheme("package")
})
}
companion object {
lateinit var application: BoxApplication
val notification by lazy { application.getSystemService()!! }
val connectivity by lazy { application.getSystemService()!! }
val packageManager by lazy { application.packageManager }
val powerManager by lazy { application.getSystemService()!! }
val notificationManager by lazy { application.getSystemService()!! }
val wifiManager by lazy { application.getSystemService()!! }
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/EventHandler.kt
================================================
package com.hiddify.hiddify
import android.util.Log
import androidx.lifecycle.Observer
import com.hiddify.hiddify.constant.Alert
import com.hiddify.hiddify.constant.Status
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.JSONMethodCodec
class EventHandler : FlutterPlugin {
companion object {
const val TAG = "A/EventHandler"
const val SERVICE_STATUS = "com.hiddify.app/service.status"
const val SERVICE_ALERTS = "com.hiddify.app/service.alerts"
}
private var statusChannel: EventChannel? = null
private var alertsChannel: EventChannel? = null
private var statusObserver: Observer? = null
private var alertsObserver: Observer? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
statusChannel = EventChannel(flutterPluginBinding.binaryMessenger, SERVICE_STATUS, JSONMethodCodec.INSTANCE)
alertsChannel = EventChannel(flutterPluginBinding.binaryMessenger, SERVICE_ALERTS, JSONMethodCodec.INSTANCE)
statusChannel!!.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
statusObserver = Observer {
Log.d(TAG, "new status: $it")
val map = listOf(
Pair("status", it.name)
)
.toMap()
events?.success(map)
}
MainActivity.instance.serviceStatus.observeForever(statusObserver!!)
}
override fun onCancel(arguments: Any?) {
if (statusObserver != null)
MainActivity.instance.serviceStatus.removeObserver(statusObserver!!)
}
})
alertsChannel!!.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
alertsObserver = Observer {
if (it == null) return@Observer
Log.d(TAG, "new alert: $it")
val map = listOf(
Pair("status", it.status.name),
Pair("alert", it.alert?.name),
Pair("message", it.message)
)
.mapNotNull { p -> p.second?.let { Pair(p.first, p.second) } }
.toMap()
events?.success(map)
}
MainActivity.instance.serviceAlerts.observeForever(alertsObserver!!)
}
override fun onCancel(arguments: Any?) {
if (alertsObserver != null)
MainActivity.instance.serviceAlerts.removeObserver(alertsObserver!!)
}
})
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
if (statusObserver != null)
MainActivity.instance.serviceStatus.removeObserver(statusObserver!!)
statusChannel?.setStreamHandler(null)
if (alertsObserver != null)
MainActivity.instance.serviceAlerts.removeObserver(alertsObserver!!)
alertsChannel?.setStreamHandler(null)
}
}
data class ServiceEvent(val status: Status, val alert: Alert? = null, val message: String? = null)
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/GroupsChannel.kt
================================================
package com.hiddify.hiddify
import android.util.Log
import com.google.gson.Gson
//import com.hiddify.hiddify.utils.CommandClient
import com.hiddify.hiddify.utils.ParsedOutboundGroup
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import com.hiddify.core.libbox.OutboundGroup
import kotlinx.coroutines.CoroutineScope
//
//class GroupsChannel(private val scope: CoroutineScope) : FlutterPlugin, CommandClient.Handler {
// companion object {
// const val TAG = "A/GroupsChannel"
// const val CHANNEL = "com.hiddify.app/groups"
// val gson = Gson()
// }
//
// private val client =
// CommandClient(scope, CommandClient.ConnectionType.Groups, this)
//
// private var channel: EventChannel? = null
// private var event: EventChannel.EventSink? = null
//
// override fun updateGroups(groups: List) {
// MainActivity.instance.runOnUiThread {
// val parsedGroups = groups.map { group -> ParsedOutboundGroup.fromOutbound(group) }
// event?.success(gson.toJson(parsedGroups))
// }
// }
//
// override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
// channel = EventChannel(
// flutterPluginBinding.binaryMessenger,
// CHANNEL
// )
//
// channel!!.setStreamHandler(object : EventChannel.StreamHandler {
// override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
// event = events
// Log.d(TAG, "connecting groups command client")
// client.connect()
// }
//
// override fun onCancel(arguments: Any?) {
// event = null
// Log.d(TAG, "disconnecting groups command client")
// client.disconnect()
// }
// })
// }
//
// override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// event = null
// client.disconnect()
// channel?.setStreamHandler(null)
// }
//}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/LogHandler.kt
================================================
package com.hiddify.hiddify
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
class LogHandler : FlutterPlugin {
companion object {
const val TAG = "A/LogHandler"
const val SERVICE_LOGS = "com.hiddify.app/service.logs"
}
private lateinit var logsChannel: EventChannel
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
logsChannel = EventChannel(flutterPluginBinding.binaryMessenger, SERVICE_LOGS)
logsChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
val activity = MainActivity.instance
events?.success(activity.logList)
activity.logCallback = {
events?.success(activity.logList)
}
}
override fun onCancel(arguments: Any?) {
MainActivity.instance.logCallback = null
}
})
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt
================================================
package com.hiddify.hiddify
import android.annotation.SuppressLint
import android.content.Intent
import android.Manifest
import android.content.pm.PackageManager
import android.net.VpnService
import android.os.Build
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import com.hiddify.hiddify.bg.ServiceConnection
import com.hiddify.hiddify.bg.ServiceNotification
import com.hiddify.hiddify.constant.Alert
import com.hiddify.hiddify.constant.ServiceMode
import com.hiddify.hiddify.constant.Status
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.LinkedList
class MainActivity : FlutterFragmentActivity(), ServiceConnection.Callback {
companion object {
private const val TAG = "ANDROID/MyActivity"
lateinit var instance: MainActivity
const val VPN_PERMISSION_REQUEST_CODE = 1001
const val NOTIFICATION_PERMISSION_REQUEST_CODE = 1010
}
private val connection = ServiceConnection(this, this)
val logList = LinkedList()
var logCallback: ((Boolean) -> Unit)? = null
val serviceStatus = MutableLiveData(Status.Stopped)
val serviceAlerts = MutableLiveData(null)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
instance = this
reconnect()
flutterEngine.plugins.add(MethodHandler(lifecycleScope))
flutterEngine.plugins.add(PlatformSettingsHandler())
flutterEngine.plugins.add(EventHandler())
flutterEngine.plugins.add(LogHandler())
// flutterEngine.plugins.add(GroupsChannel(lifecycleScope))
// flutterEngine.plugins.add(ActiveGroupsChannel(lifecycleScope))
// flutterEngine.plugins.add(StatsChannel(lifecycleScope))
}
fun reconnect() {
connection.reconnect()
}
@SuppressLint("NewApi")
fun startService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !ServiceNotification.checkPermission()) {
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
return
}
startService0()
}
private fun startService0() {
lifecycleScope.launch(Dispatchers.IO) {
if (Settings.rebuildServiceMode()) {
connection.reconnect()
}
if (Settings.serviceMode == ServiceMode.VPN) {
if (prepare()) {
return@launch
}
}
val intent = Intent(Application.application, Settings.serviceClass())
withContext(Dispatchers.Main) {
ContextCompat.startForegroundService(this@MainActivity, intent)
}
Settings.startedByUser = true
}
}
private suspend fun prepare() = withContext(Dispatchers.Main) {
try {
val intent = VpnService.prepare(this@MainActivity)
if (intent != null) {
prepareLauncher.launch(intent)
true
} else {
false
}
} catch (e: Exception) {
onServiceAlert(Alert.RequestVPNPermission, e.message)
true
}
}
private val notificationPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission(),
) { isGranted ->
if (Settings.dynamicNotification && !isGranted) {
onServiceAlert(Alert.RequestNotificationPermission, null)
} else {
startService0()
}
}
private val prepareLauncher =
registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
) { result ->
if (result.resultCode == RESULT_OK) {
startService0()
} else {
onServiceAlert(Alert.RequestVPNPermission, null)
}
}
override fun onServiceStatusChanged(status: Status) {
serviceStatus.postValue(status)
}
override fun onServiceAlert(type: Alert, message: String?) {
serviceAlerts.postValue(ServiceEvent(Status.Stopped, type, message))
}
override fun onDestroy() {
connection.disconnect()
super.onDestroy()
}
@SuppressLint("NewApi")
private fun grantNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_REQUEST_CODE
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray
) {
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startService()
} else onServiceAlert(Alert.RequestNotificationPermission, null)
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == VPN_PERMISSION_REQUEST_CODE) {
if (resultCode == RESULT_OK) startService()
else onServiceAlert(Alert.RequestVPNPermission, null)
} else if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
if (resultCode == RESULT_OK) startService()
else onServiceAlert(Alert.RequestNotificationPermission, null)
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt
================================================
package com.hiddify.hiddify
import android.util.Log
import com.hiddify.hiddify.bg.BoxService
//import com.hiddify.hiddify.bg.BoxService.Companion.workingDir
import com.hiddify.hiddify.constant.Status
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import com.hiddify.core.libbox.Libbox
import com.hiddify.core.mobile.Mobile
import com.hiddify.core.mobile.SetupOptions
import com.hiddify.hiddify.bg.Bugs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
MethodChannel.MethodCallHandler {
private var channel: MethodChannel? = null
companion object {
const val TAG = "A/MethodHandler"
const val channelName = "com.hiddify.app/method"
enum class Trigger(val method: String) {
Setup("setup"),
Start("start"),
Stop("stop"),
Restart("restart"),
AddGrpcClientPublicKey("add_grpc_client_public_key"),
GetGrpcServerPublicKey("get_grpc_server_public_key"),
}
}
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(
flutterPluginBinding.binaryMessenger,
channelName,
)
channel!!.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel?.setMethodCallHandler(null)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
Trigger.AddGrpcClientPublicKey.method -> {
GlobalScope.launch {
result.runCatching {
val args = call.arguments as Map<*, *>
val clientPub = args["clientPublicKey"] as ByteArray
// Mobile.addGrpcClientPublicKey(clientPub)
Settings.grpcFlutterPublicKey = clientPub
success("")
}
}
}
Trigger.GetGrpcServerPublicKey.method -> {
GlobalScope.launch {
result.runCatching {
result.success(Mobile.getServerPublicKey())
}
}
}
Trigger.Setup.method -> {
GlobalScope.launch {
result.runCatching {
val args = call.arguments as Map<*, *>
Settings.baseDir = args["baseDir"] as String
Settings.workingDir = args["workingDir"] as String
Settings.tempDir = args["tempDir"] as String
Settings.debugMode = args["debug"] as Boolean? ?: false
val mode = args["mode"] as Int
val grpcPort = args["grpcPort"] as Int
Log.d("debugmode","${Settings.debugMode}")
runCatching {
Mobile.setup(
SetupOptions().also {
it.basePath = Settings.baseDir
it.workingDir = Settings.workingDir
it.tempDir = Settings.tempDir
it.fixAndroidStack = Bugs.fixAndroidStack
it.mode=mode.toLong()
it.listen= "127.0.0.1:" + grpcPort
it.secret=""
it.debug = Settings.debugMode
},null)
// Libbox.setup(Settings.baseDir, Settings.workingDir, Settings.tempDir, false)
Libbox.redirectStderr(File(Settings.workingDir, "stderr2.log").path)
success("")
}.onFailure {
error(it)
}
}
}
}
Trigger.Start.method -> {
scope.launch {
result.runCatching {
val args = call.arguments as Map<*, *>
Settings.activeConfigPath = args["path"] as String? ?: ""
Settings.activeProfileName = args["name"] as String? ?: ""
Settings.debugMode = args["debug"] as Boolean? ?: false
Settings.grpcServiceModePort = args["grpcPort"] as Int
val mainActivity = MainActivity.instance
// val started = mainActivity.serviceStatus.value == Status.Started
// if (started) {
// Log.w(TAG, "service is already running")
// return@launch success(true)
// }
Settings.startCoreAfterStartingService = false
mainActivity.startService()
success(true)
}
}
}
Trigger.Stop.method -> {
scope.launch {
result.runCatching {
val mainActivity = MainActivity.instance
val started = mainActivity.serviceStatus.value == Status.Started
if (!started) {
Log.w(TAG, "service is not running")
// return@launch success(true)
}
BoxService.stop()
success(true)
}
}
}
// Trigger.Restart.method -> {
// scope.launch(Dispatchers.IO) {
// result.runCatching {
// val args = call.arguments as Map<*, *>
// Settings.activeConfigPath = args["path"] as String? ?: ""
// Settings.activeProfileName = args["name"] as String? ?: ""
// val mainActivity = MainActivity.instance
// val started = mainActivity.serviceStatus.value == Status.Started
// if (!started) return@launch success(true)
// val restart = Settings.rebuildServiceMode()
// if (restart) {
// mainActivity.reconnect()
// BoxService.stop()
// delay(1000L)
// mainActivity.startService()
// return@launch success(true)
// }
// runCatching {
// Libbox.newStandaloneCommandClient().serviceReload()
// success(true)
// }.onFailure {
// error(it)
// }
// }
// }
// }
else -> result.notImplemented()
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/PlatformSettingsHandler.kt
================================================
package com.hiddify.hiddify
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import android.os.Build
import android.util.Base64
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.hiddify.hiddify.Application.Companion.packageManager
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.StandardMethodCodec
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.ByteArrayOutputStream
class PlatformSettingsHandler : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware,
PluginRegistry.ActivityResultListener {
private var channel: MethodChannel? = null
private var activity: Activity? = null
private lateinit var ignoreRequestResult: MethodChannel.Result
companion object {
const val channelName = "com.hiddify.app/platform"
const val REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = 44
val gson = Gson()
enum class Trigger(val method: String) {
IsIgnoringBatteryOptimizations("is_ignoring_battery_optimizations"),
RequestIgnoreBatteryOptimizations("request_ignore_battery_optimizations"),
GetInstalledPackages("get_installed_packages"),
GetPackagesIcon("get_package_icon"),
}
}
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val taskQueue = flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
channel = MethodChannel(
flutterPluginBinding.binaryMessenger,
channelName,
StandardMethodCodec.INSTANCE,
taskQueue
)
channel!!.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel?.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode == REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) {
ignoreRequestResult.success(resultCode == Activity.RESULT_OK)
return true
}
return false
}
data class AppItem(
@SerializedName("package-name") val packageName: String,
@SerializedName("name") val name: String,
@SerializedName("is-system-app") val isSystemApp: Boolean
)
@SuppressLint("BatteryLife")
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
Trigger.IsIgnoringBatteryOptimizations.method -> {
result.runCatching {
success(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Application.powerManager.isIgnoringBatteryOptimizations(Application.application.packageName)
} else {
true
}
)
}
}
Trigger.RequestIgnoreBatteryOptimizations.method -> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return result.success(true)
}
val intent = Intent(
android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:${Application.application.packageName}")
)
ignoreRequestResult = result
activity?.startActivityForResult(intent, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
}
Trigger.GetInstalledPackages.method -> {
GlobalScope.launch {
result.runCatching {
val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.GET_PERMISSIONS or PackageManager.MATCH_UNINSTALLED_PACKAGES
} else {
@Suppress("DEPRECATION")
PackageManager.GET_PERMISSIONS or PackageManager.GET_UNINSTALLED_PACKAGES
}
val installedPackages =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getInstalledPackages(
PackageManager.PackageInfoFlags.of(
flag.toLong()
)
)
} else {
@Suppress("DEPRECATION")
packageManager.getInstalledPackages(flag)
}
val list = mutableListOf()
installedPackages.forEach {
if (it.packageName != Application.application.packageName &&
(it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|| it.packageName == "android")
) {
list.add(
AppItem(
it.packageName,
it.applicationInfo?.loadLabel(packageManager).toString(),
(it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM) == 1)
)
)
}
}
list.sortBy { it.name }
success(gson.toJson(list))
}
}
}
Trigger.GetPackagesIcon.method -> {
result.runCatching {
val args = call.arguments as Map<*, *>
val packageName =
args["packageName"] as String
val drawable = packageManager.getApplicationIcon(packageName)
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
val base64: String =
Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP)
success(base64)
}
}
else -> result.notImplemented()
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/Settings.kt
================================================
package com.hiddify.hiddify
import android.content.Context
import android.util.Base64
import com.hiddify.hiddify.bg.ProxyService
import com.hiddify.hiddify.bg.VPNService
import com.hiddify.hiddify.constant.PerAppProxyMode
import com.hiddify.hiddify.constant.ServiceMode
import com.hiddify.hiddify.constant.SettingsKey
import org.json.JSONObject
import java.io.ByteArrayInputStream
import java.io.File
import java.io.ObjectInputStream
object Settings {
private val preferences by lazy {
val context = Application.application.applicationContext
context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
}
private const val LIST_IDENTIFIER = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu"
var perAppProxyMode: String
get() = preferences.getString(SettingsKey.PER_APP_PROXY_MODE, PerAppProxyMode.OFF)!!
set(value) = preferences.edit().putString(SettingsKey.PER_APP_PROXY_MODE, value).apply()
val perAppProxyEnabled: Boolean
get() = perAppProxyMode != PerAppProxyMode.OFF
val perAppProxyList: List
get() {
val stringValue = if (perAppProxyMode == PerAppProxyMode.INCLUDE) {
preferences.getString(SettingsKey.PER_APP_PROXY_INCLUDE_LIST, "")!!
} else {
preferences.getString(SettingsKey.PER_APP_PROXY_EXCLUDE_LIST, "")!!
}
if (!stringValue.startsWith(LIST_IDENTIFIER)) {
return stringValue.split(";")
}
return try {
decodeListString(stringValue.substring(LIST_IDENTIFIER.length))
} catch (e: java.lang.Exception) {
emptyList()
}
}
private fun decodeListString(listString: String): List {
val stream = ObjectInputStream(ByteArrayInputStream(Base64.decode(listString, 0)))
return stream.readObject() as List
}
var activeConfigPath: String
get() = preferences.getString(SettingsKey.ACTIVE_CONFIG_PATH, "")!!
set(value) = preferences.edit().putString(SettingsKey.ACTIVE_CONFIG_PATH, value).apply()
var activeProfileName: String
get() = preferences.getString(SettingsKey.ACTIVE_PROFILE_NAME, "")!!
set(value) = preferences.edit().putString(SettingsKey.ACTIVE_PROFILE_NAME, value).apply()
var serviceMode: String
get() = preferences.getString(SettingsKey.SERVICE_MODE, ServiceMode.VPN)!!
set(value) = preferences.edit().putString(SettingsKey.SERVICE_MODE, value).apply()
var configOptions: String
get() = preferences.getString(SettingsKey.CONFIG_OPTIONS, "")!!
set(value) = preferences.edit().putString(SettingsKey.CONFIG_OPTIONS, value).apply()
var debugMode: Boolean
get() = preferences.getBoolean(SettingsKey.DEBUG_MODE, false)
set(value) = preferences.edit().putBoolean(SettingsKey.DEBUG_MODE, value).apply()
var disableMemoryLimit: Boolean
get() = preferences.getBoolean(SettingsKey.DISABLE_MEMORY_LIMIT, false)
set(value) =
preferences.edit().putBoolean(SettingsKey.DISABLE_MEMORY_LIMIT, value).apply()
var dynamicNotification: Boolean
get() = preferences.getBoolean(SettingsKey.DYNAMIC_NOTIFICATION, true)
set(value) =
preferences.edit().putBoolean(SettingsKey.DYNAMIC_NOTIFICATION, value).apply()
var systemProxyEnabled: Boolean
get() = preferences.getBoolean(SettingsKey.SYSTEM_PROXY_ENABLED, true)
set(value) =
preferences.edit().putBoolean(SettingsKey.SYSTEM_PROXY_ENABLED, value).apply()
var startedByUser: Boolean
get() = preferences.getBoolean(SettingsKey.STARTED_BY_USER, false)
set(value) = preferences.edit().putBoolean(SettingsKey.STARTED_BY_USER, value).apply()
fun serviceClass(): Class<*> {
return when (serviceMode) {
ServiceMode.VPN -> VPNService::class.java
else -> ProxyService::class.java
}
}
private var currentServiceMode : String? = null
suspend fun rebuildServiceMode(): Boolean {
var newMode = ServiceMode.NORMAL
try {
if (serviceMode == ServiceMode.VPN) {
newMode = ServiceMode.VPN
}
} catch (_: Exception) {
}
if (currentServiceMode == newMode) {
return false
}
currentServiceMode = newMode
return true
}
private suspend fun needVPNService(): Boolean {
val filePath = activeConfigPath
if (filePath.isBlank()) return false
val content = JSONObject(File(filePath).readText())
val inbounds = content.getJSONArray("inbounds")
for (index in 0 until inbounds.length()) {
val inbound = inbounds.getJSONObject(index)
if (inbound.getString("type") == "tun") {
return true
}
}
return false
}
var workingDir: String
get() = preferences.getString(SettingsKey.WORKING_DIR, "./")!!
set(value) = preferences.edit().putString(SettingsKey.WORKING_DIR, value).apply()
var tempDir: String
get() = preferences.getString(SettingsKey.TMP_DIR, "./")!!
set(value) = preferences.edit().putString(SettingsKey.TMP_DIR, value).apply()
var baseDir: String
get() = preferences.getString(SettingsKey.BASE_DIR, "./")!!
set(value) = preferences.edit().putString(SettingsKey.BASE_DIR, value).apply()
var grpcFlutterPublicKey: ByteArray
get() {
val encoded = preferences.getString(SettingsKey.GRPC_FLUTTER_PUBLIC_KEY, null)
return encoded?.let { Base64.decode(it, Base64.DEFAULT) } ?: ByteArray(0)
}
set(value) {
val encoded = Base64.encodeToString(value, Base64.DEFAULT)
preferences.edit().putString(SettingsKey.GRPC_FLUTTER_PUBLIC_KEY, encoded).apply()
}
var grpcServiceModePort: Int
get() = preferences.getInt(SettingsKey.GRPC_PORT, 17078)!!
set(value) = preferences.edit().putInt(SettingsKey.GRPC_PORT, value).apply()
var startCoreAfterStartingService: Boolean
get() = preferences.getBoolean(SettingsKey.START_CORE_ON_STARTING_SERVICE, false)
set(value) = preferences.edit().putBoolean(SettingsKey.START_CORE_ON_STARTING_SERVICE, value).apply()
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/ShortcutActivity.kt
================================================
package com.hiddify.hiddify
import android.app.Activity
import android.content.Intent
import android.content.pm.ShortcutManager
import android.os.Build
import android.os.Bundle
import androidx.core.content.getSystemService
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import com.hiddify.hiddify.bg.BoxService
import com.hiddify.hiddify.bg.ServiceConnection
import com.hiddify.hiddify.constant.Status
class ShortcutActivity : Activity(), ServiceConnection.Callback {
private val connection = ServiceConnection(this, this, false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == Intent.ACTION_CREATE_SHORTCUT) {
setResult(
RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(
this,
ShortcutInfoCompat.Builder(this, "toggle")
.setIntent(
Intent(
this,
ShortcutActivity::class.java
).setAction(Intent.ACTION_MAIN)
)
.setIcon(
IconCompat.createWithResource(
this,
R.mipmap.ic_launcher
)
)
.setShortLabel(getString(R.string.quick_toggle))
.build()
)
)
finish()
} else {
connection.connect()
if (Build.VERSION.SDK_INT >= 25) {
getSystemService()?.reportShortcutUsed("toggle")
}
}
moveTaskToBack(true)
}
override fun onServiceStatusChanged(status: Status) {
when (status) {
Status.Started -> BoxService.stop()
Status.Stopped -> BoxService.start()
else -> {}
}
finish()
}
override fun onDestroy() {
connection.disconnect()
super.onDestroy()
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/StatsChannel.kt
================================================
package com.hiddify.hiddify
import android.util.Log
//import com.hiddify.hiddify.utils.CommandClient
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.JSONMethodCodec
import com.hiddify.core.libbox.StatusMessage
import kotlinx.coroutines.CoroutineScope
//class StatsChannel(private val scope: CoroutineScope) : FlutterPlugin, CommandClient.Handler{
// companion object {
// const val TAG = "A/StatsChannel"
// const val STATS_CHANNEL = "com.hiddify.app/stats"
// }
//
// private val commandClient =
// CommandClient(scope, CommandClient.ConnectionType.Status, this)
//
// private var statsChannel: EventChannel? = null
// private var statsEvent: EventChannel.EventSink? = null
//
// override fun updateStatus(status: StatusMessage) {
// MainActivity.instance.runOnUiThread {
// val map = listOf(
// Pair("connections-in", status.connectionsIn),
// Pair("connections-out", status.connectionsOut),
// Pair("uplink", status.uplink),
// Pair("downlink", status.downlink),
// Pair("uplink-total", status.uplinkTotal),
// Pair("downlink-total", status.downlinkTotal)
// ).associate { it.first to it.second }
// statsEvent?.success(map)
// }
// }
//
// override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
// statsChannel = EventChannel(
// flutterPluginBinding.binaryMessenger,
// STATS_CHANNEL,
// JSONMethodCodec.INSTANCE
// )
//
// statsChannel!!.setStreamHandler(object : EventChannel.StreamHandler {
// override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
// statsEvent = events
// Log.d(TAG, "connecting stats command client")
// commandClient.connect()
// }
//
// override fun onCancel(arguments: Any?) {
// statsEvent = null
// Log.d(TAG, "disconnecting stats command client")
// commandClient.disconnect()
// }
// })
// }
//
// override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// statsEvent = null
// commandClient.disconnect()
// statsChannel?.setStreamHandler(null)
// }
//}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/AppChangeReceiver.kt
================================================
package com.hiddify.hiddify.bg
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.hiddify.hiddify.Settings
class AppChangeReceiver : BroadcastReceiver() {
companion object {
private const val TAG = "A/AppChangeReceiver"
}
override fun onReceive(context: Context, intent: Intent) {
checkUpdate(context, intent)
}
private fun checkUpdate(context: Context, intent: Intent) {
// if (!Settings.perAppProxyEnabled) {
// return
// }
// val perAppProxyUpdateOnChange = Settings.perAppProxyUpdateOnChange
// if (perAppProxyUpdateOnChange == Settings.PER_APP_PROXY_DISABLED) {
// return
// }
// val packageName = intent.dataString?.substringAfter("package:")
// if (packageName.isNullOrBlank()) {
// return
// }
// if ((perAppProxyUpdateOnChange == Settings.PER_APP_PROXY_INCLUDE)) {
// Settings.perAppProxyList = Settings.perAppProxyList + packageName
// } else {
// Settings.perAppProxyList = Settings.perAppProxyList - packageName
// }
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/BootReceiver.kt
================================================
package com.hiddify.hiddify.bg
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.hiddify.hiddify.MainActivity
import com.hiddify.hiddify.Settings
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class BootReceiver : BroadcastReceiver() {
@OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> {
}
else -> return
}
GlobalScope.launch(Dispatchers.IO) {
if (Settings.startedByUser) {
withContext(Dispatchers.Main) {
Settings.startCoreAfterStartingService=true //H
BoxService.start()
}
}
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt
================================================
package com.hiddify.hiddify.bg
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.os.PowerManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import com.hiddify.hiddify.Application
import com.hiddify.hiddify.R
import com.hiddify.hiddify.Settings
import com.hiddify.hiddify.constant.Action
import com.hiddify.hiddify.constant.Alert
import com.hiddify.hiddify.constant.Status
import com.hiddify.core.mobile.SetupOptions
import go.Seq
import com.hiddify.core.libbox.Libbox
import com.hiddify.core.mobile.Mobile
import com.hiddify.core.libbox.CommandServer
import com.hiddify.core.libbox.CommandServerHandler
import com.hiddify.core.libbox.Notification
import com.hiddify.core.libbox.PlatformInterface
import com.hiddify.core.libbox.SystemProxyStatus
import com.hiddify.hiddify.BuildConfig
import com.hiddify.hiddify.MainActivity
import com.hiddify.hiddify.constant.Bugs
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
class BoxService(
private val service: Service,
private val platformInterface: PlatformInterface
) {
companion object {
private const val TAG = "A/BoxService"
private var initializeOnce = false
private lateinit var workingDir: File
private fun initialize() {
System.setProperty("GODEBUG", "efence=1,stacktraceback=2");
System.setProperty("GOGC", "off");
if (initializeOnce) return
val baseDir = Application.application.filesDir
baseDir.mkdirs()
workingDir = Application.application.getExternalFilesDir(null) ?: return
workingDir.mkdirs()
val tempDir = Application.application.cacheDir
tempDir.mkdirs()
Log.d(TAG, "base dir: ${baseDir.path}")
Log.d(TAG, "working dir: ${workingDir.path}")
Log.d(TAG, "temp dir: ${tempDir.path}")
//
//Mobile.setup(baseDir.path, workingDir.path, tempDir.path, 2L ,"127.0.0.1:{Setting}","",false,this)
// Libbox.setup(baseDir.path, workingDir.path, tempDir.path, false)
// Libbox.setup(SetupOptions().also {
// it.basePath = baseDir.path
// it.workingPath = workingDir.path
// it.tempPath = tempDir.path
// it.fixAndroidStack = Bugs.fixAndroidStack
//
// })
Libbox.redirectStderr(File(Settings.workingDir, "stderr.log").path)
initializeOnce = true
return
}
fun start() {
val intent = runBlocking {
withContext(Dispatchers.IO) {
Intent(Application.application, Settings.serviceClass())
}
}
ContextCompat.startForegroundService(Application.application, intent)
}
fun stop() {
Application.application.sendBroadcast(
Intent(Action.SERVICE_CLOSE).setPackage(
Application.application.packageName
)
)
}
}
var fileDescriptor: ParcelFileDescriptor? = null
private val status = MutableLiveData(Status.Stopped)
private val binder = ServiceBinder(status)
private val notification = ServiceNotification(status, service)
// private var boxService: BoxService? = null
private var commandServer: CommandServer? = null
private var receiverRegistered = false
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Action.SERVICE_CLOSE -> {
stopService()
}
PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
serviceUpdateIdleMode()
}
}
}
}
}
private var activeProfileName = ""
private suspend fun startService() {
try {
status.postValue(Status.Starting)
Log.d(TAG, "starting service")
withContext(Dispatchers.Main) {
notification.show(activeProfileName, R.string.status_starting)
}
val selectedConfigPath = Settings.activeConfigPath
if (selectedConfigPath.isBlank()) {
stopAndAlert(Alert.EmptyConfiguration)
return
}
activeProfileName = Settings.activeProfileName
withContext(Dispatchers.Main) {
notification.show(activeProfileName, R.string.status_starting)
binder.broadcast {
it.onServiceResetLogs(listOf())
}
}
DefaultNetworkMonitor.start()
Libbox.setMemoryLimit(!Settings.disableMemoryLimit)
val newService = try {
Mobile.setup(
SetupOptions().also {
it.basePath = Settings.baseDir
it.workingDir = Settings.workingDir
it.tempDir = Settings.tempDir
it.fixAndroidStack = com.hiddify.hiddify.bg.Bugs.fixAndroidStack
it.mode=4L//mode.toLong()
it.listen= "127.0.0.1:${Settings.grpcServiceModePort}"
it.secret=""
it.debug = Settings.debugMode
},platformInterface)
// Libbox.newService(content,platformInterface)
} catch (e: Exception) {
stopAndAlert(Alert.CreateService, e.message)
return
}
status.postValue(Status.Started)
if (Settings.startCoreAfterStartingService){
Mobile.start("","")
}
// if (delayStart) {
// delay(1000L)
// }
// newService.start()
// boxService = newService
// commandServer?.setService(boxService)
withContext(Dispatchers.Main) {
notification.show(activeProfileName, R.string.status_started)
}
notification.start()
} catch (e: Exception) {
stopAndAlert(Alert.StartService, e.message)
return
}
}
fun serviceReload() {
runBlocking {
serviceReload0()
}
}
suspend fun serviceReload0() {
notification.close()
status.postValue(Status.Starting)
val pfd = fileDescriptor
if (pfd != null) {
pfd.close()
fileDescriptor = null
}
// boxService?.apply {
// runCatching {
// close()
// }.onFailure {
// writeLog("service: error when closing: $it")
// }
// Seq.destroyRef(refnum)
// }
Mobile.stop()
// boxService = null
startService()
}
fun getSystemProxyStatus(): SystemProxyStatus {
val status = SystemProxyStatus()
if (service is VPNService) {
status.available = service.systemProxyAvailable
status.enabled = service.systemProxyEnabled
}
return status
}
fun setSystemProxyEnabled(isEnabled: Boolean) {
serviceReload()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun serviceUpdateIdleMode() {
if (Application.powerManager.isDeviceIdleMode) {
// boxService?.pause()
//Mobile.pause()
} else {
Mobile.wake()
// boxService?.wake()
}
}
private fun stopService() {
if (status.value == Status.Stopped) return
status.value = Status.Stopping
if (receiverRegistered) {
service.unregisterReceiver(receiver)
receiverRegistered = false
}
notification.close()
GlobalScope.launch(Dispatchers.IO) {
val pfd = fileDescriptor
if (pfd != null) {
pfd.close()
fileDescriptor = null
}
// commandServer?.setService(null)
// boxService?.apply {
// runCatching {
// close()
// }.onFailure {
// writeLog("service: error when closing: $it")
// }
// //Seq.destroyRef(refnum)
// }
// boxService = null
// Libbox.registerLocalDNSTransport(null)
DefaultNetworkMonitor.stop()
// commandServer?.apply {
// close()
// Seq.destroyRef(refnum)
// }
// commandServer = null
Settings.startedByUser = false
withContext(Dispatchers.Main) {
Mobile.close(4L)
status.value = Status.Stopped
service.stopSelf()
}
notification.close()
}
}
private suspend fun stopAndAlert(type: Alert, message: String? = null) {
Settings.startedByUser = false
withContext(Dispatchers.Main) {
if (receiverRegistered) {
service.unregisterReceiver(receiver)
receiverRegistered = false
}
notification.close()
binder.broadcast { callback ->
callback.onServiceAlert(type.ordinal, message)
}
status.value = Status.Stopped
}
}
@OptIn(DelicateCoroutinesApi::class)
@Suppress("SameReturnValue")
internal fun onStartCommand(): Int {
if (status.value != Status.Stopped) return Service.START_NOT_STICKY
status.value = Status.Starting
if (!receiverRegistered) {
ContextCompat.registerReceiver(service, receiver, IntentFilter().apply {
addAction(Action.SERVICE_CLOSE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
}
}, ContextCompat.RECEIVER_NOT_EXPORTED)
receiverRegistered = true
}
GlobalScope.launch(Dispatchers.IO) {
Settings.startedByUser = true
initialize()
// try {
// startCommandServer()
// } catch (e: Exception) {
// stopAndAlert(Alert.StartCommandServer, e.message)
// return@launch
// }
startService()
}
return Service.START_NOT_STICKY
}
fun onBind(intent: Intent): IBinder {
return binder
}
fun onDestroy() {
binder.close()
}
fun onRevoke() {
stopService()
}
internal fun sendNotification(notification: Notification) {
return
val builder =
NotificationCompat.Builder(service, notification.identifier).setShowWhen(false)
.setContentTitle(notification.title).setContentText(notification.body)
.setOnlyAlertOnce(true).setSmallIcon(R.drawable.ic_launcher_foreground)
.setCategory(NotificationCompat.CATEGORY_EVENT)
.setPriority(NotificationCompat.PRIORITY_HIGH).setAutoCancel(true)
if (!notification.subtitle.isNullOrBlank()) {
builder.setContentInfo(notification.subtitle)
}
if (!notification.openURL.isNullOrBlank()) {
builder.setContentIntent(
PendingIntent.getActivity(
service,
0,
Intent(
service,
MainActivity::class.java,
).apply {
setAction(Action.SERVICE).setData(Uri.parse(notification.openURL))
setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
},
ServiceNotification.flags,
),
)
}
GlobalScope.launch(Dispatchers.Main) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Application.notification.createNotificationChannel(
NotificationChannel(
notification.identifier,
notification.typeName,
NotificationManager.IMPORTANCE_HIGH,
),
)
}
Application.notification.notify(notification.typeID, builder.build())
}
}
fun writeDebugMessage(message: String?) {
Log.d("BoxService", message!!)
binder.broadcast {
it.onServiceWriteLog(message)
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/Bugs.kt
================================================
package com.hiddify.hiddify.bg
import android.os.Build
import com.hiddify.hiddify.BuildConfig
object Bugs {
// TODO: remove launch after fixed
// https://github.com/golang/go/issues/68760
val fixAndroidStack =
BuildConfig.DEBUG ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/DefaultNetworkListener.kt
================================================
package com.hiddify.hiddify.bg
import android.annotation.TargetApi
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.Handler
import android.os.Looper
import com.hiddify.hiddify.Application
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.runBlocking
import java.net.UnknownHostException
object DefaultNetworkListener {
private sealed class NetworkMessage {
class Start(val key: Any, val listener: (Network?) -> Unit) : NetworkMessage()
class Get : NetworkMessage() {
val response = CompletableDeferred()
}
class Stop(val key: Any) : NetworkMessage()
class Put(val network: Network) : NetworkMessage()
class Update(val network: Network) : NetworkMessage()
class Lost(val network: Network) : NetworkMessage()
}
@OptIn(DelicateCoroutinesApi::class, ObsoleteCoroutinesApi::class)
private val networkActor =
GlobalScope.actor(Dispatchers.Unconfined) {
val listeners = mutableMapOf Unit>()
var network: Network? = null
val pendingRequests = arrayListOf()
for (message in channel) {
when (message) {
is NetworkMessage.Start -> {
if (listeners.isEmpty()) register()
listeners[message.key] = message.listener
if (network != null) message.listener(network)
}
is NetworkMessage.Get -> {
check(listeners.isNotEmpty()) { "Getting network without any listeners is not supported" }
if (network == null) {
pendingRequests += message
} else {
message.response.complete(
network,
)
}
}
is NetworkMessage.Stop ->
if (listeners.isNotEmpty() &&
// was not empty
listeners.remove(message.key) != null &&
listeners.isEmpty()
) {
network = null
unregister()
}
is NetworkMessage.Put -> {
network = message.network
pendingRequests.forEach { it.response.complete(message.network) }
pendingRequests.clear()
listeners.values.forEach { it(network) }
}
is NetworkMessage.Update ->
if (network == message.network) {
listeners.values.forEach {
it(
network,
)
}
}
is NetworkMessage.Lost ->
if (network == message.network) {
network = null
listeners.values.forEach { it(null) }
}
}
}
}
suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(
NetworkMessage.Start(
key,
listener,
),
)
suspend fun get(): Network = if (fallback) {
@TargetApi(23)
Application.connectivity.activeNetwork
?: error("missing default network") // failed to listen, return current if available
} else {
NetworkMessage.Get().run {
networkActor.send(this)
response.await()
}
}
suspend fun stop(key: Any) = networkActor.send(NetworkMessage.Stop(key))
// NB: this runs in ConnectivityThread, and this behavior cannot be changed until API 26
private object Callback : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) = runBlocking {
networkActor.send(
NetworkMessage.Put(
network,
),
)
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
// it's a good idea to refresh capabilities
runBlocking { networkActor.send(NetworkMessage.Update(network)) }
}
override fun onLost(network: Network) = runBlocking {
networkActor.send(
NetworkMessage.Lost(
network,
),
)
}
}
private var fallback = false
private val request =
NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
if (Build.VERSION.SDK_INT == 23) { // workarounds for OEM bugs
removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)
}
}.build()
private val mainHandler = Handler(Looper.getMainLooper())
/**
* Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1:
* https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
*
* This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that
* satisfies default network capabilities but only THE default network. Unfortunately, we need to have
* android.permission.CHANGE_NETWORK_STATE to be able to call requestNetwork.
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/
private fun register() {
when (Build.VERSION.SDK_INT) {
in 31..Int.MAX_VALUE ->
@TargetApi(31)
{
Application.connectivity.registerBestMatchingNetworkCallback(
request,
Callback,
mainHandler,
)
}
in 28 until 31 ->
@TargetApi(28)
{ // we want REQUEST here instead of LISTEN
Application.connectivity.requestNetwork(request, Callback, mainHandler)
}
in 26 until 28 ->
@TargetApi(26)
{
Application.connectivity.registerDefaultNetworkCallback(Callback, mainHandler)
}
in 24 until 26 ->
@TargetApi(24)
{
Application.connectivity.registerDefaultNetworkCallback(Callback)
}
else ->
try {
fallback = false
Application.connectivity.requestNetwork(request, Callback)
} catch (e: RuntimeException) {
fallback =
true // known bug on API 23: https://stackoverflow.com/a/33509180/2245107
}
}
}
private fun unregister() {
runCatching {
Application.connectivity.unregisterNetworkCallback(Callback)
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/DefaultNetworkMonitor.kt
================================================
package com.hiddify.hiddify.bg
import android.net.Network
import android.os.Build
import com.hiddify.hiddify.Application
import com.hiddify.core.libbox.InterfaceUpdateListener
import com.hiddify.hiddify.constant.Bugs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.net.NetworkInterface
object DefaultNetworkMonitor {
var defaultNetwork: Network? = null
private var listener: InterfaceUpdateListener? = null
suspend fun start() {
DefaultNetworkListener.start(this) {
defaultNetwork = it
checkDefaultInterfaceUpdate(it)
}
defaultNetwork = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Application.connectivity.activeNetwork
} else {
DefaultNetworkListener.get()
}
}
suspend fun stop() {
DefaultNetworkListener.stop(this)
}
suspend fun require(): Network {
val network = defaultNetwork
if (network != null) {
return network
}
return DefaultNetworkListener.get()
}
fun setListener(listener: InterfaceUpdateListener?) {
this.listener = listener
checkDefaultInterfaceUpdate(defaultNetwork)
}
private fun checkDefaultInterfaceUpdate(newNetwork: Network?) {
val listener = listener ?: return
if (newNetwork != null) {
val interfaceName =
(Application.connectivity.getLinkProperties(newNetwork) ?: return).interfaceName
for (times in 0 until 10) {
var interfaceIndex: Int
try {
interfaceIndex = NetworkInterface.getByName(interfaceName).index
} catch (e: Exception) {
Thread.sleep(100)
continue
}
listener.updateDefaultInterface(interfaceName, interfaceIndex, false, false)
}
} else {
listener.updateDefaultInterface("", -1, false, false)
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/LocalResolver.kt
================================================
package com.hiddify.hiddify.bg
import android.net.DnsResolver
import android.os.Build
import android.os.CancellationSignal
import android.system.ErrnoException
import androidx.annotation.RequiresApi
import com.hiddify.hiddify.ktx.tryResumeWithException
import com.hiddify.core.libbox.ExchangeContext
import com.hiddify.core.libbox.LocalDNSTransport
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.runBlocking
import java.net.InetAddress
import java.net.UnknownHostException
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
object LocalResolver : LocalDNSTransport {
private const val RCODE_NXDOMAIN = 3
override fun raw(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
@RequiresApi(Build.VERSION_CODES.Q)
override fun exchange(ctx: ExchangeContext, message: ByteArray) {
return runBlocking {
val defaultNetwork = DefaultNetworkMonitor.require()
suspendCoroutine { continuation ->
val signal = CancellationSignal()
ctx.onCancel(signal::cancel)
val callback =
object : DnsResolver.Callback {
override fun onAnswer(answer: ByteArray, rcode: Int) {
if (rcode == 0) {
ctx.rawSuccess(answer)
} else {
ctx.errorCode(rcode)
}
continuation.resume(Unit)
}
override fun onError(error: DnsResolver.DnsException) {
when (val cause = error.cause) {
is ErrnoException -> {
ctx.errnoCode(cause.errno)
continuation.resume(Unit)
return
}
}
continuation.tryResumeWithException(error)
}
}
DnsResolver.getInstance().rawQuery(
defaultNetwork,
message,
DnsResolver.FLAG_NO_RETRY,
Dispatchers.IO.asExecutor(),
signal,
callback,
)
}
}
}
override fun lookup(ctx: ExchangeContext, network: String, domain: String) {
return runBlocking {
val defaultNetwork = DefaultNetworkMonitor.require()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
suspendCoroutine { continuation ->
val signal = CancellationSignal()
ctx.onCancel(signal::cancel)
val callback =
object : DnsResolver.Callback> {
@Suppress("ThrowableNotThrown")
override fun onAnswer(answer: Collection, rcode: Int) {
if (rcode == 0) {
ctx.success(
(answer as Collection).mapNotNull { it?.hostAddress }
.joinToString("\n"),
)
} else {
ctx.errorCode(rcode)
}
continuation.resume(Unit)
}
override fun onError(error: DnsResolver.DnsException) {
when (val cause = error.cause) {
is ErrnoException -> {
ctx.errnoCode(cause.errno)
continuation.resume(Unit)
return
}
}
continuation.tryResumeWithException(error)
}
}
val type =
when {
network.endsWith("4") -> DnsResolver.TYPE_A
network.endsWith("6") -> DnsResolver.TYPE_AAAA
else -> null
}
if (type != null) {
DnsResolver.getInstance().query(
defaultNetwork,
domain,
type,
DnsResolver.FLAG_NO_RETRY,
Dispatchers.IO.asExecutor(),
signal,
callback,
)
} else {
DnsResolver.getInstance().query(
defaultNetwork,
domain,
DnsResolver.FLAG_NO_RETRY,
Dispatchers.IO.asExecutor(),
signal,
callback,
)
}
}
} else {
val answer =
try {
defaultNetwork.getAllByName(domain)
} catch (e: UnknownHostException) {
ctx.errorCode(RCODE_NXDOMAIN)
return@runBlocking
}
ctx.success(answer.mapNotNull { it.hostAddress }.joinToString("\n"))
}
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/PlatformInterfaceWrapper.kt
================================================
package com.hiddify.hiddify.bg
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Process
import android.util.Log
import androidx.annotation.RequiresApi
import com.hiddify.hiddify.Application
import com.hiddify.core.libbox.InterfaceUpdateListener
import com.hiddify.core.libbox.Libbox
import com.hiddify.core.libbox.NetworkInterfaceIterator
import com.hiddify.core.libbox.PlatformInterface
import com.hiddify.core.libbox.StringIterator
import com.hiddify.core.libbox.TunOptions
import com.hiddify.core.libbox.WIFIState
import java.net.Inet6Address
import java.net.InetSocketAddress
import java.net.InterfaceAddress
import java.net.NetworkInterface
import java.util.Enumeration
import com.hiddify.core.libbox.NetworkInterface as LibboxNetworkInterface
import android.system.OsConstants
import com.hiddify.core.libbox.ConnectionOwner
import com.hiddify.core.libbox.LocalDNSTransport
import java.security.KeyStore
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
interface PlatformInterfaceWrapper : PlatformInterface {
override fun usePlatformAutoDetectInterfaceControl(): Boolean = true
override fun autoDetectInterfaceControl(fd: Int) {
}
override fun openTun(options: TunOptions): Int {
error("invalid argument")
}
override fun useProcFS(): Boolean = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
@RequiresApi(Build.VERSION_CODES.Q)
override fun findConnectionOwner(
ipProtocol: Int,
sourceAddress: String,
sourcePort: Int,
destinationAddress: String,
destinationPort: Int,
): ConnectionOwner {
try {
val uid =
Application.connectivity.getConnectionOwnerUid(
ipProtocol,
InetSocketAddress(sourceAddress, sourcePort),
InetSocketAddress(destinationAddress, destinationPort),
)
// if (uid == Process.INVALID_UID)error("android: connection owner not found")
val owner = ConnectionOwner()
owner.userId = uid
if (uid!=Process.INVALID_UID) {
val packages = Application.packageManager.getPackagesForUid(uid)
owner.userName = packages?.firstOrNull() ?: ""
owner.androidPackageName = owner.userName
}
return owner
} catch (e: Exception) {
Log.e("PlatformInterface", "getConnectionOwnerUid", e)
e.printStackTrace(System.err)
throw e
}
}
override fun startDefaultInterfaceMonitor(listener: InterfaceUpdateListener) {
DefaultNetworkMonitor.setListener(listener)
}
override fun closeDefaultInterfaceMonitor(listener: InterfaceUpdateListener) {
DefaultNetworkMonitor.setListener(null)
}
override fun getInterfaces(): NetworkInterfaceIterator {
val networks = Application.connectivity.allNetworks
val networkInterfaces = NetworkInterface.getNetworkInterfaces().toList()
val interfaces = mutableListOf()
for (network in networks) {
val boxInterface = LibboxNetworkInterface()
val linkProperties = Application.connectivity.getLinkProperties(network) ?: continue
val networkCapabilities =
Application.connectivity.getNetworkCapabilities(network) ?: continue
boxInterface.name = linkProperties.interfaceName
val networkInterface =
networkInterfaces.find { it.name == boxInterface.name } ?: continue
boxInterface.dnsServer =
StringArray(linkProperties.dnsServers.mapNotNull { it.hostAddress }.iterator())
boxInterface.type =
when {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> Libbox.InterfaceTypeWIFI
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> Libbox.InterfaceTypeCellular
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> Libbox.InterfaceTypeEthernet
else -> Libbox.InterfaceTypeOther
}
boxInterface.index = networkInterface.index
runCatching {
boxInterface.mtu = networkInterface.mtu
}.onFailure {
Log.e(
"PlatformInterface",
"failed to get mtu for interface ${boxInterface.name}",
it,
)
}
boxInterface.addresses =
StringArray(
networkInterface.interfaceAddresses.mapTo(mutableListOf()) { it.toPrefix() }
.iterator(),
)
var dumpFlags = 0
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
dumpFlags = OsConstants.IFF_UP or OsConstants.IFF_RUNNING
}
if (networkInterface.isLoopback) {
dumpFlags = dumpFlags or OsConstants.IFF_LOOPBACK
}
if (networkInterface.isPointToPoint) {
dumpFlags = dumpFlags or OsConstants.IFF_POINTOPOINT
}
if (networkInterface.supportsMulticast()) {
dumpFlags = dumpFlags or OsConstants.IFF_MULTICAST
}
boxInterface.flags = dumpFlags
boxInterface.metered =
!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
interfaces.add(boxInterface)
}
return InterfaceArray(interfaces.iterator())
}
override fun underNetworkExtension(): Boolean = false
override fun includeAllNetworks(): Boolean = false
override fun clearDNSCache() {
}
override fun readWIFIState(): WIFIState? {
@Suppress("DEPRECATION")
val wifiInfo =
Application.wifiManager.connectionInfo ?: return null
var ssid = wifiInfo.ssid
if (ssid == "") {
return WIFIState("", "")
}
if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
ssid = ssid.substring(1, ssid.length - 1)
}
return WIFIState(ssid, wifiInfo.bssid)
}
override fun localDNSTransport(): LocalDNSTransport? = LocalResolver
@OptIn(ExperimentalEncodingApi::class)
override fun systemCertificates(): StringIterator {
val certificates = mutableListOf()
val keyStore = KeyStore.getInstance("AndroidCAStore")
if (keyStore != null) {
keyStore.load(null, null)
val aliases = keyStore.aliases()
while (aliases.hasMoreElements()) {
val cert = keyStore.getCertificate(aliases.nextElement())
certificates.add(
"-----BEGIN CERTIFICATE-----\n" + Base64.encode(cert.encoded) + "\n-----END CERTIFICATE-----",
)
}
}
return StringArray(certificates.iterator())
}
private class InterfaceArray(private val iterator: Iterator) : NetworkInterfaceIterator {
override fun hasNext(): Boolean = iterator.hasNext()
override fun next(): LibboxNetworkInterface = iterator.next()
}
class StringArray(private val iterator: Iterator) : StringIterator {
override fun len(): Int {
// not used by core
return 0
}
override fun hasNext(): Boolean = iterator.hasNext()
override fun next(): String = iterator.next()
}
private fun InterfaceAddress.toPrefix(): String = if (address is Inet6Address) {
"${Inet6Address.getByAddress(address.address).hostAddress}/$networkPrefixLength"
} else {
"${address.hostAddress}/$networkPrefixLength"
}
private val NetworkInterface.flags: Int
@SuppressLint("SoonBlockedPrivateApi")
get() {
val getFlagsMethod = NetworkInterface::class.java.getDeclaredMethod("getFlags")
return getFlagsMethod.invoke(this) as Int
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/ProxyService.kt
================================================
package com.hiddify.hiddify.bg
import android.app.Service
import android.content.Intent
import com.hiddify.core.libbox.Notification
class ProxyService :
Service(),
PlatformInterfaceWrapper {
private val service = BoxService(this, this)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = service.onStartCommand()
override fun onBind(intent: Intent) = service.onBind(intent)
override fun onDestroy() = service.onDestroy()
override fun sendNotification(notification: Notification) = service.sendNotification(notification)
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/ServiceBinder.kt
================================================
package com.hiddify.hiddify.bg
import android.os.RemoteCallbackList
import androidx.lifecycle.MutableLiveData
import com.hiddify.hiddify.IService
import com.hiddify.hiddify.IServiceCallback
import com.hiddify.hiddify.constant.Status
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class ServiceBinder(private val status: MutableLiveData) : IService.Stub() {
private val callbacks = RemoteCallbackList()
private val broadcastLock = Mutex()
init {
status.observeForever {
broadcast { callback ->
callback.onServiceStatusChanged(it.ordinal)
}
}
}
@OptIn(DelicateCoroutinesApi::class)
fun broadcast(work: (IServiceCallback) -> Unit) {
GlobalScope.launch(Dispatchers.Main) {
broadcastLock.withLock {
val count = callbacks.beginBroadcast()
try {
repeat(count) {
try {
work(callbacks.getBroadcastItem(it))
} catch (_: Exception) {
}
}
} finally {
callbacks.finishBroadcast()
}
}
}
}
override fun getStatus(): Int = (status.value ?: Status.Stopped).ordinal
override fun registerCallback(callback: IServiceCallback) {
callbacks.register(callback)
}
override fun unregisterCallback(callback: IServiceCallback?) {
callbacks.unregister(callback)
}
fun close() {
callbacks.kill()
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/ServiceConnection.kt
================================================
package com.hiddify.hiddify.bg
import com.hiddify.hiddify.IService
import com.hiddify.hiddify.IServiceCallback
import com.hiddify.hiddify.Settings
import com.hiddify.hiddify.constant.Action
import com.hiddify.hiddify.constant.Alert
import com.hiddify.hiddify.constant.Status
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class ServiceConnection(private val context: Context, callback: Callback, private val register: Boolean = true) : ServiceConnection {
companion object {
private const val TAG = "ServiceConnection"
}
private val callback = ServiceCallback(callback)
private var service: IService? = null
val status get() = service?.status?.let { Status.values()[it] } ?: Status.Stopped
fun connect() {
val intent =
runBlocking {
withContext(Dispatchers.IO) {
Intent(context, Settings.serviceClass()).setAction(Action.SERVICE)
}
}
context.bindService(intent, this, AppCompatActivity.BIND_AUTO_CREATE)
Log.d(TAG, "request connect")
}
fun disconnect() {
try {
context.unbindService(this)
} catch (_: IllegalArgumentException) {
}
Log.d(TAG, "request disconnect")
}
fun reconnect() {
try {
context.unbindService(this)
} catch (_: IllegalArgumentException) {
}
val intent =
runBlocking {
withContext(Dispatchers.IO) {
Intent(context, Settings.serviceClass()).setAction(Action.SERVICE)
}
}
context.bindService(intent, this, AppCompatActivity.BIND_AUTO_CREATE)
Log.d(TAG, "request reconnect")
}
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
val service = IService.Stub.asInterface(binder)
this.service = service
try {
if (register) service.registerCallback(callback)
callback.onServiceStatusChanged(service.status)
} catch (e: RemoteException) {
Log.e(TAG, "initialize service connection", e)
}
Log.d(TAG, "service connected")
}
override fun onServiceDisconnected(name: ComponentName?) {
try {
service?.unregisterCallback(callback)
} catch (e: RemoteException) {
Log.e(TAG, "cleanup service connection", e)
}
Log.d(TAG, "service disconnected")
}
override fun onBindingDied(name: ComponentName?) {
reconnect()
Log.d(TAG, "service dead")
}
interface Callback {
fun onServiceStatusChanged(status: Status)
fun onServiceAlert(type: Alert, message: String?) {
}
}
class ServiceCallback(private val callback: Callback) : IServiceCallback.Stub() {
override fun onServiceStatusChanged(status: Int) {
callback.onServiceStatusChanged(Status.values()[status])
}
override fun onServiceAlert(type: Int, message: String?) {
callback.onServiceAlert(Alert.values()[type], message)
}
override fun onServiceWriteLog(message: String?) {
//TODO("Not yet implemented")
}
override fun onServiceResetLogs(messages: List?) {
//TODO("Not yet implemented")
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/ServiceNotification.kt
================================================
package com.hiddify.hiddify.bg
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.util.Log
import androidx.annotation.StringRes
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.lifecycle.MutableLiveData
import com.hiddify.core.api.v2.config.Protocol
import com.hiddify.core.api.v2.hcommon.Empty
import com.hiddify.core.api.v2.hcore.CoreClient
import com.hiddify.core.api.v2.hcore.SystemInfo
import com.hiddify.core.api.v2.hello.HelloClient
import com.hiddify.core.api.v2.hello.HelloRequest
import com.hiddify.hiddify.Application
import com.hiddify.hiddify.MainActivity
import com.hiddify.hiddify.R
import com.hiddify.hiddify.Settings
import com.hiddify.hiddify.constant.Action
import com.hiddify.hiddify.constant.Status
//import com.hiddify.hiddify.utils.CommandClient
import com.hiddify.core.libbox.Libbox
import com.hiddify.hiddify.Application.Companion.notification
import com.hiddify.hiddify.utils.GrpcClientProvider
import com.squareup.wire.GrpcClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import java.io.IOException
import kotlinx.coroutines.delay
import kotlinx.coroutines.CancellationException
class ServiceNotification(private val status: MutableLiveData, private val service: Service) : BroadcastReceiver(){
companion object {
private const val notificationId = 1
private const val notificationChannel = "service"
var coreClient: CoreClient?=null
val flags =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
fun checkPermission(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
return true
}
return Application.notification.areNotificationsEnabled()
}
}
val streamingCoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
//
// private val commandClient =
// CommandClient(GlobalScope, CommandClient.ConnectionType.Status, this)
private var receiverRegistered = false
private val notificationBuilder by lazy {
NotificationCompat.Builder(service, notificationChannel)
.setShowWhen(false)
.setOngoing(true)
.setContentTitle("Hiddify")
.setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.ic_stat_logo)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setContentIntent(
PendingIntent.getActivity(
service,
0,
Intent(
service,
MainActivity::class.java
).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),
flags
)
)
.setPriority(NotificationCompat.PRIORITY_LOW).apply {
addAction(
NotificationCompat.Action.Builder(
0, service.getText(R.string.stop), PendingIntent.getBroadcast(
service,
0,
Intent(Action.SERVICE_CLOSE).setPackage(
Application.application.packageName
),
flags
)
).build()
)
}
}
fun show(profileName: String, @StringRes contentTextId: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Application.notification.createNotificationChannel(
NotificationChannel(
notificationChannel, "hiddify service", NotificationManager.IMPORTANCE_LOW
)
)
}
service.startForeground(
notificationId, notificationBuilder
.setContentTitle(profileName.takeIf { it.isNotBlank() } ?: "Hiddify")
.setContentText(service.getString(contentTextId)).build()
)
}
suspend fun start() {
if (Settings.dynamicNotification && checkPermission()) {
// commandClient.connect()
startListenSystemInfo()
withContext(Dispatchers.Main) {
registerReceiver()
}
}
}
private fun registerReceiver() {
service.registerReceiver(this, IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF)
})
receiverRegistered = true
}
fun updateStatus(previous:SystemInfo,status: SystemInfo) {
val uplink=status.uplink_total - previous.uplink_total
val downlink=status.downlink_total - previous.downlink_total
val content = "${Libbox.formatBytes(uplink)}/s ↑\t${Libbox.formatBytes(downlink)}/s ↓ \n${status.current_outbound}"
val title = "${status.current_profile}"
Application.notificationManager.notify(
notificationId,
notificationBuilder.setContentTitle(title).setContentText(content).build()
)
}
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_SCREEN_ON -> {
startListenSystemInfo()
}
Intent.ACTION_SCREEN_OFF -> {
stopListenSystemInfo()
}
}
}
fun close() {
stopListenSystemInfo()
ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE)
if (receiverRegistered) {
service.unregisterReceiver(this)
receiverRegistered = false
}
}
private var streamingJob: Job? = null
fun startListenSystemInfo() {
// Cancel any previous stream if still running
Log.d("notification","startListenSystemInfo")
streamingJob?.cancel()
streamingJob = streamingCoroutineScope.launch(Dispatchers.IO) {
Log.d("notification", "startListenSystemInfo-launch")
val coreClient = GrpcClientProvider.grpcClient.create(CoreClient::class)
try {
var previous = coreClient.GetSystemInfo().executeBlocking(Empty())
while (isActive) {
delay(1_000) // ✅ coroutine-friendly
val current = coreClient.GetSystemInfo().executeBlocking(Empty())
updateStatus(previous,current)
previous = current
}
} catch (e: CancellationException) {
// coroutine cancelled normally
Log.d("notification", "SystemInfo polling cancelled")
notification.cancel(notificationId)
} catch (e: Exception) {
Log.e("notification", "SystemInfo polling failed", e)
notification.cancel(notificationId)
}
}
}
fun stopListenSystemInfo(){
try {
streamingJob?.cancel()
}catch (e: Exception){
Log.d("notification", "Exception ${e}")
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/TileService.kt
================================================
package com.hiddify.hiddify.bg
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.hiddify.hiddify.Application
import com.hiddify.hiddify.MainActivity
import com.hiddify.hiddify.Settings
import com.hiddify.hiddify.constant.ServiceMode
import com.hiddify.hiddify.constant.Status
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@RequiresApi(24)
class TileService : TileService(), ServiceConnection.Callback {
private val connection = ServiceConnection(this, this)
override fun onServiceStatusChanged(status: Status) {
qsTile?.apply {
state =
when (status) {
Status.Started -> Tile.STATE_ACTIVE
Status.Stopped -> Tile.STATE_INACTIVE
else -> Tile.STATE_UNAVAILABLE
}
updateTile()
}
}
override fun onStartListening() {
super.onStartListening()
connection.connect()
}
override fun onStopListening() {
connection.disconnect()
super.onStopListening()
}
private fun toggleService() {
when (connection.status) {
Status.Stopped -> {
Settings.startCoreAfterStartingService = true
BoxService.start()
qsTile?.apply {
state = Tile.STATE_ACTIVE
updateTile()
}
}
Status.Started -> {
BoxService.stop()
qsTile?.apply {
state = Tile.STATE_INACTIVE
updateTile()
}
}
else -> {}
}
}
override fun onClick() {
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (keyguardManager.isKeyguardLocked) {
unlockAndRun {
toggleService()
}
} else {
toggleService()
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/bg/VPNService.kt
================================================
package com.hiddify.hiddify.bg
import android.util.Log
import com.hiddify.hiddify.Settings
import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException
import android.net.ProxyInfo
import android.net.VpnService
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import com.hiddify.core.libbox.Notification
import com.hiddify.hiddify.constant.PerAppProxyMode
import com.hiddify.hiddify.ktx.toIpPrefix
import com.hiddify.core.libbox.TunOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class VPNService : VpnService(), PlatformInterfaceWrapper {
companion object {
private const val TAG = "A/VPNService"
}
private val service = BoxService(this, this)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) =
service.onStartCommand()
override fun onBind(intent: Intent): IBinder {
val binder = super.onBind(intent)
if (binder != null) {
return binder
}
return service.onBind(intent)
}
override fun onDestroy() {
service.onDestroy()
}
override fun onRevoke() {
runBlocking {
withContext(Dispatchers.Main) {
service.onRevoke()
}
}
}
override fun autoDetectInterfaceControl(fd: Int) {
protect(fd)
}
var systemProxyAvailable = false
var systemProxyEnabled = false
fun addIncludePackage(builder: Builder, packageName: String) {
if (packageName == this.packageName) {
Log.d("VpnService","Cannot include myself: $packageName")
return
}
try {
Log.d("VpnService","Including $packageName")
builder.addAllowedApplication(packageName)
} catch (e: NameNotFoundException) {
}
}
fun addExcludePackage(builder: Builder, packageName: String) {
try {
Log.d("VpnService","Excluding $packageName")
builder.addDisallowedApplication(packageName)
} catch (e: NameNotFoundException) {
}
}
override fun openTun(options: TunOptions): Int {
var hasPermission = false
for (i in 0 until 20) {
if (prepare(this) != null) {
Log.w("VPN", "android: missing vpn permission")
} else {
hasPermission = true
break
}
Thread.sleep(50)
}
if (!hasPermission) {
error("android: missing vpn permission")
}
// service.fileDescriptor?.close()
val builder = Builder()
.setSession("hiddify")
.setMtu(options.mtu)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false)
}
val inet4Address = options.inet4Address
while (inet4Address.hasNext()) {
val address = inet4Address.next()
builder.addAddress(address.address(), address.prefix())
}
val inet6Address = options.inet6Address
while (inet6Address.hasNext()) {
val address = inet6Address.next()
builder.addAddress(address.address(), address.prefix())
}
if (options.autoRoute) {
builder.addDnsServer(options.dnsServerAddress.value)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val inet4RouteAddress = options.inet4RouteAddress
if (inet4RouteAddress.hasNext()) {
while (inet4RouteAddress.hasNext()) {
builder.addRoute(inet4RouteAddress.next().toIpPrefix())
}
} else {
builder.addRoute("0.0.0.0", 0)
}
val inet6RouteAddress = options.inet6RouteAddress
if (inet6RouteAddress.hasNext()) {
while (inet6RouteAddress.hasNext()) {
builder.addRoute(inet6RouteAddress.next().toIpPrefix())
}
} else {
builder.addRoute("::", 0)
}
val inet4RouteExcludeAddress = options.inet4RouteExcludeAddress
while (inet4RouteExcludeAddress.hasNext()) {
builder.excludeRoute(inet4RouteExcludeAddress.next().toIpPrefix())
}
val inet6RouteExcludeAddress = options.inet6RouteExcludeAddress
while (inet6RouteExcludeAddress.hasNext()) {
builder.excludeRoute(inet6RouteExcludeAddress.next().toIpPrefix())
}
} else {
val inet4RouteAddress = options.inet4RouteRange
if (inet4RouteAddress.hasNext()) {
while (inet4RouteAddress.hasNext()) {
val address = inet4RouteAddress.next()
builder.addRoute(address.address(), address.prefix())
}
}
val inet6RouteAddress = options.inet6RouteRange
if (inet6RouteAddress.hasNext()) {
while (inet6RouteAddress.hasNext()) {
val address = inet6RouteAddress.next()
builder.addRoute(address.address(), address.prefix())
}
}
}
if (Settings.perAppProxyEnabled) {
val appList = Settings.perAppProxyList
if (Settings.perAppProxyMode == PerAppProxyMode.INCLUDE) {
appList.forEach {
addIncludePackage(builder,it)
}
// addIncludePackage(builder,packageName)
} else {
appList.forEach {
addExcludePackage(builder,it)
}
addExcludePackage(builder,packageName)
}
} else {
val includePackage = options.includePackage
if (includePackage.hasNext()) {
while (includePackage.hasNext()) {
addIncludePackage(builder,includePackage.next())
}
// addIncludePackage(builder,packageName)
}else {
val excludePackage = options.excludePackage
if (excludePackage.hasNext()) {
while (excludePackage.hasNext()) {
addExcludePackage(builder, excludePackage.next())
}
}
addExcludePackage(builder, packageName)
}
}
}
if (options.isHTTPProxyEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
systemProxyAvailable = true
systemProxyEnabled = Settings.systemProxyEnabled
if (systemProxyEnabled) builder.setHttpProxy(
ProxyInfo.buildDirectProxy(
options.httpProxyServer, options.httpProxyServerPort
)
)
} else {
systemProxyAvailable = false
systemProxyEnabled = false
}
val pfd = builder.establish() ?: error("android: the application is not prepared or is revoked")
service.fileDescriptor = pfd
return pfd.fd
}
// override fun writeLog(message: String) = service.writeLog(message)
override fun sendNotification(notification: Notification) {
// service.sendNotification(notification)
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/constant/Action.kt
================================================
package com.hiddify.hiddify.constant
object Action {
const val SERVICE = "com.hiddify.app.SERVICE"
const val SERVICE_CLOSE = "com.hiddify.app.SERVICE_CLOSE"
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/constant/Alert.kt
================================================
package com.hiddify.hiddify.constant
enum class Alert {
RequestVPNPermission,
RequestNotificationPermission,
EmptyConfiguration,
StartCommandServer,
CreateService,
StartService
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/constant/PerAppProxyMode.kt
================================================
package com.hiddify.hiddify.constant
object PerAppProxyMode {
const val OFF = "off"
const val INCLUDE = "include"
const val EXCLUDE = "exclude"
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/constant/ServiceMode.kt
================================================
package com.hiddify.hiddify.constant
object ServiceMode {
const val NORMAL = "proxy"
const val VPN = "vpn"
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/constant/SettingsKey.kt
================================================
package com.hiddify.hiddify.constant
object SettingsKey {
private const val KEY_PREFIX = "flutter."
const val SERVICE_MODE = "${KEY_PREFIX}service-mode"
const val ACTIVE_CONFIG_PATH = "${KEY_PREFIX}active_config_path"
const val ACTIVE_PROFILE_NAME = "${KEY_PREFIX}active_profile_name"
const val PER_APP_PROXY_MODE = "${KEY_PREFIX}per_app_proxy_mode"
const val PER_APP_PROXY_INCLUDE_LIST = "${KEY_PREFIX}per_app_proxy_include_list"
const val PER_APP_PROXY_EXCLUDE_LIST = "${KEY_PREFIX}per_app_proxy_exclude_list"
const val DEBUG_MODE = "${KEY_PREFIX}debug_mode"
const val DISABLE_MEMORY_LIMIT = "${KEY_PREFIX}disable_memory_limit"
const val DYNAMIC_NOTIFICATION = "${KEY_PREFIX}dynamic_notification"
const val SYSTEM_PROXY_ENABLED = "${KEY_PREFIX}system_proxy_enabled"
// cache
const val STARTED_BY_USER = "${KEY_PREFIX}started_by_user"
const val CONFIG_OPTIONS = "config_options_json"
const val START_CORE_ON_STARTING_SERVICE = "${KEY_PREFIX}starting_core_on_starting_service"
const val WORKING_DIR = "working_dir"
const val BASE_DIR = "base_dir"
const val TMP_DIR = "tmp_dir"
const val GRPC_PORT = "grpc_port"
const val GRPC_FLUTTER_PUBLIC_KEY = "grpc_flutter_public_key"
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/constant/Status.kt
================================================
package com.hiddify.hiddify.constant
enum class Status {
Stopped,
Starting,
Started,
Stopping,
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/constant/bugs.kt
================================================
package com.hiddify.hiddify.constant
import android.os.Build
import com.hiddify.hiddify.BuildConfig
object Bugs {
// TODO: remove launch after fixed
// https://github.com/golang/go/issues/68760
val fixAndroidStack = BuildConfig.DEBUG ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/ktx/Continuations.kt
================================================
package com.hiddify.hiddify.ktx
import kotlin.coroutines.Continuation
fun Continuation.tryResume(value: T) {
try {
resumeWith(Result.success(value))
} catch (ignored: IllegalStateException) {
}
}
fun Continuation.tryResumeWithException(exception: Throwable) {
try {
resumeWith(Result.failure(exception))
} catch (ignored: IllegalStateException) {
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/ktx/Wrappers.kt
================================================
package com.hiddify.hiddify.ktx
import android.net.IpPrefix
import android.os.Build
import androidx.annotation.RequiresApi
import com.hiddify.core.libbox.RoutePrefix
import com.hiddify.core.libbox.StringIterator
import com.hiddify.core.libbox.StringBox
import java.net.InetAddress
val StringBox?.unwrap: String
get() {
if (this == null) return ""
return value
}
fun StringIterator.toList(): List {
return mutableListOf().apply {
while (hasNext()) {
add(next())
}
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun RoutePrefix.toIpPrefix() = IpPrefix(InetAddress.getByName(address()), prefix())
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/utils/CommandClient.kt
================================================
package com.hiddify.hiddify.utils
import com.hiddify.core.libbox.CommandClient
import com.hiddify.core.libbox.CommandClientHandler
import com.hiddify.core.libbox.CommandClientOptions
import com.hiddify.core.libbox.Connections
import com.hiddify.core.libbox.Libbox
import com.hiddify.core.libbox.OutboundGroup
import com.hiddify.core.libbox.OutboundGroupIterator
import com.hiddify.core.libbox.StatusMessage
import com.hiddify.core.libbox.StringIterator
import com.hiddify.hiddify.ktx.toList
import go.Seq
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
//open class CommandClient(
// private val scope: CoroutineScope,
// private val connectionType: ConnectionType,
// private val handler: Handler
//) {
//
// enum class ConnectionType {
// Status, Groups, Log, ClashMode, GroupOnly
// }
//
// interface Handler {
//
// fun onConnected() {}
// fun onDisconnected() {}
// fun updateStatus(status: StatusMessage) {}
// fun updateGroups(groups: List) {}
// fun clearLog() {}
// fun appendLog(message: String) {}
// fun initializeClashMode(modeList: List, currentMode: String) {}
// fun updateClashMode(newMode: String) {}
//
// }
//
//
// private var commandClient: CommandClient? = null
// private val clientHandler = ClientHandler()
// fun connect() {
// disconnect()
// val options = CommandClientOptions()
// options.command = when (connectionType) {
// ConnectionType.Status -> Libbox.CommandStatus
// ConnectionType.Groups -> Libbox.CommandGroup
// ConnectionType.Log -> Libbox.CommandLog
// ConnectionType.ClashMode -> Libbox.CommandClashMode
// ConnectionType.GroupOnly -> Libbox.CommandGroupInfoOnly
// }
// options.statusInterval = 2 * 1000 * 1000 * 1000
// val commandClient = CommandClient(clientHandler, options)
// scope.launch(Dispatchers.IO) {
// for (i in 1..10) {
// delay(100 + i.toLong() * 50)
// try {
// commandClient.connect()
// } catch (ignored: Exception) {
// continue
// }
// if (!isActive) {
// runCatching {
// commandClient.disconnect()
// }
// return@launch
// }
// this@CommandClient.commandClient = commandClient
// return@launch
// }
// runCatching {
// commandClient.disconnect()
// }
// }
// }
//
// fun disconnect() {
// commandClient?.apply {
// runCatching {
// disconnect()
// }
// Seq.destroyRef(refnum)
// }
// commandClient = null
// }
//
// private inner class ClientHandler : CommandClientHandler {
//
// override fun connected() {
// handler.onConnected()
// }
//
// override fun disconnected(message: String?) {
// handler.onDisconnected()
// }
//
// override fun writeGroups(message: OutboundGroupIterator?) {
// if (message == null) {
// return
// }
// val groups = mutableListOf()
// while (message.hasNext()) {
// groups.add(message.next())
// }
// handler.updateGroups(groups)
// }
//
// override fun clearLogs() {
// handler.clearLog()
// }
// override fun writeLogs(messageList: StringIterator?) {
//
// if (messageList == null) {
// return
// }
//
//
// while (messageList.hasNext()) {
// handler.appendLog(messageList.next())
// }
//
// }
//
// override fun writeStatus(message: StatusMessage?) {
// if (message == null) {
// return
// }
// handler.updateStatus(message)
// }
//
// override fun initializeClashMode(modeList: StringIterator, currentMode: String) {
// handler.initializeClashMode(modeList.toList(), currentMode)
// }
//
// override fun updateClashMode(newMode: String) {
// handler.updateClashMode(newMode)
// }
//
//
//
// override fun writeConnections(message: Connections?) {
// }
//
// }
//
//}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/utils/GrpcProvider.kt
================================================
package com.hiddify.hiddify.utils
/*
* Copyright (C) 2019 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.hiddify.core.api.v2.hcore.CoreClient
import com.hiddify.hiddify.Settings
import com.squareup.wire.GrpcClient
import io.grpc.CallOptions
import io.grpc.ManagedChannelBuilder
import java.io.IOException
import java.io.InputStream
import java.security.GeneralSecurityException
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.time.Duration
import java.util.Arrays
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Protocol.HTTP_1_1
import okhttp3.Protocol.HTTP_2
import okio.Buffer
import java.util.concurrent.TimeUnit
object GrpcClientProvider {
private val okHttpClient = OkHttpClient.Builder()
.protocols(listOf(Protocol.H2_PRIOR_KNOWLEDGE))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
val grpcClient: GrpcClient = GrpcClient.Builder()
.client(okHttpClient)
.baseUrl("http://127.0.0.1:${Settings.grpcServiceModePort}")
.build()
private fun socketFactoryAndTrustManager(): Pair {
val trustManager: X509TrustManager
val sslSocketFactory: SSLSocketFactory
try {
trustManager = trustManagerForCertificates(
trustedCertificatesInputStream(),
)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(trustManager), null)
sslSocketFactory = sslContext.socketFactory
} catch (e: GeneralSecurityException) {
throw RuntimeException(e)
}
return sslSocketFactory to trustManager
}
private fun trustedCertificatesInputStream(): InputStream {
val myCertificate = "-----BEGIN CERTIFICATE-----\n" +
"MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAvMS0wKwYDVQQDEyRkNWY2\n" +
"NTRhNC0zOWJlLTQyYjEtOGNlYi1kYTI3MmJmNTI2ZTQwIBcNMTkwODEyMjEwMzIx\n" +
"WhgPMjExOTA3MTkyMTAzMjFaMC8xLTArBgNVBAMTJGQ1ZjY1NGE0LTM5YmUtNDJi\n" +
"MS04Y2ViLWRhMjcyYmY1MjZlNDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" +
"ggEBAJQzrBa2Zp7lJ8vJ/EWrkGU2BAOublkMl5XI0cbSIfbvuITXgHX7W5sDeEwx\n" +
"6ultnUBVg6PmEbLAaZFtqg7gFPaVGbvP4h07FHSjRdf+y8W3QgoBIhc7/zuJiw1h\n" +
"CsJ9D7eGl2dnXO6FgdY6ISnPAfxzzrZPCJtKL+Ffm9UnfCA7AYaQQZoymqVTGIsC\n" +
"QAekkRkRia7gpUrTvR0hXST18KMcB7QKEv75rL8pEPHirJjyujBh+4VYVpLRDtbc\n" +
"QKxCCXcn/zhTsn+4TV/4SgO1IhU+TBv4/iffzLi/aXKEEoPJhgIbMOd5ri1XBsTe\n" +
"pGNaBOlYlEm8q8u1E3nGxzmkBtMCAwEAAaMhMB8wHQYDVR0RAQH/BBMwEYIJbG9j\n" +
"YWxob3N0hwQKAAICMA0GCSqGSIb3DQEBCwUAA4IBAQAjL/inUHQbYD6bosFDQfyL\n" +
"E9LOanO3ewiuZr5Sa4DJ5n8kNPdAO9M9urfmTbOUdvMfrH+fqiEwo6a7NTqT9bGk\n" +
"Ewz7/LdpvWIGMpnijLEPTDTur2VmjpjqtawvzbFiHhdzOZk3o6bKbY3qac7CxaaO\n" +
"MWZKF+o+YRCXVAJ2NQZLW2D9ee1qOXpK7VA360MFoyfo3cP8z6DDdNJm6gDAK+wI\n" +
"1pMCdrdwHuu+ExKKA8za4r6dThVQu5jp6d7GO+2qf9rGkm1idIgjGtsgC+hPmhLb\n" +
"7RK0ynU3Ai32elqwTDpD1WGuP2yacSWweh3GG6lG1NNY7n3tsccUWnsZztQ66Oh4\n" +
"-----END CERTIFICATE-----"
return Buffer()
.writeUtf8(myCertificate)
.inputStream()
}
@Throws(GeneralSecurityException::class)
private fun trustManagerForCertificates(inputStream: InputStream): X509TrustManager {
val certificateFactory = CertificateFactory.getInstance("X.509")
val certificates = certificateFactory.generateCertificates(inputStream)
if (certificates.isEmpty()) {
throw IllegalArgumentException("expected non-empty set of trusted certificates")
}
// Put the certificates a key store.
val password = "password".toCharArray() // Any password will work.
val keyStore = newEmptyKeyStore(password)
for ((index, certificate) in certificates.withIndex()) {
val certificateAlias = index.toString()
keyStore.setCertificateEntry(certificateAlias, certificate)
}
// Use it to build an X509 trust manager.
val keyManagerFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm(),
)
keyManagerFactory.init(keyStore, password)
val trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm(),
)
trustManagerFactory.init(keyStore)
val trustManagers = trustManagerFactory.trustManagers
if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
throw IllegalStateException(
"Unexpected default trust managers:" + Arrays.toString(trustManagers),
)
}
return trustManagers[0] as X509TrustManager
}
@Throws(GeneralSecurityException::class)
private fun newEmptyKeyStore(password: CharArray): KeyStore {
try {
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
val inputStream: InputStream? = null // By convention, 'null' creates an empty key store.
keyStore.load(inputStream, password)
return keyStore
} catch (e: IOException) {
throw AssertionError(e)
}
}
}
================================================
FILE: android/app/src/main/kotlin/com/hiddify/hiddify/utils/OutboundMapper.kt
================================================
package com.hiddify.hiddify.utils
import com.google.gson.annotations.SerializedName
import com.hiddify.core.libbox.OutboundGroup
import com.hiddify.core.libbox.OutboundGroupItem
data class ParsedOutboundGroup(
@SerializedName("tag") val tag: String,
@SerializedName("type") val type: String,
@SerializedName("selected") val selected: String,
@SerializedName("items") val items: List
) {
companion object {
fun fromOutbound(group: OutboundGroup): ParsedOutboundGroup {
val outboundItems = group.items
val items = mutableListOf()
while (outboundItems.hasNext()) {
items.add(ParsedOutboundGroupItem(outboundItems.next()))
}
return ParsedOutboundGroup(group.tag, group.type, group.selected, items)
}
}
}
data class ParsedOutboundGroupItem(
@SerializedName("tag") val tag: String,
@SerializedName("type") val type: String,
@SerializedName("url-test-delay") val urlTestDelay: Int,
) {
constructor(item: OutboundGroupItem) : this(item.tag, item.type, item.urlTestDelay)
}
================================================
FILE: android/app/src/main/protos/extension/extension.proto
================================================
syntax = "proto3";
import "v2/hcommon/common.proto";
package extension;
option go_package = "github.com/hiddify/hiddify-core/extension";
option java_package = "com.hiddify.core.api.extension";
message ExtensionActionResult {
string extension_id = 1;
hcommon.ResponseCode code = 2;
string message = 3;
}
message ExtensionList {
repeated ExtensionMsg extensions = 1;
}
message EditExtensionRequest {
string extension_id = 1;
bool enable = 2;
}
message ExtensionMsg {
string id = 1;
string title = 2;
string description = 3;
bool enable = 4;
}
message ExtensionRequest {
string extension_id = 1;
map data = 2;
}
message SendExtensionDataRequest {
string extension_id = 1;
string button=2;
map data = 3;
}
message ExtensionResponse {
ExtensionResponseType type = 1;
string extension_id = 2;
string json_ui = 3;
}
enum ExtensionResponseType {
NOTHING = 0;
UPDATE_UI = 1;
SHOW_DIALOG = 2;
END=3;
}
================================================
FILE: android/app/src/main/protos/extension/extension_service.proto
================================================
syntax = "proto3";
import "extension/extension.proto";
import "v2/hcommon/common.proto";
package extension;
option go_package = "github.com/hiddify/hiddify-core/extension";
option java_package = "com.hiddify.core.api.extension";
service ExtensionHostService {
rpc ListExtensions (hcommon.Empty) returns (ExtensionList) {}
rpc Connect (ExtensionRequest) returns (stream ExtensionResponse) {}
rpc EditExtension (EditExtensionRequest) returns (ExtensionActionResult) {}
rpc SubmitForm (SendExtensionDataRequest) returns (ExtensionActionResult) {}
rpc Close (ExtensionRequest) returns (ExtensionActionResult) {}
rpc GetUI (ExtensionRequest) returns (ExtensionActionResult) {}
}
================================================
FILE: android/app/src/main/protos/v2/config/route_rule.proto
================================================
syntax = "proto3";
package config;
option go_package = "github.com/hiddify/hiddify-core/v2/config";
option java_package = "com.hiddify.core.api.v2.config";
message RouteRule {
repeated Rule rules = 1 [json_name = "rules"];
}
message Rule {
uint32 list_order = 1 [json_name = "list_order"];
bool enabled = 2 [json_name = "enabled"];
string name = 3 [json_name = "name"];
Outbound outbound = 4 [json_name = "outbound"];
repeated string rule_sets = 5 [json_name = "rule_set"];
repeated string package_names = 6 [json_name = "package_name"];
repeated string process_names = 7 [json_name = "process_name"];
repeated string process_paths = 8 [json_name = "process_path"];
Network network = 9 [json_name = "network"];
repeated string port_ranges = 10 [json_name = "port_range"];
repeated string source_port_ranges = 11 [json_name = "source_port_range"];
repeated Protocol protocols = 12 [json_name = "protocol"];
repeated string ip_cidrs = 13 [json_name = "ip_cidr"];
repeated string source_ip_cidrs = 14 [json_name = "source_ip_cidr"];
repeated string domains = 15 [json_name = "domain"];
repeated string domain_suffixes = 16 [json_name = "domain_suffix"];
repeated string domain_keywords = 17 [json_name = "domain_keyword"];
repeated string domain_regexes = 18 [json_name = "domain_regex"];
}
enum Outbound {
proxy = 0;
direct = 1;
direct_with_fragment = 2;
block = 3;
}
enum Network {
all = 0;
tcp = 1;
udp = 2;
}
enum Protocol {
tls = 0;
http = 1;
quic = 2;
stun = 3;
dns = 4;
bittorrent = 5;
}
================================================
FILE: android/app/src/main/protos/v2/hcommon/common.proto
================================================
syntax = "proto3";
package hcommon;
option go_package = "github.com/hiddify/hiddify-core/v2/hcommon";
option java_package = "com.hiddify.core.api.v2.hcommon";
message Empty {
}
enum ResponseCode {
OK = 0;
FAILED = 1;
AUTH_NEED = 2;
}
message Response {
ResponseCode code = 1;
string message = 2;
}
================================================
FILE: android/app/src/main/protos/v2/hcore/hcore.proto
================================================
syntax = "proto3";
import "v2/hcommon/common.proto";
import "google/protobuf/timestamp.proto";
package hcore;
option go_package = "github.com/hiddify/hiddify-core/v2/hcore";
option java_package = "com.hiddify.core.api.v2.hcore";
enum CoreStates {
STOPPED = 0;
STARTING = 1;
STARTED = 2;
STOPPING = 3;
}
enum MessageType {
EMPTY=0;
EMPTY_CONFIGURATION = 1;
START_COMMAND_SERVER = 2;
CREATE_SERVICE = 3;
START_SERVICE = 4;
UNEXPECTED_ERROR = 5;
ALREADY_STARTED = 6;
ALREADY_STOPPED = 7;
INSTANCE_NOT_FOUND = 8;
INSTANCE_NOT_STOPPED = 9;
INSTANCE_NOT_STARTED = 10;
ERROR_BUILDING_CONFIG = 11;
ERROR_PARSING_CONFIG = 12;
ERROR_READING_CONFIG = 13;
ERROR_EXTENSION = 14;
}
message CoreInfoResponse {
CoreStates core_state = 1;
MessageType message_type = 2;
string message = 3;
}
message StartRequest {
string config_path = 1;
string config_content = 2; // Optional if configPath is not provided.
bool disable_memory_limit = 3;
bool delay_start = 4;
bool enable_old_command_server = 5;
bool enable_raw_config = 6;
string config_name = 7;
}
enum SetupMode {
OLD = 0;
GRPC_NORMAL = 1;
GRPC_BACKGROUND = 2;
GRPC_NORMAL_INSECURE = 3;
GRPC_BACKGROUND_INSECURE = 4;
}
message CloseRequest {
SetupMode mode = 1;
}
// Define the message equivalent of SetupParameters
message SetupRequest {
string base_path = 1;
string working_dir = 2;
string temp_dir = 3;
int64 flutter_status_port = 4;
string listen = 5;
string secret = 6;
bool debug = 7;
SetupMode mode = 8;
bool fix_android_stack = 9;
}
message SystemInfo {
int64 memory = 1;
int32 goroutines = 2;
int32 connections_in = 3;
int32 connections_out = 4;
bool traffic_available = 5;
int64 uplink = 6;
int64 downlink = 7;
int64 uplink_total = 8;
int64 downlink_total = 9;
string current_outbound = 10;
string current_profile = 11;
}
message OutboundInfo {
string tag = 1;
string type = 2;
google.protobuf.Timestamp url_test_time = 3;
int32 url_test_delay = 4;
optional IpInfo ipinfo = 5;
bool is_selected = 6;
bool is_group = 7;
optional string group_selected_tag=13;
optional string group_selected_tag_display=14;
bool is_secure = 8;
bool is_visible = 9;
uint32 port = 10;
string host = 11;
string tag_display = 12;
int64 upload = 15;
int64 download = 16;
}
message IpInfo {
string ip = 1 [json_name = "ip"]; // The IP address.
string country_code = 2 [json_name = "country_code"]; // The country code.
string region = 3 [json_name = "region"]; // The region (optional).
string city = 4 [json_name = "city"]; // The city (optional).
int32 asn = 5 [json_name = "asn"]; // The Autonomous System Number (optional).
string org = 6 [json_name = "org"]; // The organization (optional).
double latitude = 7 [json_name = "latitude"]; // The latitude (optional).
double longitude = 8 [json_name = "longitude"]; // The longitude (optional).
string postal_code = 9 [json_name = "postal_code"]; // The postal code (optional).
}
message OutboundGroup {
string tag = 1;
string type = 2;
string selected=3;
bool selectable=4;
bool Is_expand=5;
repeated OutboundInfo items = 6;
}
message OutboundGroupList{
repeated OutboundGroup items = 1;
}
message WarpAccount {
string account_id = 1;
string access_token = 2;
}
message WarpWireguardConfig {
string private_key = 1 [json_name = "private-key"];
string local_address_ipv4 = 2 [json_name = "local-address-ipv4"];
string local_address_ipv6 = 3 [json_name = "local-address-ipv6"];
string peer_public_key = 4 [json_name = "peer-public-key"];
string client_id=5 [json_name = "client-id"];
}
message WarpGenerationResponse {
WarpAccount account = 1;
string log = 2;
WarpWireguardConfig config = 3;
}
message SystemProxyStatus {
bool available = 1;
bool enabled = 2;
}
message ParseRequest {
string content = 1;
string config_path = 2;
string temp_path = 3;
bool debug = 4;
}
message ParseResponse {
hcommon.ResponseCode response_code = 1;
string content = 2;
string message = 3;
}
message ChangeHiddifySettingsRequest {
string hiddify_settings_json = 1;
}
message GenerateConfigRequest {
string path = 1;
string temp_path = 2;
bool debug = 3;
}
message GenerateConfigResponse {
string config_content = 1;
}
message SelectOutboundRequest {
string group_tag = 1;
string outbound_tag = 2;
}
message UrlTestRequest {
string tag = 1;
}
message GenerateWarpConfigRequest {
string license_key = 1;
string account_id = 2;
string access_token = 3;
}
message SetSystemProxyEnabledRequest {
bool is_enabled = 1;
}
enum LogLevel {
TRACE = 0;
DEBUG = 1;
INFO = 2;
WARNING = 3;
ERROR = 4;
FATAL = 5;
}
enum LogType {
CORE = 0;
SERVICE = 1;
CONFIG = 2;
}
message LogMessage {
LogLevel level = 1;
LogType type = 2;
string message = 3;
google.protobuf.Timestamp time = 4;
}
message LogRequest {
LogLevel level = 1;
}
message StopRequest{
}
================================================
FILE: android/app/src/main/protos/v2/hcore/hcore_service.proto
================================================
syntax = "proto3";
import "v2/hcommon/common.proto";
package hcore;
option go_package = "github.com/hiddify/hiddify-core/v2/hcore";
option java_package = "com.hiddify.core.api.v2.hcore";
import "v2/hcore/hcore.proto";
service Core {
rpc Start (StartRequest) returns (CoreInfoResponse);
rpc CoreInfoListener (hcommon.Empty) returns (stream CoreInfoResponse);
rpc OutboundsInfo (hcommon.Empty) returns (stream OutboundGroupList);
rpc MainOutboundsInfo (hcommon.Empty) returns (stream OutboundGroupList);
rpc GetSystemInfo (hcommon.Empty) returns (SystemInfo);
rpc GetSystemInfoStream (hcommon.Empty) returns (stream SystemInfo);
rpc Setup (SetupRequest) returns (hcommon.Response);
rpc Parse (ParseRequest) returns (ParseResponse);
rpc ChangeHiddifySettings (ChangeHiddifySettingsRequest) returns (CoreInfoResponse);
//rpc GenerateConfig (GenerateConfigRequest) returns (GenerateConfigResponse);
rpc StartService (StartRequest) returns (CoreInfoResponse);
rpc Stop (hcommon.Empty) returns (CoreInfoResponse);
rpc Restart (StartRequest) returns (CoreInfoResponse);
rpc SelectOutbound (SelectOutboundRequest) returns (hcommon.Response);
rpc UrlTest (UrlTestRequest) returns (hcommon.Response);
rpc UrlTestActive (hcommon.Empty) returns (hcommon.Response);
rpc GenerateWarpConfig (GenerateWarpConfigRequest) returns (WarpGenerationResponse);
rpc GetSystemProxyStatus (hcommon.Empty) returns (SystemProxyStatus);
rpc SetSystemProxyEnabled (SetSystemProxyEnabledRequest) returns (hcommon.Response);
rpc LogListener (LogRequest) returns (stream LogMessage);
rpc Close (CloseRequest) returns (hcommon.Empty);
}
================================================
FILE: android/app/src/main/protos/v2/hcore/tunnelservice/tunnel.proto
================================================
syntax = "proto3";
package tunnelservice;
option go_package = "github.com/hiddify/hiddify-core/v2/hcore/tunnelservice";
option java_package = "com.hiddify.core.api.v2.tunnelservice";
message TunnelStartRequest {
bool ipv6 = 1;
int32 server_port = 2;
string server_username=3;
string server_password=4;
bool strict_route = 5;
bool endpoint_independent_nat = 6;
string stack = 7;
}
message TunnelResponse {
string message = 1;
}
================================================
FILE: android/app/src/main/protos/v2/hcore/tunnelservice/tunnel_service.proto
================================================
syntax = "proto3";
import "v2/hcommon/common.proto";
package tunnelservice;
option go_package = "github.com/hiddify/hiddify-core/v2/hcore/tunnelservice";
option java_package = "com.hiddify.core.api.v2.hcore.tunnelservice";
import "v2/hcore/tunnelservice/tunnel.proto";
service TunnelService {
rpc Start(TunnelStartRequest) returns (TunnelResponse);
rpc Stop(hcommon.Empty) returns (TunnelResponse);
rpc Status(hcommon.Empty) returns (TunnelResponse);
rpc Exit(hcommon.Empty) returns (TunnelResponse);
}
================================================
FILE: android/app/src/main/protos/v2/hello/hello.proto
================================================
syntax = "proto3";
package hello;
option go_package = "github.com/hiddify/hiddify-core/v2/hello";
option java_package = "com.hiddify.core.api.v2.hello";
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
================================================
FILE: android/app/src/main/protos/v2/hello/hello_service.proto
================================================
syntax = "proto3";
package hello;
option go_package = "github.com/hiddify/hiddify-core/v2/hello";
option java_package = "com.hiddify.core.api.v2.hello";
import "v2/hello/hello.proto";
service Hello {
rpc SayHello (HelloRequest) returns (HelloResponse);
rpc SayHelloStream (stream HelloRequest) returns (stream HelloResponse);
}
================================================
FILE: android/app/src/main/protos/v2/hiddifyoptions/hiddify_options.proto
================================================
/**
* This file defines various configuration options for the Hiddify application.
*/
syntax = "proto3";
package hiddifyoptions;
option go_package = "github.com/hiddify/hiddify-core/v2/hiddifyoptions";
option java_package = "com.hiddify.core.api.v2.hiddifyoptions";
/**
* HiddifyOptions defines the configuration options for the Hiddify application.
*/
message HiddifyOptions {
bool enable_full_config = 1; // Enables full configuration options.
string log_level = 2; // Specifies the logging level (e.g., INFO, DEBUG).
string log_file = 3; // Path to the log file.
bool enable_clash_api = 4; // Indicates whether the Clash API is enabled.
uint32 clash_api_port = 5; // Port for the Clash API (using uint32 for compatibility).
string web_secret = 6; // Secret key for accessing the Clash API.
string region = 7; // Region for the application.
bool block_ads = 8; // If true, blocks ads.
bool use_xray_core_when_possible = 9; // If true, use XRay core when possible.
repeated Rule rules = 10; // List of routing rules for traffic management.
WarpOptions warp = 11; // Configuration options for Warp.
WarpOptions warp2 = 12; // Additional configuration options for a second Warp instance.
MuxOptions mux = 13; // Configuration options for multiplexing.
TLSTricks tls_tricks = 14; // Options for TLS tricks.
DNSOptions dns_options = 15; // DNS-related options.
InboundOptions inbound_options = 16; // Inbound connection options.
URLTestOptions url_test_options = 17; // URL test configuration options.
RouteOptions route_options = 18; // Routing-related options.
}
/**
* DomainStrategy defines the strategies for IP address preference when resolving domain names.
*/
enum DomainStrategy {
as_is =0; // As it is.
prefer_ipv4 = 1; // Prefer IPv4 addresses.
prefer_ipv6 = 2; // Prefer IPv6 addresses.
ipv4_only = 3; // Only use IPv4 addresses.
ipv6_only = 4; // Only use IPv6 addresses.
}
/**
* IntRange defines a range of integers for various configurations.
* It includes the starting and ending values of the range.
*/
message IntRange {
int32 from = 1; // Starting value of the range.
int32 to = 2; // Ending value of the range.
}
/**
* DNSOptions defines DNS-related configuration options.
*/
message DNSOptions {
string remote_dns_address = 1; // Remote DNS server address.
DomainStrategy remote_dns_domain_strategy = 2; // Strategy for resolving domains with remote DNS.
string direct_dns_address = 3; // Direct DNS server address.
DomainStrategy direct_dns_domain_strategy = 4; // Strategy for resolving domains with direct DNS.
bool independent_dns_cache = 5; // If true, enables independent DNS caching.
bool enable_fake_dns = 6; // If true, enables fake DNS responses.
bool enable_dns_routing = 7; // If true, enables DNS routing.
}
/**
* InboundOptions defines the configuration options for inbound connections.
*/
message InboundOptions {
bool enable_tun = 1; // If true, enables TUN interface.
bool enable_tun_service = 2; // If true, enables TUN service.
bool set_system_proxy = 3; // If true, sets the system proxy.
uint32 mixed_port = 4; // Port for mixed traffic (using uint32 for compatibility).
uint32 tproxy_port = 5; // Port for TProxy connections (using uint32 for compatibility).
uint32 redirect_port = 10; // Port for TProxy connections (using uint32 for compatibility).
uint32 direct_port = 6; // Port for local DNS service (using uint32 for compatibility).
uint32 mtu = 7; // Maximum Transmission Unit size (using uint32 for compatibility).
bool strict_route = 8; // If true, enforces strict routing.
string tun_stack = 9; // Specifies the TUN stack to use.
}
/**
* URLTestOptions defines the configuration options for URL testing.
*/
message URLTestOptions {
string connection_test_url = 1; // URL used for connection testing.
int64 url_test_interval = 2; // Interval for URL tests in milliseconds.
}
/**
* RouteOptions defines options related to traffic routing.
*/
message RouteOptions {
bool resolve_destination = 1; // If true, resolves the destination address.
DomainStrategy ipv6_mode = 2; // Strategy for handling IPv6 addresses.
bool bypass_lan = 3; // If true, bypasses LAN connections.
bool allow_connection_from_lan = 4; // If true, allows connections from LAN.
}
/**
* TLSTricks defines options for TLS tricks to obfuscate traffic.
*/
message TLSTricks {
bool enable_fragment = 1; // If true, enables fragmentation of packets.
IntRange fragment_size = 2; // Size of fragments to be used.
IntRange fragment_sleep = 3; // Sleep time between fragments.
bool mixed_sni_case = 4; // If true, enables mixed SNI case for obfuscation.
bool enable_padding = 5; // If true, enables padding of packets.
IntRange padding_size = 6; // Size of padding to be used.
}
/**
* MuxOptions defines options for multiplexing connections.
*/
message MuxOptions {
bool enable = 1; // If true, enables multiplexing.
bool padding = 2; // If true, enables padding for multiplexed connections.
int32 max_streams = 3; // Maximum number of streams allowed (using int32).
string protocol = 4; // Protocol used for multiplexing.
}
/**
* WarpOptions defines configuration options for Warp.
*/
message WarpOptions {
string id = 1; // Unique identifier for the Warp configuration.
bool enable_warp = 2; // If true, enables Warp functionality.
string mode = 3; // Operating mode for Warp.
WarpWireguardConfig wireguard_config = 5; // Configuration for WireGuard (defined elsewhere).
string fake_packets = 6; // Fake packet configuration.
IntRange fake_packet_size = 7; // Size of fake packets.
IntRange fake_packet_delay = 8; // Delay for sending fake packets.
string fake_packet_mode = 9; // Mode for sending fake packets.
string clean_ip = 10; // Clean IP address to use.
uint32 clean_port = 11; // Port for clean traffic (using uint32 for compatibility).
WarpAccount account = 12; // Account details for Warp (defined elsewhere).
}
/**
* WarpAccount defines account details for Warp.
*/
message WarpAccount {
string account_id = 1; // Unique account identifier.
string access_token = 2; // Access token for the account.
}
/**
* WarpWireguardConfig defines the configuration details for WireGuard.
*/
message WarpWireguardConfig {
string private_key = 1; // Private key for WireGuard.
string local_address_ipv4 = 2; // Local IPv4 address for WireGuard.
string local_address_ipv6 = 3; // Local IPv6 address for WireGuard.
string peer_public_key = 4; // Peer public key for WireGuard.
string client_id = 5; // Client identifier for WireGuard.
}
/**
* Rule defines routing rules for managing traffic.
*/
message Rule {
string rule_set_url = 1; // URL of the rule set.
string domains = 2; // List of domains affected by this rule.
string ip = 3; // IP address associated with this rule.
string port = 4; // Port number associated with this rule.
string network = 5; // Network type (e.g., IPv4, IPv6).
string protocol = 6; // Protocol type (e.g., TCP, UDP).
string outbound = 7; // Outbound traffic handling (e.g., allow, deny).
}
================================================
FILE: android/app/src/main/protos/v2/profile/profile.proto
================================================
syntax = "proto3";
package profile;
option go_package = "github.com/hiddify/hiddify-core/v2/profile";
option java_package = "com.hiddify.core.api.v2.profile";
import "v2/hiddifyoptions/hiddify_options.proto";
// ProfileEntity defines a profile entity.
message ProfileEntity {
string id = 1; // Unique identifier for the profile.
// bool active = 2; // Indicates if the profile is active.
string name = 3; // Name of the profile.
string url = 4; // URL associated with the profile.
int64 last_update = 5; // Last update time in milliseconds of the profile.
ProfileOptions options = 6; // Options associated with the profile.
SubscriptionInfo sub_info = 7; // Subscription-related information.
hiddifyoptions.HiddifyOptions override_hiddify_options = 8; // Override Hiddify options.
}
// ProfileOptions defines options for a profile.
message ProfileOptions {
int64 update_interval = 1; // Update interval in milliseconds.
}
// SubscriptionInfo defines subscription-related information.
message SubscriptionInfo {
int64 upload = 1; // Upload speed in bytes.
int64 download = 2; // Download speed in bytes.
int64 total = 3; // Total data in bytes.
int64 expire = 4; // Expiration time in milliseconds of the subscription.
string web_page_url = 5; // URL for the web page.
string support_url = 6; // URL for support.
}
================================================
FILE: android/app/src/main/protos/v2/profile/profile_service.proto
================================================
syntax = "proto3";
package profile;
option go_package = "github.com/hiddify/hiddify-core/v2/profile";
option java_package = "com.hiddify.core.api.v2.profile";
/**
* This proto file defines the ProfileService with RPC methods
* to manage profiles (add, fetch, update, delete, and set active profiles).
*/
// Import dependencies
import "v2/profile/profile.proto"; // Import the ProfileEntity message from another proto file.
import "v2/hcommon/common.proto"; // Import the common response codes and messages.
/**
* ProfileRequest is the request message for fetching or identifying
* a profile by ID, name, or URL.
*/
message ProfileRequest {
string id = 1; // The ID of the profile to fetch (Fastest and recommended).
string name = 2; // The name of the profile to fetch (if both 'id' and 'url' are empty).
string url = 3; // The URL of the profile to fetch (if both 'id' and 'name' are empty).
}
/**
* AddProfileRequest is the request message for adding a profile
* via URL or content.
*/
message AddProfileRequest {
string url = 1; // The URL of the profile to add.
string content = 2; // The profile content to add (used if 'url' is empty).
string name = 3; // The optional name of the profile.
bool mark_as_active = 4; // Whether to mark the profile as active.
}
/**
* ProfileResponse is the response message for profile service operations.
*/
message ProfileResponse {
ProfileEntity profile = 1; // The profile entity, populated in successful operations.
hcommon.ResponseCode response_code = 2; // The response code indicating success or failure.
string message = 3; // A message indicating the result or error, if any.
}
/**
* MultiProfilesResponse is the response message for fetching multi profiles.
*/
message MultiProfilesResponse {
repeated ProfileEntity profiles = 1; // A list of profile entities.
hcommon.ResponseCode response_code = 2; // The response code indicating success or failure.
string message = 3; // A message indicating the result or error, if any.
}
/**
* ProfileService defines the RPC methods available for managing profiles.
*/
service ProfileService {
/**
* GetProfile fetches a profile by ID, name, or URL.
*/
rpc GetProfile(ProfileRequest) returns (ProfileResponse);
/**
* UpdateProfile updates an existing profile.
*/
rpc UpdateProfile(ProfileEntity) returns (ProfileResponse);
/**
* GetAllProfiles fetches all profiles.
*/
rpc GetAllProfiles(hcommon.Empty) returns (MultiProfilesResponse);
/**
* GetActiveProfile retrieves the currently active profile.
*/
rpc GetActiveProfile(hcommon.Empty) returns (ProfileResponse);
/**
* SetActiveProfile sets a profile as active, identified by ID, name, or URL.
*/
rpc SetActiveProfile(ProfileRequest) returns (hcommon.Response);
/**
* AddProfile adds a new profile using either a URL or the raw profile content.
*/
rpc AddProfile(AddProfileRequest) returns (ProfileResponse);
/**
* DeleteProfile deletes a profile identified by ID, name, or URL.
*/
rpc DeleteProfile(ProfileRequest) returns (hcommon.Response);
}
================================================
FILE: android/app/src/main/res/drawable/android12splash.xml
================================================
================================================
FILE: android/app/src/main/res/drawable/ic_banner_foreground.xml
================================================
================================================
FILE: android/app/src/main/res/drawable/ic_launcher_background.xml
================================================
================================================
FILE: android/app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
================================================
FILE: android/app/src/main/res/drawable/launch_background.xml
================================================
-
-
================================================
FILE: android/app/src/main/res/drawable-v21/launch_background.xml
================================================
-
-
================================================
FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
================================================
================================================
FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: android/app/src/main/res/values/colors.xml
================================================
================================================
FILE: android/app/src/main/res/values/ic_banner_background.xml
================================================
#F0F3FA
================================================
FILE: android/app/src/main/res/values/ic_launcher_background.xml
================================================
#F0F3FA
================================================
FILE: android/app/src/main/res/values/strings.xml
================================================
Stop
Toggle
Service starting…
Service started
================================================
FILE: android/app/src/main/res/values/styles.xml
================================================
================================================
FILE: android/app/src/main/res/values-night/styles.xml
================================================
================================================
FILE: android/app/src/main/res/values-night-v31/styles.xml
================================================
================================================
FILE: android/app/src/main/res/values-v31/styles.xml
================================================
================================================
FILE: android/app/src/main/res/xml/network_security_config.xml
================================================
127.0.0.1
================================================
FILE: android/app/src/main/res/xml/shortcuts.xml
================================================
================================================
FILE: android/app/src/profile/AndroidManifest.xml
================================================
================================================
FILE: android/build.gradle
================================================
allprojects {
repositories {
mavenCentral()
google()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx4048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
================================================
FILE: android/settings.gradle
================================================
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
mavenCentral()
google()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.6.0' apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}
include ":app"
================================================
FILE: appcast.xml
================================================
Release
-
Version 0.13.6
Sun, 7 Jan 2024 22:00:00 +0000
-
Version 0.13.6
Sun, 7 Jan 2024 22:00:00 +0000
-
Version 0.13.6
Sun, 7 Jan 2024 22:00:00 +0000
-
Version 0.13.6
Sun, 7 Jan 2024 22:00:00 +0000
-
Version 0.13.6
Sun, 7 Jan 2024 22:00:00 +0000
================================================
FILE: assets/core/.gitkeep
================================================
================================================
FILE: assets/fonts/emoji_source.txt
================================================
https://github.com/hiddify-com/noto-emoji
pyftsubset "Emoji3.ttf" --output-file=Emoji.ttf --unicodes=U+1F1E6+1F1E9,U+1F1E6+1F1EA,U+1F1E6+1F1EB,U+1F1E6+1F1EC,U+1F1E6+1F1EE,U+1F1E6+1F1F1,U+1F1E6+1F1F2,U+1F1E6+1F1F4,U+1F1E6+1F1F6,U+1F1E6+1F1F7,U+1F1E6+1F1F8,U+1F1E6+1F1F9,U+1F1E6+1F1FA,U+1F1E6+1F1FC,U+1F1E6+1F1FD,U+1F1E6+1F1FF,U+1F1E7+1F1E6,U+1F1E7+1F1E7,U+1F1E7+1F1E9,U+1F1E7+1F1EA,U+1F1E7+1F1EB,U+1F1E7+1F1EC,U+1F1E7+1F1ED,U+1F1E7+1F1EE,U+1F1E7+1F1EF,U+1F1E7+1F1F1,U+1F1E7+1F1F2,U+1F1E7+1F1F3,U+1F1E7+1F1F4,U+1F1E7+1F1F6,U+1F1E7+1F1F7,U+1F1E7+1F1F8,U+1F1E7+1F1F9,U+1F1E7+1F1FB,U+1F1E7+1F1FC,U+1F1E7+1F1FE,U+1F1E7+1F1FF,U+1F1E8+1F1E6,U+1F1E8+1F1E8,U+1F1E8+1F1E9,U+1F1E8+1F1EB,U+1F1E8+1F1EC,U+1F1E8+1F1ED,U+1F1E8+1F1EE,U+1F1E8+1F1F0,U+1F1E8+1F1F1,U+1F1E8+1F1F2,U+1F1E8+1F1F3,U+1F1E8+1F1F4,U+1F1E8+1F1F7,U+1F1E8+1F1FA,U+1F1E8+1F1FB,U+1F1E8+1F1FC,U+1F1E8+1F1FD,U+1F1E8+1F1FE,U+1F1E8+1F1FF,U+1F1E9+1F1EA,U+1F1E9+1F1EF,U+1F1E9+1F1F0,U+1F1E9+1F1F2,U+1F1E9+1F1F4,U+1F1E9+1F1FF,U+1F1EA+1F1E8,U+1F1EA+1F1EA,U+1F1EA+1F1EC,U+1F1EA+1F1ED,U+1F1EA+1F1F7,U+1F1EA+1F1F8,U+1F1EA+1F1F9,U+1F1EB+1F1EE,U+1F1EB+1F1EF,U+1F1EB+1F1F0,U+1F1EB+1F1F2,U+1F1EB+1F1F4,U+1F1EB+1F1F7,U+1F1EC+1F1E6,U+1F1EC+1F1E7,U+1F1EC+1F1E9,U+1F1EC+1F1EA,U+1F1EC+1F1EB,U+1F1EC+1F1EC,U+1F1EC+1F1ED,U+1F1EC+1F1EE,U+1F1EC+1F1F1,U+1F1EC+1F1F2,U+1F1EC+1F1F3,U+1F1EC+1F1F5,U+1F1EC+1F1F6,U+1F1EC+1F1F7,U+1F1EC+1F1F8,U+1F1EC+1F1F9,U+1F1EC+1F1FA,U+1F1EC+1F1FC,U+1F1EC+1F1FE,U+1F1ED+1F1F0,U+1F1ED+1F1F2,U+1F1ED+1F1F3,U+1F1ED+1F1F7,U+1F1ED+1F1F9,U+1F1ED+1F1FA,U+1F1EE+1F1E9,U+1F1EE+1F1EA,U+1F1EE+1F1F1,U+1F1EE+1F1F2,U+1F1EE+1F1F3,U+1F1EE+1F1F4,U+1F1EE+1F1F6,U+1F1EE+1F1F7,U+1F1EE+1F1F8,U+1F1EE+1F1F9,U+1F1EF+1F1EA,U+1F1EF+1F1F2,U+1F1EF+1F1F4,U+1F1EF+1F1F5,U+1F1F0+1F1EA,U+1F1F0+1F1EC,U+1F1F0+1F1ED,U+1F1F0+1F1EE,U+1F1F0+1F1F2,U+1F1F0+1F1F3,U+1F1F0+1F1F5,U+1F1F0+1F1F7,U+1F1F0+1F1FC,U+1F1F0+1F1FE,U+1F1F0+1F1FF,U+1F1F1+1F1E6,U+1F1F1+1F1E7,U+1F1F1+1F1E8,U+1F1F1+1F1EE,U+1F1F1+1F1F0,U+1F1F1+1F1F7,U+1F1F1+1F1F8,U+1F1F1+1F1F9,U+1F1F1+1F1FA,U+1F1F1+1F1FB,U+1F1F1+1F1FE,U+1F1F2+1F1E6,U+1F1F2+1F1E8,U+1F1F2+1F1E9,U+1F1F2+1F1EA,U+1F1F2+1F1EB,U+1F1F2+1F1EC,U+1F1F2+1F1ED,U+1F1F2+1F1F0,U+1F1F2+1F1F1,U+1F1F2+1F1F2,U+1F1F2+1F1F3,U+1F1F2+1F1F4,U+1F1F2+1F1F5,U+1F1F2+1F1F6,U+1F1F2+1F1F7,U+1F1F2+1F1F8,U+1F1F2+1F1F9,U+1F1F2+1F1FA,U+1F1F2+1F1FB,U+1F1F2+1F1FC,U+1F1F2+1F1FD,U+1F1F2+1F1FE,U+1F1F2+1F1FF,U+1F1F3+1F1E6,U+1F1F3+1F1E8,U+1F1F3+1F1EA,U+1F1F3+1F1EB,U+1F1F3+1F1EC,U+1F1F3+1F1EE,U+1F1F3+1F1F1,U+1F1F3+1F1F4,U+1F1F3+1F1F5,U+1F1F3+1F1F7,U+1F1F3+1F1FA,U+1F1F3+1F1FF,U+1F1F4+1F1F2,U+1F1F5+1F1E6,U+1F1F5+1F1EA,U+1F1F5+1F1EB,U+1F1F5+1F1EC,U+1F1F5+1F1ED,U+1F1F5+1F1F0,U+1F1F5+1F1F1,U+1F1F5+1F1F2,U+1F1F5+1F1F3,U+1F1F5+1F1F7,U+1F1F5+1F1F8,U+1F1F5+1F1F9,U+1F1F5+1F1FC,U+1F1F5+1F1FE,U+1F1F6+1F1E6,U+1F1F7+1F1EA,U+1F1F7+1F1F4,U+1F1F7+1F1F8,U+1F1F7+1F1FA,U+1F1F7+1F1FC,U+1F1F8+1F1E6,U+1F1F8+1F1E7,U+1F1F8+1F1E8,U+1F1F8+1F1E9,U+1F1F8+1F1EA,U+1F1F8+1F1EC,U+1F1F8+1F1ED,U+1F1F8+1F1EE,U+1F1F8+1F1EF,U+1F1F8+1F1F0,U+1F1F8+1F1F1,U+1F1F8+1F1F2,U+1F1F8+1F1F3,U+1F1F8+1F1F4,U+1F1F8+1F1F7,U+1F1F8+1F1F8,U+1F1F8+1F1F9,U+1F1F8+1F1FB,U+1F1F8+1F1FD,U+1F1F8+1F1FE,U+1F1F8+1F1FF,U+1F1F9+1F1E8,U+1F1F9+1F1E9,U+1F1F9+1F1EB,U+1F1F9+1F1EC,U+1F1F9+1F1ED,U+1F1F9+1F1EF,U+1F1F9+1F1F0,U+1F1F9+1F1F1,U+1F1F9+1F1F2,U+1F1F9+1F1F3,U+1F1F9+1F1F4,U+1F1F9+1F1F7,U+1F1F9+1F1F9,U+1F1F9+1F1FB,U+1F1F9+1F1FC,U+1F1F9+1F1FF,U+1F1FA+1F1E6,U+1F1FA+1F1EC,U+1F1FA+1F1F2,U+1F1FA+1F1F8,U+1F1FA+1F1FE,U+1F1FA+1F1FF,U+1F1FB+1F1E6,U+1F1FB+1F1E8,U+1F1FB+1F1EA,U+1F1FB+1F1EC,U+1F1FB+1F1EE,U+1F1FB+1F1F3,U+1F1FB+1F1FA,U+1F1FC+1F1EB,U+1F1FC+1F1F8,U+1F1FE+1F1EA,U+1F1FE+1F1F9,U+1F1FF+1F1E6,U+1F1FF+1F1F2,U+1F1FF+1F1FC
================================================
FILE: assets/images/convert_icon.sh
================================================
in=$1
convert -define icon:auto-resize=128,64,48,32,16 -gravity center $in.png $in.ico
================================================
FILE: assets/translations/ar.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "ابدأ",
"version": "الإصدار",
"ok": "موافق",
"cancel": "إلغاء",
"kContinue": "متابعة",
"showMore": "عرض المزيد",
"showLess": "عرض أقل",
"filter": "تصفية",
"all": "الكل",
"pause": "إيقاف مؤقت",
"resume": "استئناف",
"clear": "مسح",
"close": "إغلاق",
"auto": "تلقائي",
"manually": "يدوي",
"name": "الاسم",
"url": "الرابط",
"add": "إضافة",
"clipboard": "الحافظة",
"addToClipboard": "إضافة إلى الحافظة",
"scanQr": "مسح رمز QR",
"free": "مجاني",
"warp": "WARP",
"fragment": "Fragment",
"help": "مساعدة",
"save": "حفظ",
"update": "تحديث",
"share": "مشاركة",
"edit": "تعديل",
"delete": "حذف",
"discard": "تجاهل",
"import": "استيراد",
"export": "تصدير",
"later": "لاحقًا",
"ignore": "تجاهل",
"quit": "خروج",
"notSet": "غير محدد",
"hide": "إخفاء",
"exit": "خروج",
"reset": "إعادة تعيين",
"done": "تم",
"search": "بحث",
"decline": "رفض",
"agree": "أوافق",
"empty": "فارغ",
"unknown": "غير معروف",
"hidden": "مخفي",
"timeout": "انتهى الوقت",
"sort": "فرز",
"dashboard": "لوحة التحكم",
"interval": {
"day": {
"zero": "",
"one": "يوم واحد",
"two": "يومان",
"few": "$n أيام",
"many": "$n يومًا",
"other": "$n يوم"
},
"hour": {
"zero": "",
"one": "ساعة واحدة",
"two": "ساعتان",
"few": "$n ساعات",
"many": "$n ساعة",
"other": "$n ساعة"
}
},
"msg": {
"permission": {
"denied": "تم رفض الإذن"
},
"export": {
"clipboard": {
"success": "تمت الإضافة إلى الحافظة بنجاح",
"failure": "فشل النسخ إلى الحافظة",
"contentTooLarge": "المحتوى كبير جدًا. استخدم تصدير الملف بدلاً من ذلك"
},
"file": {
"success": "تم إنشاء ملف JSON بنجاح",
"failure": "فشل إنشاء الملف"
}
},
"import": {
"confirm": "تأكيد الاستيراد",
"success": "تم الاستيراد بنجاح",
"failure": "فشل الاستيراد"
}
}
},
"intro": {
"banner": "كل ما تحتاجه لإنترنت بلا قيود",
"termsAndPolicyCaution(rich)": "بالاستمرار، أنت توافق على ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "صُنع بـ ❤️ بواسطة Hiddify - ${tap_source(مفتوح المصدر)} (${tap_license(الرخصة)})"
},
"pages": {
"home": {
"title": "الرئيسية",
"quickSettings": "الإعدادات السريعة"
},
"proxies": {
"title": "البروكسيات",
"sort": "فرز البروكسيات",
"testDelay": "اختبار التأخير",
"empty": "لا توجد بروكسيات متاحة",
"activeProxy": "البروكسي النشط",
"unknownIp": "IP غير معروف",
"sortOptions": {
"unsorted": "الافتراضي",
"name": "أبجديًا",
"delay": "حسب التأخير"
},
"ipInfo": {
"address": "عنوان IP",
"country": "الدولة",
"organization": "المُنظمة"
},
"delay": {
"result": "التأخير: ${delay} مللي ثانية",
"timeout": "انتهى وقت اختبار التأخير",
"testing": "التأخير: قيد الاختبار..."
}
},
"profiles": {
"title": "الملفات الشخصية",
"add": "إضافة ملف شخصي",
"update": "تحديث الملف الشخصي",
"viewAllProfiles": "عرض جميع الملفات الشخصية",
"activeProfileName": "اسم الملف النشط: \"${name}\".",
"nonActiveProfileName": "تحديد \"${name}\" كملف نشط",
"freeSubNotFound": "لم يتم العثور على اشتراك مجاني",
"freeSubNotFoundForRegion": "لم يتم العثور على اشتراك مجاني لمنطقة \"${region}\"",
"failedToLoad": "فشل التحميل",
"updateSubscriptions": "تحديث الاشتراكات",
"share": {
"urlToClipboard": "رابط URL إلى الحافظة",
"showUrlQr": "عرض رمز QR للرابط",
"jsonToClipboard": "JSON إلى الحافظة"
},
"msg": {
"save": {
"success": "تم حفظ الملف الشخصي بنجاح"
},
"invalidUrl": "رابط غير صالح",
"add": {
"failure": "فشل إضافة الملف الشخصي"
},
"update": {
"success": "تم تحديث الملف الشخصي بنجاح",
"successNamed": "تم تحديث \"${name}\" بنجاح",
"failure": "فشل تحديث الملف الشخصي",
"failureNamed": "فشل تحديث \"${name}\""
},
"delete": {
"success": "تم حذف الملف الشخصي بنجاح"
}
}
},
"profileDetails": {
"title": "الملف الشخصي",
"lastUpdate": "آخر تحديث",
"form": {
"nameHint": "اسم الملف الشخصي",
"emptyName": "الاسم مطلوب",
"invalidUrl": "رابط غير صالح",
"urlHint": "رابط الإعدادات الكامل",
"disableAutoUpdate": "تعطيل التحديث التلقائي",
"autoUpdateInterval": "فاصل التحديث التلقائي",
"loading": "جاري إضافة الملف الشخصي..."
}
},
"logs": {
"title": "السجلات",
"shareCoreLogs": "مشاركة سجلات النواة",
"shareAppLogs": "مشاركة سجلات التطبيق"
},
"about": {
"title": "حول التطبيق",
"notAvailableMsg": "أنت تستخدم أحدث إصدار بالفعل",
"checkForUpdate": "التحقق من وجود تحديثات",
"openWorkingDir": "فتح مجلد العمل",
"sourceCode": "الكود المصدري",
"telegramChannel": "قناة التيليجرام",
"termsAndConditions": "الشروط والأحكام",
"privacyPolicy": "سياسة الخصوصية"
},
"settings": {
"title": "الإعدادات",
"resetTunnel": "إعادة تعيين ملف VPN",
"options": {
"import": {
"clipboard": "استيراد الخيارات من الحافظة",
"file": "استيراد الخيارات من ملف"
},
"export": {
"anonymousToClipboard": "نسخ الخيارات المجهولة إلى الحافظة",
"anonymousToFile": "تصدير الخيارات المجهولة إلى ملف",
"allToClipboard": "نسخ جميع الخيارات إلى الحافظة",
"allToFile": "تصدير جميع الخيارات إلى ملف"
},
"reset": "إعادة تعيين الخيارات"
},
"general": {
"title": "عام",
"locale": "اللغة",
"themeMode": "السمة",
"themeModes": {
"system": "سمة النظام الافتراضية",
"dark": "الوضع الداكن",
"light": "الوضع الفاتح",
"black": "الوضع الأسود"
},
"enableAnalytics": "تفعيل التحليلات",
"enableAnalyticsMsg": "السماح بجمع بيانات التحليل وتقارير الأعطال لتحسين التطبيق",
"autoIpCheck": "التحقق التلقائي من IP الاتصال",
"dynamicNotification": "عرض السرعة في الإشعار",
"hapticFeedback": "ردود الفعل اللمسية",
"actionAtClosing": "الإجراء عند الإغلاق",
"autoStart": "البدء عند تسجيل الدخول",
"silentStart": "البدء في الخلفية",
"ignoreBatteryOptimizations": "تجاهل تحسينات البطارية",
"ignoreBatteryOptimizationsMsg": "إزالة القيود للحصول على أفضل أداء للـ VPN",
"memoryLimit": "حد الذاكرة",
"memoryLimitMsg": "قم بتفعيل هذا الخيار إذا كنت تواجه أخطاء نفاد الذاكرة أو تعطل التطبيق بشكل متكرر",
"debugMode": "وضع التصحيح",
"debugModeMsg": "أعد تشغيل التطبيق لتطبيق هذا التغيير",
"logLevel": "مستوى السجل",
"connectionTestUrl": "رابط اختبار الاتصال",
"urlTestInterval": "فاصل اختبار الرابط",
"clashApiPort": "منفذ Clash API",
"useXrayCoreWhenPossible": "استخدام xray-core عند الإمكان",
"useXrayCoreWhenPossibleMsg": "استخدم xray-core عند تحليل روابط الاشتراك. تحتاج إلى إعادة استيراد الرابط لتفعيل هذا الخيار"
},
"routing": {
"title": "التوجيه",
"perAppProxy": {
"title": "بروكسي لكل تطبيق",
"hideSysApps": "إخفاء تطبيقات النظام",
"options": {
"import": {
"clipboard": "استيراد التحديد من الحافظة",
"file": "استيراد التحديد من ملف",
"msg": "سيؤدي الاستيراد إلى استبدال تحديداتك الحالية. هل أنت متأكد من رغبتك في المتابعة؟"
},
"export": {
"clipboard": "نسخ التحديد إلى الحافظة",
"file": "تصدير التحديد إلى ملف"
},
"shareToAll": "مشاركة مع الجميع",
"clearAllSelections": "مسح جميع التحديدات"
},
"modes": {
"all": "الكل",
"proxy": "بروكسي",
"bypass": "تجاوز",
"allMsg": "استخدام البروكسي لجميع التطبيقات",
"proxyMsg": "استخدام البروكسي للتطبيقات المحددة فقط",
"bypassMsg": "عدم استخدام البروكسي للتطبيقات المحددة"
},
"autoSelection": {
"title": "الاختيار التلقائي",
"performNow": "تنفيذ الآن",
"resetToDefault": "إعادة التعيين إلى الافتراضي",
"autoUpdateInterval": "فاصل التحديث التلقائي",
"toast": {
"success": "اكتمل الاختيار التلقائي للتطبيقات بنجاح",
"failure": "فشل الاختيار التلقائي",
"regionNotFound": "لم يتم العثور على اختيار تلقائي لمنطقة \"${region}\"",
"alreadyInAuto": "اختياراتك موجودة بالفعل في القائمة التلقائية"
},
"dialog": {
"title": "الاختيار التلقائي للتطبيقات",
"msg": "تم تعطيل ميزة الاختيار التلقائي للبروكسي بسبب تغيير المنطقة إلى \"${region}\""
}
}
},
"region": "المنطقة",
"regions": {
"ir": "إيران (ir)",
"cn": "الصين (cn)",
"ru": "روسيا (ru)",
"af": "أفغانستان (af)",
"id": "إندونيسيا (id)",
"tr": "تركيا (tr)",
"br": "البرازيل (br)",
"other": "أخرى"
},
"balancerStrategy": {
"title": "استراتيجية Balancer",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "حظر الإعلانات",
"bypassLan": "تجاوز الشبكة المحلية",
"resolveDestination": "تحديد وجهة الاتصال",
"ipv6Route": "توجيه IPv6",
"ipv6Modes": {
"disable": "تعطيل",
"enable": "تفعيل",
"prefer": "مفضل",
"only": "فقط"
},
"routeRule": {
"title": "قواعد التوجيه",
"options": {
"import": {
"clipboard": "استيراد القواعد من الحافظة",
"file": "استيراد القواعد من ملف"
},
"export": {
"clipboard": "نسخ القواعد إلى الحافظة",
"file": "حفظ القواعد في ملف"
},
"reset": "إعادة تعيين القواعد"
},
"deleteRule": "حذف القاعدة",
"createRule": "إنشاء قاعدة جديدة",
"rule": {
"title": "قاعدة",
"ruleChanged": "تم تغيير القاعدة",
"ruleChangedMsg": "هل تريد حفظ تعديلاتك؟",
"onlyTunMode": "متوفر فقط في وضع TUN",
"notAvailabeInThisPlatform": "غير متوفر على هذا النظام",
"canNotBeEmpty": "لا يمكن أن يكون فارغًا",
"validUrlEx": "https://example.com",
"validUrl": "\"URL\" صالح مثل\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe or google chrome or chrome",
"validProcessName": "\"اسم العملية\" صالح مثل\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "\"مسار العملية\" صالح مثل\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 or 1-65000",
"validPortRange": "\"منفذ\" أو \"نطاق منافذ\" صالح مثل\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 or 10.0.0.0/24",
"validIpCidr": "IP CIDR صالح مثل\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com or dl.google.com",
"validDomain": "\"نطاق\" صالح مثل\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com or .ir",
"validDomainSuffix": "\"لاحقة نطاق\" صالحة مثل\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "الاسم",
"outbound": "التوجيه عند التطابق",
"rule_set": "رابط مجموعة القواعد",
"package_name": "أسماء الحزم",
"process_name": "أسماء العمليات",
"process_path": "مسارات العمليات",
"network": "الشبكات",
"port_range": "منافذ الوجهة",
"source_port_range": "منافذ المصدر",
"protocol": "البروتوكول",
"ip_cidr": "IP CIDR الوجهة",
"source_ip_cidr": "IP CIDR المصدر",
"domain": "النطاق",
"domain_suffixe": "لاحقة النطاق",
"domain_keyword": "كلمة مفتاحية للنطاق",
"domain_regex": "تعبير نمطي للنطاق"
},
"outbound(map)": {
"proxy": "بروكسي",
"direct": "مباشر",
"direct_with_fragment": "مباشر مع Fragment",
"block": "حظر"
},
"network(map)": {
"all": "الكل",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "الكل",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "إضافة قيمة جديدة",
"update": "تحديث القيمة",
"clearList": "مسح القائمة",
"clearListMsg": "تم حذف جميع العناصر"
},
"androidApps": {
"pageTitle": "تطبيقات أندرويد",
"showSystemApps": "عرض تطبيقات النظام",
"hideSystemApps": "إخفاء تطبيقات النظام",
"clearSelection": "مسح التحديد",
"uninstalled": "غير مثبت"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "DNS البعيد",
"remoteDnsDomainStrategy": "استراتيجية نطاق DNS البعيد",
"directDns": "محلل خادم الصادر (مباشر)",
"directDnsDomainStrategy": "استراتيجية النطاق الصادر",
"enableDnsRouting": "تمكين توجيه DNS",
"enableFakeDns": "تمكين DNS الوهمي",
"domainStrategy": {
"auto": "تلقائي",
"preferIpv6": "تفضيل IPv6",
"preferIpv4": "تفضيل IPv4",
"ipv4Only": "IPv4 فقط",
"ipv6Only": "IPv6 فقط"
}
},
"inbound": {
"title": "الوارد",
"serviceMode": "وضع الخدمة",
"serviceModes": {
"proxy": "خدمة البروكسي فقط",
"systemProxy": "تعيين بروكسي النظام",
"tun": "VPN",
"tunService": "خدمة VPN"
},
"shortServiceModes": {
"proxy": "بروكسي",
"systemProxy": "بروكسي النظام",
"tun": "VPN",
"tunService": "خدمة VPN"
},
"strictRoute": "توجيه صارم",
"tunImplementation": "تنفيذ Tun",
"tunImplementations": {
"mixed": "مختلط",
"system": "النظام",
"gvisor": "gVisor"
},
"mixedPort": "منفذ مختلط",
"tproxyPort": "منفذ البروكسي الشفاف",
"directPort": "منفذ مباشر",
"redirectPort": "منفذ إعادة التوجيه",
"allowConnectionFromLan": "مشاركة VPN على الشبكة المحلية"
},
"tlsTricks": {
"title": "حيل TLS",
"enable": "تفعيل fragment",
"packets": "حزم التجزئة",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "حجم fragment",
"sleep": "تأخير fragment",
"mixedSniCase": {
"enable": "تفعيل الأحرف المختلطة لـ SNI"
},
"padding": {
"enable": "تفعيل padding",
"size": "حجم padding"
}
},
"warp": {
"title": "WARP",
"enable": "تفعيل WARP",
"generateConfig": "إنشاء تكوين WARP",
"configGenerated": "تم إنشاء تكوين WARP",
"missingConfig": "تكوين WARP مفقود",
"detourMode": "وضع توجيه WARP",
"detourModes": {
"proxyOverWarp": "توجيه البروكسيات عبر WARP",
"warpOverProxy": "توجيه WARP عبر البروكسيات",
"proxyOverWarpExplain": "لإلغاء حظر البروكسيات بواسطة WARP",
"warpOverProxyExplain": "لأمان إضافي بواسطة WARP"
},
"licenseKey": "مفتاح الترخيص",
"cleanIp": "IP نظيف",
"port": "المنفذ",
"noise": {
"count": "عدد الضوضاء",
"mode": "وضع الضوضاء",
"size": "حجم الضوضاء",
"delay": "تأخير الضوضاء"
}
}
}
},
"components": {
"stats": {
"connection": "الاتصال",
"traffic": "البيانات",
"trafficLive": "البيانات الحالية",
"trafficTotal": "إجمالي البيانات",
"uplink": "الإرسال",
"downlink": "الاستقبال",
"speed": "السرعة",
"totalTransferred": "إجمالي النقل"
},
"subscriptionInfo": {
"upload": "الرفع",
"download": "التنزيل",
"total": "إجمالي البيانات",
"expireDate": "تاريخ الانتهاء",
"expired": "منتهي الصلاحية",
"noTraffic": "نفدت الباقة",
"remainingTime": "الوقت المتبقي",
"remainingDuration": "متبقٍ ${duration} يوم",
"remainingDurationNew": "${duration} يوم",
"remainingTrafficSemanticLabel": "تم استهلاك ${consumed} من ${total}",
"remainingTraffic": "البيانات المتبقية",
"remainingUsage": "المتبقي",
"profileSite": "المزوّد",
"profileSupport": "الدعم"
}
},
"dialogs": {
"sortProfiles": {
"title": "فرز حسب",
"sort": {
"name": "أبجديًا",
"lastUpdate": "آخر تحديث"
}
},
"warpLicense": {
"title": "موافقة Cloudflare WARP",
"description(rich)": "Cloudflare WARP هو مزود VPN مجاني لـ WireGuard. بتفعيل هذا الخيار، فإنك توافق على ${tos(شروط الخدمة)} و ${privacy(سياسة الخصوصية)} الخاصة بـ Cloudflare WARP."
},
"newVersion": {
"title": "تحديث متاح",
"msg": "إصدار جديد من @:common.appTitle متاح. هل ترغب في التحديث الآن؟",
"currentVersion": "الإصدار الحالي: ",
"newVersion": "الإصدار الجديد: ",
"updateNow": "التحديث الآن"
},
"confirmation": {
"settings": {
"import": {
"msg": "سيؤدي هذا إلى استبدال جميع إعداداتك الحالية. هل أنت متأكد؟"
}
},
"profile": {
"delete": {
"title": "حذف الملف الشخصي",
"msg": "هل أنت متأكد من رغبتك في حذف هذا الملف الشخصي نهائيًا؟"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "تحسين الاختيار التلقائي",
"msg": "بمشاركة التطبيقات التي اخترتها، فإنك تساعد في إكمال قائمة \"الاختيار التلقائي\""
},
"import": {
"msg": "سيؤدي هذا إلى استبدال جميع تحديداتك الحالية لبروكسي التطبيقات. هل أنت متأكد من رغبتك في المتابعة؟"
}
},
"routeRule": {
"delete": {
"title": "حذف القاعدة",
"msg": "هل أنت متأكد من رغبتك في حذف قاعدة \"$rulename\"؟"
}
}
},
"experimentalNotice": {
"title": "ميزات تجريبية قيد الاستخدام",
"msg": "لقد فعّلت ميزات تجريبية قد تؤثر على جودة الاتصال وتسبب أخطاء غير متوقعة. يمكنك دائمًا تغيير هذه الخيارات أو إعادة تعيينها من صفحة الإعدادات.",
"disable": "لا تعرض مرة أخرى"
},
"noActiveProfile": {
"title": "اختر ملفًا شخصيًا",
"msg": "للبدء، قم بإضافة ملف اتصال يحتوي على تفاصيل اتصال VPN الخاصة بك.\n\nأليس لديك خادم VPN بعد؟ لا تقلق، اتبع الدليل أدناه لإعداد واحد بسرعة ومجانًا.",
"helpBtn": {
"label": "أرني كيف",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "تحذير من رابط خارجي",
"youAreAboutToVisit": "أنت على وشك زيارة:",
"thisWebsiteIsNotInOurTrustedList": "هذا الموقع ليس ضمن قائمة المواقع الموثوقة لدينا. يرجى المتابعة بحذر."
},
"proxyInfo": {
"fullTag": "العلامة الكاملة:",
"type": "النوع:",
"testTime": "وقت الاختبار:",
"testDelay": "تأخير الاختبار:",
"isSelected": "محدد:",
"isGroup": "مجموعة",
"isSecure": "آمن:",
"port": "المنفذ:",
"host": "المضيف:",
"ip": "IP:",
"countryCode": "رمز الدولة:",
"region": "المنطقة:",
"city": "المدينة:",
"asn": "ASN:",
"organization": "المُنظمة:",
"location": "الموقع:",
"postalCode": "الرمز البريدي:"
},
"windowClosing": {
"askEachTime": "السؤال كل مرة",
"alertMessage": "هل تريد إخفاء التطبيق أم الخروج منه؟",
"remember": "تذكر خياري"
}
},
"connection": {
"tapToConnect": "انقر للاتصال",
"connect": "اتصال",
"connecting": "جار الاتصال...",
"connected": "متصل",
"disconnect": "قطع الاتصال",
"disconnecting": "جاري قطع الاتصال...",
"reconnect": "إعادة الاتصال",
"reconnectMsg": "جاري إعادة الاتصال لتطبيق التغييرات...",
"secure": "مُؤمَّن بواسطة WARP"
},
"errors": {
"unexpected": "خطأ غير متوقع",
"connection": {
"unexpected": "خطأ اتصال غير متوقع",
"timeout": "انتهى وقت الاتصال",
"badResponse": "استجابة غير صالحة",
"connectionError": "خطأ في الاتصال",
"badCertificate": "شهادة غير صالحة"
},
"profiles": {
"unexpected": "خطأ غير متوقع",
"notFound": "لم يتم العثور على الملف الشخصي",
"invalidConfig": "تكوينات غير صالحة",
"invalidUrl": "رابط غير صالح",
"canceledByUser": "تم الإلغاء من قبل المستخدم"
},
"connectivity": {
"unexpected": "فشل غير متوقع",
"missingVpnPermission": "إذن الـ VPN مفقود",
"missingNotificationPermission": "إذن الإشعارات مفقود",
"core": "خطأ في النواة"
},
"singbox": {
"serviceNotRunning": "الخدمة لا تعمل",
"missingPrivilege": "صلاحيات مطلوبة",
"missingPrivilegeMsg": "وضع الـ VPN يتطلب صلاحيات المسؤول. يرجى إعادة تشغيل التطبيق كمسؤول أو تغيير وضع الخدمة.",
"invalidConfigOptions": "خيارات تكوين غير صالحة",
"invalidConfig": "تكوين غير صالح"
},
"warp": {
"missingLicense": "رخصة WARP مفقودة",
"missingLicenseMsg": "الملف الشخصي المحدد يستخدم ميزة WARP. لاستخدام هذه الميزة، يجب الموافقة على شروط رخصة WARP."
}
}
}
================================================
FILE: assets/translations/en.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "Start",
"version": "Version",
"ok": "OK",
"cancel": "Cancel",
"kContinue": "Continue",
"showMore": "Show more",
"showLess": "Show less",
"filter": "Filter",
"all": "All",
"pause": "Pause",
"resume": "Resume",
"clear": "Clear",
"close": "Close",
"auto": "Auto",
"manually": "Manually",
"name": "Name",
"url": "URL",
"add": "Add",
"clipboard": "Clipboard",
"addToClipboard": "Add to clipboard",
"scanQr": "Scan QR",
"free": "Free",
"warp": "WARP",
"fragment": "Fragment",
"help": "Help",
"save": "Save",
"update": "Update",
"share": "Share",
"edit": "Edit",
"delete": "Delete",
"discard": "Discard",
"import": "Import",
"export": "Export",
"later": "Later",
"ignore": "Ignore",
"quit": "Quit",
"notSet": "Not set",
"hide": "Hide",
"exit": "Exit",
"reset": "Reset",
"done": "Done",
"search": "Search",
"decline": "Decline",
"agree": "Agree",
"empty": "Empty",
"unknown": "Unknown",
"hidden": "Hidden",
"timeout": "Timeout",
"sort": "Sort",
"dashboard": "Dashboard",
"interval": {
"day": {
"zero": "",
"one": "$n day",
"other": "$n days"
},
"hour": {
"zero": "",
"one": "$n hour",
"other": "$n hours"
}
},
"msg": {
"permission": {
"denied": "Permission denied"
},
"export": {
"clipboard": {
"success": "Added to clipboard successfully",
"failure": "Failed to copy to clipboard",
"contentTooLarge": "Content too large. Use export file instead"
},
"file": {
"success": "JSON file created successfully",
"failure": "Failed to create file"
}
},
"import": {
"confirm": "Confirm import",
"success": "Imported successfully",
"failure": "Failed to import"
}
}
},
"intro": {
"banner": "All you need for an unrestricted internet",
"termsAndPolicyCaution(rich)": "By continuing you agree to ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "Made with ❤️ by Hiddify - ${tap_source(Open Source)} (${tap_license(License)})"
},
"pages": {
"home": {
"title": "Home",
"quickSettings": "Quick settings"
},
"proxies": {
"title": "Proxies",
"sort": "Sort proxies",
"testDelay": "Test delay",
"empty": "No proxies available",
"activeProxy": "Active proxy",
"unknownIp": "Unknown IP",
"sortOptions": {
"unsorted": "Default",
"name": "Alphabetically",
"delay": "By delay",
"usage": "By usage"
},
"ipInfo": {
"address": "IP address",
"country": "Country",
"organization": "Organization"
},
"delay": {
"result": "Delay: ${delay}ms",
"timeout": "Delay test timeout",
"testing": "Delay: testing..."
}
},
"profiles": {
"title": "Profiles",
"add": "Add profile",
"update": "Update profile",
"viewAllProfiles": "View all profiles",
"activeProfileName": "Active profile name: \"${name}\".",
"nonActiveProfileName": "Select \"${name}\" as active profile",
"freeSubNotFound": "No free subscription was found",
"freeSubNotFoundForRegion": "No free subscription was found for \"${region}\" region.",
"failedToLoad": "Failed to load",
"updateSubscriptions": "Update subscriptions",
"share": {
"urlToClipboard": "URL to clipboard",
"showUrlQr": "Show URL QR",
"jsonToClipboard": "JSON to clipboard"
},
"msg": {
"save": {
"success": "Profile saved successfully"
},
"invalidUrl": "Invalid URL",
"add": {
"failure": "Failed to add profile"
},
"update": {
"success": "Profile updated successfully",
"successNamed": "\"${name}\" updated successfully",
"failure": "Failed to update profile",
"failureNamed": "Failed to update \"${name}\""
},
"delete": {
"success": "Profile deleted successfully"
}
}
},
"profileDetails": {
"title": "Profile",
"lastUpdate": "Last update",
"form": {
"nameHint": "Profile name",
"emptyName": "Name is required",
"invalidUrl": "Invalid URL",
"urlHint": "Full config URL",
"disableAutoUpdate": "Disable auto update",
"autoUpdateInterval": "Auto update interval",
"loading": "Adding profile..."
}
},
"logs": {
"title": "Logs",
"shareCoreLogs": "Share core logs",
"shareAppLogs": "Share app logs"
},
"about": {
"title": "About",
"notAvailableMsg": "Already using the latest version",
"checkForUpdate": "Check for update",
"openWorkingDir": "Open working directory",
"sourceCode": "Source code",
"telegramChannel": "Telegram channel",
"termsAndConditions": "Terms and conditions",
"privacyPolicy": "Privacy policy"
},
"settings": {
"title": "Settings",
"resetTunnel": "Reset VPN profile",
"options": {
"import": {
"clipboard": "Import options from clipboard",
"file": "Import options from file"
},
"export": {
"anonymousToClipboard": "Copy anonymous options to clipboard",
"anonymousToFile": "Export anonymous options to file",
"allToClipboard": "Copy all options to clipboard",
"allToFile": "Export all options to file"
},
"reset": "Reset options"
},
"general": {
"title": "General",
"locale": "Language",
"themeMode": "Theme mode",
"themeModes": {
"system": "System default",
"dark": "Dark mode",
"light": "Light mode",
"black": "Black mode"
},
"enableAnalytics": "Enable analytics",
"enableAnalyticsMsg": "Give permission to collect analytics and send crash reports to improve the app",
"autoIpCheck": "Automatically check connection IP",
"dynamicNotification": "Display speed in notification",
"hapticFeedback": "Haptic feedback",
"actionAtClosing": "Action at closing",
"autoStart": "Start at login",
"silentStart": "Start minimized",
"ignoreBatteryOptimizations": "Disable battery optimization",
"ignoreBatteryOptimizationsMsg": "Remove restrictions for optimal VPN performance",
"memoryLimit": "Memory limit",
"memoryLimitMsg": "Enable if you're experiencing out of memory errors or frequent app crashes",
"debugMode": "Debug mode",
"debugModeMsg": "Restart the app for applying this change",
"logLevel": "Log level",
"connectionTestUrl": "Connection test URL",
"urlTestInterval": "URL test interval",
"clashApiPort": "Clash API port",
"useXrayCoreWhenPossible": "Use xray-core when possible",
"useXrayCoreWhenPossibleMsg": "Use xray-core during parsing sub links. You need to reimport the sub link to enable this option."
},
"routing": {
"title": "Routing",
"perAppProxy": {
"title": "Per-app proxy",
"hideSysApps": "Hide system apps",
"options": {
"import": {
"clipboard": "Import selection from clipboard",
"file": "Import selection from file",
"msg": "Importing will replace your current selections. Are you sure you want to continue?"
},
"export": {
"clipboard": "Copy selection to clipboard",
"file": "Export selection to file"
},
"shareToAll": "Share to all",
"clearAllSelections": "Clear all selections"
},
"modes": {
"all": "All",
"proxy": "Proxy",
"bypass": "Bypass",
"allMsg": "Proxy all apps",
"proxyMsg": "Proxy only selected apps",
"bypassMsg": "Do not proxy selected apps"
},
"autoSelection": {
"title": "Auto selection",
"performNow": "Perform now",
"resetToDefault": "Reset to default",
"autoUpdateInterval": "Auto update interval",
"toast": {
"success": "Auto apps selection completed successfully",
"failure": "Auto selection failed",
"regionNotFound": "Auto selection not found, region \"${region}\"",
"alreadyInAuto": "Your selections are already in the auto-list"
},
"dialog": {
"title": "Auto apps selection",
"msg": "The auto selection feature for per-app proxy was disabled due to the region change to \"${region}\""
}
}
},
"region": "Region",
"regions": {
"ir": "Iran (ir)",
"cn": "China (cn)",
"ru": "Russia (ru)",
"af": "Afghanistan (af)",
"id": "Indonesia (id)",
"tr": "Türkiye (tr)",
"br": "Brazil (br)",
"other": "Other"
},
"balancerStrategy": {
"title": "Balancer strategy",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "Block advertisements",
"bypassLan": "Bypass LAN",
"resolveDestination": "Resolve destination",
"ipv6Route": "IPv6 route",
"ipv6Modes": {
"disable": "Disable",
"enable": "Enable",
"prefer": "Preferred",
"only": "Exclusive"
},
"routeRule": {
"title": "Route rules",
"options": {
"import": {
"clipboard": "Import rules from clipboard",
"file": "Import rules from file"
},
"export": {
"clipboard": "Copy rules to clipboard",
"file": "Save rules to file"
},
"reset": "Reset rules"
},
"deleteRule": "Delete rule",
"createRule": "Create new rule",
"rule": {
"title": "Rule",
"ruleChanged": "Rule changed",
"ruleChangedMsg": "Do you want to save your edits?",
"onlyTunMode": "Available only in tun mode",
"notAvailabeInThisPlatform": "Not available in this platform",
"canNotBeEmpty": "Can not be empty",
"validUrlEx": "https://example.com",
"validUrl": "Valid \"URL\" like\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe or google chrome or chrome",
"validProcessName": "Valid \"Process Name\" like\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "Valid \"Process Path\" like\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 or 1-65000",
"validPortRange": "Valid \"Port\" or \"Port Range\" like\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 or 10.0.0.0/24",
"validIpCidr": "Valid IP CIDR like\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com or dl.google.com",
"validDomain": "Valid \"Domain\" like\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com or .ir",
"validDomainSuffix": "Valid \"Domain Suffix\" like\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "Name",
"outbound": "Outbound if match",
"rule_set": "Rule set URL",
"package_name": "Package names",
"process_name": "Process names",
"process_path": "Process paths",
"network": "Networks",
"port_range": "Destination ports",
"source_port_range": "Source ports",
"protocol": "Protocol",
"ip_cidr": "Destination IP CIDR",
"source_ip_cidr": "Source IP CIDR",
"domain": "Domain",
"domain_suffixe": "Domain suffix",
"domain_keyword": "Domain keyword",
"domain_regex": "Domain regex"
},
"outbound(map)": {
"proxy": "Proxy",
"direct": "Direct",
"direct_with_fragment": "Direct with fragment",
"block": "Block"
},
"network(map)": {
"all": "All",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "All",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "Add new value",
"update": "Update value",
"clearList": "Clear list",
"clearListMsg": "All items are deleted"
},
"androidApps": {
"pageTitle": "Android apps",
"showSystemApps": "Show system apps",
"hideSystemApps": "Hide system apps",
"clearSelection": "Clear selection",
"uninstalled": "Uninstalled"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "Remote DNS",
"remoteDnsDomainStrategy": "Remote DNS domain strategy",
"directDns": "Outbound server resolver (direct)",
"directDnsDomainStrategy": "Outbound domain strategy",
"enableDnsRouting": "Enable DNS routing",
"enableFakeDns": "Enable fake DNS",
"domainStrategy": {
"auto": "Auto",
"preferIpv6": "Prefer IPv6",
"preferIpv4": "Prefer IPv4",
"ipv4Only": "IPv4 only",
"ipv6Only": "IPv6 only"
}
},
"inbound": {
"title": "Inbound",
"serviceMode": "Service mode",
"serviceModes": {
"proxy": "Proxy service only",
"systemProxy": "Set system proxy",
"tun": "VPN",
"tunService": "VPN service"
},
"shortServiceModes": {
"proxy": "Proxy",
"systemProxy": "System proxy",
"tun": "VPN",
"tunService": "VPN service"
},
"strictRoute": "Strict route",
"tunImplementation": "Tun implementation",
"tunImplementations": {
"mixed": "Mixed",
"system": "System",
"gvisor": "gVisor"
},
"mixedPort": "Mixed port",
"tproxyPort": "Transparent proxy port",
"directPort": "Direct port",
"redirectPort": "Redirect port",
"allowConnectionFromLan": "Share VPN in local network"
},
"tlsTricks": {
"title": "TLS tricks",
"enable": "Enable fragment",
"packets": "Fragmentation Packets",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "Fragment size",
"sleep": "Fragment sleep",
"mixedSniCase": {
"enable": "Enable mixed SNI case"
},
"padding": {
"enable": "Enable padding",
"size": "Padding size"
}
},
"warp": {
"title": "WARP",
"enable": "Enable WARP",
"generateConfig": "Generate WARP config",
"configGenerated": "Warp config is generated",
"missingConfig": "Missing WARP config",
"detourMode": "Detour mode",
"detourModes": {
"proxyOverWarp": "Detour proxies through WARP ",
"warpOverProxy": "Detour WARP through proxies",
"proxyOverWarpExplain": "Unblock proxies by WARP",
"warpOverProxyExplain": "Extra security by WARP"
},
"licenseKey": "License key",
"cleanIp": "Clean IP",
"port": "Port",
"noise": {
"count": "Noise count",
"mode": "Noise mode",
"size": "Noise size",
"delay": "Noise delay"
}
}
}
},
"components": {
"stats": {
"connection": "Connection",
"traffic": "Traffic",
"trafficLive": "Live traffic",
"trafficTotal": "Total traffic",
"uplink": "Uplink",
"downlink": "Downlink",
"speed": "Speed",
"totalTransferred": "Total transferred"
},
"subscriptionInfo": {
"upload": "Upload",
"download": "Download",
"total": "Total traffic",
"expireDate": "Expire date",
"expired": "Expired",
"noTraffic": "Out of quota",
"remainingTime": "Remaining time",
"remainingDuration": "${duration} days remaining",
"remainingDurationNew": "${duration} days",
"remainingTrafficSemanticLabel": "${consumed} of ${total} traffic consumed",
"remainingTraffic": "Remaining traffic",
"remainingUsage": "Remaining",
"profileSite": "Provider",
"profileSupport": "Support"
}
},
"dialogs": {
"sortProfiles": {
"title": "Sort by",
"sort": {
"name": "Alphabetically",
"lastUpdate": "Recently updated"
}
},
"warpLicense": {
"title": "Cloudflare WARP consent",
"description(rich)": "Cloudflare WARP is a free WireGuard VPN provider. By enabling this option you are agreeing to the Cloudflare WARP's ${tos(Terms of service)} and ${privacy(Privacy policy)}."
},
"newVersion": {
"title": "Update available",
"msg": "A new version of @:common.appTitle is available. Would you like to update now?",
"currentVersion": "Current version: ",
"newVersion": "New version: ",
"updateNow": "Update now"
},
"confirmation": {
"settings": {
"import": {
"msg": "This will rewrite all config options with provided values. Are you sure?"
}
},
"profile": {
"delete": {
"title": "Delete profile",
"msg": "Are you sure you want to permanently delete this profile?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "Improving auto selection",
"msg": "By sharing selected apps, you help complete the \"auto selection\" list"
},
"import": {
"msg": "This will replace all your current per-app proxy selections. Are you sure you want to continue?"
}
},
"routeRule": {
"delete": {
"title": "Delete rule",
"msg": "Are you sure you want to delete the \"$rulename\" rule?"
}
}
},
"experimentalNotice": {
"title": "Experimental features in use",
"msg": "You've enabled some experimental features which might affect connection quality and cause unexpected errors. You can always change or reset these options from config options page.",
"disable": "Don't show again"
},
"noActiveProfile": {
"title": "Choose a profile",
"msg": "Let's get started by adding a connection profile that includes your VPN connection details.\n\nDon’t have a VPN server yet? No worries—just follow the tutorial below to set one up quickly and for free.",
"helpBtn": {
"label": "Show me how",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "External link warning",
"youAreAboutToVisit": "You are about to visit :",
"thisWebsiteIsNotInOurTrustedList": "This website is not in our trusted list. Please proceed with caution."
},
"proxyInfo": {
"fullTag": "Full tag:",
"type": "Type:",
"testTime": "Test time:",
"testDelay": "Test delay:",
"isSelected": "Is selected:",
"isGroup": "Is group",
"isSecure": "Is secure:",
"port": "Port:",
"host": "Host:",
"ip": "IP:",
"countryCode": "Country code:",
"region": "Region:",
"city": "City:",
"asn": "ASN:",
"organization": "Organization:",
"location": "Location:",
"postalCode": "Postal code:",
"download": "Download:",
"upload": "Upload:"
},
"windowClosing": {
"askEachTime": "Ask each time",
"alertMessage": "Hide or exit the application?",
"remember": "Remember my choice"
}
},
"connection": {
"tapToConnect": "Tap to connect",
"connect": "Connect",
"connecting": "Connecting...",
"connected": "Connected",
"disconnect": "Disconnect",
"disconnecting": "Disconnecting...",
"reconnect": "Reconnect",
"reconnectMsg": "Reconnecting for taking into account the changes...",
"secure": "Secured by WARP"
},
"errors": {
"unexpected": "Unexpected error",
"connection": {
"unexpected": "Unexpected connection error",
"timeout": "Connection timeout",
"badResponse": "Bad response",
"connectionError": "Connection error",
"badCertificate": "Bad certificate"
},
"profiles": {
"unexpected": "Unexpected error",
"notFound": "Profile not found",
"invalidConfig": "Invalid configs",
"invalidUrl": "Invalid URL",
"canceledByUser": "Canceled by user"
},
"connectivity": {
"unexpected": "Unexpected failure",
"missingVpnPermission": "Missing VPN permission",
"missingNotificationPermission": "Missing notification permission",
"core": "Core error"
},
"singbox": {
"serviceNotRunning": "Service is not running",
"missingPrivilege": "Missing privilege",
"missingPrivilegeMsg": "VPN mode requires administrator privileges. Either relaunch the app as administrator or change service mode.",
"invalidConfigOptions": "Invalid configuration options",
"invalidConfig": "Invalid configuration"
},
"warp": {
"missingLicense": "Warp license",
"missingLicenseMsg": "The selected profile uses the WARP feature; to use this feature, the WARP license must be agreed to."
}
}
}
================================================
FILE: assets/translations/es.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "Comenzar",
"version": "Versión",
"ok": "Aceptar",
"cancel": "Cancelar",
"kContinue": "Continuar",
"showMore": "Mostrar más",
"showLess": "Mostrar menos",
"filter": "Filtrar",
"all": "Todos",
"pause": "Pausar",
"resume": "Reanudar",
"clear": "Limpiar",
"close": "Cerrar",
"auto": "Automático",
"manually": "Manualmente",
"name": "Nombre",
"url": "URL",
"add": "Añadir",
"clipboard": "Portapapeles",
"addToClipboard": "Añadir al portapapeles",
"scanQr": "Escanear QR",
"free": "Gratis",
"warp": "WARP",
"fragment": "Fragmento",
"help": "Ayuda",
"save": "Guardar",
"update": "Actualizar",
"share": "Compartir",
"edit": "Editar",
"delete": "Eliminar",
"discard": "Descartar",
"import": "Importar",
"export": "Exportar",
"later": "Más tarde",
"ignore": "Ignorar",
"quit": "Salir",
"notSet": "No establecido",
"hide": "Ocultar",
"exit": "Salir",
"reset": "Restablecer",
"done": "Hecho",
"search": "Buscar",
"decline": "Rechazar",
"agree": "Aceptar",
"empty": "Vacío",
"unknown": "Desconocido",
"hidden": "Oculto",
"timeout": "Tiempo agotado",
"sort": "Ordenar",
"dashboard": "Panel",
"interval": {
"day": {
"zero": "",
"one": "$n día",
"other": "$n días"
},
"hour": {
"zero": "",
"one": "$n hora",
"other": "$n horas"
}
},
"msg": {
"permission": {
"denied": "Permiso denegado"
},
"export": {
"clipboard": {
"success": "Añadido al portapapeles con éxito",
"failure": "Error al copiar al portapapeles",
"contentTooLarge": "Contenido demasiado grande. Use la exportación a archivo en su lugar"
},
"file": {
"success": "Archivo JSON creado con éxito",
"failure": "Error al crear el archivo"
}
},
"import": {
"confirm": "Confirmar importación",
"success": "Importado con éxito",
"failure": "Error al importar"
}
}
},
"intro": {
"banner": "Todo lo que necesitas para un internet sin restricciones",
"termsAndPolicyCaution(rich)": "Al continuar, aceptas los ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "Hecho con ❤️ por Hiddify - ${tap_source(Código Abierto)} (${tap_license(Licencia)})"
},
"pages": {
"home": {
"title": "Inicio",
"quickSettings": "Ajustes rápidos"
},
"proxies": {
"title": "Proxies",
"sort": "Ordenar proxies",
"testDelay": "Probar latencia",
"empty": "No hay proxies disponibles",
"activeProxy": "Proxy activo",
"unknownIp": "IP desconocida",
"sortOptions": {
"unsorted": "Por defecto",
"name": "Alfabéticamente",
"delay": "Por latencia"
},
"ipInfo": {
"address": "Dirección IP",
"country": "País",
"organization": "Organización"
},
"delay": {
"result": "Latencia: ${delay}ms",
"timeout": "Tiempo de prueba de latencia agotado",
"testing": "Latencia: probando..."
}
},
"profiles": {
"title": "Perfiles",
"add": "Añadir perfil",
"update": "Actualizar perfil",
"viewAllProfiles": "Ver todos los perfiles",
"activeProfileName": "Nombre del perfil activo: \"${name}\".",
"nonActiveProfileName": "Seleccionar \"${name}\" como perfil activo",
"freeSubNotFound": "No se encontró ninguna suscripción gratuita",
"freeSubNotFoundForRegion": "No se encontró ninguna suscripción gratuita para la región \"${region}\"",
"failedToLoad": "Error al cargar",
"updateSubscriptions": "Actualizar suscripciones",
"share": {
"urlToClipboard": "URL al portapapeles",
"showUrlQr": "Mostrar QR de la URL",
"jsonToClipboard": "JSON al portapapeles"
},
"msg": {
"save": {
"success": "Perfil guardado con éxito"
},
"invalidUrl": "URL no válida",
"add": {
"failure": "Error al añadir el perfil"
},
"update": {
"success": "Perfil actualizado con éxito",
"successNamed": "\"${name}\" actualizado con éxito",
"failure": "Error al actualizar el perfil",
"failureNamed": "Error al actualizar \"${name}\""
},
"delete": {
"success": "Perfil eliminado con éxito"
}
}
},
"profileDetails": {
"title": "Perfil",
"lastUpdate": "Última actualización",
"form": {
"nameHint": "Nombre del perfil",
"emptyName": "El nombre es obligatorio",
"invalidUrl": "URL no válida",
"urlHint": "URL de configuración completa",
"disableAutoUpdate": "Desactivar actualización automática",
"autoUpdateInterval": "Intervalo de actualización automática",
"loading": "Añadiendo perfil..."
}
},
"logs": {
"title": "Registros",
"shareCoreLogs": "Compartir registros del núcleo",
"shareAppLogs": "Compartir registros de la aplicación"
},
"about": {
"title": "Acerca de",
"notAvailableMsg": "Ya estás usando la última versión",
"checkForUpdate": "Buscar actualizaciones",
"openWorkingDir": "Abrir directorio de trabajo",
"sourceCode": "Código fuente",
"telegramChannel": "Canal de Telegram",
"termsAndConditions": "Términos y condiciones",
"privacyPolicy": "Política de privacidad"
},
"settings": {
"title": "Ajustes",
"resetTunnel": "Restablecer perfil de VPN",
"options": {
"import": {
"clipboard": "Importar opciones desde el portapapeles",
"file": "Importar opciones desde un archivo"
},
"export": {
"anonymousToClipboard": "Copiar opciones anónimas al portapapeles",
"anonymousToFile": "Exportar opciones anónimas a un archivo",
"allToClipboard": "Copiar todas las opciones al portapapeles",
"allToFile": "Exportar todas las opciones a un archivo"
},
"reset": "Restablecer opciones"
},
"general": {
"title": "General",
"locale": "Idioma",
"themeMode": "Tema",
"themeModes": {
"system": "Por defecto del sistema",
"dark": "Modo oscuro",
"light": "Modo claro",
"black": "Modo negro"
},
"enableAnalytics": "Habilitar análisis",
"enableAnalyticsMsg": "Dar permiso para recopilar análisis y enviar informes de fallos para mejorar la aplicación",
"autoIpCheck": "Comprobar IP de conexión automáticamente",
"dynamicNotification": "Mostrar velocidad en la notificación",
"hapticFeedback": "Respuesta háptica",
"actionAtClosing": "Acción al cerrar",
"autoStart": "Iniciar al arrancar",
"silentStart": "Iniciar minimizado",
"ignoreBatteryOptimizations": "Desactivar optimización de batería",
"ignoreBatteryOptimizationsMsg": "Eliminar restricciones para un rendimiento óptimo de la VPN",
"memoryLimit": "Límite de memoria",
"memoryLimitMsg": "Habilitar si experimentas errores de falta de memoria o fallos frecuentes de la aplicación",
"debugMode": "Modo de depuración",
"debugModeMsg": "Reinicia la aplicación para aplicar este cambio",
"logLevel": "Nivel de registro",
"connectionTestUrl": "URL de prueba de conexión",
"urlTestInterval": "Intervalo de prueba de URL",
"clashApiPort": "Puerto de la API de Clash",
"useXrayCoreWhenPossible": "Usar xray-core cuando sea posible",
"useXrayCoreWhenPossibleMsg": "Usa xray-core al analizar enlaces de suscripción. Necesitas reimportar el enlace para habilitar esta opción"
},
"routing": {
"title": "Enrutamiento",
"perAppProxy": {
"title": "Proxy por aplicación",
"hideSysApps": "Ocultar aplicaciones del sistema",
"options": {
"import": {
"clipboard": "Importar selección desde el portapapeles",
"file": "Importar selección desde un archivo",
"msg": "La importación reemplazará tus selecciones actuales. ¿Estás seguro de que quieres continuar?"
},
"export": {
"clipboard": "Copiar selección al portapapeles",
"file": "Exportar selección a un archivo"
},
"shareToAll": "Compartir con todos",
"clearAllSelections": "Borrar todas las selecciones"
},
"modes": {
"all": "Todo",
"proxy": "Proxy",
"bypass": "Omitir",
"allMsg": "Usar proxy en todas las aplicaciones",
"proxyMsg": "Usar proxy solo en aplicaciones seleccionadas",
"bypassMsg": "No usar proxy en aplicaciones seleccionadas"
},
"autoSelection": {
"title": "Selección automática",
"performNow": "Ejecutar ahora",
"resetToDefault": "Restablecer por defecto",
"autoUpdateInterval": "Intervalo de actualización automática",
"toast": {
"success": "Selección automática de aplicaciones completada con éxito",
"failure": "Fallo en la selección automática",
"regionNotFound": "No se encontró selección automática para la región \"${region}\"",
"alreadyInAuto": "Tus selecciones ya están en la lista automática"
},
"dialog": {
"title": "Selección automática de aplicaciones",
"msg": "La función de selección automática para el proxy por aplicación se ha desactivado debido al cambio de región a \"${region}\""
}
}
},
"region": "Región",
"regions": {
"ir": "Irán (ir)",
"cn": "China (cn)",
"ru": "Rusia (ru)",
"af": "Afganistán (af)",
"id": "Indonesia (id)",
"tr": "Turquía (tr)",
"br": "Brasil (br)",
"other": "Otro"
},
"balancerStrategy": {
"title": "Estrategia de Balancer",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "Bloquear anuncios",
"bypassLan": "Omitir LAN",
"resolveDestination": "Resolver destino",
"ipv6Route": "Ruta IPv6",
"ipv6Modes": {
"disable": "Desactivar",
"enable": "Activar",
"prefer": "Preferido",
"only": "Exclusivo"
},
"routeRule": {
"title": "Reglas de enrutamiento",
"options": {
"import": {
"clipboard": "Importar reglas desde el portapapeles",
"file": "Importar reglas desde un archivo"
},
"export": {
"clipboard": "Copiar reglas al portapapeles",
"file": "Guardar reglas en un archivo"
},
"reset": "Restablecer reglas"
},
"deleteRule": "Eliminar regla",
"createRule": "Crear nueva regla",
"rule": {
"title": "Regla",
"ruleChanged": "Regla modificada",
"ruleChangedMsg": "¿Quieres guardar tus cambios?",
"onlyTunMode": "Disponible solo en modo TUN",
"notAvailabeInThisPlatform": "No disponible en esta plataforma",
"canNotBeEmpty": "No puede estar vacío",
"validUrlEx": "https://example.com",
"validUrl": "\"URL\" válida como\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe or google chrome or chrome",
"validProcessName": "\"Nombre de proceso\" válido como\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "\"Ruta de proceso\" válida como\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 or 1-65000",
"validPortRange": "\"Puerto\" o \"Rango de puertos\" válido como\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 or 10.0.0.0/24",
"validIpCidr": "IP CIDR válido como\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com or dl.google.com",
"validDomain": "\"Dominio\" válido como\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com or .ir",
"validDomainSuffix": "\"Sufijo de dominio\" válido como\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "Nombre",
"outbound": "Salida si coincide",
"rule_set": "URL del conjunto de reglas",
"package_name": "Nombres de paquetes",
"process_name": "Nombres de procesos",
"process_path": "Rutas de procesos",
"network": "Redes",
"port_range": "Puertos de destino",
"source_port_range": "Puertos de origen",
"protocol": "Protocolo",
"ip_cidr": "IP CIDR de destino",
"source_ip_cidr": "IP CIDR de origen",
"domain": "Dominio",
"domain_suffixe": "Sufijo de dominio",
"domain_keyword": "Palabra clave de dominio",
"domain_regex": "Expresión regular de dominio"
},
"outbound(map)": {
"proxy": "Proxy",
"direct": "Directo",
"direct_with_fragment": "Directo con fragmento",
"block": "Bloquear"
},
"network(map)": {
"all": "Todo",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "Todos",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "Añadir nuevo valor",
"update": "Actualizar valor",
"clearList": "Limpiar lista",
"clearListMsg": "Todos los elementos han sido eliminados"
},
"androidApps": {
"pageTitle": "Aplicaciones de Android",
"showSystemApps": "Mostrar aplicaciones del sistema",
"hideSystemApps": "Ocultar aplicaciones del sistema",
"clearSelection": "Borrar selección",
"uninstalled": "Desinstalado"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "DNS remoto",
"remoteDnsDomainStrategy": "Estrategia de dominio de DNS remoto",
"directDns": "Resolución del servidor de salida (directo)",
"directDnsDomainStrategy": "Estrategia de dominio de salida",
"enableDnsRouting": "Habilitar enrutamiento de DNS",
"enableFakeDns": "Habilitar DNS falso",
"domainStrategy": {
"auto": "Automático",
"preferIpv6": "Preferir IPv6",
"preferIpv4": "Preferir IPv4",
"ipv4Only": "Solo IPv4",
"ipv6Only": "Solo IPv6"
}
},
"inbound": {
"title": "Entrada",
"serviceMode": "Modo de servicio",
"serviceModes": {
"proxy": "Solo servicio de proxy",
"systemProxy": "Establecer proxy del sistema",
"tun": "VPN",
"tunService": "Servicio VPN"
},
"shortServiceModes": {
"proxy": "Proxy",
"systemProxy": "Proxy del sistema",
"tun": "VPN",
"tunService": "Servicio VPN"
},
"strictRoute": "Ruta estricta",
"tunImplementation": "Implementación de TUN",
"tunImplementations": {
"mixed": "Mixto",
"system": "Sistema",
"gvisor": "gVisor"
},
"mixedPort": "Puerto mixto",
"tproxyPort": "Puerto de proxy transparente",
"directPort": "Puerto de Directo",
"redirectPort": "Puerto de redirección",
"allowConnectionFromLan": "Compartir VPN en la red local"
},
"tlsTricks": {
"title": "Trucos de TLS",
"enable": "Habilitar fragmento",
"packets": "Paquetes de fragmentación",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "Tamaño de fragmento",
"sleep": "Retraso de fragmento",
"mixedSniCase": {
"enable": "Habilitar mayúsculas/minúsculas mixtas en SNI"
},
"padding": {
"enable": "Habilitar relleno",
"size": "Tamaño de relleno"
}
},
"warp": {
"title": "WARP",
"enable": "Habilitar WARP",
"generateConfig": "Generar configuración de WARP",
"configGenerated": "Configuración de WARP generada",
"missingConfig": "Falta la configuración de WARP",
"detourMode": "Modo de desvío",
"detourModes": {
"proxyOverWarp": "Desviar proxies a través de WARP",
"warpOverProxy": "Desviar WARP a través de proxies",
"proxyOverWarpExplain": "Desbloquear proxies con WARP",
"warpOverProxyExplain": "Seguridad extra con WARP"
},
"licenseKey": "Clave de licencia",
"cleanIp": "IP limpia",
"port": "Puerto",
"noise": {
"count": "Cantidad de ruido",
"mode": "Modo de ruido",
"size": "Tamaño de ruido",
"delay": "Retraso de ruido"
}
}
}
},
"components": {
"stats": {
"connection": "Conexión",
"traffic": "Tráfico",
"trafficLive": "Tráfico en tiempo real",
"trafficTotal": "Tráfico total",
"uplink": "Subida",
"downlink": "Bajada",
"speed": "Velocidad",
"totalTransferred": "Total transferido"
},
"subscriptionInfo": {
"upload": "Subida",
"download": "Descarga",
"total": "Tráfico total",
"expireDate": "Fecha de caducidad",
"expired": "Caducado",
"noTraffic": "Cuota agotada",
"remainingTime": "Tiempo restante",
"remainingDuration": "${duration} días restantes",
"remainingDurationNew": "${duration} días",
"remainingTrafficSemanticLabel": "${consumed} de ${total} de tráfico consumido",
"remainingTraffic": "Tráfico restante",
"remainingUsage": "Restante",
"profileSite": "Proveedor",
"profileSupport": "Soporte"
}
},
"dialogs": {
"sortProfiles": {
"title": "Ordenar por",
"sort": {
"name": "Alfabéticamente",
"lastUpdate": "Última actualización"
}
},
"warpLicense": {
"title": "Consentimiento de Cloudflare WARP",
"description(rich)": "Cloudflare WARP es un proveedor de VPN WireGuard gratuito. Al habilitar esta opción, aceptas los ${tos(Términos de servicio)} y la ${privacy(Política de privacidad)} de Cloudflare WARP."
},
"newVersion": {
"title": "Actualización disponible",
"msg": "Hay disponible una nueva versión de @:common.appTitle. ¿Quieres actualizar ahora?",
"currentVersion": "Versión actual: ",
"newVersion": "Nueva versión: ",
"updateNow": "Actualizar ahora"
},
"confirmation": {
"settings": {
"import": {
"msg": "Esto reemplazará todas las opciones de configuración con los valores proporcionados. ¿Estás seguro?"
}
},
"profile": {
"delete": {
"title": "Eliminar perfil",
"msg": "¿Estás seguro de que quieres eliminar este perfil permanentemente?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "Mejorando la selección automática",
"msg": "Al compartir las aplicaciones seleccionadas, ayudas a completar la lista de \"selección automática\""
},
"import": {
"msg": "Esto reemplazará todas tus selecciones actuales de proxy por aplicación. ¿Estás seguro de que quieres continuar?"
}
},
"routeRule": {
"delete": {
"title": "Eliminar regla",
"msg": "¿Estás seguro de que quieres eliminar la regla \"$rulename\"?"
}
}
},
"experimentalNotice": {
"title": "Funciones experimentales en uso",
"msg": "Has habilitado algunas funciones experimentales que podrían afectar la calidad de la conexión y causar errores inesperados. Siempre puedes cambiar o restablecer estas opciones desde la página de configuración.",
"disable": "No volver a mostrar"
},
"noActiveProfile": {
"title": "Elige un perfil",
"msg": "Para empezar, añade un perfil de conexión que incluya los detalles de tu conexión VPN.\n\n¿Aún no tienes un servidor VPN? No te preocupes, sigue el tutorial a continuación para configurar uno rápidamente y de forma gratuita.",
"helpBtn": {
"label": "Muéstrame cómo",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "Advertencia de enlace externo",
"youAreAboutToVisit": "Estás a punto de visitar:",
"thisWebsiteIsNotInOurTrustedList": "Este sitio web no está en nuestra lista de confianza. Procede con precaución."
},
"proxyInfo": {
"fullTag": "Etiqueta completa:",
"type": "Tipo:",
"testTime": "Hora de la prueba:",
"testDelay": "Latencia de la prueba:",
"isSelected": "Seleccionado:",
"isGroup": "Es un grupo",
"isSecure": "Es seguro:",
"port": "Puerto:",
"host": "Host:",
"ip": "IP:",
"countryCode": "Código de país:",
"region": "Región:",
"city": "Ciudad:",
"asn": "ASN:",
"organization": "Organización:",
"location": "Ubicación:",
"postalCode": "Código postal:"
},
"windowClosing": {
"askEachTime": "Preguntar cada vez",
"alertMessage": "¿Ocultar o salir de la aplicación?",
"remember": "Recordar mi elección"
}
},
"connection": {
"tapToConnect": "Toca para conectar",
"connect": "Conectar",
"connecting": "Conectando...",
"connected": "Conectado",
"disconnect": "Desconectar",
"disconnecting": "Desconectando...",
"reconnect": "Reconectar",
"reconnectMsg": "Reconectando para aplicar los cambios...",
"secure": "Protegido por WARP"
},
"errors": {
"unexpected": "Error inesperado",
"connection": {
"unexpected": "Error de conexión inesperado",
"timeout": "Tiempo de conexión agotado",
"badResponse": "Respuesta incorrecta",
"connectionError": "Error de conexión",
"badCertificate": "Certificado no válido"
},
"profiles": {
"unexpected": "Error inesperado",
"notFound": "Perfil no encontrado",
"invalidConfig": "Configuraciones no válidas",
"invalidUrl": "URL no válida",
"canceledByUser": "Cancelado por el usuario"
},
"connectivity": {
"unexpected": "Fallo inesperado",
"missingVpnPermission": "Falta el permiso de VPN",
"missingNotificationPermission": "Falta el permiso de notificación",
"core": "Error del núcleo"
},
"singbox": {
"serviceNotRunning": "El servicio no se está ejecutando",
"missingPrivilege": "Falta privilegio",
"missingPrivilegeMsg": "El modo VPN requiere privilegios de administrador. Reinicia la aplicación como administrador o cambia el modo de servicio.",
"invalidConfigOptions": "Opciones de configuración no válidas",
"invalidConfig": "Configuración no válida"
},
"warp": {
"missingLicense": "Falta la licencia de WARP",
"missingLicenseMsg": "El perfil seleccionado utiliza la función WARP. Para usar esta función, debes aceptar la licencia de WARP."
}
}
}
================================================
FILE: assets/translations/fa.i18n.json
================================================
{
"common": {
"appTitle": "هیدیفای",
"start": "شروع",
"version": "نسخه",
"ok": "باشه",
"cancel": "لغو",
"kContinue": "ادامه",
"showMore": "نمایش بیشتر",
"showLess": "نمایش کمتر",
"filter": "فیلتر",
"all": "همه",
"pause": "مکث",
"resume": "ادامه",
"clear": "پاکسازی",
"close": "بستن",
"auto": "خودکار",
"manually": "دستی",
"name": "نام",
"url": "URL",
"add": "افزودن",
"clipboard": "کلیپبورد",
"addToClipboard": "افزودن به کلیپبورد",
"scanQr": "اسکن QR",
"free": "رایگان",
"warp": "WARP",
"fragment": "Fragment",
"help": "راهنما",
"save": "ذخیره",
"update": "بهروزرسانی",
"share": "اشتراکگذاری",
"edit": "ویرایش",
"delete": "حذف",
"discard": "صرفنظر",
"import": "وارد کردن",
"export": "خروجی گرفتن",
"later": "بعداً",
"ignore": "نادیده گرفتن",
"quit": "خروج",
"notSet": "تنظیم نشده",
"hide": "پنهان کردن",
"exit": "خروج",
"reset": "بازنشانی",
"done": "انجام شد",
"search": "جستجو",
"decline": "رد کردن",
"agree": "موافقم",
"empty": "خالی",
"unknown": "ناشناخته",
"hidden": "پنهان",
"timeout": "عدم پاسخ",
"sort": "مرتبسازی",
"dashboard": "داشبورد",
"interval": {
"day": {
"zero": "",
"one": "$n روز",
"other": "$n روز"
},
"hour": {
"zero": "",
"one": "$n ساعت",
"other": "$n ساعت"
}
},
"msg": {
"permission": {
"denied": "دسترسی رد شد"
},
"export": {
"clipboard": {
"success": "با موفقیت در کلیپبورد کپی شد",
"failure": "کپی در کلیپبورد ناموفق بود",
"contentTooLarge": "محتوا بیش از حد بزرگ است. به جای آن از خروجی فایل استفاده کنید"
},
"file": {
"success": "فایل JSON با موفقیت ایجاد شد",
"failure": "ایجاد فایل ناموفق بود"
}
},
"import": {
"confirm": "تأیید ورود اطلاعات",
"success": "با موفقیت وارد شد",
"failure": "وارد کردن ناموفق بود"
}
}
},
"intro": {
"banner": "هرآنچه برای اینترنت بدون محدودیت نیاز دارید",
"termsAndPolicyCaution(rich)": "با ادامه، شما با ${tap(@:pages.about.termsAndConditions)} موافقت میکنید",
"info(rich)": "ساخته شده با ❤️ توسط هیدیفای - ${tap_source(متنباز)} (${tap_license(مجوز)})"
},
"pages": {
"home": {
"title": "خانه",
"quickSettings": "تنظیمات سریع"
},
"proxies": {
"title": "پروکسیها",
"sort": "مرتبسازی پروکسیها",
"testDelay": "تست تأخیر",
"empty": "هیچ پروکسی موجود نیست",
"activeProxy": "پروکسی فعال",
"unknownIp": "IP ناشناخته",
"sortOptions": {
"unsorted": "پیشفرض",
"name": "بر اساس نام",
"delay": "بر اساس تأخیر"
},
"ipInfo": {
"address": "آدرس IP",
"country": "کشور",
"organization": "سازمان"
},
"delay": {
"result": "تأخیر: ${delay} میلیثانیه",
"timeout": "تست تأخیر ناموفق بود",
"testing": "تأخیر: در حال تست..."
}
},
"profiles": {
"title": "پروفایلها",
"add": "افزودن پروفایل",
"update": "بهروزرسانی پروفایل",
"viewAllProfiles": "مشاهده همه پروفایلها",
"activeProfileName": "نام پروفایل فعال: «${name}»",
"nonActiveProfileName": "انتخاب «${name}» به عنوان پروفایل فعال",
"freeSubNotFound": "اشتراک رایگان یافت نشد",
"freeSubNotFoundForRegion": "اشتراک رایگان برای منطقه «${region}» یافت نشد",
"failedToLoad": "بارگذاری ناموفق بود",
"updateSubscriptions": "بهروزرسانی اشتراکها",
"share": {
"urlToClipboard": "URL به کلیپبورد",
"showUrlQr": "نمایش QR کد URL",
"jsonToClipboard": "JSON به کلیپبورد"
},
"msg": {
"save": {
"success": "پروفایل با موفقیت ذخیره شد"
},
"invalidUrl": "URL نامعتبر است",
"add": {
"failure": "افزودن پروفایل ناموفق بود"
},
"update": {
"success": "پروفایل با موفقیت بهروز شد",
"successNamed": "«${name}» با موفقیت بهروز شد",
"failure": "بهروزرسانی پروفایل ناموفق بود",
"failureNamed": "بهروزرسانی «${name}» ناموفق بود"
},
"delete": {
"success": "پروفایل با موفقیت حذف شد"
}
}
},
"profileDetails": {
"title": "پروفایل",
"lastUpdate": "آخرین بهروزرسانی",
"form": {
"nameHint": "نام پروفایل",
"emptyName": "نام الزامی است",
"invalidUrl": "URL نامعتبر است",
"urlHint": "URL کامل کانفیگ",
"disableAutoUpdate": "غیرفعال کردن بهروزرسانی خودکار",
"autoUpdateInterval": "فاصله زمانی بهروزرسانی خودکار",
"loading": "در حال افزودن پروفایل..."
}
},
"logs": {
"title": "گزارشها",
"shareCoreLogs": "اشتراکگذاری گزارشهای هسته",
"shareAppLogs": "اشتراکگذاری گزارشهای برنامه"
},
"about": {
"title": "درباره",
"notAvailableMsg": "شما در حال استفاده از آخرین نسخه هستید",
"checkForUpdate": "بررسی برای بهروزرسانی",
"openWorkingDir": "باز کردن پوشه کاری",
"sourceCode": "کد منبع",
"telegramChannel": "کانال تلگرام",
"termsAndConditions": "شرایط و ضوابط",
"privacyPolicy": "سیاست حفظ حریم خصوصی"
},
"settings": {
"title": "تنظیمات",
"resetTunnel": "بازنشانی پروفایل VPN",
"options": {
"import": {
"clipboard": "وارد کردن تنظیمات از کلیپبورد",
"file": "وارد کردن تنظیمات از فایل"
},
"export": {
"anonymousToClipboard": "کپی تنظیمات ناشناس به کلیپبورد",
"anonymousToFile": "خروجی تنظیمات ناشناس به فایل",
"allToClipboard": "کپی تمام تنظیمات به کلیپبورد",
"allToFile": "خروجی تمام تنظیمات به فایل"
},
"reset": "بازنشانی تنظیمات"
},
"general": {
"title": "عمومی",
"locale": "زبان",
"themeMode": "حالت پوسته",
"themeModes": {
"system": "پیشفرض سیستم",
"dark": "حالت تیره",
"light": "حالت روشن",
"black": "حالت مشکی"
},
"enableAnalytics": "فعالسازی آمار",
"enableAnalyticsMsg": "اجازه جمعآوری آمار و ارسال گزارشهای خطا برای بهبود برنامه",
"autoIpCheck": "بررسی خودکار IP اتصال",
"dynamicNotification": "نمایش سرعت در اعلان",
"hapticFeedback": "بازخورد لرزشی",
"actionAtClosing": "اقدام هنگام بستن",
"autoStart": "شروع هنگام ورود به سیستم",
"silentStart": "شروع در حالت کمینه",
"ignoreBatteryOptimizations": "غیرفعال کردن بهینهسازی باتری",
"ignoreBatteryOptimizationsMsg": "حذف محدودیتها برای عملکرد بهینه VPN",
"memoryLimit": "محدودیت حافظه",
"memoryLimitMsg": "در صورت مواجهه با خطای کمبود حافظه یا بستهشدنهای مکرر برنامه، این گزینه را فعال کنید",
"debugMode": "حالت اشکالزدایی",
"debugModeMsg": "برای اعمال این تغییر، برنامه را مجدداً راهاندازی کنید",
"logLevel": "سطح گزارشگیری",
"connectionTestUrl": "URL تست اتصال",
"urlTestInterval": "فاصله زمانی تست URL",
"clashApiPort": "پورت Clash API",
"useXrayCoreWhenPossible": "استفاده از xray-core در صورت امکان",
"useXrayCoreWhenPossibleMsg": "هنگام پردازش لینکهای اشتراک از xray-core استفاده شود. برای فعالسازی این گزینه باید لینک اشتراک را مجدداً وارد کنید."
},
"routing": {
"title": "مسیریابی",
"perAppProxy": {
"title": "پروکسی برای هر برنامه",
"hideSysApps": "پنهان کردن برنامههای سیستمی",
"options": {
"import": {
"clipboard": "وارد کردن انتخابها از کلیپبورد",
"file": "وارد کردن انتخابها از فایل",
"msg": "وارد کردن، انتخابهای فعلی شما را جایگزین خواهد کرد. آیا مطمئن هستید که میخواهید ادامه دهید؟"
},
"export": {
"clipboard": "کپی انتخابها به کلیپبورد",
"file": "خروجی انتخابها به فایل"
},
"shareToAll": "اشتراکگذاری با همه",
"clearAllSelections": "پاک کردن تمام انتخابها"
},
"modes": {
"all": "همه",
"proxy": "پروکسی",
"bypass": "دور زدن",
"allMsg": "پروکسی کردن تمام برنامهها",
"proxyMsg": "فقط برنامههای انتخابشده پروکسی شوند",
"bypassMsg": "برنامههای انتخابشده پروکسی نشوند"
},
"autoSelection": {
"title": "انتخاب خودکار",
"performNow": "اکنون انجام بده",
"resetToDefault": "بازنشانی به پیشفرض",
"autoUpdateInterval": "فاصله زمانی بهروزرسانی خودکار",
"toast": {
"success": "انتخاب خودکار برنامهها با موفقیت انجام شد",
"failure": "انتخاب خودکار ناموفق بود",
"regionNotFound": "انتخاب خودکار برای منطقه «${region}» یافت نشد",
"alreadyInAuto": "انتخابهای شما از قبل در لیست خودکار موجود است"
},
"dialog": {
"title": "انتخاب خودکار برنامهها",
"msg": "قابلیت انتخاب خودکار برای پروکسی هر برنامه به دلیل تغییر منطقه به «${region}» غیرفعال شد"
}
}
},
"region": "منطقه",
"regions": {
"ir": "ایران (ir)",
"cn": "چین (cn)",
"ru": "روسیه (ru)",
"af": "افغانستان (af)",
"id": "اندونزی (id)",
"tr": "ترکیه (tr)",
"br": "برزیل (br)",
"other": "سایر"
},
"balancerStrategy": {
"title": "استراتژی Balancer",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "مسدودسازی تبلیغات",
"bypassLan": "دور زدن LAN",
"resolveDestination": "تحلیل آدرس مقصد",
"ipv6Route": "مسیریابی IPv6",
"ipv6Modes": {
"disable": "غیرفعال",
"enable": "فعال",
"prefer": "ترجیحی",
"only": "انحصاری"
},
"routeRule": {
"title": "قوانین مسیریابی",
"options": {
"import": {
"clipboard": "وارد کردن قوانین از کلیپبورد",
"file": "وارد کردن قوانین از فایل"
},
"export": {
"clipboard": "کپی قوانین در کلیپبورد",
"file": "ذخیره قوانین در فایل"
},
"reset": "بازنشانی قوانین"
},
"deleteRule": "حذف قانون",
"createRule": "ایجاد قانون جدید",
"rule": {
"title": "قانون",
"ruleChanged": "قانون تغییر کرد",
"ruleChangedMsg": "آیا میخواهید ویرایشهای خود را ذخیره کنید؟",
"onlyTunMode": "فقط در حالت TUN در دسترس است",
"notAvailabeInThisPlatform": "در این پلتفرم در دسترس نیست",
"canNotBeEmpty": "نمیتواند خالی باشد",
"validUrlEx": "https://example.com",
"validUrl": "«URL» معتبر مانند\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe یا google chrome یا chrome",
"validProcessName": "«نام فرآیند» معتبر مانند\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "«مسیر فرآیند» معتبر مانند\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 یا 1-65000",
"validPortRange": "«پورت» یا «محدوده پورت» معتبر مانند\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 یا 10.0.0.0/24",
"validIpCidr": "IP CIDR معتبر مانند\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com یا dl.google.com",
"validDomain": "«دامنه» معتبر مانند\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com یا .ir",
"validDomainSuffix": "«پسوند دامنه» معتبر مانند\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "نام",
"outbound": "خروجی در صورت تطابق",
"rule_set": "URL مجموعه قوانین",
"package_name": "نامهای بسته",
"process_name": "نامهای فرآیند",
"process_path": "مسیرهای فرآیند",
"network": "شبکهها",
"port_range": "پورتهای مقصد",
"source_port_range": "پورتهای مبدأ",
"protocol": "پروتکل",
"ip_cidr": "IP CIDR مقصد",
"source_ip_cidr": "IP CIDR مبدأ",
"domain": "دامنه",
"domain_suffixe": "پسوند دامنه",
"domain_keyword": "کلمه کلیدی دامنه",
"domain_regex": "عبارت منظم دامنه"
},
"outbound(map)": {
"proxy": "پروکسی",
"direct": "مستقیم",
"direct_with_fragment": "مستقیم با فرگمنت",
"block": "مسدود"
},
"network(map)": {
"all": "همه",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "همه",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "افزودن مقدار جدید",
"update": "بهروزرسانی مقدار",
"clearList": "پاک کردن لیست",
"clearListMsg": "تمام موارد حذف شدند"
},
"androidApps": {
"pageTitle": "برنامههای اندروید",
"showSystemApps": "نمایش برنامههای سیستمی",
"hideSystemApps": "پنهان کردن برنامههای سیستمی",
"clearSelection": "پاک کردن انتخابها",
"uninstalled": "نصب نشده"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "DNS اصلی (ریموت)",
"remoteDnsDomainStrategy": "استراتژی دامنه DNS",
"directDns": "DNS پروکسیها (مستقیم)",
"directDnsDomainStrategy": "استراتژی دامنه DNS مستقیم",
"enableDnsRouting": "فعالسازی مسیریابی DNS",
"enableFakeDns": "فعالسازی DNS جعلی",
"domainStrategy": {
"auto": "خودکار",
"preferIpv6": "ترجیح IPv6",
"preferIpv4": "ترجیح IPv4",
"ipv4Only": "فقط IPv4",
"ipv6Only": "فقط IPv6"
}
},
"inbound": {
"title": "ورودی",
"serviceMode": "حالت سرویس",
"serviceModes": {
"proxy": "فقط سرویس پروکسی",
"systemProxy": "تنظیم پروکسی سیستم",
"tun": "VPN",
"tunService": "سرویس VPN"
},
"shortServiceModes": {
"proxy": "پروکسی",
"systemProxy": "پروکسی سیستم",
"tun": "VPN",
"tunService": "سرویس VPN"
},
"strictRoute": "مسیربندی سختگیرانه",
"tunImplementation": "پیادهسازی Tun",
"tunImplementations": {
"mixed": "ترکیبی",
"system": "سیستم",
"gvisor": "gVisor"
},
"mixedPort": "پورت ترکیبی",
"tproxyPort": "پورت پروکسی شفاف",
"directPort": "پورت مستقیم",
"redirectPort": "پورت تغییر مسیر",
"allowConnectionFromLan": "اشتراک VPN در شبکه محلی"
},
"tlsTricks": {
"title": "ترفندهای TLS",
"enable": "فعالسازی fragment",
"packets": "بستههای Fragment",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "اندازه fragment",
"sleep": "تأخیر fragment",
"mixedSniCase": {
"enable": "فعالسازی mixed SNI case"
},
"padding": {
"enable": "فعالسازی padding",
"size": "اندازه padding"
}
},
"warp": {
"title": "WARP",
"enable": "فعالسازی WARP",
"generateConfig": "ایجاد کانفیگ WARP",
"configGenerated": "کانفیگ Warp ایجاد شد",
"missingConfig": "کانفیگ WARP موجود نیست",
"detourMode": "حالت مسیریابی WARP",
"detourModes": {
"proxyOverWarp": "عبور پروکسیها از طریق WARP",
"warpOverProxy": "عبور WARP از طریق پروکسیها",
"proxyOverWarpExplain": "رفع انسداد پروکسیها با WARP",
"warpOverProxyExplain": "امنیت بیشتر با WARP"
},
"licenseKey": "کلید لایسنس",
"cleanIp": "IP تمیز",
"port": "پورت",
"noise": {
"count": "تعداد نویز",
"mode": "حالت نویز",
"size": "اندازه نویز",
"delay": "تأخیر نویز"
}
}
}
},
"components": {
"stats": {
"connection": "اتصال",
"traffic": "ترافیک",
"trafficLive": "ترافیک لحظهای",
"trafficTotal": "ترافیک کل",
"uplink": "ارسال",
"downlink": "دریافت",
"speed": "سرعت",
"totalTransferred": "کل منتقل شده"
},
"subscriptionInfo": {
"upload": "آپلود",
"download": "دانلود",
"total": "کل ترافیک",
"expireDate": "تاریخ انقضا",
"expired": "منقضی شده",
"noTraffic": "حجم تمام شده",
"remainingTime": "زمان باقیمانده",
"remainingDuration": "${duration} روز باقیمانده",
"remainingDurationNew": "${duration} روز",
"remainingTrafficSemanticLabel": "${consumed} از ${total} ترافیک مصرف شده",
"remainingTraffic": "ترافیک باقیمانده",
"remainingUsage": "باقیمانده",
"profileSite": "سرویسدهنده",
"profileSupport": "پشتیبانی"
}
},
"dialogs": {
"sortProfiles": {
"title": "مرتبسازی بر اساس",
"sort": {
"name": "بر اساس نام",
"lastUpdate": "آخرین بهروزرسانی"
}
},
"warpLicense": {
"title": "رضایتنامه Cloudflare WARP",
"description(rich)": "Cloudflare WARP یک ارائهدهنده رایگان WireGuard VPN است. با فعال کردن این گزینه، شما با ${tos(شرایط خدمات)} و ${privacy(سیاست حفظ حریم خصوصی)} Cloudflare WARP موافقت میکنید."
},
"newVersion": {
"title": "بهروزرسانی موجود است",
"msg": "نسخه جدیدی از @:common.appTitle در دسترس است. آیا مایل به بهروزرسانی هستید؟",
"currentVersion": "نسخه فعلی: ",
"newVersion": "نسخه جدید: ",
"updateNow": "اکنون بهروزرسانی کن"
},
"confirmation": {
"settings": {
"import": {
"msg": "این عمل تمام تنظیمات فعلی شما را بازنویسی خواهد کرد. آیا مطمئن هستید؟"
}
},
"profile": {
"delete": {
"title": "حذف پروفایل",
"msg": "آیا از حذف دائمی این پروفایل مطمئن هستید؟"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "بهبود انتخاب خودکار",
"msg": "با اشتراکگذاری برنامههای انتخابشده، به تکمیل لیست «انتخاب خودکار» کمک میکنید"
},
"import": {
"msg": "این عمل تمام انتخابهای فعلی شما برای پروکسی برنامهها را جایگزین خواهد کرد. آیا مطمئن هستید؟"
}
},
"routeRule": {
"delete": {
"title": "حذف قانون",
"msg": "آیا از حذف قانون «$rulename» مطمئن هستید؟"
}
}
},
"experimentalNotice": {
"title": "ویژگیهای آزمایشی در حال استفاده",
"msg": "شما برخی ویژگیهای آزمایشی را فعال کردهاید که ممکن است بر کیفیت اتصال تأثیر گذاشته و باعث خطاهای غیرمنتظره شوند. همیشه میتوانید این گزینهها را از صفحه تنظیمات تغییر داده یا بازنشانی کنید.",
"disable": "دیگر نمایش نده"
},
"noActiveProfile": {
"title": "یک پروفایل انتخاب کنید",
"msg": "بیایید با افزودن یک پروفایل اتصال که شامل جزئیات اتصال VPN شماست، شروع کنیم.\n\nهنوز سرور VPN ندارید؟ نگران نباشید، با دنبال کردن راهنمای زیر میتوانید به سرعت و رایگان یکی برای خودتان بسازید.",
"helpBtn": {
"label": "راهنمایی کن",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "هشدار لینک خارجی",
"youAreAboutToVisit": "شما در حال بازدید از این آدرس هستید:",
"thisWebsiteIsNotInOurTrustedList": "این وبسایت در لیست مورد اعتماد ما نیست. لطفاً با احتیاط ادامه دهید."
},
"proxyInfo": {
"fullTag": "تگ کامل:",
"type": "نوع:",
"testTime": "زمان تست:",
"testDelay": "تأخیر تست:",
"isSelected": "انتخاب شده:",
"isGroup": "گروه است",
"isSecure": "امن است:",
"port": "پورت:",
"host": "میزبان:",
"ip": "IP:",
"countryCode": "کد کشور:",
"region": "منطقه:",
"city": "شهر:",
"asn": "ASN:",
"organization": "سازمان:",
"location": "مکان:",
"postalCode": "کد پستی:"
},
"windowClosing": {
"askEachTime": "هر بار بپرس",
"alertMessage": "پنهان کردن یا خروج از برنامه؟",
"remember": "انتخابم را به خاطر بسپار"
}
},
"connection": {
"tapToConnect": "برای اتصال ضربه بزنید",
"connect": "اتصال",
"connecting": "در حال اتصال...",
"connected": "متصل شد",
"disconnect": "قطع اتصال",
"disconnecting": "در حال قطع اتصال...",
"reconnect": "اتصال مجدد",
"reconnectMsg": "در حال اتصال مجدد برای اعمال تغییرات...",
"secure": "ایمن شده با WARP"
},
"errors": {
"unexpected": "خطای غیرمنتظره",
"connection": {
"unexpected": "خطای غیرمنتظره در اتصال",
"timeout": "اتصال با وقفه زمانی مواجه شد",
"badResponse": "پاسخ نامعتبر",
"connectionError": "خطای اتصال",
"badCertificate": "گواهینامه نامعتبر"
},
"profiles": {
"unexpected": "خطای غیرمنتظره",
"notFound": "پروفایل یافت نشد",
"invalidConfig": "کانفیگهای نامعتبر",
"invalidUrl": "URL نامعتبر",
"canceledByUser": "توسط کاربر لغو شد"
},
"connectivity": {
"unexpected": "شکست غیرمنتظره",
"missingVpnPermission": "مجوز VPN وجود ندارد",
"missingNotificationPermission": "مجوز اعلان وجود ندارد",
"core": "خطای هسته"
},
"singbox": {
"serviceNotRunning": "سرویس در حال اجرا نیست",
"missingPrivilege": "دسترسی وجود ندارد",
"missingPrivilegeMsg": "حالت VPN به دسترسی ادمین نیاز دارد. یا برنامه را به عنوان ادمین مجدداً راهاندازی کنید یا حالت سرویس را تغییر دهید.",
"invalidConfigOptions": "گزینههای پیکربندی نامعتبر",
"invalidConfig": "پیکربندی نامعتبر"
},
"warp": {
"missingLicense": "لایسنس Warp",
"missingLicenseMsg": "پروفایل انتخابشده از ویژگی WARP استفاده میکند؛ برای استفاده از این قابلیت، باید با لایسنس WARP موافقت شود."
}
}
}
================================================
FILE: assets/translations/fr.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "Commencer",
"version": "Version",
"ok": "OK",
"cancel": "Annuler",
"kContinue": "Continuer",
"showMore": "Afficher plus",
"showLess": "Afficher moins",
"filter": "Filtrer",
"all": "Tous",
"pause": "Pause",
"resume": "Reprendre",
"clear": "Effacer",
"close": "Fermer",
"auto": "Automatique",
"manually": "Manuellement",
"name": "Nom",
"url": "URL",
"add": "Ajouter",
"clipboard": "Presse-papiers",
"addToClipboard": "Ajouter au presse-papiers",
"scanQr": "Scanner le QR code",
"free": "Gratuit",
"warp": "WARP",
"fragment": "Fragment",
"help": "Aide",
"save": "Enregistrer",
"update": "Mettre à jour",
"share": "Partager",
"edit": "Modifier",
"delete": "Supprimer",
"discard": "Ignorer",
"import": "Importer",
"export": "Exporter",
"later": "Plus tard",
"ignore": "Ignorer",
"quit": "Quitter",
"notSet": "Non défini",
"hide": "Masquer",
"exit": "Quitter",
"reset": "Réinitialiser",
"done": "Terminé",
"search": "Rechercher",
"decline": "Refuser",
"agree": "Accepter",
"empty": "Vide",
"unknown": "Inconnu",
"hidden": "Caché",
"timeout": "Délai expiré",
"sort": "Trier",
"dashboard": "Tableau de bord",
"interval": {
"day": {
"zero": "",
"one": "$n jour",
"other": "$n jours"
},
"hour": {
"zero": "",
"one": "$n heure",
"other": "$n heures"
}
},
"msg": {
"permission": {
"denied": "Permission refusée"
},
"export": {
"clipboard": {
"success": "Ajouté au presse-papiers avec succès",
"failure": "Échec de la copie dans le presse-papiers",
"contentTooLarge": "Contenu trop volumineux. Utilisez plutôt l'exportation de fichier"
},
"file": {
"success": "Fichier JSON créé avec succès",
"failure": "Échec de la création du fichier"
}
},
"import": {
"confirm": "Confirmer l'importation",
"success": "Importé avec succès",
"failure": "Échec de l'importation"
}
}
},
"intro": {
"banner": "Tout ce dont vous avez besoin pour un internet sans restrictions",
"termsAndPolicyCaution(rich)": "En continuant, vous acceptez les ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "Fait avec ❤️ par Hiddify - ${tap_source(Open Source)} (${tap_license(Licence)})"
},
"pages": {
"home": {
"title": "Accueil",
"quickSettings": "Réglages rapides"
},
"proxies": {
"title": "Proxys",
"sort": "Trier les proxys",
"testDelay": "Tester le délai",
"empty": "Aucun proxy disponible",
"activeProxy": "Proxy actif",
"unknownIp": "IP inconnue",
"sortOptions": {
"unsorted": "Par défaut",
"name": "Alphabétiquement",
"delay": "Par latence"
},
"ipInfo": {
"address": "Adresse IP",
"country": "Pays",
"organization": "Organisation"
},
"delay": {
"result": "Latence : ${delay} ms",
"timeout": "Délai du test de latence expiré",
"testing": "Latence : test en cours..."
}
},
"profiles": {
"title": "Profils",
"add": "Ajouter un profil",
"update": "Mettre à jour le profil",
"viewAllProfiles": "Voir tous les profils",
"activeProfileName": "Nom du profil actif : \"${name}\".",
"nonActiveProfileName": "Sélectionner \"${name}\" comme profil actif",
"freeSubNotFound": "Aucun abonnement gratuit trouvé",
"freeSubNotFoundForRegion": "Aucun abonnement gratuit trouvé pour la région \"${region}\"",
"failedToLoad": "Échec du chargement",
"updateSubscriptions": "Mettre à jour les abonnements",
"share": {
"urlToClipboard": "URL dans le presse-papiers",
"showUrlQr": "Afficher le QR code de l'URL",
"jsonToClipboard": "JSON dans le presse-papiers"
},
"msg": {
"save": {
"success": "Profil enregistré avec succès"
},
"invalidUrl": "URL invalide",
"add": {
"failure": "Échec de l'ajout du profil"
},
"update": {
"success": "Profil mis à jour avec succès",
"successNamed": "\"${name}\" a été mis à jour avec succès",
"failure": "Échec de la mise à jour du profil",
"failureNamed": "Échec de la mise à jour de \"${name}\""
},
"delete": {
"success": "Profil supprimé avec succès"
}
}
},
"profileDetails": {
"title": "Profil",
"lastUpdate": "Dernière mise à jour",
"form": {
"nameHint": "Nom du profil",
"emptyName": "Le nom est requis",
"invalidUrl": "URL invalide",
"urlHint": "URL complète de la configuration",
"disableAutoUpdate": "Désactiver la mise à jour automatique",
"autoUpdateInterval": "Intervalle de mise à jour automatique",
"loading": "Ajout du profil..."
}
},
"logs": {
"title": "Journaux",
"shareCoreLogs": "Partager les journaux du noyau",
"shareAppLogs": "Partager les journaux de l'application"
},
"about": {
"title": "À propos",
"notAvailableMsg": "Vous utilisez déjà la dernière version",
"checkForUpdate": "Vérifier les mises à jour",
"openWorkingDir": "Ouvrir le répertoire de travail",
"sourceCode": "Code source",
"telegramChannel": "Canal Telegram",
"termsAndConditions": "Conditions d'utilisation",
"privacyPolicy": "Politique de confidentialité"
},
"settings": {
"title": "Paramètres",
"resetTunnel": "Réinitialiser le profil VPN",
"options": {
"import": {
"clipboard": "Importer les options depuis le presse-papiers",
"file": "Importer les options depuis un fichier"
},
"export": {
"anonymousToClipboard": "Copier les options anonymes dans le presse-papiers",
"anonymousToFile": "Exporter les options anonymes vers un fichier",
"allToClipboard": "Copier toutes les options dans le presse-papiers",
"allToFile": "Exporter toutes les options vers un fichier"
},
"reset": "Réinitialiser les options"
},
"general": {
"title": "Général",
"locale": "Langue",
"themeMode": "Thème",
"themeModes": {
"system": "Thème du système par défaut",
"dark": "Mode sombre",
"light": "Mode clair",
"black": "Mode noir"
},
"enableAnalytics": "Activer les statistiques",
"enableAnalyticsMsg": "Autoriser la collecte de statistiques et de rapports d'erreurs pour améliorer l'application",
"autoIpCheck": "Vérifier automatiquement l'IP de connexion",
"dynamicNotification": "Afficher la vitesse dans la notification",
"hapticFeedback": "Retour haptique",
"actionAtClosing": "Action à la fermeture",
"autoStart": "Lancer au démarrage",
"silentStart": "Démarrer réduit",
"ignoreBatteryOptimizations": "Désactiver l'optimisation de la batterie",
"ignoreBatteryOptimizationsMsg": "Supprimer les restrictions pour une performance VPN optimale",
"memoryLimit": "Limite de mémoire",
"memoryLimitMsg": "Activer si vous rencontrez des erreurs de mémoire insuffisante ou des plantages fréquents de l'application",
"debugMode": "Mode de débogage",
"debugModeMsg": "Redémarrez l'application pour appliquer cette modification",
"logLevel": "Niveau de journalisation",
"connectionTestUrl": "URL de test de connexion",
"urlTestInterval": "Intervalle de test de l'URL",
"clashApiPort": "Port de l'API Clash",
"useXrayCoreWhenPossible": "Utiliser xray-core si possible",
"useXrayCoreWhenPossibleMsg": "Utilisez xray-core lors de l'analyse des liens d'abonnement. Vous devez réimporter le lien pour activer cette option"
},
"routing": {
"title": "Routage",
"perAppProxy": {
"title": "Proxy par application",
"hideSysApps": "Masquer les applications système",
"options": {
"import": {
"clipboard": "Importer la sélection depuis le presse-papiers",
"file": "Importer la sélection depuis un fichier",
"msg": "L'importation remplacera vos sélections actuelles. Êtes-vous sûr de vouloir continuer ?"
},
"export": {
"clipboard": "Copier la sélection dans le presse-papiers",
"file": "Exporter la sélection vers un fichier"
},
"shareToAll": "Partager avec tous",
"clearAllSelections": "Effacer toutes les sélections"
},
"modes": {
"all": "Toutes",
"proxy": "Proxy",
"bypass": "Contourner",
"allMsg": "Utiliser le proxy pour toutes les applications",
"proxyMsg": "Utiliser le proxy uniquement pour les applications sélectionnées",
"bypassMsg": "Ne pas utiliser le proxy pour les applications sélectionnées"
},
"autoSelection": {
"title": "Sélection automatique",
"performNow": "Exécuter maintenant",
"resetToDefault": "Réinitialiser par défaut",
"autoUpdateInterval": "Intervalle de mise à jour automatique",
"toast": {
"success": "Sélection automatique des applications terminée avec succès",
"failure": "Échec de la sélection automatique",
"regionNotFound": "Sélection automatique non trouvée pour la région \"${region}\"",
"alreadyInAuto": "Vos sélections sont déjà dans la liste automatique"
},
"dialog": {
"title": "Sélection automatique des applications",
"msg": "La fonction de sélection automatique pour le proxy par application a été désactivée en raison du changement de région vers \"${region}\""
}
}
},
"region": "Région",
"regions": {
"ir": "Iran (ir)",
"cn": "Chine (cn)",
"ru": "Russie (ru)",
"af": "Afghanistan (af)",
"id": "Indonésie (id)",
"tr": "Turquie (tr)",
"br": "Brésil (br)",
"other": "Autre"
},
"balancerStrategy": {
"title": "Stratégie de Balancer",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "Bloquer les publicités",
"bypassLan": "Contourner le LAN",
"resolveDestination": "Résoudre la destination",
"ipv6Route": "Route IPv6",
"ipv6Modes": {
"disable": "Désactiver",
"enable": "Activer",
"prefer": "Préféré",
"only": "Uniquement"
},
"routeRule": {
"title": "Règles de routage",
"options": {
"import": {
"clipboard": "Importer les règles depuis le presse-papiers",
"file": "Importer les règles depuis un fichier"
},
"export": {
"clipboard": "Copier les règles dans le presse-papiers",
"file": "Enregistrer les règles dans un fichier"
},
"reset": "Réinitialiser les règles"
},
"deleteRule": "Supprimer la règle",
"createRule": "Créer une nouvelle règle",
"rule": {
"title": "Règle",
"ruleChanged": "Règle modifiée",
"ruleChangedMsg": "Voulez-vous enregistrer vos modifications ?",
"onlyTunMode": "Disponible uniquement en mode TUN",
"notAvailabeInThisPlatform": "Non disponible sur cette plateforme",
"canNotBeEmpty": "Ne peut pas être vide",
"validUrlEx": "https://example.com",
"validUrl": "\"URL\" valide comme\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe ou google chrome ou chrome",
"validProcessName": "\"Nom de processus\" valide comme\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "\"Chemin de processus\" valide comme\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 ou 1-65000",
"validPortRange": "\"Port\" ou \"Plage de ports\" valide comme\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 ou 10.0.0.0/24",
"validIpCidr": "IP CIDR valide comme\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com ou dl.google.com",
"validDomain": "\"Domaine\" valide comme\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com ou .ir",
"validDomainSuffix": "\"Suffixe de domaine\" valide comme\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "Nom",
"outbound": "Sortie si correspondance",
"rule_set": "URL de l'ensemble de règles",
"package_name": "Noms de paquets",
"process_name": "Noms de processus",
"process_path": "Chemins de processus",
"network": "Réseaux",
"port_range": "Ports de destination",
"source_port_range": "Ports source",
"protocol": "Protocole",
"ip_cidr": "IP CIDR de destination",
"source_ip_cidr": "IP CIDR source",
"domain": "Domaine",
"domain_suffixe": "Suffixe de domaine",
"domain_keyword": "Mot-clé de domaine",
"domain_regex": "Expression régulière de domaine"
},
"outbound(map)": {
"proxy": "Proxy",
"direct": "Direct",
"direct_with_fragment": "Direct avec fragment",
"block": "Bloquer"
},
"network(map)": {
"all": "Tous",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "Tous",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "Ajouter une nouvelle valeur",
"update": "Mettre à jour la valeur",
"clearList": "Vider la liste",
"clearListMsg": "Tous les éléments ont été supprimés"
},
"androidApps": {
"pageTitle": "Applications Android",
"showSystemApps": "Afficher les applications système",
"hideSystemApps": "Masquer les applications système",
"clearSelection": "Effacer la sélection",
"uninstalled": "Désinstallé"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "DNS distant",
"remoteDnsDomainStrategy": "Stratégie de domaine DNS distant",
"directDns": "Résolveur de serveur sortant (direct)",
"directDnsDomainStrategy": "Stratégie de domaine sortant",
"enableDnsRouting": "Activer le routage DNS",
"enableFakeDns": "Activer le faux DNS",
"domainStrategy": {
"auto": "Auto",
"preferIpv6": "Préférer IPv6",
"preferIpv4": "Préférer IPv4",
"ipv4Only": "IPv4 uniquement",
"ipv6Only": "IPv6 uniquement"
}
},
"inbound": {
"title": "Entrant",
"serviceMode": "Mode de service",
"serviceModes": {
"proxy": "Service proxy uniquement",
"systemProxy": "Définir le proxy système",
"tun": "VPN",
"tunService": "Service VPN"
},
"shortServiceModes": {
"proxy": "Proxy",
"systemProxy": "Proxy système",
"tun": "VPN",
"tunService": "Service VPN"
},
"strictRoute": "Routage strict",
"tunImplementation": "Implémentation TUN",
"tunImplementations": {
"mixed": "Mixte",
"system": "Système",
"gvisor": "gVisor"
},
"mixedPort": "Port mixte",
"tproxyPort": "Port de proxy transparent",
"directPort": "Port direct local",
"redirectPort": "Port de redirection",
"allowConnectionFromLan": "Partager le VPN sur le réseau local"
},
"tlsTricks": {
"title": "Astuces TLS",
"enable": "Activer le fragment",
"packets": "Paquets de fragmentation",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "Taille du fragment",
"sleep": "Délai du fragment",
"mixedSniCase": {
"enable": "Activer la casse mixte pour SNI"
},
"padding": {
"enable": "Activer le remplissage",
"size": "Taille du remplissage"
}
},
"warp": {
"title": "WARP",
"enable": "Activer WARP",
"generateConfig": "Générer la configuration WARP",
"configGenerated": "Configuration WARP générée",
"missingConfig": "Configuration WARP manquante",
"detourMode": "Mode de détour",
"detourModes": {
"proxyOverWarp": "Détourner les proxys via WARP",
"warpOverProxy": "Détourner WARP via les proxys",
"proxyOverWarpExplain": "Débloquer les proxys avec WARP",
"warpOverProxyExplain": "Sécurité supplémentaire avec WARP"
},
"licenseKey": "Clé de licence",
"cleanIp": "IP propre",
"port": "Port",
"noise": {
"count": "Nombre de bruits",
"mode": "Mode bruit",
"size": "Taille du bruit",
"delay": "Délai du bruit"
}
}
}
},
"components": {
"stats": {
"connection": "Connexion",
"traffic": "Trafic",
"trafficLive": "Trafic en direct",
"trafficTotal": "Trafic total",
"uplink": "Envoi",
"downlink": "Réception",
"speed": "Vitesse",
"totalTransferred": "Total transféré"
},
"subscriptionInfo": {
"upload": "Envoi",
"download": "Téléchargement",
"total": "Trafic total",
"expireDate": "Date d'expiration",
"expired": "Expiré",
"noTraffic": "Quota épuisé",
"remainingTime": "Temps restant",
"remainingDuration": "${duration} jours restants",
"remainingDurationNew": "${duration} jours",
"remainingTrafficSemanticLabel": "${consumed} sur ${total} de trafic consommé",
"remainingTraffic": "Trafic restant",
"remainingUsage": "Restant",
"profileSite": "Fournisseur",
"profileSupport": "Support"
}
},
"dialogs": {
"sortProfiles": {
"title": "Trier par",
"sort": {
"name": "Alphabétiquement",
"lastUpdate": "Dernière mise à jour"
}
},
"warpLicense": {
"title": "Consentement Cloudflare WARP",
"description(rich)": "Cloudflare WARP est un fournisseur VPN WireGuard gratuit. En activant cette option, vous acceptez les ${tos(Conditions d'utilisation)} et la ${privacy(Politique de confidentialité)} de Cloudflare WARP."
},
"newVersion": {
"title": "Mise à jour disponible",
"msg": "Une nouvelle version de @:common.appTitle est disponible. Voulez-vous mettre à jour maintenant ?",
"currentVersion": "Version actuelle : ",
"newVersion": "Nouvelle version : ",
"updateNow": "Mettre à jour"
},
"confirmation": {
"settings": {
"import": {
"msg": "Cette action remplacera toutes les options de configuration par les valeurs fournies. Êtes-vous sûr ?"
}
},
"profile": {
"delete": {
"title": "Supprimer le profil",
"msg": "Êtes-vous sûr de vouloir supprimer ce profil définitivement ?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "Améliorer la sélection automatique",
"msg": "En partageant les applications sélectionnées, vous aidez à compléter la liste de \"sélection automatique\""
},
"import": {
"msg": "Cette action remplacera toutes vos sélections actuelles de proxy par application. Êtes-vous sûr de vouloir continuer ?"
}
},
"routeRule": {
"delete": {
"title": "Supprimer la règle",
"msg": "Êtes-vous sûr de vouloir supprimer la règle \"$rulename\" ?"
}
}
},
"experimentalNotice": {
"title": "Fonctionnalités expérimentales en cours d'utilisation",
"msg": "Vous avez activé des fonctionnalités expérimentales qui pourraient affecter la qualité de la connexion et causer des erreurs inattendues. Vous pouvez toujours modifier ou réinitialiser ces options depuis la page de configuration.",
"disable": "Ne plus afficher"
},
"noActiveProfile": {
"title": "Choisissez un profil",
"msg": "Pour commencer, ajoutez un profil de connexion qui inclut les détails de votre connexion VPN.\n\nVous n'avez pas encore de serveur VPN ? Pas de problème, suivez le tutoriel ci-dessous pour en configurer un rapidement et gratuitement.",
"helpBtn": {
"label": "Montrez-moi comment",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "Avertissement de lien externe",
"youAreAboutToVisit": "Vous êtes sur le point de visiter :",
"thisWebsiteIsNotInOurTrustedList": "Ce site web ne figure pas dans notre liste de confiance. Veuillez procéder avec prudence."
},
"proxyInfo": {
"fullTag": "Tag complet :",
"type": "Type :",
"testTime": "Heure du test :",
"testDelay": "Délai du test :",
"isSelected": "Sélectionné :",
"isGroup": "Est un groupe",
"isSecure": "Est sécurisé :",
"port": "Port :",
"host": "Hôte :",
"ip": "IP :",
"countryCode": "Code pays :",
"region": "Région :",
"city": "Ville :",
"asn": "ASN :",
"organization": "Organisation :",
"location": "Emplacement :",
"postalCode": "Code postal :"
},
"windowClosing": {
"askEachTime": "Demander à chaque fois",
"alertMessage": "Masquer ou quitter l'application ?",
"remember": "Mémoriser mon choix"
}
},
"connection": {
"tapToConnect": "Appuyez pour vous connecter",
"connect": "Connecter",
"connecting": "Connexion en cours...",
"connected": "Connecté",
"disconnect": "Déconnecter",
"disconnecting": "Déconnexion en cours...",
"reconnect": "Reconnecter",
"reconnectMsg": "Reconnexion pour prendre en compte les changements...",
"secure": "Sécurisé par WARP"
},
"errors": {
"unexpected": "Erreur inattendue",
"connection": {
"unexpected": "Erreur de connexion inattendue",
"timeout": "Délai de connexion expiré",
"badResponse": "Mauvaise réponse",
"connectionError": "Erreur de connexion",
"badCertificate": "Certificat invalide"
},
"profiles": {
"unexpected": "Erreur inattendue",
"notFound": "Profil non trouvé",
"invalidConfig": "Configurations invalides",
"invalidUrl": "URL invalide",
"canceledByUser": "Annulé par l'utilisateur"
},
"connectivity": {
"unexpected": "Échec inattendu",
"missingVpnPermission": "Autorisation VPN manquante",
"missingNotificationPermission": "Autorisation de notification manquante",
"core": "Erreur du noyau"
},
"singbox": {
"serviceNotRunning": "Le service n'est pas en cours d'exécution",
"missingPrivilege": "Autorisation manquante",
"missingPrivilegeMsg": "Le mode VPN nécessite des droits d'administrateur. Veuillez relancer l'application en tant qu'administrateur ou changer le mode de service.",
"invalidConfigOptions": "Options de configuration invalides",
"invalidConfig": "Configuration invalide"
},
"warp": {
"missingLicense": "Licence WARP manquante",
"missingLicenseMsg": "Le profil sélectionné utilise la fonctionnalité WARP. Pour utiliser cette fonctionnalité, vous devez accepter la licence WARP."
}
}
}
================================================
FILE: assets/translations/id.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "Mulai",
"version": "Versi",
"ok": "Oke",
"cancel": "Batal",
"kContinue": "Lanjutkan",
"showMore": "Tampilkan lebih banyak",
"showLess": "Tampilkan lebih sedikit",
"filter": "Filter",
"all": "Semua",
"pause": "Jeda",
"resume": "Lanjutkan",
"clear": "Bersihkan",
"close": "Tutup",
"auto": "Otomatis",
"manually": "Manual",
"name": "Nama",
"url": "URL",
"add": "Tambah",
"clipboard": "Papan klip",
"addToClipboard": "Tambah ke papan klip",
"scanQr": "Pindai QR",
"free": "Gratis",
"warp": "WARP",
"fragment": "Fragmen",
"help": "Bantuan",
"save": "Simpan",
"update": "Perbarui",
"share": "Bagikan",
"edit": "Edit",
"delete": "Hapus",
"discard": "Buang",
"import": "Impor",
"export": "Ekspor",
"later": "Nanti",
"ignore": "Abaikan",
"quit": "Keluar",
"notSet": "Belum diatur",
"hide": "Sembunyikan",
"exit": "Keluar",
"reset": "Setel ulang",
"done": "Selesai",
"search": "Cari",
"decline": "Tolak",
"agree": "Setuju",
"empty": "Kosong",
"unknown": "Tidak dikenal",
"hidden": "Tersembunyi",
"timeout": "Waktu habis",
"sort": "Urutkan",
"dashboard": "Dasbor",
"interval": {
"day": {
"zero": "",
"one": "$n hari",
"other": "$n hari"
},
"hour": {
"zero": "",
"one": "$n jam",
"other": "$n jam"
}
},
"msg": {
"permission": {
"denied": "Izin ditolak"
},
"export": {
"clipboard": {
"success": "Berhasil ditambahkan ke papan klip",
"failure": "Gagal menyalin ke papan klip",
"contentTooLarge": "Konten terlalu besar. Gunakan ekspor file saja"
},
"file": {
"success": "File JSON berhasil dibuat",
"failure": "Gagal membuat file"
}
},
"import": {
"confirm": "Konfirmasi impor",
"success": "Berhasil diimpor",
"failure": "Gagal mengimpor"
}
}
},
"intro": {
"banner": "Semua yang Anda butuhkan untuk internet tanpa batasan",
"termsAndPolicyCaution(rich)": "Dengan melanjutkan, Anda menyetujui ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "Dibuat dengan ❤️ oleh Hiddify - ${tap_source(Sumber Terbuka)} (${tap_license(Lisensi)})"
},
"pages": {
"home": {
"title": "Beranda",
"quickSettings": "Pengaturan cepat"
},
"proxies": {
"title": "Proxy",
"sort": "Urutkan proksi",
"testDelay": "Uji latensi",
"empty": "Tidak ada proksi yang tersedia",
"activeProxy": "Proksi aktif",
"unknownIp": "IP tidak dikenal",
"sortOptions": {
"unsorted": "Default",
"name": "Berdasarkan abjad",
"delay": "Berdasarkan latensi"
},
"ipInfo": {
"address": "Alamat IP",
"country": "Negara",
"organization": "Organisasi"
},
"delay": {
"result": "Latensi: ${delay}ms",
"timeout": "Waktu tes latensi habis",
"testing": "Latensi: sedang menguji..."
}
},
"profiles": {
"title": "Profil",
"add": "Tambah profil",
"update": "Perbarui profil",
"viewAllProfiles": "Lihat semua profil",
"activeProfileName": "Nama profil aktif: \"${name}\".",
"nonActiveProfileName": "Pilih \"${name}\" sebagai profil aktif",
"freeSubNotFound": "Tidak ada langganan gratis yang ditemukan",
"freeSubNotFoundForRegion": "Tidak ada langganan gratis yang ditemukan untuk wilayah \"${region}\"",
"failedToLoad": "Gagal memuat",
"updateSubscriptions": "Perbarui langganan",
"share": {
"urlToClipboard": "URL ke papan klip",
"showUrlQr": "Tampilkan QR URL",
"jsonToClipboard": "JSON ke papan klip"
},
"msg": {
"save": {
"success": "Profil berhasil disimpan"
},
"invalidUrl": "URL tidak valid",
"add": {
"failure": "Gagal menambahkan profil"
},
"update": {
"success": "Profil berhasil diperbarui",
"successNamed": "\"${name}\" berhasil diperbarui",
"failure": "Gagal memperbarui profil",
"failureNamed": "Gagal memperbarui \"${name}\""
},
"delete": {
"success": "Profil berhasil dihapus"
}
}
},
"profileDetails": {
"title": "Profil",
"lastUpdate": "Terakhir diperbarui",
"form": {
"nameHint": "Nama profil",
"emptyName": "Nama wajib diisi",
"invalidUrl": "URL tidak valid",
"urlHint": "URL konfigurasi lengkap",
"disableAutoUpdate": "Nonaktifkan pembaruan otomatis",
"autoUpdateInterval": "Interval pembaruan otomatis",
"loading": "Menambahkan profil..."
}
},
"logs": {
"title": "Log",
"shareCoreLogs": "Bagikan log inti",
"shareAppLogs": "Bagikan log aplikasi"
},
"about": {
"title": "Tentang",
"notAvailableMsg": "Anda sudah menggunakan versi terbaru",
"checkForUpdate": "Periksa pembaruan",
"openWorkingDir": "Buka direktori kerja",
"sourceCode": "Kode sumber",
"telegramChannel": "Saluran Telegram",
"termsAndConditions": "Syarat dan Ketentuan",
"privacyPolicy": "Kebijakan Privasi"
},
"settings": {
"title": "Pengaturan",
"resetTunnel": "Setel ulang profil VPN",
"options": {
"import": {
"clipboard": "Impor opsi dari papan klip",
"file": "Impor opsi dari file"
},
"export": {
"anonymousToClipboard": "Salin opsi anonim ke papan klip",
"anonymousToFile": "Ekspor opsi anonim ke file",
"allToClipboard": "Salin semua opsi ke papan klip",
"allToFile": "Ekspor semua opsi ke file"
},
"reset": "Setel ulang opsi"
},
"general": {
"title": "Umum",
"locale": "Bahasa",
"themeMode": "Tema",
"themeModes": {
"system": "Default sistem",
"dark": "Mode gelap",
"light": "Mode terang",
"black": "Mode hitam"
},
"enableAnalytics": "Aktifkan analitik",
"enableAnalyticsMsg": "Berikan izin untuk mengumpulkan analitik dan mengirim laporan kerusakan untuk meningkatkan aplikasi",
"autoIpCheck": "Periksa IP koneksi secara otomatis",
"dynamicNotification": "Tampilkan kecepatan di notifikasi",
"hapticFeedback": "Umpan balik haptik",
"actionAtClosing": "Tindakan saat menutup",
"autoStart": "Mulai saat masuk",
"silentStart": "Mulai diminimalkan",
"ignoreBatteryOptimizations": "Nonaktifkan optimasi baterai",
"ignoreBatteryOptimizationsMsg": "Hapus batasan untuk kinerja VPN yang optimal",
"memoryLimit": "Batas memori",
"memoryLimitMsg": "Aktifkan jika Anda mengalami kesalahan kehabisan memori atau aplikasi sering macet",
"debugMode": "Mode debug",
"debugModeMsg": "Mulai ulang aplikasi untuk menerapkan perubahan ini",
"logLevel": "Level log",
"connectionTestUrl": "URL uji koneksi",
"urlTestInterval": "Interval uji URL",
"clashApiPort": "Port API Clash",
"useXrayCoreWhenPossible": "Gunakan xray-core jika memungkinkan",
"useXrayCoreWhenPossibleMsg": "Gunakan xray-core saat mengurai tautan langganan. Anda perlu mengimpor ulang tautan untuk mengaktifkan opsi ini."
},
"routing": {
"title": "Perutean",
"perAppProxy": {
"title": "Proxy per aplikasi",
"hideSysApps": "Sembunyikan aplikasi sistem",
"options": {
"import": {
"clipboard": "Impor pilihan dari papan klip",
"file": "Impor pilihan dari file",
"msg": "Mengimpor akan menggantikan pilihan Anda saat ini. Anda yakin ingin melanjutkan?"
},
"export": {
"clipboard": "Salin pilihan ke papan klip",
"file": "Ekspor pilihan ke file"
},
"shareToAll": "Bagikan ke semua",
"clearAllSelections": "Hapus semua pilihan"
},
"modes": {
"all": "Semua",
"proxy": "Proxy",
"bypass": "Lewati",
"allMsg": "Proxy semua aplikasi",
"proxyMsg": "Proxy hanya aplikasi yang dipilih",
"bypassMsg": "Jangan proxy aplikasi yang dipilih"
},
"autoSelection": {
"title": "Pilihan otomatis",
"performNow": "Lakukan sekarang",
"resetToDefault": "Setel ulang ke default",
"autoUpdateInterval": "Interval pembaruan otomatis",
"toast": {
"success": "Pilihan aplikasi otomatis berhasil diselesaikan",
"failure": "Pilihan otomatis gagal",
"regionNotFound": "Pilihan otomatis tidak ditemukan untuk wilayah \"${region}\"",
"alreadyInAuto": "Pilihan Anda sudah ada dalam daftar otomatis"
},
"dialog": {
"title": "Pilihan aplikasi otomatis",
"msg": "Fitur pilihan otomatis untuk proxy per aplikasi dinonaktifkan karena perubahan wilayah menjadi \"${region}\""
}
}
},
"region": "Wilayah",
"regions": {
"ir": "Iran (ir)",
"cn": "Tiongkok (cn)",
"ru": "Rusia (ru)",
"af": "Afghanistan (af)",
"id": "Indonesia (id)",
"tr": "Turki (tr)",
"br": "Brasil (br)",
"other": "Lainnya"
},
"balancerStrategy": {
"title": "Strategi Balancer",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "Blokir iklan",
"bypassLan": "Lewati LAN",
"resolveDestination": "Resolusi tujuan",
"ipv6Route": "Rute IPv6",
"ipv6Modes": {
"disable": "Nonaktifkan",
"enable": "Aktifkan",
"prefer": "Prioritaskan",
"only": "Hanya"
},
"routeRule": {
"title": "Aturan perutean",
"options": {
"import": {
"clipboard": "Impor aturan dari papan klip",
"file": "Impor aturan dari file"
},
"export": {
"clipboard": "Salin aturan ke papan klip",
"file": "Simpan aturan ke file"
},
"reset": "Setel ulang aturan"
},
"deleteRule": "Hapus aturan",
"createRule": "Buat aturan baru",
"rule": {
"title": "Aturan",
"ruleChanged": "Aturan diubah",
"ruleChangedMsg": "Apakah Anda ingin menyimpan editan Anda?",
"onlyTunMode": "Hanya tersedia dalam mode TUN",
"notAvailabeInThisPlatform": "Tidak tersedia di platform ini",
"canNotBeEmpty": "Tidak boleh kosong",
"validUrlEx": "https://example.com",
"validUrl": "\"URL\" yang valid seperti\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe atau google chrome atau chrome",
"validProcessName": "\"Nama Proses\" yang valid seperti\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "\"Jalur Proses\" yang valid seperti\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 atau 1-65000",
"validPortRange": "\"Port\" atau \"Rentang Port\" yang valid seperti\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 atau 10.0.0.0/24",
"validIpCidr": "IP CIDR yang valid seperti\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com atau dl.google.com",
"validDomain": "\"Domain\" yang valid seperti\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com atau .ir",
"validDomainSuffix": "\"Sufiks Domain\" yang valid seperti\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "Nama",
"outbound": "Keluar jika cocok",
"rule_set": "URL set aturan",
"package_name": "Nama paket",
"process_name": "Nama proses",
"process_path": "Jalur proses",
"network": "Jaringan",
"port_range": "Port tujuan",
"source_port_range": "Port sumber",
"protocol": "Protokol",
"ip_cidr": "IP CIDR tujuan",
"source_ip_cidr": "IP CIDR sumber",
"domain": "Domain",
"domain_suffixe": "Sufiks domain",
"domain_keyword": "Kata kunci domain",
"domain_regex": "Regex domain"
},
"outbound(map)": {
"proxy": "Proxy",
"direct": "Langsung",
"direct_with_fragment": "Langsung dengan fragmen",
"block": "Blokir"
},
"network(map)": {
"all": "Semua",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "Semua",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "Tambah nilai baru",
"update": "Perbarui nilai",
"clearList": "Bersihkan daftar",
"clearListMsg": "Semua item dihapus"
},
"androidApps": {
"pageTitle": "Aplikasi Android",
"showSystemApps": "Tampilkan aplikasi sistem",
"hideSystemApps": "Sembunyikan aplikasi sistem",
"clearSelection": "Hapus pilihan",
"uninstalled": "Dihapus"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "DNS jarak jauh",
"remoteDnsDomainStrategy": "Strategi domain DNS jarak jauh",
"directDns": "Penyelesai server keluar (langsung)",
"directDnsDomainStrategy": "Strategi domain keluar",
"enableDnsRouting": "Aktifkan perutean DNS",
"enableFakeDns": "Aktifkan DNS palsu",
"domainStrategy": {
"auto": "Otomatis",
"preferIpv6": "Pilih IPv6",
"preferIpv4": "Pilih IPv4",
"ipv4Only": "Hanya IPv4",
"ipv6Only": "Hanya IPv6"
}
},
"inbound": {
"title": "Masuk",
"serviceMode": "Mode layanan",
"serviceModes": {
"proxy": "Hanya layanan proksi",
"systemProxy": "Atur proksi sistem",
"tun": "VPN",
"tunService": "Layanan VPN"
},
"shortServiceModes": {
"proxy": "Proxy",
"systemProxy": "Proxy sistem",
"tun": "VPN",
"tunService": "Layanan VPN"
},
"strictRoute": "Rute ketat",
"tunImplementation": "Implementasi TUN",
"tunImplementations": {
"mixed": "Campuran",
"system": "Sistem",
"gvisor": "gVisor"
},
"mixedPort": "Port campuran",
"tproxyPort": "Port proksi transparan",
"directPort": "Port direct",
"redirectPort": "Port pengalihan",
"allowConnectionFromLan": "Bagikan VPN di jaringan lokal"
},
"tlsTricks": {
"title": "Trik TLS",
"enable": "Aktifkan fragmen",
"packets": "Paket Fragmentasi",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "Ukuran fragmen",
"sleep": "Jeda fragmen",
"mixedSniCase": {
"enable": "Aktifkan kasus SNI campuran"
},
"padding": {
"enable": "Aktifkan padding",
"size": "Ukuran padding"
}
},
"warp": {
"title": "WARP",
"enable": "Aktifkan WARP",
"generateConfig": "Buat konfigurasi WARP",
"configGenerated": "Konfigurasi WARP dibuat",
"missingConfig": "Konfigurasi WARP hilang",
"detourMode": "Mode Rute WARP",
"detourModes": {
"proxyOverWarp": "Alihkan proksi melalui WARP",
"warpOverProxy": "Alihkan WARP melalui proksi",
"proxyOverWarpExplain": "Buka blokir proksi dengan WARP",
"warpOverProxyExplain": "Keamanan ekstra dengan WARP"
},
"licenseKey": "Kunci lisensi",
"cleanIp": "IP bersih",
"port": "Port",
"noise": {
"count": "Jumlah kebisingan",
"mode": "Mode kebisingan",
"size": "Ukuran kebisingan",
"delay": "Penundaan kebisingan"
}
}
}
},
"components": {
"stats": {
"connection": "Koneksi",
"traffic": "Lalu lintas",
"trafficLive": "Lalu lintas langsung",
"trafficTotal": "Total lalu lintas",
"uplink": "Unggah",
"downlink": "Unduh",
"speed": "Kecepatan",
"totalTransferred": "Total ditransfer"
},
"subscriptionInfo": {
"upload": "Unggah",
"download": "Unduh",
"total": "Total lalu lintas",
"expireDate": "Tanggal kedaluwarsa",
"expired": "Kedaluwarsa",
"noTraffic": "Kuota habis",
"remainingTime": "Sisa waktu",
"remainingDuration": "tersisa ${duration} hari",
"remainingDurationNew": "${duration} hari",
"remainingTrafficSemanticLabel": "${consumed} dari ${total} lalu lintas terpakai",
"remainingTraffic": "Sisa lalu lintas",
"remainingUsage": "Sisa",
"profileSite": "Penyedia",
"profileSupport": "Dukungan"
}
},
"dialogs": {
"sortProfiles": {
"title": "Urut berdasarkan",
"sort": {
"name": "Menurut abjad",
"lastUpdate": "Terakhir diperbarui"
}
},
"warpLicense": {
"title": "Persetujuan Cloudflare WARP",
"description(rich)": "Cloudflare WARP adalah penyedia VPN WireGuard gratis. Dengan mengaktifkan opsi ini, Anda menyetujui ${tos(Ketentuan Layanan)} dan ${privacy(Kebijakan Privasi)} Cloudflare WARP."
},
"newVersion": {
"title": "Pembaruan tersedia",
"msg": "Versi baru @:common.appTitle tersedia. Apakah Anda ingin memperbarui sekarang?",
"currentVersion": "Versi saat ini: ",
"newVersion": "Versi baru: ",
"updateNow": "Perbarui sekarang"
},
"confirmation": {
"settings": {
"import": {
"msg": "Ini akan menimpa semua opsi konfigurasi dengan nilai yang diberikan. Anda yakin?"
}
},
"profile": {
"delete": {
"title": "Hapus profil",
"msg": "Anda yakin ingin menghapus profil ini secara permanen?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "Meningkatkan pilihan otomatis",
"msg": "Dengan membagikan aplikasi yang dipilih, Anda membantu melengkapi daftar \"pilihan otomatis\""
},
"import": {
"msg": "Ini akan menggantikan semua pilihan proksi per aplikasi Anda saat ini. Anda yakin ingin melanjutkan?"
}
},
"routeRule": {
"delete": {
"title": "Hapus aturan",
"msg": "Anda yakin ingin menghapus aturan \"$rulename\"?"
}
}
},
"experimentalNotice": {
"title": "Fitur eksperimental sedang digunakan",
"msg": "Anda telah mengaktifkan beberapa fitur eksperimental yang mungkin memengaruhi kualitas koneksi dan menyebabkan kesalahan tak terduga. Anda selalu dapat mengubah atau menyetel ulang opsi ini dari halaman konfigurasi.",
"disable": "Jangan tampilkan lagi"
},
"noActiveProfile": {
"title": "Pilih profil",
"msg": "Mari kita mulai dengan menambahkan profil koneksi yang berisi detail koneksi VPN Anda.\n\nBelum punya server VPN? Jangan khawatir, ikuti tutorial di bawah ini untuk mengaturnya dengan cepat dan gratis.",
"helpBtn": {
"label": "Tunjukkan caranya",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "Peringatan tautan eksternal",
"youAreAboutToVisit": "Anda akan mengunjungi:",
"thisWebsiteIsNotInOurTrustedList": "Situs web ini tidak ada dalam daftar tepercaya kami. Harap lanjutkan dengan hati-hati."
},
"proxyInfo": {
"fullTag": "Tag lengkap:",
"type": "Tipe:",
"testTime": "Waktu uji:",
"testDelay": "Jeda uji:",
"isSelected": "Dipilih:",
"isGroup": "Grup",
"isSecure": "Aman:",
"port": "Port:",
"host": "Host:",
"ip": "IP:",
"countryCode": "Kode negara:",
"region": "Wilayah:",
"city": "Kota:",
"asn": "ASN:",
"organization": "Organisasi:",
"location": "Lokasi:",
"postalCode": "Kode pos:"
},
"windowClosing": {
"askEachTime": "Tanyakan setiap kali",
"alertMessage": "Sembunyikan atau keluar dari aplikasi?",
"remember": "Ingat pilihan saya"
}
},
"connection": {
"tapToConnect": "Ketuk untuk menyambungkan",
"connect": "Sambungkan",
"connecting": "Menyambungkan...",
"connected": "Tersambung",
"disconnect": "Putuskan",
"disconnecting": "Memutuskan...",
"reconnect": "Sambungkan kembali",
"reconnectMsg": "Menyambungkan kembali untuk menerapkan perubahan...",
"secure": "Diamankan oleh WARP"
},
"errors": {
"unexpected": "Kesalahan tak terduga",
"connection": {
"unexpected": "Kesalahan koneksi tak terduga",
"timeout": "Waktu koneksi habis",
"badResponse": "Respons buruk",
"connectionError": "Kesalahan koneksi",
"badCertificate": "Sertifikat rusak"
},
"profiles": {
"unexpected": "Kesalahan tak terduga",
"notFound": "Profil tidak ditemukan",
"invalidConfig": "Konfigurasi tidak valid",
"invalidUrl": "URL tidak valid",
"canceledByUser": "Dibatalkan oleh pengguna"
},
"connectivity": {
"unexpected": "Kegagalan tak terduga",
"missingVpnPermission": "Izin VPN hilang",
"missingNotificationPermission": "Izin notifikasi hilang",
"core": "Kesalahan inti"
},
"singbox": {
"serviceNotRunning": "Layanan tidak berjalan",
"missingPrivilege": "Izin hilang",
"missingPrivilegeMsg": "Mode VPN memerlukan izin administrator. Harap mulai ulang aplikasi sebagai administrator atau ubah mode layanan.",
"invalidConfigOptions": "Opsi konfigurasi tidak valid",
"invalidConfig": "Konfigurasi tidak valid"
},
"warp": {
"missingLicense": "Lisensi WARP hilang",
"missingLicenseMsg": "Profil yang dipilih menggunakan fitur WARP. Untuk menggunakan fitur ini, Anda harus menyetujui lisensi WARP."
}
}
}
================================================
FILE: assets/translations/pt-BR.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "Começar",
"version": "Versão",
"ok": "OK",
"cancel": "Cancelar",
"kContinue": "Continuar",
"showMore": "Mostrar mais",
"showLess": "Mostrar menos",
"filter": "Filtrar",
"all": "Todos",
"pause": "Pausar",
"resume": "Retomar",
"clear": "Limpar",
"close": "Fechar",
"auto": "Automático",
"manually": "Manualmente",
"name": "Nome",
"url": "URL",
"add": "Adicionar",
"clipboard": "Área de transferência",
"addToClipboard": "Adicionar à área de transferência",
"scanQr": "Escanear QR",
"free": "Grátis",
"warp": "WARP",
"fragment": "Fragmento",
"help": "Ajuda",
"save": "Salvar",
"update": "Atualizar",
"share": "Compartilhar",
"edit": "Editar",
"delete": "Excluir",
"discard": "Descartar",
"import": "Importar",
"export": "Exportar",
"later": "Mais tarde",
"ignore": "Ignorar",
"quit": "Sair",
"notSet": "Não definido",
"hide": "Ocultar",
"exit": "Sair",
"reset": "Redefinir",
"done": "Concluído",
"search": "Buscar",
"decline": "Recusar",
"agree": "Aceitar",
"empty": "Vazio",
"unknown": "Desconhecido",
"hidden": "Oculto",
"timeout": "Tempo esgotado",
"sort": "Ordenar",
"dashboard": "Painel",
"interval": {
"day": {
"zero": "",
"one": "$n dia",
"other": "$n dias"
},
"hour": {
"zero": "",
"one": "$n hora",
"other": "$n horas"
}
},
"msg": {
"permission": {
"denied": "Permissão negada"
},
"export": {
"clipboard": {
"success": "Adicionado à área de transferência com sucesso",
"failure": "Falha ao copiar para a área de transferência",
"contentTooLarge": "Conteúdo muito grande. Use a exportação para arquivo"
},
"file": {
"success": "Arquivo JSON criado com sucesso",
"failure": "Falha ao criar o arquivo"
}
},
"import": {
"confirm": "Confirmar importação",
"success": "Importado com sucesso",
"failure": "Falha ao importar"
}
}
},
"intro": {
"banner": "Tudo o que você precisa para uma internet sem restrições",
"termsAndPolicyCaution(rich)": "Ao continuar, você concorda com os ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "Feito com ❤️ por Hiddify - ${tap_source(Código Aberto)} (${tap_license(Licença)})"
},
"pages": {
"home": {
"title": "Início",
"quickSettings": "Configurações rápidas"
},
"proxies": {
"title": "Proxies",
"sort": "Ordenar proxies",
"testDelay": "Testar latência",
"empty": "Nenhum proxy disponível",
"activeProxy": "Proxy ativo",
"unknownIp": "IP desconhecido",
"sortOptions": {
"unsorted": "Padrão",
"name": "Alfabeticamente",
"delay": "Por latência"
},
"ipInfo": {
"address": "Endereço de IP",
"country": "País",
"organization": "Organização"
},
"delay": {
"result": "Latência: ${delay}ms",
"timeout": "Tempo limite do teste de latência esgotado",
"testing": "Latência: testando..."
}
},
"profiles": {
"title": "Perfis",
"add": "Adicionar perfil",
"update": "Atualizar perfil",
"viewAllProfiles": "Ver todos os perfis",
"activeProfileName": "Nome do perfil ativo: \"${name}\".",
"nonActiveProfileName": "Selecionar \"${name}\" como perfil ativo",
"freeSubNotFound": "Nenhuma assinatura gratuita encontrada",
"freeSubNotFoundForRegion": "Nenhuma assinatura gratuita encontrada para a região \"${region}\"",
"failedToLoad": "Falha ao carregar",
"updateSubscriptions": "Atualizar assinaturas",
"share": {
"urlToClipboard": "URL para a área de transferência",
"showUrlQr": "Mostrar QR code da URL",
"jsonToClipboard": "JSON para a área de transferência"
},
"msg": {
"save": {
"success": "Perfil salvo com sucesso"
},
"invalidUrl": "URL inválida",
"add": {
"failure": "Falha ao adicionar perfil"
},
"update": {
"success": "Perfil atualizado com sucesso",
"successNamed": "\"${name}\" atualizado com sucesso",
"failure": "Falha ao atualizar perfil",
"failureNamed": "Falha ao atualizar \"${name}\""
},
"delete": {
"success": "Perfil excluído com sucesso"
}
}
},
"profileDetails": {
"title": "Perfil",
"lastUpdate": "Última atualização",
"form": {
"nameHint": "Nome do perfil",
"emptyName": "O nome é obrigatório",
"invalidUrl": "URL inválida",
"urlHint": "URL de configuração completa",
"disableAutoUpdate": "Desativar atualização automática",
"autoUpdateInterval": "Intervalo de atualização automática",
"loading": "Adicionando perfil..."
}
},
"logs": {
"title": "Registros",
"shareCoreLogs": "Compartilhar registros do núcleo",
"shareAppLogs": "Compartilhar registros do aplicativo"
},
"about": {
"title": "Sobre",
"notAvailableMsg": "Você já está usando a versão mais recente",
"checkForUpdate": "Verificar atualizações",
"openWorkingDir": "Abrir diretório de trabalho",
"sourceCode": "Código-fonte",
"telegramChannel": "Canal do Telegram",
"termsAndConditions": "Termos e Condições",
"privacyPolicy": "Política de Privacidade"
},
"settings": {
"title": "Configurações",
"resetTunnel": "Redefinir perfil de VPN",
"options": {
"import": {
"clipboard": "Importar opções da área de transferência",
"file": "Importar opções de um arquivo"
},
"export": {
"anonymousToClipboard": "Copiar opções anônimas para a área de transferência",
"anonymousToFile": "Exportar opções anônimas para um arquivo",
"allToClipboard": "Copiar todas as opções para a área de transferência",
"allToFile": "Exportar todas as opções para um arquivo"
},
"reset": "Redefinir opções"
},
"general": {
"title": "Geral",
"locale": "Idioma",
"themeMode": "Tema",
"themeModes": {
"system": "Padrão do sistema",
"dark": "Modo escuro",
"light": "Modo claro",
"black": "Modo preto"
},
"enableAnalytics": "Ativar análise de dados",
"enableAnalyticsMsg": "Permitir a coleta de dados de análise e relatórios de falhas para melhorar o aplicativo",
"autoIpCheck": "Verificar IP da conexão automaticamente",
"dynamicNotification": "Exibir velocidade na notificação",
"hapticFeedback": "Feedback tátil",
"actionAtClosing": "Ação ao fechar",
"autoStart": "Iniciar com o sistema",
"silentStart": "Iniciar minimizado",
"ignoreBatteryOptimizations": "Desativar otimização de bateria",
"ignoreBatteryOptimizationsMsg": "Remover restrições para um desempenho ideal da VPN",
"memoryLimit": "Limite de memória",
"memoryLimitMsg": "Ative se estiver enfrentando erros de falta de memória ou travamentos frequentes do aplicativo",
"debugMode": "Modo de depuração",
"debugModeMsg": "Reinicie o aplicativo para aplicar esta alteração",
"logLevel": "Nível de registro",
"connectionTestUrl": "URL de teste de conexão",
"urlTestInterval": "Intervalo de teste de URL",
"clashApiPort": "Porta da API do Clash",
"useXrayCoreWhenPossible": "Usar xray-core quando possível",
"useXrayCoreWhenPossibleMsg": "Use o xray-core ao analisar links de assinatura. Você precisa reimportar o link para ativar esta opção"
},
"routing": {
"title": "Roteamento",
"perAppProxy": {
"title": "Proxy por aplicativo",
"hideSysApps": "Ocultar aplicativos do sistema",
"options": {
"import": {
"clipboard": "Importar seleção da área de transferência",
"file": "Importar seleção de um arquivo",
"msg": "A importação substituirá suas seleções atuais. Tem certeza de que deseja continuar?"
},
"export": {
"clipboard": "Copiar seleção para a área de transferência",
"file": "Exportar seleção para um arquivo"
},
"shareToAll": "Compartilhar com todos",
"clearAllSelections": "Limpar todas as seleções"
},
"modes": {
"all": "Todos",
"proxy": "Proxy",
"bypass": "Ignorar",
"allMsg": "Usar proxy para todos os aplicativos",
"proxyMsg": "Usar proxy apenas para aplicativos selecionados",
"bypassMsg": "Não usar proxy para aplicativos selecionados"
},
"autoSelection": {
"title": "Seleção automática",
"performNow": "Executar agora",
"resetToDefault": "Redefinir para o padrão",
"autoUpdateInterval": "Intervalo de atualização automática",
"toast": {
"success": "Seleção automática de aplicativos concluída com sucesso",
"failure": "Falha na seleção automática",
"regionNotFound": "Seleção automática não encontrada para a região \"${region}\"",
"alreadyInAuto": "Suas seleções já estão na lista automática"
},
"dialog": {
"title": "Seleção automática de aplicativos",
"msg": "A função de seleção automática para o proxy por aplicativo foi desativada devido à mudança de região para \"${region}\""
}
}
},
"region": "Região",
"regions": {
"ir": "Irã (ir)",
"cn": "China (cn)",
"ru": "Rússia (ru)",
"af": "Afeganistão (af)",
"id": "Indonésia (id)",
"tr": "Turquia (tr)",
"br": "Brasil (br)",
"other": "Outro"
},
"balancerStrategy": {
"title": "Estratégia de Balancer",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "Bloquear anúncios",
"bypassLan": "Ignorar LAN",
"resolveDestination": "Resolver destino",
"ipv6Route": "Rota IPv6",
"ipv6Modes": {
"disable": "Desativar",
"enable": "Ativar",
"prefer": "Preferencial",
"only": "Exclusivo"
},
"routeRule": {
"title": "Regras de Roteamento",
"options": {
"import": {
"clipboard": "Importar regras da área de transferência",
"file": "Importar regras de um arquivo"
},
"export": {
"clipboard": "Copiar regras para a área de transferência",
"file": "Salvar regras em um arquivo"
},
"reset": "Redefinir regras"
},
"deleteRule": "Excluir regra",
"createRule": "Criar nova regra",
"rule": {
"title": "Regra",
"ruleChanged": "Regra alterada",
"ruleChangedMsg": "Deseja salvar suas edições?",
"onlyTunMode": "Disponível apenas no modo TUN",
"notAvailabeInThisPlatform": "Não disponível nesta plataforma",
"canNotBeEmpty": "Não pode estar vazio",
"validUrlEx": "https://example.com",
"validUrl": "\"URL\" válido como\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe ou google chrome ou chrome",
"validProcessName": "\"Nome do Processo\" válido como\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "\"Caminho do Processo\" válido como\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 ou 1-65000",
"validPortRange": "\"Porta\" ou \"Intervalo de Portas\" válido como\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 ou 10.0.0.0/24",
"validIpCidr": "IP CIDR válido como\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com ou dl.google.com",
"validDomain": "\"Domínio\" válido como\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com ou .ir",
"validDomainSuffix": "\"Sufixo de Domínio\" válido como\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "Nome",
"outbound": "Saída se corresponder",
"rule_set": "URL do conjunto de regras",
"package_name": "Nomes dos pacotes",
"process_name": "Nomes dos processos",
"process_path": "Caminhos dos processos",
"network": "Redes",
"port_range": "Portas de destino",
"source_port_range": "Portas de origem",
"protocol": "Protocolo",
"ip_cidr": "IP CIDR de destino",
"source_ip_cidr": "IP CIDR de origem",
"domain": "Domínio",
"domain_suffixe": "Sufixo de domínio",
"domain_keyword": "Palavra-chave de domínio",
"domain_regex": "Expressão regular de domínio"
},
"outbound(map)": {
"proxy": "Proxy",
"direct": "Direto",
"direct_with_fragment": "Direto com fragmento",
"block": "Bloquear"
},
"network(map)": {
"all": "Todos",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "Todos",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "Adicionar novo valor",
"update": "Atualizar valor",
"clearList": "Limpar lista",
"clearListMsg": "Todos os itens foram excluídos"
},
"androidApps": {
"pageTitle": "Aplicativos Android",
"showSystemApps": "Mostrar aplicativos do sistema",
"hideSystemApps": "Ocultar aplicativos do sistema",
"clearSelection": "Limpar seleção",
"uninstalled": "Desinstalado"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "DNS remoto",
"remoteDnsDomainStrategy": "Estratégia de domínio de DNS remoto",
"directDns": "Resolvedor de servidor de saída (direto)",
"directDnsDomainStrategy": "Estratégia de domínio de saída",
"enableDnsRouting": "Ativar roteamento de DNS",
"enableFakeDns": "Ativar DNS falso",
"domainStrategy": {
"auto": "Automático",
"preferIpv6": "Preferir IPv6",
"preferIpv4": "Preferir IPv4",
"ipv4Only": "Apenas IPv4",
"ipv6Only": "Apenas IPv6"
}
},
"inbound": {
"title": "Entrada",
"serviceMode": "Modo de serviço",
"serviceModes": {
"proxy": "Apenas serviço de proxy",
"systemProxy": "Definir proxy do sistema",
"tun": "VPN",
"tunService": "Serviço VPN"
},
"shortServiceModes": {
"proxy": "Proxy",
"systemProxy": "Proxy do sistema",
"tun": "VPN",
"tunService": "Serviço VPN"
},
"strictRoute": "Roteamento estrito",
"tunImplementation": "Implementação de TUN",
"tunImplementations": {
"mixed": "Misto",
"system": "Sistema",
"gvisor": "gVisor"
},
"mixedPort": "Porta mista",
"tproxyPort": "Porta de proxy transparente",
"directPort": "Porta de directo",
"redirectPort": "Porta de redirecionamento",
"allowConnectionFromLan": "Compartilhar VPN na rede local"
},
"tlsTricks": {
"title": "Truques de TLS",
"enable": "Ativar fragmento",
"packets": "Pacotes de Fragmentação",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "Tamanho do fragmento",
"sleep": "Atraso do fragmento",
"mixedSniCase": {
"enable": "Ativar maiúsculas/minúsculas mistas no SNI"
},
"padding": {
"enable": "Ativar preenchimento",
"size": "Tamanho do preenchimento"
}
},
"warp": {
"title": "WARP",
"enable": "Ativar WARP",
"generateConfig": "Gerar configuração WARP",
"configGenerated": "Configuração WARP gerada",
"missingConfig": "Configuração WARP ausente",
"detourMode": "Modo de Roteamento WARP",
"detourModes": {
"proxyOverWarp": "Desviar proxies através do WARP",
"warpOverProxy": "Desviar WARP através de proxies",
"proxyOverWarpExplain": "Desbloquear proxies com WARP",
"warpOverProxyExplain": "Segurança extra com WARP"
},
"licenseKey": "Chave de licença",
"cleanIp": "IP limpo",
"port": "Porta",
"noise": {
"count": "Contagem de ruído",
"mode": "Modo de ruído",
"size": "Tamanho do ruído",
"delay": "Atraso do ruído"
}
}
}
},
"components": {
"stats": {
"connection": "Conexão",
"traffic": "Tráfego",
"trafficLive": "Tráfego ao vivo",
"trafficTotal": "Tráfego total",
"uplink": "Envio",
"downlink": "Recebimento",
"speed": "Velocidade",
"totalTransferred": "Total transferido"
},
"subscriptionInfo": {
"upload": "Upload",
"download": "Download",
"total": "Tráfego total",
"expireDate": "Data de validade",
"expired": "Expirado",
"noTraffic": "Cota esgotada",
"remainingTime": "Tempo restante",
"remainingDuration": "${duration} dias restantes",
"remainingDurationNew": "${duration} dias",
"remainingTrafficSemanticLabel": "${consumed} de ${total} de tráfego consumido",
"remainingTraffic": "Tráfego restante",
"remainingUsage": "Restante",
"profileSite": "Provedor",
"profileSupport": "Suporte"
}
},
"dialogs": {
"sortProfiles": {
"title": "Ordenar por",
"sort": {
"name": "Alfabeticamente",
"lastUpdate": "Última atualização"
}
},
"warpLicense": {
"title": "Consentimento do Cloudflare WARP",
"description(rich)": "O Cloudflare WARP é um provedor de VPN WireGuard gratuito. Ao ativar esta opção, você concorda com os ${tos(Termos de Serviço)} e a ${privacy(Política de Privacidade)} do Cloudflare WARP."
},
"newVersion": {
"title": "Atualização disponível",
"msg": "Uma nova versão do @:common.appTitle está disponível. Deseja atualizar agora?",
"currentVersion": "Versão atual: ",
"newVersion": "Nova versão: ",
"updateNow": "Atualizar agora"
},
"confirmation": {
"settings": {
"import": {
"msg": "Isso substituirá todas as opções de configuração pelos valores fornecidos. Você tem certeza?"
}
},
"profile": {
"delete": {
"title": "Excluir perfil",
"msg": "Tem certeza de que deseja excluir este perfil permanentemente?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "Melhorando a seleção automática",
"msg": "Ao compartilhar os aplicativos selecionados, você ajuda a completar a lista de \"seleção automática\""
},
"import": {
"msg": "Isso substituirá todas as suas seleções atuais de proxy por aplicativo. Tem certeza de que deseja continuar?"
}
},
"routeRule": {
"delete": {
"title": "Excluir regra",
"msg": "Tem certeza de que deseja excluir a regra \"$rulename\"?"
}
}
},
"experimentalNotice": {
"title": "Recursos experimentais em uso",
"msg": "Você ativou alguns recursos experimentais que podem afetar a qualidade da conexão e causar erros inesperados. Você sempre pode alterar ou redefinir essas opções na página de configurações.",
"disable": "Não mostrar novamente"
},
"noActiveProfile": {
"title": "Escolha um perfil",
"msg": "Para começar, adicione um perfil de conexão que inclua os detalhes da sua conexão VPN.\n\nAinda não tem um servidor VPN? Não se preocupe, siga o tutorial abaixo para configurar um rapidamente e de graça.",
"helpBtn": {
"label": "Mostre-me como",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "Aviso de link externo",
"youAreAboutToVisit": "Você está prestes a visitar:",
"thisWebsiteIsNotInOurTrustedList": "Este site não está na nossa lista de confiança. Prossiga com cautela."
},
"proxyInfo": {
"fullTag": "Tag completa:",
"type": "Tipo:",
"testTime": "Hora do teste:",
"testDelay": "Latência do teste:",
"isSelected": "Selecionado:",
"isGroup": "É um grupo",
"isSecure": "É seguro:",
"port": "Porta:",
"host": "Host:",
"ip": "IP:",
"countryCode": "Código do país:",
"region": "Região:",
"city": "Cidade:",
"asn": "ASN:",
"organization": "Organização:",
"location": "Localização:",
"postalCode": "Código postal:"
},
"windowClosing": {
"askEachTime": "Perguntar sempre",
"alertMessage": "Ocultar ou sair do aplicativo?",
"remember": "Lembrar minha escolha"
}
},
"connection": {
"tapToConnect": "Toque para conectar",
"connect": "Conectar",
"connecting": "Conectando...",
"connected": "Conectado",
"disconnect": "Desconectar",
"disconnecting": "Desconectando...",
"reconnect": "Reconectar",
"reconnectMsg": "Reconectando para aplicar as alterações...",
"secure": "Protegido por WARP"
},
"errors": {
"unexpected": "Erro inesperado",
"connection": {
"unexpected": "Erro de conexão inesperado",
"timeout": "Tempo limite de conexão esgotado",
"badResponse": "Resposta inválida",
"connectionError": "Erro de conexão",
"badCertificate": "Certificado inválido"
},
"profiles": {
"unexpected": "Erro inesperado",
"notFound": "Perfil não encontrado",
"invalidConfig": "Configurações inválidas",
"invalidUrl": "URL inválida",
"canceledByUser": "Cancelado pelo usuário"
},
"connectivity": {
"unexpected": "Falha inesperada",
"missingVpnPermission": "Permissão de VPN ausente",
"missingNotificationPermission": "Permissão de notificação ausente",
"core": "Erro no núcleo"
},
"singbox": {
"serviceNotRunning": "O serviço não está em execução",
"missingPrivilege": "Permissão ausente",
"missingPrivilegeMsg": "O modo VPN requer privilégios de administrador. Reinicie o aplicativo como administrador ou altere o modo de serviço.",
"invalidConfigOptions": "Opções de configuração inválidas",
"invalidConfig": "Configuração inválida"
},
"warp": {
"missingLicense": "Licença do WARP ausente",
"missingLicenseMsg": "O perfil selecionado usa o recurso WARP. Para usar este recurso, você deve concordar com a licença do WARP."
}
}
}
================================================
FILE: assets/translations/ru.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "Начать",
"version": "Версия",
"ok": "OK",
"cancel": "Отмена",
"kContinue": "Продолжить",
"showMore": "Показать больше",
"showLess": "Показать меньше",
"filter": "Фильтр",
"all": "Все",
"pause": "Пауза",
"resume": "Возобновить",
"clear": "Очистить",
"close": "Закрыть",
"auto": "Авто",
"manually": "Вручную",
"name": "Имя",
"url": "URL",
"add": "Добавить",
"clipboard": "Буфер обмена",
"addToClipboard": "Добавить в буфер обмена",
"scanQr": "Сканировать QR",
"free": "Бесплатно",
"warp": "WARP",
"fragment": "Фрагмент",
"help": "Справка",
"save": "Сохранить",
"update": "Обновить",
"share": "Поделиться",
"edit": "Изменить",
"delete": "Удалить",
"discard": "Отменить",
"import": "Импорт",
"export": "Экспорт",
"later": "Позже",
"ignore": "Игнорировать",
"quit": "Выход",
"notSet": "Не задано",
"hide": "Скрыть",
"exit": "Выйти",
"reset": "Сброс",
"done": "Готово",
"search": "Поиск",
"decline": "Отклонить",
"agree": "Согласен",
"empty": "Пусто",
"unknown": "Неизвестно",
"hidden": "Скрытый",
"timeout": "Тайм-аут",
"sort": "Сортировать",
"dashboard": "Панель управления",
"interval": {
"day": {
"zero": "",
"one": "$n день",
"few": "$n дня",
"many": "$n дней",
"other": "$n дня"
},
"hour": {
"zero": "",
"one": "$n час",
"few": "$n часа",
"many": "$n часов",
"other": "$n часа"
}
},
"msg": {
"permission": {
"denied": "Доступ запрещен"
},
"export": {
"clipboard": {
"success": "Успешно добавлено в буфер обмена",
"failure": "Не удалось скопировать в буфер обмена",
"contentTooLarge": "Слишком большой контент. Используйте экспорт в файл"
},
"file": {
"success": "Файл JSON успешно создан",
"failure": "Не удалось создать файл"
}
},
"import": {
"confirm": "Подтвердить импорт",
"success": "Успешно импортировано",
"failure": "Не удалось импортировать"
}
}
},
"intro": {
"banner": "Все, что вам нужно для интернета без ограничений",
"termsAndPolicyCaution(rich)": "Продолжая, вы соглашаетесь с ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "Сделано с ❤️ Hiddify - ${tap_source(Открытый исходный код)} (${tap_license(Лицензия)})"
},
"pages": {
"home": {
"title": "Главная",
"quickSettings": "Быстрые настройки"
},
"proxies": {
"title": "Прокси",
"sort": "Сортировать прокси",
"testDelay": "Проверить задержку",
"empty": "Нет доступных прокси",
"activeProxy": "Активный прокси",
"unknownIp": "Неизвестный IP",
"sortOptions": {
"unsorted": "По умолчанию",
"name": "По алфавиту",
"delay": "По задержке"
},
"ipInfo": {
"address": "IP-адрес",
"country": "Страна",
"organization": "Организация"
},
"delay": {
"result": "Задержка: ${delay} мс",
"timeout": "Тайм-аут теста задержки",
"testing": "Задержка: тестирование..."
}
},
"profiles": {
"title": "Профили",
"add": "Добавить профиль",
"update": "Обновить профиль",
"viewAllProfiles": "Посмотреть все профили",
"activeProfileName": "Имя активного профиля: \"${name}\".",
"nonActiveProfileName": "Выбрать \"${name}\" как активный профиль",
"freeSubNotFound": "Бесплатная подписка не найдена",
"freeSubNotFoundForRegion": "Бесплатная подписка для региона \"${region}\" не найдена",
"failedToLoad": "Не удалось загрузить",
"updateSubscriptions": "Обновить подписки",
"share": {
"urlToClipboard": "URL в буфер обмена",
"showUrlQr": "Показать QR-код URL",
"jsonToClipboard": "JSON в буфер обмена"
},
"msg": {
"save": {
"success": "Профиль успешно сохранен"
},
"invalidUrl": "Неверный URL",
"add": {
"failure": "Не удалось добавить профиль"
},
"update": {
"success": "Профиль успешно обновлен",
"successNamed": "\"${name}\" успешно обновлен",
"failure": "Не удалось обновить профиль",
"failureNamed": "Не удалось обновить \"${name}\""
},
"delete": {
"success": "Профиль успешно удален"
}
}
},
"profileDetails": {
"title": "Профиль",
"lastUpdate": "Последнее обновление",
"form": {
"nameHint": "Имя профиля",
"emptyName": "Имя обязательно для заполнения",
"invalidUrl": "Неверный URL",
"urlHint": "Полный URL конфигурации",
"disableAutoUpdate": "Отключить автообновление",
"autoUpdateInterval": "Интервал автообновления",
"loading": "Добавление профиля..."
}
},
"logs": {
"title": "Логи",
"shareCoreLogs": "Поделиться логами ядра",
"shareAppLogs": "Поделиться логами приложения"
},
"about": {
"title": "О программе",
"notAvailableMsg": "Вы уже используете последнюю версию",
"checkForUpdate": "Проверить обновления",
"openWorkingDir": "Открыть рабочую папку",
"sourceCode": "Исходный код",
"telegramChannel": "Канал в Telegram",
"termsAndConditions": "Условия использования",
"privacyPolicy": "Политика конфиденциальности"
},
"settings": {
"title": "Настройки",
"resetTunnel": "Сбросить профиль VPN",
"options": {
"import": {
"clipboard": "Импортировать настройки из буфера обмена",
"file": "Импортировать настройки из файла"
},
"export": {
"anonymousToClipboard": "Копировать анонимные настройки в буфер обмена",
"anonymousToFile": "Экспортировать анонимные настройки в файл",
"allToClipboard": "Копировать все настройки в буфер обмена",
"allToFile": "Экспортировать все настройки в файл"
},
"reset": "Сбросить настройки"
},
"general": {
"title": "Общие",
"locale": "Язык",
"themeMode": "Тема оформления",
"themeModes": {
"system": "Системная",
"dark": "Темная",
"light": "Светлая",
"black": "Черная"
},
"enableAnalytics": "Включить аналитику",
"enableAnalyticsMsg": "Разрешить сбор аналитики и отправку отчетов о сбоях для улучшения приложения",
"autoIpCheck": "Автоматически проверять IP-адрес",
"dynamicNotification": "Отображать скорость в уведомлении",
"hapticFeedback": "Тактильный отклик",
"actionAtClosing": "Действие при закрытии",
"autoStart": "Запускать при входе в систему",
"silentStart": "Запускать свернутым",
"ignoreBatteryOptimizations": "Отключить оптимизацию батареи",
"ignoreBatteryOptimizationsMsg": "Снять ограничения для оптимальной работы VPN",
"memoryLimit": "Ограничение памяти",
"memoryLimitMsg": "Включите, если вы сталкиваетесь с ошибками нехватки памяти или частыми сбоями приложения",
"debugMode": "Режим отладки",
"debugModeMsg": "Перезапустите приложение, чтобы применить это изменение",
"logLevel": "Уровень логирования",
"connectionTestUrl": "URL для теста соединения",
"urlTestInterval": "Интервал теста URL",
"clashApiPort": "Порт Clash API",
"useXrayCoreWhenPossible": "Использовать xray-core, если возможно",
"useXrayCoreWhenPossibleMsg": "Использовать xray-core при обработке ссылок на подписку. Необходимо повторно импортировать ссылку, чтобы включить эту опцию."
},
"routing": {
"title": "Маршрутизация",
"perAppProxy": {
"title": "Прокси для приложений",
"hideSysApps": "Скрыть системные приложения",
"options": {
"import": {
"clipboard": "Импортировать выбор из буфера обмена",
"file": "Импортировать выбор из файла",
"msg": "Импорт заменит ваш текущий выбор. Вы уверены, что хотите продолжить?"
},
"export": {
"clipboard": "Копировать выбор в буфер обмена",
"file": "Экспортировать выбор в файл"
},
"shareToAll": "Поделиться со всеми",
"clearAllSelections": "Очистить весь выбор"
},
"modes": {
"all": "Все",
"proxy": "Прокси",
"bypass": "В обход",
"allMsg": "Проксировать все приложения",
"proxyMsg": "Проксировать только выбранные приложения",
"bypassMsg": "Не проксировать выбранные приложения"
},
"autoSelection": {
"title": "Автовыбор",
"performNow": "Выполнить сейчас",
"resetToDefault": "Сбросить по умолчанию",
"autoUpdateInterval": "Интервал автообновления",
"toast": {
"success": "Автовыбор приложений успешно завершен",
"failure": "Ошибка автовыбора",
"regionNotFound": "Автовыбор для региона \"${region}\" не найден",
"alreadyInAuto": "Ваш выбор уже находится в списке автовыбора"
},
"dialog": {
"title": "Автовыбор приложений",
"msg": "Функция автовыбора для прокси приложений была отключена из-за смены региона на \"${region}\""
}
}
},
"region": "Регион",
"regions": {
"ir": "Иран (ir)",
"cn": "Китай (cn)",
"ru": "Россия (ru)",
"af": "Афганистан (af)",
"id": "Индонезия (id)",
"tr": "Турция (tr)",
"br": "Бразилия (br)",
"other": "Другой"
},
"balancerStrategy": {
"title": "Стратегия Balancer",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "Блокировать рекламу",
"bypassLan": "Обход LAN",
"resolveDestination": "Определять адрес назначения",
"ipv6Route": "Маршрут IPv6",
"ipv6Modes": {
"disable": "Отключить",
"enable": "Включить",
"prefer": "Предпочтительно",
"only": "Только"
},
"routeRule": {
"title": "Правила маршрутизации",
"options": {
"import": {
"clipboard": "Импортировать правила из буфера обмена",
"file": "Импортировать правила из файла"
},
"export": {
"clipboard": "Копировать правила в буфер обмена",
"file": "Сохранить правила в файл"
},
"reset": "Сбросить правила"
},
"deleteRule": "Удалить правило",
"createRule": "Создать новое правило",
"rule": {
"title": "Правило",
"ruleChanged": "Правило изменено",
"ruleChangedMsg": "Вы хотите сохранить изменения?",
"onlyTunMode": "Доступно только в режиме TUN",
"notAvailabeInThisPlatform": "Недоступно на этой платформе",
"canNotBeEmpty": "Не может быть пустым",
"validUrlEx": "https://example.com",
"validUrl": "Валидный \"URL\", например\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe или google chrome или chrome",
"validProcessName": "Валидное \"Имя процесса\", например\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "Валидный \"Путь к процессу\", например\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 или 1-65000",
"validPortRange": "Валидный \"Порт\" или \"Диапазон портов\", например\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 или 10.0.0.0/24",
"validIpCidr": "Валидный IP CIDR, например\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com или dl.google.com",
"validDomain": "Валидный \"Домен\", например\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com или .ru",
"validDomainSuffix": "Валидный \"Суффикс домена\", например\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "Имя",
"outbound": "Исходящий при совпадении",
"rule_set": "URL набора правил",
"package_name": "Имена пакетов",
"process_name": "Имена процессов",
"process_path": "Пути к процессам",
"network": "Сети",
"port_range": "Порты назначения",
"source_port_range": "Исходные порты",
"protocol": "Протокол",
"ip_cidr": "IP CIDR назначения",
"source_ip_cidr": "Исходный IP CIDR",
"domain": "Домен",
"domain_suffixe": "Суффикс домена",
"domain_keyword": "Ключевое слово домена",
"domain_regex": "Регулярное выражение домена"
},
"outbound(map)": {
"proxy": "Прокси",
"direct": "Напрямую",
"direct_with_fragment": "Напрямую с фрагментом",
"block": "Блокировать"
},
"network(map)": {
"all": "Все",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "Все",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "Добавить новое значение",
"update": "Обновить значение",
"clearList": "Очистить список",
"clearListMsg": "Все элементы удалены"
},
"androidApps": {
"pageTitle": "Приложения Android",
"showSystemApps": "Показать системные приложения",
"hideSystemApps": "Скрыть системные приложения",
"clearSelection": "Очистить выбор",
"uninstalled": "Удалено"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "Удаленный DNS",
"remoteDnsDomainStrategy": "Стратегия домена удаленного DNS",
"directDns": "Распознаватель исходящего сервера (напрямую)",
"directDnsDomainStrategy": "Стратегия исходящего домена",
"enableDnsRouting": "Включить маршрутизацию DNS",
"enableFakeDns": "Включить поддельный DNS",
"domainStrategy": {
"auto": "Авто",
"preferIpv6": "Предпочитать IPv6",
"preferIpv4": "Предпочитать IPv4",
"ipv4Only": "Только IPv4",
"ipv6Only": "Только IPv6"
}
},
"inbound": {
"title": "Входящие",
"serviceMode": "Режим службы",
"serviceModes": {
"proxy": "Только прокси-служба",
"systemProxy": "Установить системный прокси",
"tun": "VPN",
"tunService": "Служба VPN"
},
"shortServiceModes": {
"proxy": "Прокси",
"systemProxy": "Системный прокси",
"tun": "VPN",
"tunService": "Служба VPN"
},
"strictRoute": "Строгая маршрутизация",
"tunImplementation": "Реализация TUN",
"tunImplementations": {
"mixed": "Смешанная",
"system": "Системная",
"gvisor": "gVisor"
},
"mixedPort": "Смешанный порт",
"tproxyPort": "Порт прозрачного прокси",
"directPort": "Локальный порт direct",
"redirectPort": "Порт перенаправления",
"allowConnectionFromLan": "Поделиться VPN в локальной сети"
},
"tlsTricks": {
"title": "Трюки TLS",
"enable": "Включить фрагментацию",
"packets": "Пакеты фрагментации",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "Размер фрагмента",
"sleep": "Задержка фрагмента",
"mixedSniCase": {
"enable": "Включить смешанный регистр SNI"
},
"padding": {
"enable": "Включить дополнение",
"size": "Размер дополнения"
}
},
"warp": {
"title": "WARP",
"enable": "Включить WARP",
"generateConfig": "Сгенерировать конфигурацию WARP",
"configGenerated": "Конфигурация Warp сгенерирована",
"missingConfig": "Отсутствует конфигурация WARP",
"detourMode": "Режим маршрутизации WARP",
"detourModes": {
"proxyOverWarp": "Направлять прокси через WARP",
"warpOverProxy": "Направлять WARP через прокси",
"proxyOverWarpExplain": "Разблокировать прокси с помощью WARP",
"warpOverProxyExplain": "Дополнительная безопасность с WARP"
},
"licenseKey": "Лицензионный ключ",
"cleanIp": "Чистый IP",
"port": "Порт",
"noise": {
"count": "Количество шума",
"mode": "Режим шума",
"size": "Размер шума",
"delay": "Задержка шума"
}
}
}
},
"components": {
"stats": {
"connection": "Соединение",
"traffic": "Трафик",
"trafficLive": "Текущий трафик",
"trafficTotal": "Общий трафик",
"uplink": "Отправка",
"downlink": "Прием",
"speed": "Скорость",
"totalTransferred": "Всего передано"
},
"subscriptionInfo": {
"upload": "Отправлено",
"download": "Получено",
"total": "Всего трафика",
"expireDate": "Дата окончания",
"expired": "Истек",
"noTraffic": "Квота исчерпана",
"remainingTime": "Оставшееся время",
"remainingDuration": "осталось ${duration} дней",
"remainingDurationNew": "${duration} дней",
"remainingTrafficSemanticLabel": "израсходовано ${consumed} из ${total} трафика",
"remainingTraffic": "Оставшийся трафик",
"remainingUsage": "Осталось",
"profileSite": "Провайдер",
"profileSupport": "Поддержка"
}
},
"dialogs": {
"sortProfiles": {
"title": "Сортировать по",
"sort": {
"name": "По алфавиту",
"lastUpdate": "Последнему обновлению"
}
},
"warpLicense": {
"title": "Соглашение о WARP от Cloudflare",
"description(rich)": "Cloudflare WARP - это бесплатный провайдер WireGuard VPN. Включая эту опцию, вы соглашаетесь с ${tos(Условиями обслуживания)} и ${privacy(Политикой конфиденциальности)} Cloudflare WARP."
},
"newVersion": {
"title": "Доступно обновление",
"msg": "Доступна новая версия @:common.appTitle. Хотите обновить сейчас?",
"currentVersion": "Текущая версия: ",
"newVersion": "Новая версия: ",
"updateNow": "Обновить сейчас"
},
"confirmation": {
"settings": {
"import": {
"msg": "Это перезапишет все параметры конфигурации предоставленными значениями. Вы уверены?"
}
},
"profile": {
"delete": {
"title": "Удалить профиль",
"msg": "Вы уверены, что хотите навсегда удалить этот профиль?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "Улучшение автовыбора",
"msg": "Делясь выбранными приложениями, вы помогаете пополнить список \"автовыбора\""
},
"import": {
"msg": "Это заменит все ваши текущие настройки прокси для приложений. Вы уверены, что хотите продолжить?"
}
},
"routeRule": {
"delete": {
"title": "Удалить правило",
"msg": "Вы уверены, что хотите удалить правило \"$rulename\"?"
}
}
},
"experimentalNotice": {
"title": "Используются экспериментальные функции",
"msg": "Вы включили некоторые экспериментальные функции, которые могут повлиять на качество соединения и вызвать непредвиденные ошибки. Вы всегда можете изменить или сбросить эти параметры на странице настроек.",
"disable": "Больше не показывать"
},
"noActiveProfile": {
"title": "Выберите профиль",
"msg": "Давайте начнем с добавления профиля подключения, который содержит данные вашего VPN-соединения.\n\nЕще нет VPN-сервера? Не беспокойтесь — просто следуйте руководству ниже, чтобы быстро и бесплатно настроить его.",
"helpBtn": {
"label": "Покажите, как",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "Предупреждение о внешней ссылке",
"youAreAboutToVisit": "Вы собираетесь перейти на сайт:",
"thisWebsiteIsNotInOurTrustedList": "Этот сайт не входит в наш список доверенных. Пожалуйста, действуйте с осторожностью."
},
"proxyInfo": {
"fullTag": "Полный тег:",
"type": "Тип:",
"testTime": "Время теста:",
"testDelay": "Задержка теста:",
"isSelected": "Выбран:",
"isGroup": "Это группа",
"isSecure": "Защищено:",
"port": "Порт:",
"host": "Хост:",
"ip": "IP:",
"countryCode": "Код страны:",
"region": "Регион:",
"city": "Город:",
"asn": "ASN:",
"organization": "Организация:",
"location": "Местоположение:",
"postalCode": "Почтовый индекс:"
},
"windowClosing": {
"askEachTime": "Спрашивать каждый раз",
"alertMessage": "Скрыть или выйти из приложения?",
"remember": "Запомнить мой выбор"
}
},
"connection": {
"tapToConnect": "Нажмите для подключения",
"connect": "Подключить",
"connecting": "Подключение...",
"connected": "Подключено",
"disconnect": "Отключить",
"disconnecting": "Отключение...",
"reconnect": "Переподключить",
"reconnectMsg": "Переподключение для учета изменений...",
"secure": "Защищено с помощью WARP"
},
"errors": {
"unexpected": "Непредвиденная ошибка",
"connection": {
"unexpected": "Непредвиденная ошибка подключения",
"timeout": "Тайм-аут подключения",
"badResponse": "Неверный ответ",
"connectionError": "Ошибка подключения",
"badCertificate": "Недействительный сертификат"
},
"profiles": {
"unexpected": "Непредвиденная ошибка",
"notFound": "Профиль не найден",
"invalidConfig": "Неверная конфигурация",
"invalidUrl": "Неверный URL",
"canceledByUser": "Отменено пользователем"
},
"connectivity": {
"unexpected": "Непредвиденный сбой",
"missingVpnPermission": "Отсутствует разрешение на VPN",
"missingNotificationPermission": "Отсутствует разрешение на уведомления",
"core": "Ошибка ядра"
},
"singbox": {
"serviceNotRunning": "Служба не запущена",
"missingPrivilege": "Отсутствуют права",
"missingPrivilegeMsg": "Режим VPN требует прав администратора. Либо перезапустите приложение от имени администратора, либо измените режим службы.",
"invalidConfigOptions": "Неверные параметры конфигурации",
"invalidConfig": "Неверная конфигурация"
},
"warp": {
"missingLicense": "Отсутствует лицензия Warp",
"missingLicenseMsg": "Выбранный профиль использует функцию WARP; для использования этой функции необходимо принять лицензию WARP."
}
}
}
================================================
FILE: assets/translations/tr.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "Başlat",
"version": "Sürüm",
"ok": "Tamam",
"cancel": "İptal",
"kContinue": "Devam et",
"showMore": "Daha fazla göster",
"showLess": "Daha az göster",
"filter": "Filtrele",
"all": "Tümü",
"pause": "Duraklat",
"resume": "Devam et",
"clear": "Temizle",
"close": "Kapat",
"auto": "Otomatik",
"manually": "Manuel",
"name": "İsim",
"url": "URL",
"add": "Ekle",
"clipboard": "Pano",
"addToClipboard": "Panoya ekle",
"scanQr": "QR Tara",
"free": "Ücretsiz",
"warp": "WARP",
"fragment": "Fragment",
"help": "Yardım",
"save": "Kaydet",
"update": "Güncelle",
"share": "Paylaş",
"edit": "Düzenle",
"delete": "Sil",
"discard": "Vazgeç",
"import": "İçe aktar",
"export": "Dışa aktar",
"later": "Daha sonra",
"ignore": "Yoksay",
"quit": "Çık",
"notSet": "Ayarlanmadı",
"hide": "Gizle",
"exit": "Çıkış",
"reset": "Sıfırla",
"done": "Bitti",
"search": "Ara",
"decline": "Reddet",
"agree": "Kabul et",
"empty": "Boş",
"unknown": "Bilinmeyen",
"hidden": "Gizli",
"timeout": "Zaman aşımı",
"sort": "Sırala",
"dashboard": "Gösterge Paneli",
"interval": {
"day": {
"zero": "",
"one": "$n gün",
"other": "$n gün"
},
"hour": {
"zero": "",
"one": "$n saat",
"other": "$n saat"
}
},
"msg": {
"permission": {
"denied": "İzin reddedildi"
},
"export": {
"clipboard": {
"success": "Panoya başarıyla eklendi",
"failure": "Panoya kopyalanamadı",
"contentTooLarge": "İçerik çok büyük. Bunun yerine dosyaya aktarmayı kullanın"
},
"file": {
"success": "JSON dosyası başarıyla oluşturuldu",
"failure": "Dosya oluşturulamadı"
}
},
"import": {
"confirm": "İçe aktarmayı onayla",
"success": "Başarıyla içe aktarıldı",
"failure": "İçe aktarılamadı"
}
}
},
"intro": {
"banner": "Sınırsız bir internet için ihtiyacınız olan her şey",
"termsAndPolicyCaution(rich)": "Devam ederek ${tap(@:pages.about.termsAndConditions)} kabul etmiş olursunuz",
"info(rich)": "Hiddify tarafından ❤️ ile yapıldı - ${tap_source(Açık Kaynak)} (${tap_license(Lisans)})"
},
"pages": {
"home": {
"title": "Ana Sayfa",
"quickSettings": "Hızlı ayarlar"
},
"proxies": {
"title": "Proxy'ler",
"sort": "Proxy'leri sırala",
"testDelay": "Gecikmeyi test et",
"empty": "Kullanılabilir proxy yok",
"activeProxy": "Aktif proxy",
"unknownIp": "Bilinmeyen IP",
"sortOptions": {
"unsorted": "Varsayılan",
"name": "Alfabetik",
"delay": "Gecikmeye göre"
},
"ipInfo": {
"address": "IP adresi",
"country": "Ülke",
"organization": "Kuruluş"
},
"delay": {
"result": "Gecikme: ${delay}ms",
"timeout": "Gecikme testi zaman aşımına uğradı",
"testing": "Gecikme: test ediliyor..."
}
},
"profiles": {
"title": "Profiller",
"add": "Profil ekle",
"update": "Profili güncelle",
"viewAllProfiles": "Tüm profilleri görüntüle",
"activeProfileName": "Aktif profil adı: \"${name}\".",
"nonActiveProfileName": "Aktif profil olarak \"${name}\" seçin",
"freeSubNotFound": "Ücretsiz abonelik bulunamadı",
"freeSubNotFoundForRegion": "\"${region}\" bölgesi için ücretsiz abonelik bulunamadı",
"failedToLoad": "Yüklenemedi",
"updateSubscriptions": "Abonelikleri güncelle",
"share": {
"urlToClipboard": "URL'yi panoya kopyala",
"showUrlQr": "URL QR kodunu göster",
"jsonToClipboard": "JSON'u panoya kopyala"
},
"msg": {
"save": {
"success": "Profil başarıyla kaydedildi"
},
"invalidUrl": "Geçersiz URL",
"add": {
"failure": "Profil eklenemedi"
},
"update": {
"success": "Profil başarıyla güncellendi",
"successNamed": "\"${name}\" başarıyla güncellendi",
"failure": "Profil güncellenemedi",
"failureNamed": "\"${name}\" güncellenemedi"
},
"delete": {
"success": "Profil başarıyla silindi"
}
}
},
"profileDetails": {
"title": "Profil",
"lastUpdate": "Son güncelleme",
"form": {
"nameHint": "Profil adı",
"emptyName": "İsim gerekli",
"invalidUrl": "Geçersiz URL",
"urlHint": "Tam yapılandırma URL'si",
"disableAutoUpdate": "Otomatik güncellemeyi devre dışı bırak",
"autoUpdateInterval": "Otomatik güncelleme aralığı",
"loading": "Profil ekleniyor..."
}
},
"logs": {
"title": "Loglar",
"shareCoreLogs": "Çekirdek loglarını paylaş",
"shareAppLogs": "Uygulama loglarını paylaş"
},
"about": {
"title": "Hakkında",
"notAvailableMsg": "Zaten en son sürümü kullanıyorsunuz",
"checkForUpdate": "Güncellemeleri kontrol et",
"openWorkingDir": "Çalışma dizinini aç",
"sourceCode": "Kaynak kodu",
"telegramChannel": "Telegram kanalı",
"termsAndConditions": "Şartlar ve Koşullar",
"privacyPolicy": "Gizlilik Politikası"
},
"settings": {
"title": "Ayarlar",
"resetTunnel": "VPN profilini sıfırla",
"options": {
"import": {
"clipboard": "Seçenekleri panodan içe aktar",
"file": "Seçenekleri dosyadan içe aktar"
},
"export": {
"anonymousToClipboard": "Anonim seçenekleri panoya kopyala",
"anonymousToFile": "Anonim seçenekleri dosyaya aktar",
"allToClipboard": "Tüm seçenekleri panoya kopyala",
"allToFile": "Tüm seçenekleri dosyaya aktar"
},
"reset": "Seçenekleri sıfırla"
},
"general": {
"title": "Genel",
"locale": "Dil",
"themeMode": "Tema modu",
"themeModes": {
"system": "Sistem varsayılanı",
"dark": "Karanlık mod",
"light": "Açık mod",
"black": "Siyah mod"
},
"enableAnalytics": "Analizi etkinleştir",
"enableAnalyticsMsg": "Uygulamayı iyileştirmek için analiz ve kilitlenme raporları toplanmasına izin verin",
"autoIpCheck": "Bağlantı IP'sini otomatik kontrol et",
"dynamicNotification": "Hızı bildirimde göster",
"hapticFeedback": "Dokunsal geri bildirim",
"actionAtClosing": "Kapatma eylemi",
"autoStart": "Oturum açılışında başlat",
"silentStart": "Simge durumunda başlat",
"ignoreBatteryOptimizations": "Pil optimizasyonunu devre dışı bırak",
"ignoreBatteryOptimizationsMsg": "Optimum VPN performansı için kısıtlamaları kaldırın",
"memoryLimit": "Bellek limiti",
"memoryLimitMsg": "Bellek yetersizliği hataları veya sık uygulama çökmeleri yaşıyorsanız etkinleştirin",
"debugMode": "Hata ayıklama modu",
"debugModeMsg": "Bu değişikliği uygulamak için uygulamayı yeniden başlatın",
"logLevel": "Log seviyesi",
"connectionTestUrl": "Bağlantı testi URL'si",
"urlTestInterval": "URL testi aralığı",
"clashApiPort": "Clash API portu",
"useXrayCoreWhenPossible": "Mümkün olduğunda xray-core kullan",
"useXrayCoreWhenPossibleMsg": "Abonelik bağlantılarını ayrıştırırken xray-core kullanın. Bu seçeneği etkinleştirmek için bağlantıyı yeniden içe aktarmanız gerekir."
},
"routing": {
"title": "Yönlendirme",
"perAppProxy": {
"title": "Uygulama bazlı proxy",
"hideSysApps": "Sistem uygulamalarını gizle",
"options": {
"import": {
"clipboard": "Seçimi panodan içe aktar",
"file": "Seçimi dosyadan içe aktar",
"msg": "İçe aktarma mevcut seçimlerinizin üzerine yazacaktır. Devam etmek istediğinizden emin misiniz?"
},
"export": {
"clipboard": "Seçimi panoya kopyala",
"file": "Seçimi dosyaya aktar"
},
"shareToAll": "Herkesle paylaş",
"clearAllSelections": "Tüm seçimleri temizle"
},
"modes": {
"all": "Tümü",
"proxy": "Proxy",
"bypass": "Atla",
"allMsg": "Tüm uygulamaları proxy'le",
"proxyMsg": "Yalnızca seçili uygulamaları proxy'le",
"bypassMsg": "Seçili uygulamaları proxy'leme"
},
"autoSelection": {
"title": "Otomatik seçim",
"performNow": "Şimdi gerçekleştir",
"resetToDefault": "Varsayılana sıfırla",
"autoUpdateInterval": "Otomatik güncelleme aralığı",
"toast": {
"success": "Otomatik uygulama seçimi başarıyla tamamlandı",
"failure": "Otomatik seçim başarısız oldu",
"regionNotFound": "\"${region}\" bölgesi için otomatik seçim bulunamadı",
"alreadyInAuto": "Seçimleriniz zaten otomatik listede"
},
"dialog": {
"title": "Otomatik uygulama seçimi",
"msg": "Uygulama bazlı proxy için otomatik seçim özelliği, bölge \"${region}\" olarak değiştirildiği için devre dışı bırakıldı"
}
}
},
"region": "Bölge",
"regions": {
"ir": "İran (ir)",
"cn": "Çin (cn)",
"ru": "Rusya (ru)",
"af": "Afganistan (af)",
"id": "Endonezya (id)",
"tr": "Türkiye (tr)",
"br": "Brezilya (br)",
"other": "Diğer"
},
"balancerStrategy": {
"title": "Balancer stratejisi",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "Reklamları engelle",
"bypassLan": "LAN'ı atla",
"resolveDestination": "Hedefi çözümle",
"ipv6Route": "IPv6 rotası",
"ipv6Modes": {
"disable": "Devre dışı bırak",
"enable": "Etkinleştir",
"prefer": "Tercih et",
"only": "Sadece"
},
"routeRule": {
"title": "Yönlendirme kuralları",
"options": {
"import": {
"clipboard": "Kuralları panodan içe aktar",
"file": "Kuralları dosyadan içe aktar"
},
"export": {
"clipboard": "Kuralları panoya kopyala",
"file": "Kuralları dosyaya kaydet"
},
"reset": "Kuralları sıfırla"
},
"deleteRule": "Kuralı sil",
"createRule": "Yeni kural oluştur",
"rule": {
"title": "Kural",
"ruleChanged": "Kural değiştirildi",
"ruleChangedMsg": "Düzenlemelerinizi kaydetmek istiyor musunuz?",
"onlyTunMode": "Yalnızca TUN modunda kullanılabilir",
"notAvailabeInThisPlatform": "Bu platformda mevcut değil",
"canNotBeEmpty": "Boş olamaz",
"validUrlEx": "https://example.com",
"validUrl": "Geçerli \"URL\", örn.\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe veya google chrome veya chrome",
"validProcessName": "Geçerli \"İşlem Adı\", örn.\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "Geçerli \"İşlem Yolu\", örn.\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 veya 1-65000",
"validPortRange": "Geçerli \"Port\" veya \"Port Aralığı\", örn.\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 veya 10.0.0.0/24",
"validIpCidr": "Geçerli IP CIDR, örn.\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com veya dl.google.com",
"validDomain": "Geçerli \"Alan Adı\", örn.\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com veya .tr",
"validDomainSuffix": "Geçerli \"Alan Adı Son Eki\", örn.\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "İsim",
"outbound": "Eşleşirse giden",
"rule_set": "Kural seti URL'si",
"package_name": "Paket adları",
"process_name": "İşlem adları",
"process_path": "İşlem yolları",
"network": "Ağlar",
"port_range": "Hedef portlar",
"source_port_range": "Kaynak portlar",
"protocol": "Protokol",
"ip_cidr": "Hedef IP CIDR",
"source_ip_cidr": "Kaynak IP CIDR",
"domain": "Alan adı",
"domain_suffixe": "Alan adı son eki",
"domain_keyword": "Alan adı anahtar kelimesi",
"domain_regex": "Alan adı regex"
},
"outbound(map)": {
"proxy": "Proxy",
"direct": "Doğrudan",
"direct_with_fragment": "Doğrudan (fragment ile)",
"block": "Engelle"
},
"network(map)": {
"all": "Tümü",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "Tümü",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "Yeni değer ekle",
"update": "Değeri güncelle",
"clearList": "Listeyi temizle",
"clearListMsg": "Tüm öğeler silindi"
},
"androidApps": {
"pageTitle": "Android uygulamaları",
"showSystemApps": "Sistem uygulamalarını göster",
"hideSystemApps": "Sistem uygulamalarını gizle",
"clearSelection": "Seçimi temizle",
"uninstalled": "Kaldırıldı"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "Uzak DNS",
"remoteDnsDomainStrategy": "Uzak DNS alan adı stratejisi",
"directDns": "Giden sunucu çözümleyicisi (doğrudan)",
"directDnsDomainStrategy": "Giden alan adı stratejisi",
"enableDnsRouting": "DNS yönlendirmeyi etkinleştir",
"enableFakeDns": "Sahte DNS'i etkinleştir",
"domainStrategy": {
"auto": "Otomatik",
"preferIpv6": "IPv6 tercih et",
"preferIpv4": "IPv4 tercih et",
"ipv4Only": "Sadece IPv4",
"ipv6Only": "Sadece IPv6"
}
},
"inbound": {
"title": "Gelen",
"serviceMode": "Servis modu",
"serviceModes": {
"proxy": "Yalnızca proxy servisi",
"systemProxy": "Sistem proxy'sini ayarla",
"tun": "VPN",
"tunService": "VPN servisi"
},
"shortServiceModes": {
"proxy": "Proxy",
"systemProxy": "Sistem proxy'si",
"tun": "VPN",
"tunService": "VPN servisi"
},
"strictRoute": "Katı yönlendirme",
"tunImplementation": "TUN uygulaması",
"tunImplementations": {
"mixed": "Karışık",
"system": "Sistem",
"gvisor": "gVisor"
},
"mixedPort": "Karışık port",
"tproxyPort": "Şeffaf proxy portu",
"directPort": "Direct portu",
"redirectPort": "Yönlendirme portu",
"allowConnectionFromLan": "VPN'i yerel ağda paylaş"
},
"tlsTricks": {
"title": "TLS hileleri",
"enable": "Fragment'ı etkinleştir",
"packets": "Fragmentation Paketleri",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "Fragment boyutu",
"sleep": "Fragment gecikmesi",
"mixedSniCase": {
"enable": "Karışık SNI harf durumunu etkinleştir"
},
"padding": {
"enable": "Dolguyu etkinleştir",
"size": "Dolgu boyutu"
}
},
"warp": {
"title": "WARP",
"enable": "WARP'ı etkinleştir",
"generateConfig": "WARP yapılandırması oluştur",
"configGenerated": "WARP yapılandırması oluşturuldu",
"missingConfig": "WARP yapılandırması eksik",
"detourMode": "Yönlendirme modu",
"detourModes": {
"proxyOverWarp": "Proxy'leri WARP üzerinden yönlendir",
"warpOverProxy": "WARP'ı proxy'ler üzerinden yönlendir",
"proxyOverWarpExplain": "Proxy'lerin engelini WARP ile kaldır",
"warpOverProxyExplain": "WARP ile ekstra güvenlik"
},
"licenseKey": "Lisans anahtarı",
"cleanIp": "Temiz IP",
"port": "Port",
"noise": {
"count": "Gürültü sayısı",
"mode": "Gürültü modu",
"size": "Gürültü boyutu",
"delay": "Gürültü gecikmesi"
}
}
}
},
"components": {
"stats": {
"connection": "Bağlantı",
"traffic": "Trafik",
"trafficLive": "Canlı trafik",
"trafficTotal": "Toplam trafik",
"uplink": "Yükleme",
"downlink": "İndirme",
"speed": "Hız",
"totalTransferred": "Toplam aktarılan"
},
"subscriptionInfo": {
"upload": "Yükleme",
"download": "İndirme",
"total": "Toplam trafik",
"expireDate": "Bitiş tarihi",
"expired": "Süresi doldu",
"noTraffic": "Kota doldu",
"remainingTime": "Kalan süre",
"remainingDuration": "kalan ${duration} gün",
"remainingDurationNew": "${duration} gün",
"remainingTrafficSemanticLabel": "${consumed} / ${total} trafik kullanıldı",
"remainingTraffic": "Kalan trafik",
"remainingUsage": "Kalan",
"profileSite": "Sağlayıcı",
"profileSupport": "Destek"
}
},
"dialogs": {
"sortProfiles": {
"title": "Sıralama ölçütü",
"sort": {
"name": "Alfabetik",
"lastUpdate": "Son güncelleme"
}
},
"warpLicense": {
"title": "Cloudflare WARP onayı",
"description(rich)": "Cloudflare WARP ücretsiz bir WireGuard VPN sağlayıcısıdır. Bu seçeneği etkinleştirerek Cloudflare WARP'ın ${tos(Hizmet Şartları)}'nı ve ${privacy(Gizlilik Politikası)}'nı kabul etmiş olursunuz."
},
"newVersion": {
"title": "Güncelleme mevcut",
"msg": "@:common.appTitle'ın yeni bir sürümü mevcut. Şimdi güncellemek ister misiniz?",
"currentVersion": "Mevcut sürüm: ",
"newVersion": "Yeni sürüm: ",
"updateNow": "Şimdi güncelle"
},
"confirmation": {
"settings": {
"import": {
"msg": "Bu işlem tüm yapılandırma seçeneklerini verilen değerlerle yeniden yazacaktır. Emin misiniz?"
}
},
"profile": {
"delete": {
"title": "Profili sil",
"msg": "Bu profili kalıcı olarak silmek istediğinizden emin misiniz?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "Otomatik seçimi iyileştirme",
"msg": "Seçili uygulamaları paylaşarak \"otomatik seçim\" listesini tamamlamaya yardımcı olursunuz"
},
"import": {
"msg": "Bu işlem mevcut tüm uygulama bazlı proxy seçimlerinizi değiştirecektir. Devam etmek istediğinizden emin misiniz?"
}
},
"routeRule": {
"delete": {
"title": "Kuralı sil",
"msg": "\"$rulename\" kuralını silmek istediğinizden emin misiniz?"
}
}
},
"experimentalNotice": {
"title": "Deneysel özellikler kullanılıyor",
"msg": "Bağlantı kalitesini etkileyebilecek ve beklenmedik hatalara neden olabilecek bazı deneysel özellikleri etkinleştirdiniz. Bu seçenekleri istediğiniz zaman yapılandırma sayfasından değiştirebilir veya sıfırlayabilirsiniz.",
"disable": "Tekrar gösterme"
},
"noActiveProfile": {
"title": "Bir profil seçin",
"msg": "VPN bağlantı ayrıntılarınızı içeren bir bağlantı profili ekleyerek başlayalım.\n\nHenüz bir VPN sunucunuz yok mu? Endişelenmeyin, hızlı ve ücretsiz bir şekilde kurmak için aşağıdaki eğitimi takip edin.",
"helpBtn": {
"label": "Nasıl yapıldığını göster",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "Dış bağlantı uyarısı",
"youAreAboutToVisit": "Şu adresi ziyaret etmek üzeresiniz:",
"thisWebsiteIsNotInOurTrustedList": "Bu web sitesi güvenilir listemizde değil. Lütfen dikkatli devam edin."
},
"proxyInfo": {
"fullTag": "Tam etiket:",
"type": "Tür:",
"testTime": "Test zamanı:",
"testDelay": "Test gecikmesi:",
"isSelected": "Seçili:",
"isGroup": "Grup",
"isSecure": "Güvenli:",
"port": "Port:",
"host": "Sunucu:",
"ip": "IP:",
"countryCode": "Ülke kodu:",
"region": "Bölge:",
"city": "Şehir:",
"asn": "ASN:",
"organization": "Kuruluş:",
"location": "Konum:",
"postalCode": "Posta kodu:"
},
"windowClosing": {
"askEachTime": "Her seferinde sor",
"alertMessage": "Uygulama gizlensin mi, kapatılsın mı?",
"remember": "Seçimimi hatırla"
}
},
"connection": {
"tapToConnect": "Bağlanmak için dokunun",
"connect": "Bağlan",
"connecting": "Bağlanıyor...",
"connected": "Bağlı",
"disconnect": "Bağlantıyı kes",
"disconnecting": "Bağlantı kesiliyor...",
"reconnect": "Yeniden bağlan",
"reconnectMsg": "Değişiklikleri uygulamak için yeniden bağlanılıyor...",
"secure": "WARP ile güvende"
},
"errors": {
"unexpected": "Beklenmeyen hata",
"connection": {
"unexpected": "Beklenmeyen bağlantı hatası",
"timeout": "Bağlantı zaman aşımına uğradı",
"badResponse": "Hatalı yanıt",
"connectionError": "Bağlantı hatası",
"badCertificate": "Geçersiz sertifika"
},
"profiles": {
"unexpected": "Beklenmeyen hata",
"notFound": "Profil bulunamadı",
"invalidConfig": "Geçersiz yapılandırmalar",
"invalidUrl": "Geçersiz URL",
"canceledByUser": "Kullanıcı tarafından iptal edildi"
},
"connectivity": {
"unexpected": "Beklenmeyen hata",
"missingVpnPermission": "VPN izni eksik",
"missingNotificationPermission": "Bildirim izni eksik",
"core": "Çekirdek hatası"
},
"singbox": {
"serviceNotRunning": "Servis çalışmıyor",
"missingPrivilege": "Eksik yetki",
"missingPrivilegeMsg": "VPN modu yönetici yetkisi gerektirir. Lütfen uygulamayı yönetici olarak yeniden başlatın veya servis modunu değiştirin.",
"invalidConfigOptions": "Geçersiz yapılandırma seçenekleri",
"invalidConfig": "Geçersiz yapılandırma"
},
"warp": {
"missingLicense": "WARP lisansı eksik",
"missingLicenseMsg": "Seçili profil WARP özelliğini kullanıyor. Bu özelliği kullanmak için WARP lisansını kabul etmeniz gerekir."
}
}
}
================================================
FILE: assets/translations/zh-CN.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "开始",
"version": "版本",
"ok": "确定",
"cancel": "取消",
"kContinue": "继续",
"showMore": "显示更多",
"showLess": "显示更少",
"filter": "筛选",
"all": "全部",
"pause": "暂停",
"resume": "恢复",
"clear": "清除",
"close": "关闭",
"auto": "自动",
"manually": "手动",
"name": "名称",
"url": "URL",
"add": "添加",
"clipboard": "剪贴板",
"addToClipboard": "添加到剪贴板",
"scanQr": "扫描二维码",
"free": "免费",
"warp": "WARP",
"fragment": "Fragment",
"help": "帮助",
"save": "保存",
"update": "更新",
"share": "分享",
"edit": "编辑",
"delete": "删除",
"discard": "放弃",
"import": "导入",
"export": "导出",
"later": "稍后",
"ignore": "忽略",
"quit": "退出",
"notSet": "未设置",
"hide": "隐藏",
"exit": "退出",
"reset": "重置",
"done": "完成",
"search": "搜索",
"decline": "拒绝",
"agree": "同意",
"empty": "空",
"unknown": "未知",
"hidden": "隐藏",
"timeout": "超时",
"sort": "排序",
"dashboard": "仪表盘",
"interval": {
"day": {
"zero": "",
"one": "$n 天",
"other": "$n 天"
},
"hour": {
"zero": "",
"one": "$n 小时",
"other": "$n 小时"
}
},
"msg": {
"permission": {
"denied": "权限被拒绝"
},
"export": {
"clipboard": {
"success": "已成功添加到剪贴板",
"failure": "复制到剪贴板失败",
"contentTooLarge": "内容过大,请使用导出文件"
},
"file": {
"success": "JSON 文件创建成功",
"failure": "创建文件失败"
}
},
"import": {
"confirm": "确认导入",
"success": "导入成功",
"failure": "导入失败"
}
}
},
"intro": {
"banner": "畅享无限制网络的所需一切",
"termsAndPolicyCaution(rich)": "继续即表示您同意 ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "由 Hiddify 用 ❤️ 制作 - ${tap_source(开源)} (${tap_license(许可证)})"
},
"pages": {
"home": {
"title": "主页",
"quickSettings": "快速设置"
},
"proxies": {
"title": "代理",
"sort": "排序代理",
"testDelay": "测试延迟",
"empty": "无可用代理",
"activeProxy": "当前代理",
"unknownIp": "未知 IP",
"sortOptions": {
"unsorted": "默认",
"name": "按名称",
"delay": "按延迟"
},
"ipInfo": {
"address": "IP 地址",
"country": "国家",
"organization": "组织"
},
"delay": {
"result": "延迟:${delay}毫秒",
"timeout": "延迟测试超时",
"testing": "延迟:测试中..."
}
},
"profiles": {
"title": "配置文件",
"add": "添加配置文件",
"update": "更新配置文件",
"viewAllProfiles": "查看所有配置文件",
"activeProfileName": "当前配置文件:\"${name}\"",
"nonActiveProfileName": "选择 \"${name}\" 作为当前配置文件",
"freeSubNotFound": "未找到免费订阅",
"freeSubNotFoundForRegion": "未找到 \"${region}\" 地区的免费订阅",
"failedToLoad": "加载失败",
"updateSubscriptions": "更新订阅",
"share": {
"urlToClipboard": "URL 到剪贴板",
"showUrlQr": "显示 URL 二维码",
"jsonToClipboard": "JSON 到剪贴板"
},
"msg": {
"save": {
"success": "配置文件保存成功"
},
"invalidUrl": "无效的 URL",
"add": {
"failure": "添加配置文件失败"
},
"update": {
"success": "配置文件更新成功",
"successNamed": "\"${name}\" 更新成功",
"failure": "更新配置文件失败",
"failureNamed": "更新 \"${name}\" 失败"
},
"delete": {
"success": "配置文件删除成功"
}
}
},
"profileDetails": {
"title": "配置文件",
"lastUpdate": "最后更新",
"form": {
"nameHint": "配置文件名称",
"emptyName": "名称为必填项",
"invalidUrl": "无效的 URL",
"urlHint": "完整的配置 URL",
"disableAutoUpdate": "禁用自动更新",
"autoUpdateInterval": "自动更新间隔",
"loading": "正在添加配置文件..."
}
},
"logs": {
"title": "日志",
"shareCoreLogs": "分享核心日志",
"shareAppLogs": "分享应用日志"
},
"about": {
"title": "关于",
"notAvailableMsg": "已是最新版本",
"checkForUpdate": "检查更新",
"openWorkingDir": "打开工作目录",
"sourceCode": "源代码",
"telegramChannel": "Telegram 频道",
"termsAndConditions": "条款与条件",
"privacyPolicy": "隐私政策"
},
"settings": {
"title": "设置",
"resetTunnel": "重置 VPN 配置文件",
"options": {
"import": {
"clipboard": "从剪贴板导入选项",
"file": "从文件导入选项"
},
"export": {
"anonymousToClipboard": "复制匿名选项到剪贴板",
"anonymousToFile": "导出匿名选项到文件",
"allToClipboard": "复制所有选项到剪贴板",
"allToFile": "导出所有选项到文件"
},
"reset": "重置选项"
},
"general": {
"title": "通用",
"locale": "语言",
"themeMode": "主题模式",
"themeModes": {
"system": "跟随系统",
"dark": "深色模式",
"light": "浅色模式",
"black": "纯黑模式"
},
"enableAnalytics": "启用分析",
"enableAnalyticsMsg": "允许收集分析数据和发送崩溃报告以改进应用",
"autoIpCheck": "自动检查连接 IP",
"dynamicNotification": "在通知中显示速度",
"hapticFeedback": "触觉反馈",
"actionAtClosing": "关闭时操作",
"autoStart": "开机自启",
"silentStart": "静默启动",
"ignoreBatteryOptimizations": "忽略电池优化",
"ignoreBatteryOptimizationsMsg": "移除限制以获得最佳 VPN 性能",
"memoryLimit": "内存限制",
"memoryLimitMsg": "如果您遇到内存不足错误或应用频繁崩溃,请启用此选项",
"debugMode": "调试模式",
"debugModeMsg": "重启应用以应用此更改",
"logLevel": "日志级别",
"connectionTestUrl": "连接测试 URL",
"urlTestInterval": "URL 测试间隔",
"clashApiPort": "Clash API 端口",
"useXrayCoreWhenPossible": "尽可能使用 xray-core",
"useXrayCoreWhenPossibleMsg": "解析订阅链接时使用 xray-core。您需要重新导入订阅链接才能启用此选项"
},
"routing": {
"title": "路由",
"perAppProxy": {
"title": "分应用代理",
"hideSysApps": "隐藏系统应用",
"options": {
"import": {
"clipboard": "从剪贴板导入选择",
"file": "从文件导入选择",
"msg": "导入将替换您当前的选择。确定要继续吗?"
},
"export": {
"clipboard": "复制选择到剪贴板",
"file": "导出选择到文件"
},
"shareToAll": "分享给所有人",
"clearAllSelections": "清除所有选择"
},
"modes": {
"all": "全部",
"proxy": "代理",
"bypass": "绕过",
"allMsg": "代理所有应用",
"proxyMsg": "仅代理选定应用",
"bypassMsg": "不代理选定应用"
},
"autoSelection": {
"title": "自动选择",
"performNow": "立即执行",
"resetToDefault": "重置为默认",
"autoUpdateInterval": "自动更新间隔",
"toast": {
"success": "自动选择应用成功完成",
"failure": "自动选择失败",
"regionNotFound": "未找到 \"${region}\" 地区的自动选择",
"alreadyInAuto": "您的选择已在自动列表中"
},
"dialog": {
"title": "自动选择应用",
"msg": "由于地区更改为 \"${region}\",分应用代理的自动选择功能已被禁用"
}
}
},
"region": "地区",
"regions": {
"ir": "伊朗 (ir)",
"cn": "中国 (cn)",
"ru": "俄罗斯 (ru)",
"af": "阿富汗 (af)",
"id": "印度尼西亚 (id)",
"tr": "土耳其 (tr)",
"br": "巴西 (br)",
"other": "其他"
},
"balancerStrategy": {
"title": "Balancer 策略",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "拦截广告",
"bypassLan": "绕过局域网",
"resolveDestination": "解析目的地",
"ipv6Route": "IPv6 路由",
"ipv6Modes": {
"disable": "禁用",
"enable": "启用",
"prefer": "首选",
"only": "仅"
},
"routeRule": {
"title": "路由规则",
"options": {
"import": {
"clipboard": "从剪贴板导入规则",
"file": "从文件导入规则"
},
"export": {
"clipboard": "复制规则到剪贴板",
"file": "保存规则到文件"
},
"reset": "重置规则"
},
"deleteRule": "删除规则",
"createRule": "创建新规则",
"rule": {
"title": "规则",
"ruleChanged": "规则已更改",
"ruleChangedMsg": "您想保存您的编辑吗?",
"onlyTunMode": "仅在 TUN 模式下可用",
"notAvailabeInThisPlatform": "在此平台上不可用",
"canNotBeEmpty": "不能为空",
"validUrlEx": "https://example.com",
"validUrl": "有效的 \"URL\",例如\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe 或 google chrome 或 chrome",
"validProcessName": "有效的 \"进程名\",例如\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "有效的 \"进程路径\",例如\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 或 1-65000",
"validPortRange": "有效的 \"端口\" 或 \"端口范围\",例如\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 或 10.0.0.0/24",
"validIpCidr": "有效的 IP CIDR,例如\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com 或 dl.google.com",
"validDomain": "有效的 \"域名\",例如\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com 或 .cn",
"validDomainSuffix": "有效的 \"域名后缀\",例如\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "名称",
"outbound": "匹配时出站",
"rule_set": "规则集 URL",
"package_name": "包名",
"process_name": "进程名",
"process_path": "进程路径",
"network": "网络",
"port_range": "目标端口",
"source_port_range": "源端口",
"protocol": "协议",
"ip_cidr": "目标 IP CIDR",
"source_ip_cidr": "源 IP CIDR",
"domain": "域名",
"domain_suffixe": "域名后缀",
"domain_keyword": "域名关键词",
"domain_regex": "域名正则表达式"
},
"outbound(map)": {
"proxy": "代理",
"direct": "直连",
"direct_with_fragment": "直连并分片",
"block": "拦截"
},
"network(map)": {
"all": "全部",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "全部",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "添加新值",
"update": "更新值",
"clearList": "清空列表",
"clearListMsg": "所有项目已删除"
},
"androidApps": {
"pageTitle": "安卓应用",
"showSystemApps": "显示系统应用",
"hideSystemApps": "隐藏系统应用",
"clearSelection": "清除选择",
"uninstalled": "已卸载"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "远程 DNS",
"remoteDnsDomainStrategy": "远程 DNS 域名策略",
"directDns": "出站服务器解析器(直连)",
"directDnsDomainStrategy": "出站域名策略",
"enableDnsRouting": "启用 DNS 路由",
"enableFakeDns": "启用伪造 DNS",
"domainStrategy": {
"auto": "自动",
"preferIpv6": "偏好 IPv6",
"preferIpv4": "偏好 IPv4",
"ipv4Only": "仅 IPv4",
"ipv6Only": "仅 IPv6"
}
},
"inbound": {
"title": "入站",
"serviceMode": "服务模式",
"serviceModes": {
"proxy": "仅代理服务",
"systemProxy": "设置系统代理",
"tun": "VPN",
"tunService": "VPN 服务"
},
"shortServiceModes": {
"proxy": "代理",
"systemProxy": "系统代理",
"tun": "VPN",
"tunService": "VPN 服务"
},
"strictRoute": "严格路由",
"tunImplementation": "TUN 实现",
"tunImplementations": {
"mixed": "混合",
"system": "系统",
"gvisor": "gVisor"
},
"mixedPort": "混合端口",
"tproxyPort": "透明代理端口",
"directPort": "本地 Direct 端口",
"redirectPort": "重定向端口",
"allowConnectionFromLan": "允许来自局域网的连接"
},
"tlsTricks": {
"title": "TLS 技巧",
"enable": "启用分片",
"packets": "分片数据包",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "分片大小",
"sleep": "分片延迟",
"mixedSniCase": {
"enable": "启用混合大小写 SNI"
},
"padding": {
"enable": "启用填充",
"size": "填充大小"
}
},
"warp": {
"title": "WARP",
"enable": "启用 WARP",
"generateConfig": "生成 WARP 配置",
"configGenerated": "WARP 配置已生成",
"missingConfig": "WARP 配置缺失",
"detourMode": "WARP 路由模式",
"detourModes": {
"proxyOverWarp": "通过 WARP 路由代理",
"warpOverProxy": "通过代理路由 WARP",
"proxyOverWarpExplain": "通过 WARP 解锁代理",
"warpOverProxyExplain": "通过 WARP 增强安全性"
},
"licenseKey": "许可证密钥",
"cleanIp": "优选 IP",
"port": "端口",
"noise": {
"count": "噪声数量",
"mode": "噪声模式",
"size": "噪声大小",
"delay": "噪声延迟"
}
}
}
},
"components": {
"stats": {
"connection": "连接",
"traffic": "流量",
"trafficLive": "实时流量",
"trafficTotal": "总流量",
"uplink": "上传",
"downlink": "下载",
"speed": "速度",
"totalTransferred": "总传输量"
},
"subscriptionInfo": {
"upload": "上传",
"download": "下载",
"total": "总流量",
"expireDate": "到期日期",
"expired": "已过期",
"noTraffic": "流量已用尽",
"remainingTime": "剩余时间",
"remainingDuration": "剩余 ${duration} 天",
"remainingDurationNew": "${duration} 天",
"remainingTrafficSemanticLabel": "已使用 ${consumed} / ${total} 流量",
"remainingTraffic": "剩余流量",
"remainingUsage": "剩余",
"profileSite": "提供商",
"profileSupport": "支持"
}
},
"dialogs": {
"sortProfiles": {
"title": "排序方式",
"sort": {
"name": "按字母顺序",
"lastUpdate": "最近更新"
}
},
"warpLicense": {
"title": "Cloudflare WARP 同意书",
"description(rich)": "Cloudflare WARP 是一个免费的 WireGuard VPN 提供商。启用此选项即表示您同意 Cloudflare WARP 的 ${tos(服务条款)} 和 ${privacy(隐私政策)}。"
},
"newVersion": {
"title": "有可用更新",
"msg": "@:common.appTitle 的新版本已发布。您想现在更新吗?",
"currentVersion": "当前版本:",
"newVersion": "新版本:",
"updateNow": "立即更新"
},
"confirmation": {
"settings": {
"import": {
"msg": "这将用提供的值覆盖所有配置选项。您确定吗?"
}
},
"profile": {
"delete": {
"title": "删除配置文件",
"msg": "您确定要永久删除此配置文件吗?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "改进自动选择",
"msg": "通过分享您选择的应用,可以帮助完善“自动选择”列表"
},
"import": {
"msg": "这将替换您当前所有的分应用代理选择。您确定要继续吗?"
}
},
"routeRule": {
"delete": {
"title": "删除规则",
"msg": "您确定要删除规则 \"$rulename\" 吗?"
}
}
},
"experimentalNotice": {
"title": "正在使用实验性功能",
"msg": "您已启用了一些实验性功能,这可能会影响连接质量并导致意外错误。您可以随时在配置选项页面更改或重置这些选项。",
"disable": "不再显示"
},
"noActiveProfile": {
"title": "选择一个配置文件",
"msg": "让我们从添加一个包含您 VPN 连接详细信息的配置文件开始。\n\n还没有 VPN 服务器?别担心,按照下面的教程,您可以快速免费地设置一个。",
"helpBtn": {
"label": "告诉我如何操作",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "外部链接警告",
"youAreAboutToVisit": "您即将访问:",
"thisWebsiteIsNotInOurTrustedList": "此网站不在我们的信任列表中。请谨慎操作。"
},
"proxyInfo": {
"fullTag": "完整标签:",
"type": "类型:",
"testTime": "测试时间:",
"testDelay": "测试延迟:",
"isSelected": "已选择:",
"isGroup": "是组",
"isSecure": "安全:",
"port": "端口:",
"host": "主机:",
"ip": "IP:",
"countryCode": "国家代码:",
"region": "地区:",
"city": "城市:",
"asn": "ASN:",
"organization": "组织:",
"location": "位置:",
"postalCode": "邮政编码:"
},
"windowClosing": {
"askEachTime": "每次询问",
"alertMessage": "隐藏还是退出应用程序?",
"remember": "记住我的选择"
}
},
"connection": {
"tapToConnect": "点击连接",
"connect": "连接",
"connecting": "连接中...",
"connected": "已连接",
"disconnect": "断开连接",
"disconnecting": "断开连接中...",
"reconnect": "重新连接",
"reconnectMsg": "正在重新连接以应用更改...",
"secure": "由 WARP 保护"
},
"errors": {
"unexpected": "意外错误",
"connection": {
"unexpected": "意外连接错误",
"timeout": "连接超时",
"badResponse": "响应错误",
"connectionError": "连接错误",
"badCertificate": "证书无效"
},
"profiles": {
"unexpected": "意外错误",
"notFound": "未找到配置文件",
"invalidConfig": "配置无效",
"invalidUrl": "URL 无效",
"canceledByUser": "用户已取消"
},
"connectivity": {
"unexpected": "意外失败",
"missingVpnPermission": "缺少 VPN 权限",
"missingNotificationPermission": "缺少通知权限",
"core": "核心错误"
},
"singbox": {
"serviceNotRunning": "服务未运行",
"missingPrivilege": "缺少权限",
"missingPrivilegeMsg": "VPN 模式需要管理员权限。请以管理员身份重新启动应用,或更改服务模式。",
"invalidConfigOptions": "配置选项无效",
"invalidConfig": "配置无效"
},
"warp": {
"missingLicense": "WARP 许可证缺失",
"missingLicenseMsg": "所选配置文件使用 WARP 功能;要使用此功能,必须同意 WARP 许可证。"
}
}
}
================================================
FILE: assets/translations/zh-TW.i18n.json
================================================
{
"common": {
"appTitle": "Hiddify",
"start": "開始",
"version": "版本",
"ok": "確定",
"cancel": "取消",
"kContinue": "繼續",
"showMore": "顯示更多",
"showLess": "顯示較少",
"filter": "篩選",
"all": "全部",
"pause": "暫停",
"resume": "恢復",
"clear": "清除",
"close": "關閉",
"auto": "自動",
"manually": "手動",
"name": "名稱",
"url": "URL",
"add": "新增",
"clipboard": "剪貼簿",
"addToClipboard": "新增至剪貼簿",
"scanQr": "掃描 QR Code",
"free": "免費",
"warp": "WARP",
"fragment": "Fragment",
"help": "說明",
"save": "儲存",
"update": "更新",
"share": "分享",
"edit": "編輯",
"delete": "刪除",
"discard": "捨棄",
"import": "匯入",
"export": "匯出",
"later": "稍後",
"ignore": "忽略",
"quit": "退出",
"notSet": "未設定",
"hide": "隱藏",
"exit": "退出",
"reset": "重設",
"done": "完成",
"search": "搜尋",
"decline": "拒絕",
"agree": "同意",
"empty": "空",
"unknown": "未知",
"hidden": "隱藏",
"timeout": "超時",
"sort": "排序",
"dashboard": "儀表板",
"interval": {
"day": {
"zero": "",
"one": "$n 天",
"other": "$n 天"
},
"hour": {
"zero": "",
"one": "$n 小時",
"other": "$n 小時"
}
},
"msg": {
"permission": {
"denied": "權限被拒絕"
},
"export": {
"clipboard": {
"success": "已成功新增至剪貼簿",
"failure": "複製到剪貼簿失敗",
"contentTooLarge": "內容過大,請改用匯出檔案"
},
"file": {
"success": "JSON 檔案建立成功",
"failure": "建立檔案失敗"
}
},
"import": {
"confirm": "確認匯入",
"success": "匯入成功",
"failure": "匯入失敗"
}
}
},
"intro": {
"banner": "暢享無限制網路的所需一切",
"termsAndPolicyCaution(rich)": "繼續即表示您同意 ${tap(@:pages.about.termsAndConditions)}",
"info(rich)": "由 Hiddify 以 ❤️ 製作 - ${tap_source(開源)} (${tap_license(授權)})"
},
"pages": {
"home": {
"title": "首頁",
"quickSettings": "快速設定"
},
"proxies": {
"title": "代理",
"sort": "排序代理",
"testDelay": "測試延遲",
"empty": "無可用代理",
"activeProxy": "當前代理",
"unknownIp": "未知 IP",
"sortOptions": {
"unsorted": "預設",
"name": "按名稱",
"delay": "按延遲"
},
"ipInfo": {
"address": "IP 位址",
"country": "國家",
"organization": "組織"
},
"delay": {
"result": "延遲:${delay}毫秒",
"timeout": "延遲測試超時",
"testing": "延遲:測試中..."
}
},
"profiles": {
"title": "設定檔",
"add": "新增設定檔",
"update": "更新設定檔",
"viewAllProfiles": "檢視所有設定檔",
"activeProfileName": "目前設定檔:「${name}」",
"nonActiveProfileName": "選擇「${name}」作為目前設定檔",
"freeSubNotFound": "未找到免費訂閱",
"freeSubNotFoundForRegion": "未找到「${region}」地區的免費訂閱",
"failedToLoad": "載入失敗",
"updateSubscriptions": "更新訂閱",
"share": {
"urlToClipboard": "URL 到剪貼簿",
"showUrlQr": "顯示 URL QR Code",
"jsonToClipboard": "JSON 到剪貼簿"
},
"msg": {
"save": {
"success": "設定檔儲存成功"
},
"invalidUrl": "無效的 URL",
"add": {
"failure": "新增設定檔失敗"
},
"update": {
"success": "設定檔更新成功",
"successNamed": "「${name}」更新成功",
"failure": "更新設定檔失敗",
"failureNamed": "更新「${name}」失敗"
},
"delete": {
"success": "設定檔刪除成功"
}
}
},
"profileDetails": {
"title": "設定檔",
"lastUpdate": "最後更新",
"form": {
"nameHint": "設定檔名稱",
"emptyName": "名稱為必填項",
"invalidUrl": "無效的 URL",
"urlHint": "完整的設定 URL",
"disableAutoUpdate": "停用自動更新",
"autoUpdateInterval": "自動更新間隔",
"loading": "正在新增設定檔..."
}
},
"logs": {
"title": "日誌",
"shareCoreLogs": "分享核心日誌",
"shareAppLogs": "分享應用程式日誌"
},
"about": {
"title": "關於",
"notAvailableMsg": "已是最新版本",
"checkForUpdate": "檢查更新",
"openWorkingDir": "開啟工作目錄",
"sourceCode": "原始碼",
"telegramChannel": "Telegram 頻道",
"termsAndConditions": "條款與條件",
"privacyPolicy": "隱私權政策"
},
"settings": {
"title": "設定",
"resetTunnel": "重設 VPN 設定檔",
"options": {
"import": {
"clipboard": "從剪貼簿匯入選項",
"file": "從檔案匯入選項"
},
"export": {
"anonymousToClipboard": "複製匿名選項到剪貼簿",
"anonymousToFile": "匯出匿名選項到檔案",
"allToClipboard": "複製所有選項到剪貼簿",
"allToFile": "匯出所有選項到檔案"
},
"reset": "重設選項"
},
"general": {
"title": "一般",
"locale": "語言",
"themeMode": "主題模式",
"themeModes": {
"system": "跟隨系統",
"dark": "深色模式",
"light": "淺色模式",
"black": "純黑模式"
},
"enableAnalytics": "啟用分析",
"enableAnalyticsMsg": "允許收集分析資料和傳送崩潰報告以改進應用程式",
"autoIpCheck": "自動檢查連線 IP",
"dynamicNotification": "在通知中顯示速度",
"hapticFeedback": "觸覺回饋",
"actionAtClosing": "關閉時操作",
"autoStart": "開機自啟",
"silentStart": "靜默啟動",
"ignoreBatteryOptimizations": "忽略電池最佳化",
"ignoreBatteryOptimizationsMsg": "移除限制以獲得最佳 VPN 效能",
"memoryLimit": "記憶體限制",
"memoryLimitMsg": "如果您遇到記憶體不足錯誤或應用程式頻繁崩潰,請啟用此選項",
"debugMode": "偵錯模式",
"debugModeMsg": "重新啟動應用程式以套用此變更",
"logLevel": "日誌級別",
"connectionTestUrl": "連線測試 URL",
"urlTestInterval": "URL 測試間隔",
"clashApiPort": "Clash API 連接埠",
"useXrayCoreWhenPossible": "盡可能使用 xray-core",
"useXrayCoreWhenPossibleMsg": "解析訂閱連結時使用 xray-core。您需要重新匯入訂閱連結才能啟用此選項"
},
"routing": {
"title": "路由",
"perAppProxy": {
"title": "依應用程式代理",
"hideSysApps": "隱藏系統應用程式",
"options": {
"import": {
"clipboard": "從剪貼簿匯入選擇",
"file": "從檔案匯入選擇",
"msg": "匯入將取代您目前的選擇。確定要繼續嗎?"
},
"export": {
"clipboard": "複製選擇到剪貼簿",
"file": "匯出選擇到檔案"
},
"shareToAll": "分享給所有人",
"clearAllSelections": "清除所有選擇"
},
"modes": {
"all": "全部",
"proxy": "代理",
"bypass": "繞過",
"allMsg": "代理所有應用程式",
"proxyMsg": "僅代理選定應用程式",
"bypassMsg": "不代理選定應用程式"
},
"autoSelection": {
"title": "自動選擇",
"performNow": "立即執行",
"resetToDefault": "重設為預設",
"autoUpdateInterval": "自動更新間隔",
"toast": {
"success": "自動選擇應用程式成功完成",
"failure": "自動選擇失敗",
"regionNotFound": "未找到「${region}」地區的自動選擇",
"alreadyInAuto": "您的選擇已在自動清單中"
},
"dialog": {
"title": "自動選擇應用程式",
"msg": "由於地區更改為「${region}」,依應用程式代理的自動選擇功能已被停用"
}
}
},
"region": "地區",
"regions": {
"ir": "伊朗 (ir)",
"cn": "中國 (cn)",
"ru": "俄羅斯 (ru)",
"af": "阿富汗 (af)",
"id": "印尼 (id)",
"tr": "土耳其 (tr)",
"br": "巴西 (br)",
"other": "其他"
},
"balancerStrategy": {
"title": "Balancer 策略",
"roundRobin": "Round robin",
"consistentHash": "Consistent hash",
"stickySession": "Sticky session"
},
"blockAds": "攔截廣告",
"bypassLan": "繞過區域網路",
"resolveDestination": "解析目的地",
"ipv6Route": "IPv6 路由",
"ipv6Modes": {
"disable": "停用",
"enable": "啟用",
"prefer": "首選",
"only": "僅"
},
"routeRule": {
"title": "路由規則",
"options": {
"import": {
"clipboard": "從剪貼簿匯入規則",
"file": "從檔案匯入規則"
},
"export": {
"clipboard": "複製規則到剪貼簿",
"file": "儲存規則到檔案"
},
"reset": "重設規則"
},
"deleteRule": "刪除規則",
"createRule": "建立新規則",
"rule": {
"title": "規則",
"ruleChanged": "規則已變更",
"ruleChangedMsg": "您想儲存您的編輯嗎?",
"onlyTunMode": "僅在 TUN 模式下可用",
"notAvailabeInThisPlatform": "在此平台上不可用",
"canNotBeEmpty": "不能為空",
"validUrlEx": "https://example.com",
"validUrl": "有效的「URL」,例如\n@:pages.settings.routing.routeRule.rule.validUrlEx",
"validProcessNameEx": "Chrome.exe 或 google chrome 或 chrome",
"validProcessName": "有效的「處理程序名稱」,例如\n@:pages.settings.routing.routeRule.rule.validProcessNameEx",
"validProcessPathEx": "C:\\Pro...\\chrome.exe\n/App.../Google Chrome\n/usr/lib.../chrome",
"validProcessPath": "有效的「處理程序路徑」,例如\n@:pages.settings.routing.routeRule.rule.validProcessPathEx",
"validPortRangeEx": "80 或 1-65000",
"validPortRange": "有效的「連接埠」或「連接埠範圍」,例如\n@:pages.settings.routing.routeRule.rule.validPortRangeEx",
"validIpCidrEx": "8.8.8.8 或 10.0.0.0/24",
"validIpCidr": "有效的 IP CIDR,例如\n@:pages.settings.routing.routeRule.rule.validIpCidrEx",
"validDomainEx": "Google.com 或 dl.google.com",
"validDomain": "有效的「網域」,例如\n@:pages.settings.routing.routeRule.rule.validDomainEx",
"validDomainSuffixEx": ".com 或 .tw",
"validDomainSuffix": "有效的「網域後綴」,例如\n@:pages.settings.routing.routeRule.rule.validDomainSuffixEx",
"tileTitle(map)": {
"name": "名稱",
"outbound": "符合時出站",
"rule_set": "規則集 URL",
"package_name": "套件名稱",
"process_name": "處理程序名稱",
"process_path": "處理程序路徑",
"network": "網路",
"port_range": "目標連接埠",
"source_port_range": "來源連接埠",
"protocol": "協定",
"ip_cidr": "目標 IP CIDR",
"source_ip_cidr": "來源 IP CIDR",
"domain": "網域",
"domain_suffixe": "網域後綴",
"domain_keyword": "網域關鍵字",
"domain_regex": "網域正規表示式"
},
"outbound(map)": {
"proxy": "代理",
"direct": "直連",
"direct_with_fragment": "直連並分片",
"block": "攔截"
},
"network(map)": {
"all": "全部",
"tcp": "TCP",
"udp": "UDP"
},
"protocol(map)": {
"": "全部",
"tls": "TLS",
"http": "HTTP",
"quic": "QUIC",
"stun": "STUN",
"dns": "DNS",
"bittorrent": "BitTorrent"
}
},
"genericList": {
"addNew": "新增值",
"update": "更新值",
"clearList": "清除清單",
"clearListMsg": "所有項目已刪除"
},
"androidApps": {
"pageTitle": "Android 應用程式",
"showSystemApps": "顯示系統應用程式",
"hideSystemApps": "隱藏系統應用程式",
"clearSelection": "清除選擇",
"uninstalled": "已解除安裝"
}
}
},
"dns": {
"title": "DNS",
"remoteDns": "遠端 DNS",
"remoteDnsDomainStrategy": "遠端 DNS 網域策略",
"directDns": "出站伺服器解析器(直連)",
"directDnsDomainStrategy": "出站網域策略",
"enableDnsRouting": "啟用 DNS 路由",
"enableFakeDns": "啟用偽造 DNS",
"domainStrategy": {
"auto": "自動",
"preferIpv6": "偏好 IPv6",
"preferIpv4": "偏好 IPv4",
"ipv4Only": "僅 IPv4",
"ipv6Only": "僅 IPv6"
}
},
"inbound": {
"title": "入站",
"serviceMode": "服務模式",
"serviceModes": {
"proxy": "僅代理服務",
"systemProxy": "設定系統代理",
"tun": "VPN",
"tunService": "VPN 服務"
},
"shortServiceModes": {
"proxy": "代理",
"systemProxy": "系統代理",
"tun": "VPN",
"tunService": "VPN 服務"
},
"strictRoute": "嚴格路由",
"tunImplementation": "TUN 實現",
"tunImplementations": {
"mixed": "混合",
"system": "系統",
"gvisor": "gVisor"
},
"mixedPort": "混合連接埠",
"tproxyPort": "透明代理連接埠",
"directPort": "本地 Direct 連接埠",
"redirectPort": "重新導向連接埠",
"allowConnectionFromLan": "允許來自區域網路的連線"
},
"tlsTricks": {
"title": "TLS 技巧",
"enable": "啟用分片",
"packets": "分片封包",
"packetsTlsHello": "TLS Hello",
"packets1_1": "1-1",
"packets1_2": "1-2",
"packets1_3": "1-3",
"packets1_4": "1-4",
"packets1_5": "1-5",
"size": "分片大小",
"sleep": "分片延遲",
"mixedSniCase": {
"enable": "啟用混合大小寫 SNI"
},
"padding": {
"enable": "啟用填充",
"size": "填充大小"
}
},
"warp": {
"title": "WARP",
"enable": "啟用 WARP",
"generateConfig": "產生 WARP 設定",
"configGenerated": "WARP 設定已產生",
"missingConfig": "WARP 設定缺失",
"detourMode": "WARP 路由模式",
"detourModes": {
"proxyOverWarp": "透過 WARP 路由代理",
"warpOverProxy": "透過代理路由 WARP",
"proxyOverWarpExplain": "透過 WARP 解鎖代理",
"warpOverProxyExplain": "透過 WARP 增強安全性"
},
"licenseKey": "授權金鑰",
"cleanIp": "優選 IP",
"port": "連接埠",
"noise": {
"count": "噪音數量",
"mode": "噪音模式",
"size": "噪音大小",
"delay": "噪音延遲"
}
}
}
},
"components": {
"stats": {
"connection": "連線",
"traffic": "流量",
"trafficLive": "即時流量",
"trafficTotal": "總流量",
"uplink": "上傳",
"downlink": "下載",
"speed": "速度",
"totalTransferred": "總傳輸量"
},
"subscriptionInfo": {
"upload": "上傳",
"download": "下載",
"total": "總流量",
"expireDate": "到期日期",
"expired": "已到期",
"noTraffic": "流量已用盡",
"remainingTime": "剩餘時間",
"remainingDuration": "剩餘 ${duration} 天",
"remainingDurationNew": "${duration} 天",
"remainingTrafficSemanticLabel": "已使用 ${consumed} / ${total} 流量",
"remainingTraffic": "剩餘流量",
"remainingUsage": "剩餘",
"profileSite": "提供商",
"profileSupport": "支援"
}
},
"dialogs": {
"sortProfiles": {
"title": "排序方式",
"sort": {
"name": "按字母順序",
"lastUpdate": "最近更新"
}
},
"warpLicense": {
"title": "Cloudflare WARP 同意書",
"description(rich)": "Cloudflare WARP 是一個免費的 WireGuard VPN 提供商。啟用此選項即表示您同意 Cloudflare WARP 的 ${tos(服務條款)} 和 ${privacy(隱私權政策)}。"
},
"newVersion": {
"title": "有可用更新",
"msg": "@:common.appTitle 的新版本已發布。您想立即更新嗎?",
"currentVersion": "目前版本:",
"newVersion": "新版本:",
"updateNow": "立即更新"
},
"confirmation": {
"settings": {
"import": {
"msg": "這會使用提供的值覆蓋所有設定選項。您確定嗎?"
}
},
"profile": {
"delete": {
"title": "刪除設定檔",
"msg": "您確定要永久刪除此設定檔嗎?"
}
},
"perAppProxy": {
"shareOnGithub": {
"title": "改進自動選擇",
"msg": "透過分享您選擇的應用程式,可以幫助完善「自動選擇」清單"
},
"import": {
"msg": "這將取代您目前所有的依應用程式代理選擇。您確定要繼續嗎?"
}
},
"routeRule": {
"delete": {
"title": "刪除規則",
"msg": "您確定要刪除規則「$rulename」嗎?"
}
}
},
"experimentalNotice": {
"title": "正在使用實驗性功能",
"msg": "您已啟用了一些實驗性功能,這可能會影響連線品質並導致意外錯誤。您可以隨時在設定選項頁面變更或重設這些選項。",
"disable": "不再顯示"
},
"noActiveProfile": {
"title": "選擇一個設定檔",
"msg": "讓我們從新增一個包含您 VPN 連線詳細資訊的設定檔開始。\n\n還沒有 VPN 伺服器?別擔心,按照下面的教學,您可以快速免費地設定一個。",
"helpBtn": {
"label": "告訴我如何操作",
"url": "https://hiddify.com/manager/"
}
},
"unknownDomainsWarning": {
"title": "外部連結警告",
"youAreAboutToVisit": "您即將造訪:",
"thisWebsiteIsNotInOurTrustedList": "此網站不在我們的信任清單中。請謹慎操作。"
},
"proxyInfo": {
"fullTag": "完整標籤:",
"type": "類型:",
"testTime": "測試時間:",
"testDelay": "測試延遲:",
"isSelected": "已選擇:",
"isGroup": "是群組",
"isSecure": "安全:",
"port": "連接埠:",
"host": "主機:",
"ip": "IP:",
"countryCode": "國家代碼:",
"region": "地區:",
"city": "城市:",
"asn": "ASN:",
"organization": "組織:",
"location": "位置:",
"postalCode": "郵遞區號:"
},
"windowClosing": {
"askEachTime": "每次詢問",
"alertMessage": "隱藏還是退出應用程式?",
"remember": "記住我的選擇"
}
},
"connection": {
"tapToConnect": "點擊連線",
"connect": "連線",
"connecting": "連線中...",
"connected": "已連線",
"disconnect": "中斷連線",
"disconnecting": "中斷連線中...",
"reconnect": "重新連線",
"reconnectMsg": "正在重新連線以套用變更...",
"secure": "由 WARP 保護"
},
"errors": {
"unexpected": "意外錯誤",
"connection": {
"unexpected": "意外連線錯誤",
"timeout": "連線超時",
"badResponse": "回應錯誤",
"connectionError": "連線錯誤",
"badCertificate": "憑證無效"
},
"profiles": {
"unexpected": "意外錯誤",
"notFound": "未找到設定檔",
"invalidConfig": "設定無效",
"invalidUrl": "URL 無效",
"canceledByUser": "使用者已取消"
},
"connectivity": {
"unexpected": "意外失敗",
"missingVpnPermission": "缺少 VPN 權限",
"missingNotificationPermission": "缺少通知權限",
"core": "核心錯誤"
},
"singbox": {
"serviceNotRunning": "服務未運行",
"missingPrivilege": "缺少權限",
"missingPrivilegeMsg": "VPN 模式需要管理員權限。請以管理員身份重新啟動應用,或更改服務模式。",
"invalidConfigOptions": "設定選項無效",
"invalidConfig": "設定無效"
},
"warp": {
"missingLicense": "WARP 授權缺失",
"missingLicenseMsg": "所選設定檔使用 WARP 功能;要使用此功能,必須同意 WARP 授權條款。"
}
}
}
================================================
FILE: build.yaml
================================================
targets:
$default:
builders:
json_serializable:
options:
explicit_to_json: true
drift_dev:
options:
databases:
db: "lib/core/db/db.dart"
schema_dir: "lib/core/db/schemas"
store_date_time_values_as_text: true
slang_build_runner:
options:
base_locale: en
fallback_strategy: base_locale
input_directory: assets/translations
input_file_pattern: .i18n.json
output_directory: lib/gen
output_file_name: translations.g.dart
translation_class_visibility: public
locale_handling: false
# protoc_builder:
# options:
# protobuf_version: "3.1.0"
# protoc_plugin_version: "21.1.2"
# root_dir: "assets/proto"
# proto_paths:
# - "assets/proto"
# out_dir: "lib/gen/proto"
# use_installed_protoc: true
# precompile_protoc_plugin: false
================================================
FILE: dependencies.properties
================================================
core.version=4.1.0
================================================
FILE: devtools_options.yaml
================================================
extensions:
================================================
FILE: ios/.gitignore
================================================
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
================================================
FILE: ios/.stignore
================================================
/**/dgph
/*.mode1v3
/*.mode2v3
/*.moved-aside
/*.pbxuser
/*.perspectivev3
/**/*sync/
/.sconsign.dblite
/.tags*
/**/.vagrant/
/**/DerivedData/
/Icon?
/**/Pods/
/**/.symlinks/
/profile
/xcuserdata
/**/.generated/
/Flutter/App.framework
/Flutter/Flutter.framework
/Flutter/Flutter.podspec
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/app.flx
/Flutter/app.zip
/Flutter/flutter_assets/
/Flutter/flutter_export_environment.sh
/ServiceDefinitions.json
/Runner/GeneratedPluginRegistrant.*
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
================================================
FILE: ios/Base.xcconfig
================================================
//
// Base.xcconfig
// Runner
//
// Created by GFWFighter on 7/24/1402 AP.
//
BASE_BUNDLE_IDENTIFIER=apple.hiddify.com
SERVICE_IDENTIFIER=com.hiddify.app
DEVELOPMENT_TEAM=3JFTY5BP58
================================================
FILE: ios/Flutter/AppFrameworkInfo.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
App
CFBundleIdentifier
io.flutter.flutter.app
CFBundleInfoDictionaryVersion
6.0
CFBundleName
App
CFBundlePackageType
FMWK
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1.0
MinimumOSVersion
15.0
================================================
FILE: ios/Flutter/Debug.xcconfig
================================================
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#include "Base.xcconfig"
================================================
FILE: ios/Flutter/Release.xcconfig
================================================
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
#include "Base.xcconfig"
================================================
FILE: ios/Frameworks/.gitkeep
================================================
================================================
FILE: ios/HiddifyPacketTunnel/HiddifyPacketTunnel.entitlements
================================================
com.apple.developer.networking.networkextension
app-proxy-provider
dns-proxy
packet-tunnel-provider
content-filter-provider
com.apple.security.app-sandbox
com.apple.security.application-groups
group.$(BASE_BUNDLE_IDENTIFIER)
com.apple.security.network.client
com.apple.security.network.server
com.apple.developer.networking.vpn.api
allow-vpn
================================================
FILE: ios/HiddifyPacketTunnel/Info.plist
================================================
BASE_BUNDLE_IDENTIFIER
$(BASE_BUNDLE_IDENTIFIER)
NSExtension
NSExtensionPointIdentifier
com.apple.networkextension.packet-tunnel
NSExtensionPrincipalClass
$(PRODUCT_MODULE_NAME).PacketTunnelProvider
================================================
FILE: ios/HiddifyPacketTunnel/Logger.swift
================================================
//
// Logger.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
class Logger2 {
private static let queue = DispatchQueue.init(label: "\(FilePath.packageName).PacketTunnelLog", qos: .utility)
private let fileManager = FileManager.default
private let url: URL
private var _fileHandle: FileHandle?
private var fileHandle: FileHandle? {
get {
if let _fileHandle { return _fileHandle }
let handle = try? FileHandle(forWritingTo: url)
_fileHandle = handle
return handle
}
}
private var lock = NSLock()
init(path: URL) {
url = path
}
func write(_ message: String) {
Logger2.queue.async { [message, unowned self] () in
lock.lock()
defer { lock.unlock() }
let output = message + "\n"
do {
if !self.fileManager.fileExists(atPath: url.path) {
try output.write(to: url, atomically: true, encoding: .utf8)
} else {
guard let fileHandle else {
return
}
fileHandle.seekToEndOfFile()
if let data = output.data(using: .utf8) {
fileHandle.write(data)
}
}
} catch {}
}
}
}
================================================
FILE: ios/HiddifyPacketTunnel/PacketTunnelProvider.swift
================================================
//
// PacketTunnelProvider.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/24/1402 AP.
//
import NetworkExtension
class PacketTunnelProvider: ExtensionProvider {
private var upload: Int64 = 0
private var download: Int64 = 0
// private var trafficLock: NSLock = NSLock()
// var trafficReader: TrafficReader!
override func startTunnel(options: [String : NSObject]?) async throws {
// override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
NSLog("H?C1")
try await super.startTunnel(options: options)
/*trafficReader = TrafficReader { [unowned self] traffic in
trafficLock.lock()
upload += traffic.up
download += traffic.down
trafficLock.unlock()
}*/
}
override func handleAppMessage(_ messageData: Data) async -> Data? {
let message = String(data: messageData, encoding: .utf8)
// NSLog("H?C2"+message??"")
switch message {
case "stats":
return "\(upload),\(download)".data(using: .utf8)!
default:
return nil
}
}
}
================================================
FILE: ios/HiddifyPacketTunnel/PrivacyInfo.xcprivacy
================================================
NSPrivacyAccessedAPITypes
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryFileTimestamp
NSPrivacyAccessedAPITypeReasons
C617.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategorySystemBootTime
NSPrivacyAccessedAPITypeReasons
35F9.1
================================================
FILE: ios/HiddifyPacketTunnel/SingBox/Extension+RunBlocking.swift
================================================
//
// Extension+RunBlocking.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
import HiddifyCore
import NetworkExtension
func runBlocking(_ block: @escaping () async -> T) -> T {
let semaphore = DispatchSemaphore(value: 0)
let box = resultBox()
Task.detached {
let value = await block()
box.result0 = value
semaphore.signal()
}
semaphore.wait()
return box.result0
}
func runBlocking(_ tBlock: @escaping () async throws -> T) throws -> T {
let semaphore = DispatchSemaphore(value: 0)
let box = resultBox()
Task.detached {
do {
let value = try await tBlock()
box.result = .success(value)
} catch {
box.result = .failure(error)
}
semaphore.signal()
}
semaphore.wait()
return try box.result.get()
}
private class resultBox {
var result: Result!
var result0: T!
}
================================================
FILE: ios/HiddifyPacketTunnel/SingBox/ExtensionPlatformInterface.swift
================================================
//
// ExtensionPlatformInterface.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
import HiddifyCore
import NetworkExtension
public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol {
private var tunnel: ExtensionProvider
private var networkSettings: NEPacketTunnelNetworkSettings?
init(_ tunnel: ExtensionProvider) {
self.tunnel = tunnel
}
public func openTun(_ options: LibboxTunOptionsProtocol?, ret0_: UnsafeMutablePointer?) throws {
NSLog("H?A1")
try runBlocking { [self] in
try await openTun0(options, ret0_)
}
}
private func openTun0(_ options: LibboxTunOptionsProtocol?, _ ret0_: UnsafeMutablePointer?) async throws {
guard let options else {
throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Nil options")])
}
guard let ret0_ else {
throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Nil return pointer")])
}
// let prefs = tunnel.overridePreferences ?? ExtensionProvider.OverridePreferences()
let autoRouteUseSubRangesByDefault = true//prefs.autoRouteUseSubRangesByDefault // await SharedPreferences.autoRouteUseSubRangesByDefault.get()
let excludeAPNs = false//prefs.excludeAPNsRoute //await SharedPreferences.excludeAPNsRoute.get()
let excludeDefaultRoute = false// prefs.excludeDefaultRoute
let systemProxyEnabled = false//prefs.systemProxyEnabled
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "::1")
if options.getAutoRoute() {
settings.mtu = NSNumber(value: options.getMTU())
let dnsServer = try options.getDNSServerAddress()
let dnsSettings = NEDNSSettings(servers: [dnsServer.value,"fdfe:dcba:9876::1"])
dnsSettings.matchDomains = [""]
dnsSettings.matchDomainsNoSearch = true
settings.dnsSettings = dnsSettings
var ipv4Address: [String] = []
var ipv4Mask: [String] = []
let ipv4AddressIterator = options.getInet4Address()!
while ipv4AddressIterator.hasNext() {
let ipv4Prefix = ipv4AddressIterator.next()!
ipv4Address.append(ipv4Prefix.address())
ipv4Mask.append(ipv4Prefix.mask())
}
let ipv4Settings = NEIPv4Settings(addresses: ipv4Address, subnetMasks: ipv4Mask)
var ipv4Routes: [NEIPv4Route] = []
var ipv4ExcludeRoutes: [NEIPv4Route] = []
let inet4RouteAddressIterator = options.getInet4RouteAddress()!
if inet4RouteAddressIterator.hasNext() {
while inet4RouteAddressIterator.hasNext() {
let ipv4RoutePrefix = inet4RouteAddressIterator.next()!
ipv4Routes.append(NEIPv4Route(destinationAddress: ipv4RoutePrefix.address(), subnetMask: ipv4RoutePrefix.mask()))
}
} else if autoRouteUseSubRangesByDefault {
ipv4Routes.append(NEIPv4Route(destinationAddress: "1.0.0.0", subnetMask: "255.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "2.0.0.0", subnetMask: "254.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "4.0.0.0", subnetMask: "252.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "8.0.0.0", subnetMask: "248.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "16.0.0.0", subnetMask: "240.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "32.0.0.0", subnetMask: "224.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "64.0.0.0", subnetMask: "192.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "128.0.0.0", subnetMask: "128.0.0.0"))
ipv4Routes.append(NEIPv4Route.default())
} else {
ipv4Routes.append(NEIPv4Route.default())
}
let inet4RouteExcludeAddressIterator = options.getInet4RouteExcludeAddress()!
while inet4RouteExcludeAddressIterator.hasNext() {
let ipv4RoutePrefix = inet4RouteExcludeAddressIterator.next()!
ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: ipv4RoutePrefix.address(), subnetMask: ipv4RoutePrefix.mask()))
}
if excludeDefaultRoute, !ipv4Routes.isEmpty {
if !ipv4ExcludeRoutes.contains(where: { it in
it.destinationAddress == "0.0.0.0" && it.destinationSubnetMask == "255.255.255.254"
}) {
ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "255.255.255.254"))
}
}
if excludeAPNs, !ipv4Routes.isEmpty {
if !ipv4ExcludeRoutes.contains(where: { it in
it.destinationAddress == "17.0.0.0" && it.destinationSubnetMask == "255.0.0.0"
}) {
ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: "17.0.0.0", subnetMask: "255.0.0.0"))
}
}
ipv4Settings.includedRoutes = ipv4Routes
ipv4Settings.excludedRoutes = ipv4ExcludeRoutes
settings.ipv4Settings = ipv4Settings
var ipv6Address: [String] = []
var ipv6Prefixes: [NSNumber] = []
let ipv6AddressIterator = options.getInet6Address()!
while ipv6AddressIterator.hasNext() {
let ipv6Prefix = ipv6AddressIterator.next()!
ipv6Address.append(ipv6Prefix.address())
ipv6Prefixes.append(NSNumber(value: ipv6Prefix.prefix()))
}
let ipv6Settings = NEIPv6Settings(addresses: ipv6Address, networkPrefixLengths: ipv6Prefixes)
var ipv6Routes: [NEIPv6Route] = []
var ipv6ExcludeRoutes: [NEIPv6Route] = []
let inet6RouteAddressIterator = options.getInet6RouteAddress()!
if inet6RouteAddressIterator.hasNext() {
while inet6RouteAddressIterator.hasNext() {
let ipv6RoutePrefix = inet6RouteAddressIterator.next()!
ipv6Routes.append(NEIPv6Route(destinationAddress: ipv6RoutePrefix.address(), networkPrefixLength: NSNumber(value: ipv6RoutePrefix.prefix())))
}
} else if autoRouteUseSubRangesByDefault {
ipv6Routes.append(NEIPv6Route(destinationAddress: "100::", networkPrefixLength: 8))
ipv6Routes.append(NEIPv6Route(destinationAddress: "200::", networkPrefixLength: 7))
ipv6Routes.append(NEIPv6Route(destinationAddress: "400::", networkPrefixLength: 6))
ipv6Routes.append(NEIPv6Route(destinationAddress: "800::", networkPrefixLength: 5))
ipv6Routes.append(NEIPv6Route(destinationAddress: "1000::", networkPrefixLength: 4))
ipv6Routes.append(NEIPv6Route(destinationAddress: "2000::", networkPrefixLength: 3))
ipv6Routes.append(NEIPv6Route(destinationAddress: "4000::", networkPrefixLength: 2))
ipv6Routes.append(NEIPv6Route(destinationAddress: "8000::", networkPrefixLength: 1))
ipv6Routes.append(NEIPv6Route.default())
} else {
ipv6Routes.append(NEIPv6Route.default())
}
let inet6RouteExcludeAddressIterator = options.getInet6RouteExcludeAddress()!
while inet6RouteExcludeAddressIterator.hasNext() {
let ipv6RoutePrefix = inet6RouteExcludeAddressIterator.next()!
ipv6ExcludeRoutes.append(NEIPv6Route(destinationAddress: ipv6RoutePrefix.address(), networkPrefixLength: NSNumber(value: ipv6RoutePrefix.prefix())))
}
if excludeDefaultRoute, !ipv6Routes.isEmpty {
if !ipv6ExcludeRoutes.contains(where: { it in
it.destinationAddress == "::" && it.destinationNetworkPrefixLength == 127
}) {
ipv6ExcludeRoutes.append(NEIPv6Route(destinationAddress: "::", networkPrefixLength: 127))
}
}
ipv6Settings.includedRoutes = ipv6Routes
ipv6Settings.excludedRoutes = ipv6ExcludeRoutes
settings.ipv6Settings = ipv6Settings
}
if options.isHTTPProxyEnabled() {
let proxySettings = NEProxySettings()
let proxyServer = NEProxyServer(address: options.getHTTPProxyServer(), port: Int(options.getHTTPProxyServerPort()))
proxySettings.httpServer = proxyServer
proxySettings.httpsServer = proxyServer
if systemProxyEnabled {
proxySettings.httpEnabled = true
proxySettings.httpsEnabled = true
}
var bypassDomains: [String] = []
let bypassDomainIterator = options.getHTTPProxyBypassDomain()!
while bypassDomainIterator.hasNext() {
bypassDomains.append(bypassDomainIterator.next())
}
if excludeAPNs {
if !bypassDomains.contains(where: { it in
it == "push.apple.com"
}) {
bypassDomains.append("push.apple.com")
}
}
if !bypassDomains.isEmpty {
proxySettings.exceptionList = bypassDomains
}
var matchDomains: [String] = []
let matchDomainIterator = options.getHTTPProxyMatchDomain()!
while matchDomainIterator.hasNext() {
matchDomains.append(matchDomainIterator.next())
}
if !matchDomains.isEmpty {
proxySettings.matchDomains = matchDomains
}
settings.proxySettings = proxySettings
}
networkSettings = settings
try await tunnel.setTunnelNetworkSettings(settings)
if let tunFd = tunnel.packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32 {
ret0_.pointee = tunFd
return
}
let tunFdFromLoop = LibboxGetTunnelFileDescriptor()
if tunFdFromLoop != -1 {
ret0_.pointee = tunFdFromLoop
} else {
throw NSError(domain: "missing file descriptor", code: 0)
}
}
public func usePlatformAutoDetectControl() -> Bool {
false
}
public func findConnectionOwner(_ ipProtocol: Int32, sourceAddress: String?, sourcePort: Int32, destinationAddress: String?, destinationPort: Int32) throws -> LibboxConnectionOwner {
#if os(macOS)
if Variant.useSystemExtension {
guard let sourceAddress, let destinationAddress else {
throw NSError(domain: "findConnectionOwner", code: 0, userInfo: [
NSLocalizedDescriptionKey: "Missing source or destination address",
])
}
let owner = try RootHelperClient.shared.findConnectionOwner(
ipProtocol: ipProtocol,
sourceAddress: sourceAddress,
sourcePort: sourcePort,
destinationAddress: destinationAddress,
destinationPort: destinationPort
)
let result = LibboxConnectionOwner()
result.userId = owner.userId
result.userName = owner.userName
result.processPath = owner.processPath
return result
}
#endif
throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Not implemented")])
}
public func useProcFS() -> Bool {
false
}
public func writeLog(_ message: String?) {
guard let message else {
return
}
tunnel.writeMessage(message)
}
private var nwMonitor: NWPathMonitor?
public func startDefaultInterfaceMonitor(_ listener: LibboxInterfaceUpdateListenerProtocol?) throws {
return
guard let listener else {
return
}
let monitor = NWPathMonitor()
nwMonitor = monitor
let semaphore = DispatchSemaphore(value: 0)
monitor.pathUpdateHandler = { path in
self.onUpdateDefaultInterface(listener, path)
semaphore.signal()
monitor.pathUpdateHandler = { path in
self.onUpdateDefaultInterface(listener, path)
}
}
monitor.start(queue: DispatchQueue.global())
semaphore.wait()
}
private func onUpdateDefaultInterface(_ listener: LibboxInterfaceUpdateListenerProtocol, _ path: Network.NWPath) {
return
guard path.status != .unsatisfied,
let defaultInterface = path.availableInterfaces.first
else {
listener.updateDefaultInterface("", interfaceIndex: -1, isExpensive: false, isConstrained: false)
return
}
listener.updateDefaultInterface(defaultInterface.name, interfaceIndex: Int32(defaultInterface.index), isExpensive: path.isExpensive, isConstrained: path.isConstrained)
}
public func closeDefaultInterfaceMonitor(_: LibboxInterfaceUpdateListenerProtocol?) throws {
nwMonitor?.cancel()
nwMonitor = nil
}
public func getInterfaces() throws -> LibboxNetworkInterfaceIteratorProtocol {
throw NSError(domain: "not implemented", code: 0)
guard let nwMonitor else {
throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "NWMonitor not started")])
}
let path = nwMonitor.currentPath
if path.status == .unsatisfied {
return networkInterfaceArray([])
}
var interfaces: [LibboxNetworkInterface] = []
for it in path.availableInterfaces {
let interface = LibboxNetworkInterface()
interface.name = it.name
interface.index = Int32(it.index)
switch it.type {
case .wifi:
interface.type = LibboxInterfaceTypeWIFI
case .cellular:
interface.type = LibboxInterfaceTypeCellular
case .wiredEthernet:
interface.type = LibboxInterfaceTypeEthernet
default:
interface.type = LibboxInterfaceTypeOther
}
interfaces.append(interface)
}
return networkInterfaceArray(interfaces)
}
class networkInterfaceArray: NSObject, LibboxNetworkInterfaceIteratorProtocol {
private var iterator: IndexingIterator<[LibboxNetworkInterface]>
init(_ array: [LibboxNetworkInterface]) {
iterator = array.makeIterator()
}
private var nextValue: LibboxNetworkInterface?
func hasNext() -> Bool {
nextValue = iterator.next()
return nextValue != nil
}
func next() -> LibboxNetworkInterface? {
nextValue
}
}
public func underNetworkExtension() -> Bool {
true
}
public func includeAllNetworks() -> Bool {
#if os(tvOS)
return false
#else
return false
// return tunnel.overridePreferences?.includeAllNetworks ?? false
#endif
}
public func clearDNSCache() {
guard let networkSettings else {
return
}
runBlocking {
self.tunnel.reasserting = true
defer { self.tunnel.reasserting = false }
await withCheckedContinuation { continuation in
self.tunnel.setTunnelNetworkSettings(nil) { _ in
continuation.resume()
}
}
await withCheckedContinuation { continuation in
self.tunnel.setTunnelNetworkSettings(networkSettings) { _ in
continuation.resume()
}
}
}
}
public func readWIFIState() -> LibboxWIFIState? {
// #if os(iOS)
// let network = runBlocking {
// await NEHotspotNetwork.fetchCurrent()
// }
// guard let network else {
// return nil
// }
// return LibboxWIFIState(network.ssid, wifiBSSID: network.bssid)!
// #elseif os(macOS)
// if Variant.useSystemExtension {
// return UserServiceClient.shared.readWIFIState()
// }
// guard let interface = CWWiFiClient.shared().interface() else {
// return nil
// }
// guard let ssid = interface.ssid() else {
// return nil
// }
// guard let bssid = interface.bssid() else {
// return nil
// }
// return LibboxWIFIState(ssid, wifiBSSID: bssid)!
// #else
return nil
// #endif
}
public func readWIFISSID() -> String? {
// #if os(iOS)
// return runBlocking {
// await NEHotspotNetwork.fetchCurrent()?.ssid
// }
// #elseif os(macOS)
// return CWWiFiClient.shared().interface()?.ssid()
// #else
return nil
// #endif
}
// public func serviceStop() throws {
// tunnel.stopService()
// }
public func serviceReload() throws {
try runBlocking { [self] in
try await tunnel.reloadService()
}
}
public func getSystemProxyStatus() throws -> LibboxSystemProxyStatus {
let status = LibboxSystemProxyStatus()
guard let networkSettings else {
return status
}
guard let proxySettings = networkSettings.proxySettings else {
return status
}
if proxySettings.httpServer == nil {
return status
}
status.available = true
status.enabled = proxySettings.httpEnabled
return status
}
public func setSystemProxyEnabled(_ isEnabled: Bool) throws {
guard let networkSettings else {
return
}
guard let proxySettings = networkSettings.proxySettings else {
return
}
if proxySettings.httpServer == nil {
return
}
if proxySettings.httpEnabled == isEnabled {
return
}
proxySettings.httpEnabled = isEnabled
proxySettings.httpsEnabled = isEnabled
networkSettings.proxySettings = proxySettings
try runBlocking {
try await self.tunnel.setTunnelNetworkSettings(networkSettings)
}
}
public func writeDebugMessage(_ message: String?) {
guard let message else {
return
}
// tunnel.writeMessage(message)
}
func reset() {
networkSettings = nil
nwMonitor?.cancel()
nwMonitor = nil
}
public func send(_ notification: LibboxNotification?) throws {
#if !os(tvOS)
guard let notification else {
return
}
#if os(macOS)
if Variant.useSystemExtension {
try UserServiceClient.shared.sendNotification(notification)
return
}
#endif
// let center = UNUserNotificationCenter.current()
// let content = UNMutableNotificationContent()
//
// content.title = notification.title
// content.subtitle = notification.subtitle
// content.body = notification.body
// if !notification.openURL.isEmpty {
// content.userInfo["OPEN_URL"] = notification.openURL
// content.categoryIdentifier = "OPEN_URL"
// }
// content.interruptionLevel = .active
// let request = UNNotificationRequest(identifier: notification.identifier, content: content, trigger: nil)
// try runBlocking {
// try await center.requestAuthorization(options: [.alert])
// try await center.add(request)
// }
#endif
}
public func localDNSTransport() -> (any LibboxLocalDNSTransportProtocol)? {
nil
}
public func systemCertificates() -> (any LibboxStringIteratorProtocol)? {
nil
}
public func autoDetectControl(_: Int32) throws {}
}
================================================
FILE: ios/HiddifyPacketTunnel/SingBox/ExtensionProvider.swift
================================================
import Foundation
import HiddifyCore
import NetworkExtension
import os.log
open class ExtensionProvider: NEPacketTunnelProvider {
public static let errorFile = FilePath.workingDirectory.appendingPathComponent("network_extension_error.log")
private let logger = Logger(subsystem: "apple.hiddify.com.HiddifyPacketTunnel", category: "PacketTunnel")
// private var commandServer: LibboxCommandServer!
private var systemProxyAvailable = false
private var systemProxyEnabled = false
private var platformInterface: ExtensionPlatformInterface!
private var config: String!
override open func startTunnel(options: [String: NSObject]?) async throws {
// Clear previous logs
try? FileManager.default.removeItem(at: ExtensionProvider.errorFile)
try? FileManager.default.removeItem(at: FilePath.workingDirectory.appendingPathComponent("TestLog"))
do {
writeMessage("(packet-tunnel) starting")
// Extract options with better error handling
let disableMemoryLimit = false && (options?["DisableMemoryLimit"] as? NSString as? String ?? "NO") == "YES"
let grpcServiceModePort = (options?["GrpcServiceModePort"] as? NSNumber)?.intValue ?? 17079
let config = options?["Config"] as? NSString as? String ?? ""
// guard let config = SingBox.setupConfig(config: config2) else {
// writeFatalError("(packet-tunnel) error: config is invalid")
// return
// }
// self.config = config
do {
try FileManager.default.createDirectory(at: FilePath.workingDirectory, withIntermediateDirectories: true)
} catch {
writeFatalError("(packet-tunnel) error: create working directory: \(error.localizedDescription)")
return
}
// Ensure directories exist
try createRequiredDirectories()
// Log directory paths for debugging
let sharedDir = FilePath.sharedDirectory.relativePath
let workDir = FilePath.workingDirectory.relativePath
let cacheDir = FilePath.cacheDirectory.relativePath
if platformInterface == nil {
platformInterface = ExtensionPlatformInterface(self)
}
// Initialize mobile setup with error handling
var setupError: NSError?
let opts = MobileSetupOptions()
opts.basePath = sharedDir
opts.workingDir = workDir
opts.tempDir = cacheDir
opts.listen = "127.0.0.1:\(grpcServiceModePort)"
opts.secret = ""
opts.debug = false
opts.mode = 4
opts.fixAndroidStack = false
MobileSetup(opts,
platformInterface,
&setupError
)
if let setupError = setupError {
throw setupError
}
LibboxSetMemoryLimit(!disableMemoryLimit)
writeMessage("(packet-tunnel) setup completed successfully")
if (config==""){
try await startService1(config)
}
} catch {
logger.error("Tunnel setup failed: \(error.localizedDescription)")
writeFatalError("(packet-tunnel) setup failed: \(error.localizedDescription)")
throw error
}
}
private func startService1(_ config: String) async throws {
writeMessage("Starting service")
var error: NSError?
//
do {
try MobileStart(config, "", &error)
if let error = error {
throw error
}
writeMessage("(packet-tunnel) service started successfully")
} catch {
writeFatalError("(packet-tunnel) error: start service: \(error.localizedDescription)")
throw error
}
}
private func createRequiredDirectories() throws {
let directories = [
FilePath.workingDirectory,
FilePath.sharedDirectory,
FilePath.cacheDirectory
]
for directory in directories {
do {
try FileManager.default.createDirectory(
at: directory,
withIntermediateDirectories: true,
attributes: nil
)
} catch {
logger.error("Failed to create directory at \(directory.path): \(error.localizedDescription)")
throw error
}
}
}
func writeMessage(_ message: String) {
logger.debug("\(message)")
writeError(message)
}
func writeError(_ message: String) {
let messageWithNewline = "[\(Date())] \(message)\n"
do {
if FileManager.default.fileExists(atPath: ExtensionProvider.errorFile.path) {
if let fileHandle = try? FileHandle(forWritingTo: ExtensionProvider.errorFile) {
defer { fileHandle.closeFile() }
fileHandle.seekToEndOfFile()
if let data = messageWithNewline.data(using: .utf8) {
fileHandle.write(data)
}
}
} else {
try messageWithNewline.write(to: ExtensionProvider.errorFile, atomically: true, encoding: .utf8)
}
} catch {
logger.error("Failed to write to error file: \(error.localizedDescription)")
}
}
public func writeFatalError(_ message: String) {
logger.fault("Fatal error: \(message)")
writeError("FATAL: \(message)")
cancelTunnelWithError(NSError(domain: "ExtensionProvider", code: 0, userInfo: [NSLocalizedDescriptionKey: message]))
}
override open func stopTunnel(with reason: NEProviderStopReason) async {
// logger.debug("Stopping tunnel with reason: \(reason)")
writeMessage("(packet-tunnel) stopping, reason: \(reason)")
stopService()
// // Allow time for cleanup
// try? await Task.sleep(nanoseconds: 100 * NSEC_PER_MSEC)
//
// if let server = commandServer {
// try? server.close()
// commandServer = nil
// }
}
private func stopService() {
logger.debug("Stopping service")
MobileClose(4)
if let platformInterface {
platformInterface.reset()
}
}
func reloadService() async {
logger.debug("Reloading service")
writeMessage("(packet-tunnel) reloading service")
// reasserting = true
// defer { reasserting = false }
//
// stopService()
// do {
// guard let config = try? String(contentsOf: FilePath.configFile) else {
// writeFatalError("(packet-tunnel) error: cannot read config file")
// return
// }
// try await startService(config)
// } catch {
// writeFatalError("(packet-tunnel) error: reload service: \(error.localizedDescription)")
// }
}
override open func handleAppMessage(_ messageData: Data) async -> Data? {
logger.debug("Handling app message")
return messageData
}
override open func sleep() async {
logger.debug("Entering sleep mode")
// MobilePause()
// Add any sleep mode handling if needed
}
override open func wake() {
logger.debug("Waking from sleep")
MobileWake()
// Add any wake handling if needed
}
}
// Extension to support error handling
extension ExtensionProvider {
enum ExtensionError: Error {
case configurationMissing
case directoryCreationFailed
case serviceStartFailed
var localizedDescription: String {
switch self {
case .configurationMissing:
return "Configuration not provided"
case .directoryCreationFailed:
return "Failed to create required directories"
case .serviceStartFailed:
return "Failed to start the service"
}
}
}
}
================================================
FILE: ios/HiddifyPacketTunnel/SingBox/SingBox.swift
================================================
//
// SingBox.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
class SingBox {
static func setupConfig(config: String, mtu: Int = 9000) -> String? {
NSLog("H?B1")
guard
let config = config.data(using: .utf8),
var json = try? JSONSerialization
.jsonObject(
with: config,
options: [.mutableLeaves, .mutableContainers]
) as? [String:Any]
else {
return nil
}
/*json["log"] = [
"disabled": false,
"level": "info",
"output": "log",
"timestamp": true
] as [String:Any]
json["experimental"] = [
"clash_api": [
"external_controller": "127.0.0.1:10864"
]
]
json["inbounds"] = [
[
"type": "tun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"mtu": mtu,
"sniff": true
] as [String:Any]
]
var routing = (json["route"] as? [String:Any]) ?? [
"rules": [Any](),
"auto_detect_interface": true,
"final": (json["inbounds"] as? [[String:Any]])?.first?["tag"] ?? "proxy"
]
routing["geoip"] = [
"path": FilePath.assetsDirectory.appendingPathComponent("geoip.db"),
]
routing["geosite"] = [
"path": FilePath.assetsDirectory.appendingPathComponent("geosite.db"),
]
json["route"] = routing*/
guard let data = try? JSONSerialization.data(withJSONObject: json) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}
================================================
FILE: ios/HiddifyPacketTunnel/TrafficReader.swift
================================================
//
// TrafficReader.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
struct TrafficReaderUpdate: Codable {
let up: Int64
let down: Int64
}
class TrafficReader {
private var task: URLSessionWebSocketTask!
private let callback: (TrafficReaderUpdate) -> ()
init(onUpdate: @escaping (TrafficReaderUpdate) -> ()) {
NSLog("H?D2")
self.callback = onUpdate
Task(priority: .background) { [weak self] () in
await self?.setup()
}
}
private func setup() async {
NSLog("H?D1")
try? await Task.sleep(nanoseconds: 5_000_000_000)
//return
while true {
do {
let (_, response) = try await URLSession.shared.data(from: URL(string: "http://127.0.0.1:10864")!)
let code = (response as? HTTPURLResponse)?.statusCode ?? -1
if code >= 200 && code < 300 {
break
}
} catch {
// pass
}
try? await Task.sleep(nanoseconds: 5_000_000)
}
let task = URLSession.shared.webSocketTask(with: URL(string: "ws://127.0.0.1:10864/traffic")!)
self.task = task
read()
task.resume()
}
private func read() {
NSLog("H?D3")
task.receive { [weak self] result in
switch result {
case .failure(_):
break
case .success(let message):
switch message {
case .string(let message):
guard let data = message.data(using: .utf8) else {
break
}
guard let response = try? JSONDecoder().decode(TrafficReaderUpdate.self, from: data) else {
break
}
self?.callback(response)
default:
break
}
self?.read()
}
}
}
}
================================================
FILE: ios/Local Packages/Package.swift
================================================
// swift-tools-version: 5.4
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Hiddify Packages",
platforms: [
// Minimum platform version
.iOS(.v13)
],
products: [
.library(
name: "HiddifyCore",
targets: ["HiddifyCore"]),
],
dependencies: [
// No dependencies
],
targets: [
.binaryTarget(
name: "HiddifyCore",
path: "../Frameworks/HiddifyCore.xcframework"
)
]
)
================================================
FILE: ios/Podfile
================================================
# Uncomment this line to define a global platform for your project
platform :ios, '15.5'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
pod 'EasyPermissionX/Camera'
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end
================================================
FILE: ios/Runner/AppDelegate.swift
================================================
import UIKit
import Flutter
import HiddifyCore
import Sentry
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
setupFileManager()
registerHandlers()
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func setupFileManager() {
try? FileManager.default.createDirectory(at: FilePath.workingDirectory, withIntermediateDirectories: true)
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
}
func registerHandlers() {
MethodHandler.register(with: self.registrar(forPlugin: MethodHandler.name)!)
PlatformMethodHandler.register(with: self.registrar(forPlugin: PlatformMethodHandler.name)!)
FileMethodHandler.register(with: self.registrar(forPlugin: FileMethodHandler.name)!)
StatusEventHandler.register(with: self.registrar(forPlugin: StatusEventHandler.name)!)
AlertsEventHandler.register(with: self.registrar(forPlugin: AlertsEventHandler.name)!)
// LogsEventHandler.register(with: self.registrar(forPlugin: LogsEventHandler.name)!)
// GroupsEventHandler.register(with: self.registrar(forPlugin: GroupsEventHandler.name)!)
// ActiveGroupsEventHandler.register(with: self.registrar(forPlugin: ActiveGroupsEventHandler.name)!)
// StatsEventHandler.register(with: self.registrar(forPlugin: StatsEventHandler.name)!)
}
}
================================================
FILE: ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "app-icon-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
================================================
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
================================================
FILE: ios/Runner/Base.lproj/LaunchScreen.storyboard
================================================
================================================
FILE: ios/Runner/Base.lproj/Main.storyboard
================================================
================================================
FILE: ios/Runner/Extensions/Bundle+Properties.swift
================================================
//
// Bundle+Properties.swift
// Runner
//
// Created by Hiddify on 12/26/23.
//
import Foundation
extension Bundle {
var serviceIdentifier: String {
(infoDictionary?["SERVICE_IDENTIFIER"] as? String)!
}
var baseBundleIdentifier: String {
(infoDictionary?["BASE_BUNDLE_IDENTIFIER"] as? String)!
}
}
================================================
FILE: ios/Runner/Handlers/ActiveGroupsEventHandler.swift
================================================
import Foundation
import Combine
import HiddifyCore
public class ActiveGroupsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(Bundle.main.serviceIdentifier)/active-groups"
private var commandClient: CommandClient?
private var channel: FlutterEventChannel?
private var events: FlutterEventSink?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = ActiveGroupsEventHandler()
instance.channel = FlutterEventChannel(name: Self.name,
binaryMessenger: registrar.messenger())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
self.events = events
commandClient = CommandClient(.groupsInfoOnly)
commandClient?.connect()
cancellable = commandClient?.$groups.sink{ [self] sbGroups in
self.writeGroups(sbGroups)
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
commandClient?.disconnect()
cancellable?.cancel()
events = nil
return nil
}
func writeGroups(_ sbGroups: [SBGroup]?) {
guard let sbGroups else {return}
if
let groups = try? JSONEncoder().encode(sbGroups),
let groups = String(data: groups, encoding: .utf8)
{
DispatchQueue.main.async { [events = self.events, groups] () in
events?(groups)
}
}
}
}
================================================
FILE: ios/Runner/Handlers/AlertsEventHandler.swift
================================================
//
// AlertEventHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
import Combine
public class AlertsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(Bundle.main.serviceIdentifier)/service.alerts"
private var channel: FlutterEventChannel?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = AlertsEventHandler()
instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger(), codec: FlutterJSONMethodCodec())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
cancellable = VPNManager.shared.$alert.sink { [events] alert in
var data = [
"status": "Stopped",
"alert": alert.alert?.rawValue,
"message": alert.message,
]
for key in data.keys {
if data[key] == nil {
data.removeValue(forKey: key)
}
}
events(data)
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
cancellable?.cancel()
return nil
}
}
================================================
FILE: ios/Runner/Handlers/FileMethodHandler.swift
================================================
//
// FileMethodHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
public class FileMethodHandler: NSObject, FlutterPlugin {
public static let name = "\(Bundle.main.serviceIdentifier)/files.method"
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: Self.name, binaryMessenger: registrar.messenger())
let instance = FileMethodHandler()
registrar.addMethodCallDelegate(instance, channel: channel)
instance.channel = channel
}
private var channel: FlutterMethodChannel?
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "get_paths":
result([
"working": FilePath.workingDirectory.path,
"temp": FilePath.cacheDirectory.path,
"base": FilePath.sharedDirectory.path
])
default:
result(FlutterMethodNotImplemented)
}
}
}
================================================
FILE: ios/Runner/Handlers/GroupsEventHandler.swift
================================================
import Foundation
import Combine
import HiddifyCore
public class GroupsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler{
static let name = "\(Bundle.main.serviceIdentifier)/groups"
private var commandClient: CommandClient?
private var channel: FlutterEventChannel?
private var events: FlutterEventSink?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = GroupsEventHandler()
instance.channel = FlutterEventChannel(name: Self.name,
binaryMessenger: registrar.messenger())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
self.events = events
commandClient = CommandClient(.groups)
commandClient?.connect()
cancellable = commandClient?.$groups.sink{ [self] groups in
self.writeGroups(groups)
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
commandClient?.disconnect()
cancellable?.cancel()
events = nil
return nil
}
func writeGroups(_ sbGroups: [SBGroup]?) {
guard let sbGroups else {return}
if
let groups = try? JSONEncoder().encode(sbGroups),
let groups = String(data: groups, encoding: .utf8)
{
DispatchQueue.main.async { [events = self.events, groups] in
events?(groups)
}
}
}
}
================================================
FILE: ios/Runner/Handlers/LogsEventHandler.swift
================================================
import Foundation
import Combine
import HiddifyCore
class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(Bundle.main.serviceIdentifier)/service.logs"
private var commandClient: CommandClient?
private var events: FlutterEventSink?
private var channel: FlutterEventChannel?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = LogsEventHandler()
instance.channel = FlutterEventChannel(name: Self.name,
binaryMessenger: registrar.messenger())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
self.events = events
commandClient = CommandClient(.log)
commandClient?.connect()
cancellable = commandClient?.$logList.sink{ [self] logs in
events(logs)
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
commandClient?.disconnect()
cancellable?.cancel()
events = nil
return nil
}
}
/*
extension LogsEventHandler {
public func clearLog() {}
public func connected() {}
public func disconnected(_ message: String?) {}
public func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) {}
public func updateClashMode(_ newMode: String?) {}
public func writeGroups(_ message: LibboxOutboundGroupIteratorProtocol?) {}
public func writeStatus(_ message: LibboxStatusMessage?) {}
}
*/
================================================
FILE: ios/Runner/Handlers/MethodHandler.swift
================================================
//
// MethodHandler.swift
// Runner
//
// Created by GFWFighter on 10/23/23.
//
import Flutter
import Combine
import HiddifyCore
public class MethodHandler: NSObject, FlutterPlugin {
private var cancelBag: Set = []
public static let name = "\(Bundle.main.serviceIdentifier)/method"
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: Self.name, binaryMessenger: registrar.messenger())
let instance = MethodHandler()
registrar.addMethodCallDelegate(instance, channel: channel)
instance.channel = channel
}
private var channel: FlutterMethodChannel?
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@Sendable func mainResult(_ res: Any?) async -> Void {
await MainActor.run {
result(res)
}
}
switch call.method {
case "get_grpc_server_public_key":
result("")
case "add_grpc_client_public_key":
result("")
case "parse_config":
guard
let args = call.arguments as? [String:Any?],
let path = args["path"] as? String,
let tempPath = args["tempPath"] as? String,
let debug = (args["debug"] as? NSNumber)?.boolValue
else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
var error: NSError?
//MobileParse(path, tempPath, debug, &error)
if let error {
result(FlutterError(code: String(error.code), message: error.description, details: nil))
return
}
result("")
case "change_hiddify_options":
guard let options = call.arguments as? String else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
VPNConfig.shared.configOptions = options
result(true)
case "setup":
Task {
guard
let args = call.arguments as? [String: Any?],
let baseDir = args["baseDir"] as? String,
let workingDir = args["workingDir"] as? String,
let tempDir = args["tempDir"] as? String,
let mode = args["mode"] as? Int,
let grpcPort = args["grpcPort"] as? Int
else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
VPNConfig.shared.baseDir=baseDir
VPNConfig.shared.workingDir=workingDir
VPNConfig.shared.tempDir=tempDir
var error: NSError?
let opts = MobileSetupOptions()
opts.basePath = baseDir
opts.workingDir = workingDir
opts.tempDir = tempDir
opts.listen = "127.0.0.1:\(grpcPort)"
opts.secret = ""
opts.debug = false
opts.mode = 4
opts.fixAndroidStack = false
MobileSetup(opts,
nil,
&error
)
if let error {
result(FlutterError(code: String(error.code), message: error.localizedDescription, details: nil))
return
}
do {
try await VPNManager.shared.setup()
} catch {
result(FlutterError(code: "SETUP", message: error.localizedDescription, details: nil))
return
}
result(true)
}
case "start":
Task {
guard
let args = call.arguments as? [String:Any?],
let path = args["path"] as? String,
let name = args["name"] as? String,
let grpcPort=args["grpcPort"] as? Int
else {
await mainResult(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
VPNConfig.shared.activeConfigPath = path
VPNConfig.shared.activeProfileName = name
VPNConfig.shared.grpcServiceModePort=grpcPort
var error: NSError?
//let configstr=MobileBuildConfig(path,&error) as String
if let error {
await mainResult(FlutterError(code: String(error.code), message: error.description, details: nil))
return
}
do {
try await VPNManager.shared.setup()
try await VPNManager.shared.connect(with: path, grpcServiceModePort: grpcPort, disableMemoryLimit: VPNConfig.shared.disableMemoryLimit)
} catch {
await mainResult(FlutterError(code: "SETUP_CONNECTION", message: error.localizedDescription, details: nil))
return
}
await mainResult(true)
}
// case "restart":
// Task { [unowned self] in
// guard
// let args = call.arguments as? [String:Any?],
// let path = args["path"] as? String,
// let name = args["name"] as? String,
// let grpcPort=args["grpcPort"] as? Int
// else {
// await mainResult(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
// return
// }
// VPNConfig.shared.activeConfigPath = path
// VPNConfig.shared.activeProfileName = name
// VPNConfig.shared.grpcServiceModePort=grpcPort
//
//
// VPNManager.shared.disconnect()
// await waitForStop().value
// var error: NSError?
// do {
// try await VPNManager.shared.setup()
// try await VPNManager.shared.connect(with: path, disableMemoryLimit: VPNConfig.shared.disableMemoryLimit)
// } catch {
// await mainResult(FlutterError(code: "SETUP_CONNECTION", message: error.localizedDescription, details: nil))
// return
// }
// await mainResult(true)
// }
case "stop":
VPNManager.shared.disconnect()
result(true)
case "reset":
VPNManager.shared.reset()
result(true)
case "url_test":
guard
let args = call.arguments as? [String:Any?]
else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
let group = args["groupTag"] as? String
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
do {
try LibboxNewStandaloneCommandClient()?.urlTest(group)
} catch {
result(FlutterError(code: "URL_TEST", message: error.localizedDescription, details: nil))
return
}
result(true)
case "select_outbound":
guard
let args = call.arguments as? [String:Any?],
let group = args["groupTag"] as? String,
let outbound = args["outboundTag"] as? String
else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
do {
try LibboxNewStandaloneCommandClient()?.selectOutbound(group, outboundTag: outbound)
} catch {
result(FlutterError(code: "SELECT_OUTBOUND", message: error.localizedDescription, details: nil))
return
}
result(true)
case "generate_config":
guard
let args = call.arguments as? [String:Any?],
let path = args["path"] as? String
else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
var error: NSError?
// let config = MobileBuildConfig(path, VPNConfig.shared.configOptions, &error)
// if let error {
// result(FlutterError(code: "BUILD_CONFIG", message: error.localizedDescription, details: nil))
// return
// }
// result(config)
case "generate_warp_config":
guard let args = call.arguments as? [String: Any],
let licenseKey = args["license-key"] as? String,
let accountId = args["previous-account-id"] as? String,
let accessToken = args["previous-access-token"] as? String else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))
return
}
// let warpConfig = MobileGenerateWarpConfig(licenseKey, accountId, accessToken, nil)
// result(warpConfig)
default:
result(FlutterMethodNotImplemented)
}
}
private func waitForStop() -> Future {
return Future { promise in
var cancellable: AnyCancellable? = nil
cancellable = VPNManager.shared.$state
.filter { $0 == .disconnected }
.first()
.delay(for: 0.5, scheduler: RunLoop.current)
.sink(receiveValue: { _ in
promise(.success(()))
cancellable?.cancel()
})
}
}
}
================================================
FILE: ios/Runner/Handlers/PlatformMethodHandler.swift
================================================
//
// PlatformMethodHandler.swift
// Runner
//
// Created by Hiddify on 12/27/23.
//
import Flutter
import Combine
import HiddifyCore
public class PlatformMethodHandler: NSObject, FlutterPlugin {
public static let name = "\(Bundle.main.serviceIdentifier)/platform"
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: Self.name, binaryMessenger: registrar.messenger())
let instance = PlatformMethodHandler()
registrar.addMethodCallDelegate(instance, channel: channel)
instance.channel = channel
}
private var channel: FlutterMethodChannel?
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "get_paths":
result(getPaths(args: call.arguments) as NSDictionary)
default:
result(FlutterMethodNotImplemented)
}
}
public func getPaths(args: Any?) -> [String:String] {
return [
"base": FilePath.sharedDirectory.path,
"working": FilePath.workingDirectory.path,
"temp": FilePath.cacheDirectory.path
]
}
}
================================================
FILE: ios/Runner/Handlers/StatsEventHandler.swift
================================================
import Foundation
import Flutter
import Combine
import HiddifyCore
public class StatsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(Bundle.main.serviceIdentifier)/stats"
private var commandClient: CommandClient?
private var channel: FlutterEventChannel?
private var events: FlutterEventSink?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = StatsEventHandler()
instance.channel = FlutterEventChannel(name: Self.name,
binaryMessenger: registrar.messenger(),
codec: FlutterJSONMethodCodec())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
self.events = events
commandClient = CommandClient(.status)
commandClient?.connect()
cancellable = commandClient?.$status.sink{ [self] status in
self.writeStatus(status)
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
commandClient?.disconnect()
cancellable?.cancel()
events = nil
return nil
}
func writeStatus(_ message: LibboxStatusMessage?) {
guard let message else { return }
let data = [
"connections-in": message.connectionsIn,
"connections-out": message.connectionsOut,
"uplink": message.uplink,
"downlink": message.downlink,
"uplink-total": message.uplinkTotal,
"downlink-total": message.downlinkTotal
] as [String:Any]
events?(data)
}
}
================================================
FILE: ios/Runner/Handlers/StatusEventHandler.swift
================================================
//
// StatusEventHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
import Combine
public class StatusEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(Bundle.main.serviceIdentifier)/service.status"
private var channel: FlutterEventChannel?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = StatusEventHandler()
instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger(), codec: FlutterJSONMethodCodec())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
cancellable = VPNManager.shared.$state.sink { [events] status in
switch status {
case .reasserting, .connecting:
events(["status": "Starting"])
case .connected:
events(["status": "Started"])
case .disconnecting:
events(["status": "Stopping"])
case .disconnected, .invalid:
events(["status": "Stopped"])
@unknown default:
events(["status": "Stopped"])
}
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
cancellable?.cancel()
return nil
}
}
================================================
FILE: ios/Runner/Info.plist
================================================
BASE_BUNDLE_IDENTIFIER
$(BASE_BUNDLE_IDENTIFIER)
CADisableMinimumFrameDurationOnPhone
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
Hiddify
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
CFBundleURLTypes
CFBundleURLSchemes
hiddify
v2ray
v2rayn
v2rayng
clash
clashmeta
sing-box
CFBundleTypeRole
Editor
CFBundleURLName
com.hiddify.ios
FlutterDeepLinkingEnabled
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
EXAppExtensionAttributes
EXExtensionPointIdentifier
com.apple.appintents-extension
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
NSCameraUsageDescription
This app needs camera access to scan QR codes
NSPhotoLibraryUsageDescription
This app needs photos access to get QR code from photo library
SERVICE_IDENTIFIER
$(SERVICE_IDENTIFIER)
UIApplicationSupportsIndirectInputEvents
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UIRequiresFullScreen
UIStatusBarHidden
UISupportedInterfaceOrientations
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIInterfaceOrientationPortrait
UIViewControllerBasedStatusBarAppearance
CFBundleLocalizations
ar
en
es
fa
fr
id
pt-BR
ru
tr
zh-CN
zh-TW
================================================
FILE: ios/Runner/PrivacyInfo.xcprivacy
================================================
NSPrivacyAccessedAPITypes
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryFileTimestamp
NSPrivacyAccessedAPITypeReasons
C617.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryDiskSpace
NSPrivacyAccessedAPITypeReasons
E174.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategorySystemBootTime
NSPrivacyAccessedAPITypeReasons
35F9.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryUserDefaults
NSPrivacyAccessedAPITypeReasons
1C8F.1
================================================
FILE: ios/Runner/Runner-Bridging-Header.h
================================================
#import "GeneratedPluginRegistrant.h"
================================================
FILE: ios/Runner/Runner.entitlements
================================================
aps-environment
development
com.apple.developer.networking.networkextension
app-proxy-provider
dns-proxy
packet-tunnel-provider
com.apple.developer.networking.vpn.api
allow-vpn
com.apple.security.app-sandbox
com.apple.security.application-groups
group.$(BASE_BUNDLE_IDENTIFIER)
com.apple.security.network.client
com.apple.security.network.server
================================================
FILE: ios/Runner/VPN/Helpers/Stored.swift
================================================
//
// Stored.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
import Combine
enum StoredLocation {
case standard
func data(for key: String) -> Data? {
switch self {
case .standard:
return UserDefaults.standard.data(forKey: key)
}
}
func set(_ value: Data, for key: String) {
switch self {
case .standard:
UserDefaults.standard.set(value, forKey: key)
}
}
}
@propertyWrapper
struct Stored {
let location: StoredLocation
let key: String
var wrappedValue: Value {
willSet { // Before modifying wrappedValue
publisher.subject.send(newValue)
guard let value = try? JSONEncoder().encode(newValue) else {
return
}
location.set(value, for: key)
}
}
var projectedValue: Publisher {
publisher
}
private var publisher: Publisher
struct Publisher: Combine.Publisher {
typealias Output = Value
typealias Failure = Never
var subject: CurrentValueSubject // PassthroughSubject will lack the call of initial assignment
func receive(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
subject.subscribe(subscriber)
}
init(_ output: Output) {
subject = .init(output)
}
}
init(wrappedValue: Value, key: String, in location: StoredLocation = .standard) {
self.location = location
self.key = key
var value = wrappedValue
if let data = location.data(for: key) {
do {
value = try JSONDecoder().decode(Value.self, from: data)
} catch {}
}
self.wrappedValue = value
publisher = Publisher(value)
}
static subscript(
_enclosingInstance observed: OuterSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath,
storage storageKeyPath: ReferenceWritableKeyPath
) -> Value {
get {
observed[keyPath: storageKeyPath].wrappedValue
}
set {
if let subject = observed.objectWillChange as? ObservableObjectPublisher {
subject.send() // Before modifying wrappedValue
observed[keyPath: storageKeyPath].wrappedValue = newValue
}
}
}
}
================================================
FILE: ios/Runner/VPN/VPNConfig.swift
================================================
//
// VPNConfig.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
import Combine
class VPNConfig: ObservableObject {
static let shared = VPNConfig()
@Stored(key: "VPN.ActiveConfigPath")
var activeConfigPath: String = ""
@Stored(key: "VPN.ActiveProfileName")
var activeProfileName: String = ""
@Stored(key: "VPN.GrpcServiceModePort")
var grpcServiceModePort: Int = 0
@Stored(key: "VPN.workingDir")
var workingDir: String = ""
@Stored(key: "VPN.tempDir")
var tempDir: String = ""
@Stored(key: "VPN.baseDir")
var baseDir: String = ""
// @Stored(key: "VPN.grpcFlutterPublicKey")
// var grpcFlutterPublicKey: String = ""
@Stored(key: "VPN.ConfigOptions")
var configOptions: String = ""
@Stored(key: "VPN.DisableMemoryLimit")
var disableMemoryLimit: Bool = false
}
================================================
FILE: ios/Runner/VPN/VPNManager.swift
================================================
//
// VPNManager.swift
// Runner
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
import Combine
import NetworkExtension
enum VPNManagerAlertType: String {
case RequestVPNPermission
case RequestNotificationPermission
case EmptyConfiguration
case StartCommandServer
case CreateService
case StartService
}
struct VPNManagerAlert {
let alert: VPNManagerAlertType?
let message: String?
}
class VPNManager: ObservableObject {
private var cancelBag: Set = []
private var observer: NSObjectProtocol?
private var manager = NEVPNManager.shared()
private var loaded: Bool = false
private var timer: Timer?
static let shared: VPNManager = VPNManager()
@Published private(set) var state: NEVPNStatus = .invalid
@Published private(set) var alert: VPNManagerAlert = .init(alert: nil, message: nil)
@Published private(set) var upload: Int64 = 0
@Published private(set) var download: Int64 = 0
@Published private(set) var elapsedTime: TimeInterval = 0
private var _connectTime: Date?
private var connectTime: Date? {
set {
UserDefaults(suiteName: FilePath.groupName)?.set(newValue?.timeIntervalSince1970, forKey: "SingBoxConnectTime")
_connectTime = newValue
}
get {
if let _connectTime {
return _connectTime
}
guard let interval = UserDefaults(suiteName: FilePath.groupName)?.value(forKey: "SingBoxConnectTime") as? TimeInterval else {
return nil
}
return Date(timeIntervalSince1970: interval)
}
}
private var readingWS: Bool = false
@Published var isConnectedToAnyVPN: Bool = false
init() {
observer = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: nil) { [weak self] notification in
guard let connection = notification.object as? NEVPNConnection else { return }
self?.state = connection.status
}
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
guard let self else { return }
updateStats()
elapsedTime = -1 * (connectTime?.timeIntervalSinceNow ?? 0)
}
}
deinit {
if let observer {
NotificationCenter.default.removeObserver(observer)
}
timer?.invalidate()
}
func setup() async throws {
// guard !loaded else { return }
loaded = true
do {
try await loadVPNPreference()
} catch {
print(error.localizedDescription)
}
}
private func loadVPNPreference() async throws {
do {
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
if let manager = managers.first {
self.manager = manager
return
}
let newManager = NETunnelProviderManager()
let `protocol` = NETunnelProviderProtocol()
`protocol`.providerBundleIdentifier = Bundle.main.baseBundleIdentifier + ".HiddifyPacketTunnel"
`protocol`.serverAddress = "localhost"
newManager.protocolConfiguration = `protocol`
newManager.localizedDescription = "Hiddify"
try await newManager.saveToPreferences()
try await newManager.loadFromPreferences()
self.manager = newManager
} catch {
print(error.localizedDescription)
}
}
private func enableVPNManager() async throws {
manager.isEnabled = true
let rule = NEOnDemandRuleConnect()
rule.interfaceTypeMatch = .any
rule.probeURL = URL(string: "http://captive.apple.com")
manager.onDemandRules = [rule]
manager.isOnDemandEnabled = true
do {
try await manager.saveToPreferences()
try await manager.loadFromPreferences()
} catch {
print(error.localizedDescription)
}
}
@MainActor private func set(upload: Int64, download: Int64) {
self.upload = upload
self.download = download
}
var isAnyVPNConnected: Bool {
guard let cfDict = CFNetworkCopySystemProxySettings() else { return false }
let nsDict = cfDict.takeRetainedValue() as NSDictionary
guard let keys = nsDict["__SCOPED__"] as? NSDictionary else {
return false
}
for key: String in keys.allKeys as! [String] {
if key == "tap" || key == "tun" || key == "ppp" || key == "ipsec" || key == "ipsec0" {
return true
} else if key.starts(with: "utun") {
return true
}
}
return false
}
func reset() {
loaded = false
if state != .disconnected && state != .invalid {
disconnect()
}
$state.filter { $0 == .disconnected || $0 == .invalid }.first().sink { [weak self] _ in
Task { [weak self] () in
self?.manager = .shared()
do {
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
for manager in managers ?? [] {
try await manager.removeFromPreferences()
}
try await self?.loadVPNPreference()
} catch {
print(error.localizedDescription)
}
}
}.store(in: &cancelBag)
}
private func updateStats() {
let isAnyVPNConnected = self.isAnyVPNConnected
if isConnectedToAnyVPN != isAnyVPNConnected {
isConnectedToAnyVPN = isAnyVPNConnected
}
guard state == .connected else { return }
guard let connection = manager.connection as? NETunnelProviderSession else { return }
do {
try connection.sendProviderMessage("stats".data(using: .utf8)!) { [weak self] response in
guard
let response,
let response = String(data: response, encoding: .utf8)
else { return }
let responseComponents = response.components(separatedBy: ",")
guard
responseComponents.count == 2,
let upload = Int64(responseComponents[0]),
let download = Int64(responseComponents[1])
else { return }
Task { [upload, download, weak self] () in
await self?.set(upload: upload, download: download)
}
}
} catch {
print(error.localizedDescription)
}
}
func connect(with config: String, grpcServiceModePort:Int, disableMemoryLimit: Bool = false) async throws {
await set(upload: 0, download: 0)
// guard state == .disconnected else { return }
do {
try await enableVPNManager()
try manager.connection.startVPNTunnel(options: [
"Config": config as NSString,
"GrpcServiceModePort":NSNumber(value: grpcServiceModePort),
"DisableMemoryLimit": (disableMemoryLimit ? "YES" : "NO") as NSString,
])
} catch {
print(error.localizedDescription)
}
connectTime = .now
}
func disconnect() {
if manager.isOnDemandEnabled {
manager.isOnDemandEnabled = false
manager.onDemandRules = []
manager.saveToPreferences { error in
if let error = error {
print("save error:", error)
return
}
}
}
// guard state == .connected else { return }
manager.connection.stopVPNTunnel()
}
}
================================================
FILE: ios/Runner.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032158B72ADDF8BF008D943B /* VPNManager.swift */; };
032158BA2ADDFCC9008D943B /* TrafficReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032158B92ADDFCC9008D943B /* TrafficReader.swift */; };
032158BC2ADDFD09008D943B /* SingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032158BB2ADDFD09008D943B /* SingBox.swift */; };
03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B516662AE6B93A00EA47E2 /* MethodHandler.swift */; };
03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B516682AE7306B00EA47E2 /* StatusEventHandler.swift */; };
03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B5166A2AE7315E00EA47E2 /* AlertsEventHandler.swift */; };
03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B5166C2AE7325500EA47E2 /* LogsEventHandler.swift */; };
03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B516702AE74CCD00EA47E2 /* VPNConfig.swift */; };
03B516742AE74D2200EA47E2 /* Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B516732AE74D2200EA47E2 /* Stored.swift */; };
03B516762AE762F700EA47E2 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B516752AE762F700EA47E2 /* Logger.swift */; };
03B516772AE7634400EA47E2 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B516752AE762F700EA47E2 /* Logger.swift */; };
03B5167B2AE79DB400EA47E2 /* FileMethodHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B5167A2AE79DB400EA47E2 /* FileMethodHandler.swift */; };
03B5167D2AE7AC6200EA47E2 /* GroupsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B5167C2AE7AC6200EA47E2 /* GroupsEventHandler.swift */; };
03E392BB2ADDA00F000ADF15 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E392BA2ADDA00F000ADF15 /* PacketTunnelProvider.swift */; };
03E392CC2ADDE078000ADF15 /* ExtensionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E392CB2ADDE078000ADF15 /* ExtensionProvider.swift */; };
03E392CF2ADDEFC8000ADF15 /* FilePath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E392CE2ADDEFC8000ADF15 /* FilePath.swift */; };
03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E392CE2ADDEFC8000ADF15 /* FilePath.swift */; };
03E392D22ADDF1F4000ADF15 /* ExtensionPlatformInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E392D12ADDF1F4000ADF15 /* ExtensionPlatformInterface.swift */; };
03E392D42ADDF262000ADF15 /* Extension+RunBlocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E392D32ADDF262000ADF15 /* Extension+RunBlocking.swift */; };
0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0736958A2B3AC96D007249BE /* Bundle+Properties.swift */; };
0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0736958C2B3B79E0007249BE /* StatsEventHandler.swift */; };
0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0736958E2B3B8047007249BE /* PlatformMethodHandler.swift */; };
075637BA2B01583F005AFB8E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60F1D4AAC33ACF5C8307310D /* Pods_Runner.framework */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
41E9FF922D1C0CC3003780C1 /* HiddifyPacketTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 03E392B62ADDA00E000ADF15 /* HiddifyPacketTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
41E9FF962D1C82BA003780C1 /* HiddifyCore.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 413270682C8A9BA2003A1E9B /* HiddifyCore.xcframework */; };
59E8864FB99B37076B22F32B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30560DB3EFDF5E86AAD00AB8 /* Pods_RunnerTests.framework */; };
68885DD72B4EF33400D214BA /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03E392B72ADDA00E000ADF15 /* NetworkExtension.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D703EE932B764EA3001D88B3 /* CommandClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D703EE922B764EA3001D88B3 /* CommandClient.swift */; };
D703EE962B765176001D88B3 /* ActiveGroupsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */; };
D7CC50862B768C50006BC140 /* Outbound.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CC50852B768C50006BC140 /* Outbound.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
03E392BE2ADDA00F000ADF15 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 03E392B52ADDA00E000ADF15;
remoteInfo = HiddifyPacketTunnel;
};
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
03E392C12ADDA00F000ADF15 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
41E9FF922D1C0CC3003780C1 /* HiddifyPacketTunnel.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
032158B72ADDF8BF008D943B /* VPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNManager.swift; sourceTree = ""; };
032158B92ADDFCC9008D943B /* TrafficReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficReader.swift; sourceTree = ""; };
032158BB2ADDFD09008D943B /* SingBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingBox.swift; sourceTree = ""; };
0337C822BEDF7A95576475B3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
03B516662AE6B93A00EA47E2 /* MethodHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodHandler.swift; sourceTree = ""; };
03B516682AE7306B00EA47E2 /* StatusEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEventHandler.swift; sourceTree = ""; };
03B5166A2AE7315E00EA47E2 /* AlertsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsEventHandler.swift; sourceTree = ""; };
03B5166C2AE7325500EA47E2 /* LogsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsEventHandler.swift; sourceTree = ""; };
03B516702AE74CCD00EA47E2 /* VPNConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNConfig.swift; sourceTree = ""; };
03B516732AE74D2200EA47E2 /* Stored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stored.swift; sourceTree = ""; };
03B516752AE762F700EA47E2 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; };
03B5167A2AE79DB400EA47E2 /* FileMethodHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileMethodHandler.swift; sourceTree = ""; };
03B5167C2AE7AC6200EA47E2 /* GroupsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupsEventHandler.swift; sourceTree = ""; };
03E392B62ADDA00E000ADF15 /* HiddifyPacketTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HiddifyPacketTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
03E392B72ADDA00E000ADF15 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
03E392BA2ADDA00F000ADF15 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; };
03E392BC2ADDA00F000ADF15 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
03E392BD2ADDA00F000ADF15 /* HiddifyPacketTunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = HiddifyPacketTunnel.entitlements; sourceTree = ""; };
03E392C62ADDA064000ADF15 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; };
03E392C72ADDA26A000ADF15 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
03E392CB2ADDE078000ADF15 /* ExtensionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionProvider.swift; sourceTree = ""; };
03E392CE2ADDEFC8000ADF15 /* FilePath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePath.swift; sourceTree = ""; };
03E392D12ADDF1F4000ADF15 /* ExtensionPlatformInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPlatformInterface.swift; sourceTree = ""; };
03E392D32ADDF262000ADF15 /* Extension+RunBlocking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+RunBlocking.swift"; sourceTree = ""; };
040FA3EB0673B72D60CC7145 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
0736954E2B1FEB3E007249BE /* mobile_scanner.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = mobile_scanner.xcframework; path = ../build/ios/framework/Release/mobile_scanner.xcframework; sourceTree = ""; };
0736958A2B3AC96D007249BE /* Bundle+Properties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Properties.swift"; sourceTree = ""; };
0736958C2B3B79E0007249BE /* StatsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsEventHandler.swift; sourceTree = ""; };
0736958E2B3B8047007249BE /* PlatformMethodHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformMethodHandler.swift; sourceTree = ""; };
07A63A832B1E728C00CAFA4D /* Release */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Release; path = ../build/ios/framework/release; sourceTree = ""; };
07A63A842B1E72AE00CAFA4D /* App.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = App.xcframework; path = ../build/ios/framework/release/App.xcframework; sourceTree = ""; };
07A63A872B1E72C800CAFA4D /* Flutter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = ../build/ios/framework/release/Flutter.xcframework; sourceTree = ""; };
07A63A8C2B1E72FA00CAFA4D /* GTMSessionFetcher.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GTMSessionFetcher.xcframework; path = ../build/ios/framework/release/GTMSessionFetcher.xcframework; sourceTree = ""; };
07A63A8D2B1E72FB00CAFA4D /* package_info_plus.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = package_info_plus.xcframework; path = ../build/ios/framework/release/package_info_plus.xcframework; sourceTree = ""; };
07A63A8E2B1E72FB00CAFA4D /* SentryPrivate.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SentryPrivate.xcframework; path = ../build/ios/framework/release/SentryPrivate.xcframework; sourceTree = ""; };
07A63A8F2B1E72FB00CAFA4D /* share_plus.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = share_plus.xcframework; path = ../build/ios/framework/release/share_plus.xcframework; sourceTree = ""; };
07A63A902B1E72FB00CAFA4D /* url_launcher_ios.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = url_launcher_ios.xcframework; path = ../build/ios/framework/release/url_launcher_ios.xcframework; sourceTree = ""; };
07A63A912B1E72FB00CAFA4D /* mobile_scanner.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = mobile_scanner.xcframework; path = ../build/ios/framework/release/mobile_scanner.xcframework; sourceTree = ""; };
07A63A922B1E72FB00CAFA4D /* sqlite3_flutter_libs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = sqlite3_flutter_libs.xcframework; path = ../build/ios/framework/release/sqlite3_flutter_libs.xcframework; sourceTree = ""; };
07A63A932B1E72FB00CAFA4D /* cupertino_http.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = cupertino_http.xcframework; path = ../build/ios/framework/release/cupertino_http.xcframework; sourceTree = ""; };
07A63A942B1E72FB00CAFA4D /* flutter_native_splash.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = flutter_native_splash.xcframework; path = ../build/ios/framework/release/flutter_native_splash.xcframework; sourceTree = ""; };
07A63A952B1E72FB00CAFA4D /* FBLPromises.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = FBLPromises.xcframework; path = ../build/ios/framework/release/FBLPromises.xcframework; sourceTree = ""; };
07A63A962B1E72FB00CAFA4D /* GoogleUtilities.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GoogleUtilities.xcframework; path = ../build/ios/framework/release/GoogleUtilities.xcframework; sourceTree = ""; };
07A63A972B1E72FB00CAFA4D /* device_info_plus.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = device_info_plus.xcframework; path = ../build/ios/framework/release/device_info_plus.xcframework; sourceTree = ""; };
07A63A982B1E72FB00CAFA4D /* GoogleDataTransport.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GoogleDataTransport.xcframework; path = ../build/ios/framework/release/GoogleDataTransport.xcframework; sourceTree = ""; };
07A63A992B1E72FB00CAFA4D /* sentry_flutter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = sentry_flutter.xcframework; path = ../build/ios/framework/release/sentry_flutter.xcframework; sourceTree = ""; };
07A63A9A2B1E72FB00CAFA4D /* protocol_handler.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = protocol_handler.xcframework; path = ../build/ios/framework/release/protocol_handler.xcframework; sourceTree = ""; };
07A63A9B2B1E72FC00CAFA4D /* sqlite3.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = sqlite3.xcframework; path = ../build/ios/framework/release/sqlite3.xcframework; sourceTree = ""; };
07A63A9C2B1E72FC00CAFA4D /* GoogleToolboxForMac.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GoogleToolboxForMac.xcframework; path = ../build/ios/framework/release/GoogleToolboxForMac.xcframework; sourceTree = ""; };
07A63A9D2B1E72FC00CAFA4D /* GoogleUtilitiesComponents.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GoogleUtilitiesComponents.xcframework; path = ../build/ios/framework/release/GoogleUtilitiesComponents.xcframework; sourceTree = ""; };
07A63A9E2B1E72FC00CAFA4D /* nanopb.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = nanopb.xcframework; path = ../build/ios/framework/release/nanopb.xcframework; sourceTree = ""; };
07A63A9F2B1E72FC00CAFA4D /* path_provider_foundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = path_provider_foundation.xcframework; path = ../build/ios/framework/release/path_provider_foundation.xcframework; sourceTree = ""; };
07A63AA02B1E72FC00CAFA4D /* shared_preferences_foundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = shared_preferences_foundation.xcframework; path = ../build/ios/framework/release/shared_preferences_foundation.xcframework; sourceTree = ""; };
07A63AA12B1E72FC00CAFA4D /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../build/ios/framework/release/Sentry.xcframework; sourceTree = ""; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
30560DB3EFDF5E86AAD00AB8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
413270682C8A9BA2003A1E9B /* HiddifyCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = HiddifyCore.xcframework; path = Frameworks/HiddifyCore.xcframework; sourceTree = ""; };
574F12C7748958784380337F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
60F1D4AAC33ACF5C8307310D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6836D3FA2B57FDFF00A79D75 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
68DCEB762BD7D7590081FF65 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
68DCEB772BD7DA3F0081FF65 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
7CA62594950187FCFE36B54C /* Pods-Runner-HiddifyPacketTunnel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-HiddifyPacketTunnel.debug.xcconfig"; path = "Target Support Files/Pods-Runner-HiddifyPacketTunnel/Pods-Runner-HiddifyPacketTunnel.debug.xcconfig"; sourceTree = ""; };
808A94F72872B54716770416 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
90E93DE403BDFA627F3AA51E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
9AC67B4DCF829F5B6F63AA7D /* Pods-Runner-HiddifyPacketTunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-HiddifyPacketTunnel.release.xcconfig"; path = "Target Support Files/Pods-Runner-HiddifyPacketTunnel/Pods-Runner-HiddifyPacketTunnel.release.xcconfig"; sourceTree = ""; };
C20A211B58CE31B2738D133C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
D703EE922B764EA3001D88B3 /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = ""; };
D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveGroupsEventHandler.swift; sourceTree = ""; };
D7CC50852B768C50006BC140 /* Outbound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Outbound.swift; sourceTree = ""; };
F3FFE1D9C2D5629FACC123EE /* Pods-Runner-HiddifyPacketTunnel.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-HiddifyPacketTunnel.profile.xcconfig"; path = "Target Support Files/Pods-Runner-HiddifyPacketTunnel/Pods-Runner-HiddifyPacketTunnel.profile.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
03E392B32ADDA00E000ADF15 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
41E9FF962D1C82BA003780C1 /* HiddifyCore.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
531FE8242BCD501C24C8E9FA /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
59E8864FB99B37076B22F32B /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
075637BA2B01583F005AFB8E /* Pods_Runner.framework in Frameworks */,
68885DD72B4EF33400D214BA /* NetworkExtension.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
032158B62ADDF8AF008D943B /* VPN */ = {
isa = PBXGroup;
children = (
03B516722AE74D1700EA47E2 /* Helpers */,
032158B72ADDF8BF008D943B /* VPNManager.swift */,
03B516702AE74CCD00EA47E2 /* VPNConfig.swift */,
);
path = VPN;
sourceTree = "";
};
03B5166E2AE7325D00EA47E2 /* Handlers */ = {
isa = PBXGroup;
children = (
03B516662AE6B93A00EA47E2 /* MethodHandler.swift */,
0736958E2B3B8047007249BE /* PlatformMethodHandler.swift */,
03B516682AE7306B00EA47E2 /* StatusEventHandler.swift */,
03B5167A2AE79DB400EA47E2 /* FileMethodHandler.swift */,
03B5166A2AE7315E00EA47E2 /* AlertsEventHandler.swift */,
03B5166C2AE7325500EA47E2 /* LogsEventHandler.swift */,
03B5167C2AE7AC6200EA47E2 /* GroupsEventHandler.swift */,
0736958C2B3B79E0007249BE /* StatsEventHandler.swift */,
D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */,
);
path = Handlers;
sourceTree = "";
};
03B516722AE74D1700EA47E2 /* Helpers */ = {
isa = PBXGroup;
children = (
03B516732AE74D2200EA47E2 /* Stored.swift */,
);
path = Helpers;
sourceTree = "";
};
03E392B92ADDA00F000ADF15 /* HiddifyPacketTunnel */ = {
isa = PBXGroup;
children = (
03E392CA2ADDE063000ADF15 /* SingBox */,
03E392BA2ADDA00F000ADF15 /* PacketTunnelProvider.swift */,
032158B92ADDFCC9008D943B /* TrafficReader.swift */,
03B516752AE762F700EA47E2 /* Logger.swift */,
03E392BC2ADDA00F000ADF15 /* Info.plist */,
03E392BD2ADDA00F000ADF15 /* HiddifyPacketTunnel.entitlements */,
68DCEB762BD7D7590081FF65 /* PrivacyInfo.xcprivacy */,
);
path = HiddifyPacketTunnel;
sourceTree = "";
};
03E392CA2ADDE063000ADF15 /* SingBox */ = {
isa = PBXGroup;
children = (
03E392CB2ADDE078000ADF15 /* ExtensionProvider.swift */,
03E392D12ADDF1F4000ADF15 /* ExtensionPlatformInterface.swift */,
03E392D32ADDF262000ADF15 /* Extension+RunBlocking.swift */,
032158BB2ADDFD09008D943B /* SingBox.swift */,
);
path = SingBox;
sourceTree = "";
};
03E392CD2ADDE103000ADF15 /* Shared */ = {
isa = PBXGroup;
children = (
03E392CE2ADDEFC8000ADF15 /* FilePath.swift */,
D703EE922B764EA3001D88B3 /* CommandClient.swift */,
D7CC50852B768C50006BC140 /* Outbound.swift */,
);
path = Shared;
sourceTree = "";
};
073695892B3AC954007249BE /* Extensions */ = {
isa = PBXGroup;
children = (
0736958A2B3AC96D007249BE /* Bundle+Properties.swift */,
);
path = Extensions;
sourceTree = "";
};
311A4F4314861E02331B8DAC /* Pods */ = {
isa = PBXGroup;
children = (
574F12C7748958784380337F /* Pods-Runner.debug.xcconfig */,
90E93DE403BDFA627F3AA51E /* Pods-Runner.release.xcconfig */,
C20A211B58CE31B2738D133C /* Pods-Runner.profile.xcconfig */,
7CA62594950187FCFE36B54C /* Pods-Runner-HiddifyPacketTunnel.debug.xcconfig */,
9AC67B4DCF829F5B6F63AA7D /* Pods-Runner-HiddifyPacketTunnel.release.xcconfig */,
F3FFE1D9C2D5629FACC123EE /* Pods-Runner-HiddifyPacketTunnel.profile.xcconfig */,
0337C822BEDF7A95576475B3 /* Pods-RunnerTests.debug.xcconfig */,
808A94F72872B54716770416 /* Pods-RunnerTests.release.xcconfig */,
040FA3EB0673B72D60CC7145 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "";
};
6836D3FF2B57FECF00A79D75 /* Local Packages */ = {
isa = PBXGroup;
children = (
6836D3FA2B57FDFF00A79D75 /* Package.swift */,
);
path = "Local Packages";
sourceTree = "";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
03E392C62ADDA064000ADF15 /* Base.xcconfig */,
);
name = Flutter;
sourceTree = "";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
6836D3FF2B57FECF00A79D75 /* Local Packages */,
03E392CD2ADDE103000ADF15 /* Shared */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
03E392B92ADDA00F000ADF15 /* HiddifyPacketTunnel */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
311A4F4314861E02331B8DAC /* Pods */,
B8133545EEE13EDD5549E6A3 /* Frameworks */,
);
sourceTree = "";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
03E392B62ADDA00E000ADF15 /* HiddifyPacketTunnel.appex */,
);
name = Products;
sourceTree = "";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
073695892B3AC954007249BE /* Extensions */,
03B5166E2AE7325D00EA47E2 /* Handlers */,
032158B62ADDF8AF008D943B /* VPN */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
03E392C72ADDA26A000ADF15 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
68DCEB772BD7DA3F0081FF65 /* PrivacyInfo.xcprivacy */,
);
path = Runner;
sourceTree = "";
};
B8133545EEE13EDD5549E6A3 /* Frameworks */ = {
isa = PBXGroup;
children = (
413270682C8A9BA2003A1E9B /* HiddifyCore.xcframework */,
0736954E2B1FEB3E007249BE /* mobile_scanner.xcframework */,
07A63A932B1E72FB00CAFA4D /* cupertino_http.xcframework */,
07A63A972B1E72FB00CAFA4D /* device_info_plus.xcframework */,
07A63A952B1E72FB00CAFA4D /* FBLPromises.xcframework */,
07A63A942B1E72FB00CAFA4D /* flutter_native_splash.xcframework */,
07A63A982B1E72FB00CAFA4D /* GoogleDataTransport.xcframework */,
07A63A9C2B1E72FC00CAFA4D /* GoogleToolboxForMac.xcframework */,
07A63A962B1E72FB00CAFA4D /* GoogleUtilities.xcframework */,
07A63A9D2B1E72FC00CAFA4D /* GoogleUtilitiesComponents.xcframework */,
07A63A8C2B1E72FA00CAFA4D /* GTMSessionFetcher.xcframework */,
07A63A912B1E72FB00CAFA4D /* mobile_scanner.xcframework */,
07A63A9E2B1E72FC00CAFA4D /* nanopb.xcframework */,
07A63A8D2B1E72FB00CAFA4D /* package_info_plus.xcframework */,
07A63A9F2B1E72FC00CAFA4D /* path_provider_foundation.xcframework */,
07A63A9A2B1E72FB00CAFA4D /* protocol_handler.xcframework */,
07A63A992B1E72FB00CAFA4D /* sentry_flutter.xcframework */,
07A63AA12B1E72FC00CAFA4D /* Sentry.xcframework */,
07A63A8E2B1E72FB00CAFA4D /* SentryPrivate.xcframework */,
07A63A8F2B1E72FB00CAFA4D /* share_plus.xcframework */,
07A63AA02B1E72FC00CAFA4D /* shared_preferences_foundation.xcframework */,
07A63A922B1E72FB00CAFA4D /* sqlite3_flutter_libs.xcframework */,
07A63A9B2B1E72FC00CAFA4D /* sqlite3.xcframework */,
07A63A902B1E72FB00CAFA4D /* url_launcher_ios.xcframework */,
07A63A872B1E72C800CAFA4D /* Flutter.xcframework */,
07A63A842B1E72AE00CAFA4D /* App.xcframework */,
07A63A832B1E728C00CAFA4D /* Release */,
60F1D4AAC33ACF5C8307310D /* Pods_Runner.framework */,
03E392B72ADDA00E000ADF15 /* NetworkExtension.framework */,
30560DB3EFDF5E86AAD00AB8 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
03E392B52ADDA00E000ADF15 /* HiddifyPacketTunnel */ = {
isa = PBXNativeTarget;
buildConfigurationList = 03E392C52ADDA00F000ADF15 /* Build configuration list for PBXNativeTarget "HiddifyPacketTunnel" */;
buildPhases = (
03E392B22ADDA00E000ADF15 /* Sources */,
03E392B32ADDA00E000ADF15 /* Frameworks */,
03E392B42ADDA00E000ADF15 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = HiddifyPacketTunnel;
productName = HiddifyPacketTunnel;
productReference = 03E392B62ADDA00E000ADF15 /* HiddifyPacketTunnel.appex */;
productType = "com.apple.product-type.app-extension";
};
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
2058E420D1A8B6F0E5E03873 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
531FE8242BCD501C24C8E9FA /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
B971F0749B278D190A7A7315 /* [CP] Check Pods Manifest.lock */,
03E392C12ADDA00F000ADF15 /* Embed Foundation Extensions */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EC1CF9000F007C117D /* Resources */,
97C146EA1CF9000F007C117D /* Sources */,
FBEFD3291AEA65EDE2F5AEF6 /* [CP] Embed Pods Frameworks */,
97C146EB1CF9000F007C117D /* Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
03E392BF2ADDA00F000ADF15 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1500;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
03E392B52ADDA00E000ADF15 = {
CreatedOnToolsVersion = 15.0;
DevelopmentTeamName = "Mark Pashmfouroush";
ProvisioningStyle = Automatic;
};
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
DevelopmentTeamName = "Mark Pashmfouroush";
ProvisioningStyle = Automatic;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeamName = "Mark Pashmfouroush";
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
413270672C8A9B88003A1E9B /* XCLocalSwiftPackageReference "Local Packages" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
03E392B52ADDA00E000ADF15 /* HiddifyPacketTunnel */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
03E392B42ADDA00E000ADF15 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
2058E420D1A8B6F0E5E03873 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
};
B971F0749B278D190A7A7315 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FBEFD3291AEA65EDE2F5AEF6 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
03E392B22ADDA00E000ADF15 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
032158BA2ADDFCC9008D943B /* TrafficReader.swift in Sources */,
032158BC2ADDFD09008D943B /* SingBox.swift in Sources */,
03E392D22ADDF1F4000ADF15 /* ExtensionPlatformInterface.swift in Sources */,
03E392CC2ADDE078000ADF15 /* ExtensionProvider.swift in Sources */,
03E392BB2ADDA00F000ADF15 /* PacketTunnelProvider.swift in Sources */,
03E392CF2ADDEFC8000ADF15 /* FilePath.swift in Sources */,
03E392D42ADDF262000ADF15 /* Extension+RunBlocking.swift in Sources */,
03B516762AE762F700EA47E2 /* Logger.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
03B516742AE74D2200EA47E2 /* Stored.swift in Sources */,
03B5167B2AE79DB400EA47E2 /* FileMethodHandler.swift in Sources */,
03B516772AE7634400EA47E2 /* Logger.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
D703EE962B765176001D88B3 /* ActiveGroupsEventHandler.swift in Sources */,
03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */,
03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */,
0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */,
0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */,
03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */,
D7CC50862B768C50006BC140 /* Outbound.swift in Sources */,
032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */,
0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */,
D703EE932B764EA3001D88B3 /* CommandClient.swift in Sources */,
03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */,
03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */,
03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
03B5167D2AE7AC6200EA47E2 /* GroupsEventHandler.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
03E392BF2ADDA00F000ADF15 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 03E392B52ADDA00E000ADF15 /* HiddifyPacketTunnel */;
targetProxy = 03E392BE2ADDA00F000ADF15 /* PBXContainerItemProxy */;
};
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
03E392C22ADDA00F000ADF15 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03E392C62ADDA064000ADF15 /* Base.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = HiddifyPacketTunnel/HiddifyPacketTunnel.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40102;
DEVELOPMENT_TEAM = M7Q8ASP66Z;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = HiddifyPacketTunnel/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiddifyPacketTunnel;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)",
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
"@executable_path/../hiddify-core/",
"@executable_path/hiddify-core/",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 4.1.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "-lresolv";
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).HiddifyPacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TVOS_DEPLOYMENT_TARGET = 15.0;
};
name = Debug;
};
03E392C32ADDA00F000ADF15 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03E392C62ADDA064000ADF15 /* Base.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = HiddifyPacketTunnel/HiddifyPacketTunnel.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40102;
DEVELOPMENT_TEAM = M7Q8ASP66Z;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = HiddifyPacketTunnel/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiddifyPacketTunnel;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)",
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
"@executable_path/../hiddify-core/",
"@executable_path/hiddify-core/",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 4.1.2;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = "-lresolv";
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).HiddifyPacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TVOS_DEPLOYMENT_TARGET = 15.0;
};
name = Release;
};
03E392C42ADDA00F000ADF15 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03E392C62ADDA064000ADF15 /* Base.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = HiddifyPacketTunnel/HiddifyPacketTunnel.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40102;
DEVELOPMENT_TEAM = M7Q8ASP66Z;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = HiddifyPacketTunnel/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiddifyPacketTunnel;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)",
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
"@executable_path/../hiddify-core/",
"@executable_path/hiddify-core/",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 4.1.2;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = "-lresolv";
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).HiddifyPacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TVOS_DEPLOYMENT_TARGET = 15.0;
};
name = Profile;
};
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = NO;
OTHER_CPLUSPLUSFLAGS = (
"-fcxx-modules",
"$(OTHER_CFLAGS)",
);
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = M7Q8ASP66Z;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(PROJECT_DIR)/build/ios/framework/$(CONFIGURATION)",
"$(PROJECT_DIR)/../build/ios/framework/$(CONFIGURATION)",
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)",
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
"@executable_path/../hiddify-core/",
"@executable_path/hiddify-core/",
);
ONLY_ACTIVE_ARCH = NO;
OTHER_CPLUSPLUSFLAGS = (
"-fcxx-modules",
"$(OTHER_CFLAGS)",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-ld_classic",
"-lresolv",
);
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0337C822BEDF7A95576475B3 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40102;
DEVELOPMENT_TEAM = M7Q8ASP66Z;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 808A94F72872B54716770416 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40102;
DEVELOPMENT_TEAM = M7Q8ASP66Z;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 040FA3EB0673B72D60CC7145 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40102;
DEVELOPMENT_TEAM = M7Q8ASP66Z;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CPLUSPLUSFLAGS = (
"-fcxx-modules",
"$(OTHER_CFLAGS)",
);
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = NO;
OTHER_CPLUSPLUSFLAGS = (
"-fcxx-modules",
"$(OTHER_CFLAGS)",
);
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = M7Q8ASP66Z;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(PROJECT_DIR)/build/ios/framework/$(CONFIGURATION)",
"$(PROJECT_DIR)/../build/ios/framework/$(CONFIGURATION)",
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)",
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
"@executable_path/../hiddify-core/",
"@executable_path/hiddify-core/",
);
OTHER_CPLUSPLUSFLAGS = (
"-fcxx-modules",
"$(OTHER_CFLAGS)",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-ld_classic",
"-lresolv",
);
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = M7Q8ASP66Z;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(PROJECT_DIR)/build/ios/framework/$(CONFIGURATION)",
"$(PROJECT_DIR)/../build/ios/framework/$(CONFIGURATION)",
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)",
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
"@executable_path/../hiddify-core/",
"@executable_path/hiddify-core/",
);
ONLY_ACTIVE_ARCH = NO;
OTHER_CPLUSPLUSFLAGS = (
"-fcxx-modules",
"$(OTHER_CFLAGS)",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-ld_classic",
"-lresolv",
);
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
03E392C52ADDA00F000ADF15 /* Build configuration list for PBXNativeTarget "HiddifyPacketTunnel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
03E392C22ADDA00F000ADF15 /* Debug */,
03E392C32ADDA00F000ADF15 /* Release */,
03E392C42ADDA00F000ADF15 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
413270672C8A9B88003A1E9B /* XCLocalSwiftPackageReference "Local Packages" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "Local Packages";
};
/* End XCLocalSwiftPackageReference section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
PreviewsEnabled
================================================
FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/HiddifyPacketTunnel.xcscheme
================================================
================================================
FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
================================================
================================================
FILE: ios/Runner.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
PreviewsEnabled
================================================
FILE: ios/RunnerTests/RunnerTests.swift
================================================
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}
================================================
FILE: ios/Shared/CommandClient.swift
================================================
import Foundation
import HiddifyCore
public class CommandClient: ObservableObject {
public enum ConnectionType {
case status
case groups
case log
case groupsInfoOnly
}
private let connectionType: ConnectionType
private let logMaxLines: Int
private var commandClient: LibboxCommandClient?
private var connectTask: Task?
@Published private(set) var isConnected: Bool
@Published private(set) var status: LibboxStatusMessage?
@Published private(set) var groups: [SBGroup]?
@Published private(set) var logList: [String]
public init(_ connectionType: ConnectionType, logMaxLines: Int = 300) {
self.connectionType = connectionType
self.logMaxLines = logMaxLines
logList = []
isConnected = false
}
public func connect() {
if isConnected {
return
}
if let connectTask {
connectTask.cancel()
}
connectTask = Task {
await connect0()
}
}
public func disconnect() {
if let connectTask {
connectTask.cancel()
self.connectTask = nil
}
if let commandClient {
try? commandClient.disconnect()
self.commandClient = nil
}
}
private nonisolated func connect0() async {
// let clientOptions = LibboxCommandClientOptions()
// switch connectionType {
// case .status:
// clientOptions.command = LibboxCommandStatus
// case .groups:
// clientOptions.command = LibboxCommandGroup
// case .log:
// clientOptions.command = LibboxCommandLog
// case .groupsInfoOnly:
// clientOptions.command = LibboxCommandGroupInfoOnly
// }
// clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC)
// let client = LibboxNewCommandClient(clientHandler(self), clientOptions)!
// do {
// for i in 0 ..< 10 {
// try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC)))
// try Task.checkCancellation()
// do {
// try client.connect()
// await MainActor.run {
// commandClient = client
// }
// return
// } catch {}
// try Task.checkCancellation()
// }
// } catch {
// try? client.disconnect()
// }
}
// private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol {
//
//
// private let commandClient: CommandClient
//
// init(_ commandClient: CommandClient) {
// self.commandClient = commandClient
// }
//
// func connected() {
// DispatchQueue.main.async { [self] in
// if commandClient.connectionType == .log {
// commandClient.logList = []
// }
// commandClient.isConnected = true
// }
// }
//
// func disconnected(_: String?) {
// DispatchQueue.main.async { [self] in
// commandClient.isConnected = false
// }
// }
//
// func clearLogs() {
// DispatchQueue.main.async { [self] in
// commandClient.logList.removeAll()
// }
// }
//
// func writeLogs(_ messageList: (any LibboxStringIteratorProtocol)?) {
//// guard let message else {
//// return
//// }
//// DispatchQueue.main.async { [self] in
//// if commandClient.logList.count > commandClient.logMaxLines {
//// commandClient.logList.removeFirst()
//// }
//// commandClient.logList.append(message)
//// }
// }
//
// func writeStatus(_ message: LibboxStatusMessage?) {
// DispatchQueue.main.async { [self] in
// commandClient.status = message
// }
// }
//
// func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) {
// guard let groups else {
// return
// }
// var sbGroups = [SBGroup]()
// while groups.hasNext() {
// let group = groups.next()!
// var items = [SBItem]()
// let groupItems = group.getItems()
// while groupItems?.hasNext() ?? false {
// let item = groupItems?.next()!
// items.append(SBItem(tag: item!.tag,
// type: item!.type,
// urlTestDelay: Int(item!.urlTestDelay)
// )
// )
// }
//
// sbGroups.append(.init(tag: group.tag,
// type: group.type,
// selected: group.selected,
// items: items)
// )
//
// }
// DispatchQueue.main.async { [self] in
// commandClient.groups = sbGroups
// }
// }
//
// func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) {
// }
//
// func updateClashMode(_ newMode: String?) {
// }
// func write(_ message: LibboxConnections?) {
// }
//
// }
}
================================================
FILE: ios/Shared/FilePath.swift
================================================
//
// FilePath.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
public enum FilePath {
public static let packageName = {
Bundle.main.infoDictionary?["BASE_BUNDLE_IDENTIFIER"] as? String ?? "unknown"
}()
}
public extension FilePath {
static let groupName = "group.\(packageName)"
private static let defaultSharedDirectory: URL! = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: FilePath.groupName)
static let sharedDirectory = defaultSharedDirectory!
static let cacheDirectory = sharedDirectory
.appendingPathComponent("Library", isDirectory: true)
.appendingPathComponent("Caches", isDirectory: true)
static let workingDirectory = cacheDirectory.appendingPathComponent("Working", isDirectory: true)
}
public extension URL {
var fileName: String {
var path = relativePath
if let index = path.lastIndex(of: "/") {
path = String(path[path.index(index, offsetBy: 1)...])
}
return path
}
}
================================================
FILE: ios/Shared/Outbound.swift
================================================
public struct SBItem: Codable {
let tag: String
let type: String
let urlTestDelay: Int
enum CodingKeys: String, CodingKey {
case tag
case type
case urlTestDelay = "url-test-delay"
}
}
public struct SBGroup: Codable {
let tag: String
let type: String
let selected: String
let items: [SBItem]
}
================================================
FILE: ios/exportOptions.plist
================================================
destination
export
manageAppVersionAndBuildNumber
method
app-store
provisioningProfiles
apple.hiddify.com
dist.apple.apple.hiddify.com
apple.hiddify.com.SingBoxPacketTunnel
dist.apple.apple.hiddify.com.singboxpackettunnel
signingCertificate
E2217AF6F3AD11BA09F9FD95E025D7E637F8B081
signingStyle
manual
stripSwiftSymbols
teamID
3JFTY5BP58
uploadSymbols
================================================
FILE: ios/packaging/ios/make_config.yaml
================================================
display_name: Hiddify
icon: ..\..\assets\images\windows.ico
keywords:
- Hiddify
- Proxy
- VPN
- V2ray
- Nekoray
- Xray
- Psiphon
- OpenVPN
generic_name: Hiddify
actions:
- name: Start
label: start
arguments:
- --start
- name: Stop
label: stop
arguments:
- --stop
categories:
- Internet
startup_notify: true
# You can specify the shared libraries that you want to bundle with your app
#
# flutter_distributor automatically detects the shared libraries that your app
# depends on, but you can also specify them manually here.
#
# The following example shows how to bundle the libcurl library with your app.
#
# include:
# - libcurl.so.4
include: []
================================================
FILE: lib/bootstrap.dart
================================================
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:hiddify/core/analytics/analytics_controller.dart';
import 'package:hiddify/core/app_info/app_info_provider.dart';
import 'package:hiddify/core/directories/directories_provider.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/logger/logger.dart';
import 'package:hiddify/core/logger/logger_controller.dart';
import 'package:hiddify/core/model/environment.dart';
import 'package:hiddify/core/preferences/general_preferences.dart';
import 'package:hiddify/core/preferences/preferences_migration.dart';
import 'package:hiddify/core/preferences/preferences_provider.dart';
import 'package:hiddify/features/app/widget/app.dart';
import 'package:hiddify/features/auto_start/notifier/auto_start_notifier.dart';
import 'package:hiddify/features/log/data/log_data_providers.dart';
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
import 'package:hiddify/features/system_tray/notifier/system_tray_notifier.dart';
import 'package:hiddify/features/window/notifier/window_notifier.dart';
import 'package:hiddify/hiddifycore/hiddify_core_service_provider.dart';
import 'package:hiddify/riverpod_observer.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future lazyBootstrap(WidgetsBinding widgetsBinding, Environment env) async {
if (!kIsWeb) {
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
}
LoggerController.preInit();
FlutterError.onError = Logger.logFlutterError;
WidgetsBinding.instance.platformDispatcher.onError = Logger.logPlatformDispatcherError;
final stopWatch = Stopwatch()..start();
final container = ProviderContainer(overrides: [environmentProvider.overrideWithValue(env)]);
await _init("directories", () => container.read(appDirectoriesProvider.future));
LoggerController.init(container.read(logPathResolverProvider).appFile().path);
final appInfo = await _init("app info", () => container.read(appInfoProvider.future));
await _init("preferences", () => container.read(sharedPreferencesProvider.future));
final enableAnalytics = await container.read(analyticsControllerProvider.future);
if (enableAnalytics) {
await _init("analytics", () => container.read(analyticsControllerProvider.notifier).enableAnalytics());
}
await _init("preferences migration", () async {
try {
await PreferencesMigration(sharedPreferences: container.read(sharedPreferencesProvider).requireValue).migrate();
} catch (e, stackTrace) {
Logger.bootstrap.error("preferences migration failed", e, stackTrace);
if (env == Environment.dev) rethrow;
Logger.bootstrap.info("clearing preferences");
await container.read(sharedPreferencesProvider).requireValue.clear();
}
});
final debug = container.read(debugModeNotifierProvider) || kDebugMode;
if (PlatformUtils.isDesktop) {
await _init("window controller", () => container.read(windowNotifierProvider.future));
final silentStart = container.read(Preferences.silentStart);
Logger.bootstrap.debug("silent start [${silentStart ? "Enabled" : "Disabled"}]");
if (!silentStart) {
await container.read(windowNotifierProvider.notifier).show(focus: false);
} else {
Logger.bootstrap.debug("silent start, remain hidden accessible via tray");
}
await _init("auto start service", () => container.read(autoStartNotifierProvider.future));
}
await _init("logs repository", () => container.read(logRepositoryProvider.future));
await _init("logger controller", () => LoggerController.postInit(debug));
Logger.bootstrap.info(appInfo.format());
await _init("profile repository", () => container.read(profileRepositoryProvider.future));
await _init("translations", () => container.read(translationsProvider.future));
await _safeInit("active profile", () => container.read(activeProfileProvider.future), timeout: 1000);
await _init("hiddify-core", () => container.read(hiddifyCoreServiceProvider).init());
if (!kIsWeb) {
// await _safeInit(
// "deep link service",
// () => container.read(deepLinkNotifierProvider.future),
// timeout: 1000,
// );
if (PlatformUtils.isDesktop) {
await _safeInit("system tray", () => container.read(systemTrayNotifierProvider.future), timeout: 1000);
}
if (PlatformUtils.isAndroid) {
await _safeInit("android display mode", () async {
await FlutterDisplayMode.setHighRefreshRate();
});
}
}
Logger.bootstrap.info("bootstrap took [${stopWatch.elapsedMilliseconds}ms]");
stopWatch.stop();
runApp(
ProviderScope(
parent: container,
observers: [RiverpodObserver()],
child: SentryUserInteractionWidget(child: const App()),
),
);
if (!kIsWeb) {
FlutterNativeSplash.remove();
}
// SentryFlutter.s(DateTime.now().toUtc());
}
Future _init(String name, Future Function() initializer, {int? timeout}) async {
final stopWatch = Stopwatch()..start();
Logger.bootstrap.info("initializing [$name]");
Future func() => timeout != null ? initializer().timeout(Duration(milliseconds: timeout)) : initializer();
try {
final result = await func();
Logger.bootstrap.debug("[$name] initialized in ${stopWatch.elapsedMilliseconds}ms");
return result;
} catch (e, stackTrace) {
Logger.bootstrap.error("[$name] error initializing", e, stackTrace);
rethrow;
} finally {
stopWatch.stop();
}
}
Future _safeInit(String name, Future Function() initializer, {int? timeout}) async {
try {
return await _init(name, initializer, timeout: timeout);
} catch (e) {
return null;
}
}
================================================
FILE: lib/core/analytics/analytics_controller.dart
================================================
import 'package:flutter/foundation.dart';
import 'package:hiddify/core/analytics/analytics_filter.dart';
import 'package:hiddify/core/analytics/analytics_logger.dart';
import 'package:hiddify/core/logger/logger_controller.dart';
import 'package:hiddify/core/model/environment.dart';
import 'package:hiddify/core/preferences/preferences_provider.dart';
import 'package:hiddify/utils/custom_loggers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'analytics_controller.g.dart';
const String enableAnalyticsPrefKey = "enable_analytics";
bool _testCrashReport = false;
@Riverpod(keepAlive: true)
class AnalyticsController extends _$AnalyticsController with AppLogger {
@override
Future build() async {
return _preferences.getBool(enableAnalyticsPrefKey) ?? true;
}
SharedPreferences get _preferences => ref.read(sharedPreferencesProvider).requireValue;
Future enableAnalytics() async {
if (state case AsyncData(value: final enabled)) {
loggy.debug("enabling analytics");
state = const AsyncLoading();
if (!enabled) {
await _preferences.setBool(enableAnalyticsPrefKey, true);
}
// final env = ref.read(environmentProvider);
// final appInfo = await ref.read(appInfoProvider.future);
final dsn = !kDebugMode || _testCrashReport ? Environment.sentryDSN : "";
final sentryLogger = SentryLoggyIntegration();
LoggerController.instance.addPrinter("analytics", sentryLogger);
await SentryFlutter.init((options) {
options.dsn = dsn;
// options.environment = env.name;
// options.dist = appInfo.release.name;
options.debug = kDebugMode;
options.enableNativeCrashHandling = true;
options.enableNdkScopeSync = true;
// options.autoAppStart = false;
// options.attachScreenshot = true;
options.serverName = "";
options.attachThreads = true;
options.tracesSampleRate = 0.20;
options.enableUserInteractionTracing = true;
options.addIntegration(sentryLogger);
options.beforeSend = sentryBeforeSend;
});
state = const AsyncData(true);
}
}
Future disableAnalytics() async {
if (state case AsyncData()) {
loggy.debug("disabling analytics");
state = const AsyncLoading();
await _preferences.setBool(enableAnalyticsPrefKey, false);
await Sentry.close();
LoggerController.instance.removePrinter("analytics");
state = const AsyncData(false);
}
}
}
================================================
FILE: lib/core/analytics/analytics_filter.dart
================================================
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:hiddify/core/model/failures.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
FutureOr sentryBeforeSend(SentryEvent event, Hint hint) async {
if (!canSendEvent(event.throwable)) return null;
return event.copyWith(
user: SentryUser(email: "", username: "", ipAddress: "0.0.0.0"),
);
}
bool canSendEvent(dynamic throwable) {
return switch (throwable) {
UnexpectedFailure(:final error) => canSendEvent(error),
DioException _ => false,
SocketException _ => false,
HttpException _ => false,
HandshakeException _ => false,
ExpectedFailure _ => false,
ExpectedMeasuredFailure _ => false,
_ => true,
};
}
bool canLogEvent(dynamic throwable) => switch (throwable) {
ExpectedMeasuredFailure _ => true,
_ => canSendEvent(throwable),
};
================================================
FILE: lib/core/analytics/analytics_logger.dart
================================================
import 'package:hiddify/utils/sentry_utils.dart';
import 'package:loggy/loggy.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
// modified version of https://github.com/getsentry/sentry-dart/tree/main/logging
class SentryLoggyIntegration extends LoggyPrinter implements Integration {
SentryLoggyIntegration({LogLevel minBreadcrumbLevel = LogLevel.info, LogLevel minEventLevel = LogLevel.error})
: _minBreadcrumbLevel = minBreadcrumbLevel,
_minEventLevel = minEventLevel;
final LogLevel _minBreadcrumbLevel;
final LogLevel _minEventLevel;
late Hub _hub;
@override
void call(Hub hub, SentryOptions options) {
_hub = hub;
options.sdk.addIntegration('LoggyIntegration');
}
@override
Future close() async {}
bool _shouldLog(LogLevel logLevel, LogLevel minLevel) {
if (logLevel == LogLevel.off) {
return false;
}
return logLevel.priority >= minLevel.priority;
}
@override
Future onLog(LogRecord record) async {
if (!canLogEvent(record.error)) return;
if (_shouldLog(record.level, _minEventLevel)) {
await _hub.captureEvent(
record.toEvent(),
stackTrace: record.stackTrace,
hint: Hint.withMap({TypeCheckHint.record: record}),
);
}
if (_shouldLog(record.level, _minBreadcrumbLevel)) {
await _hub.addBreadcrumb(record.toBreadcrumb(), hint: Hint.withMap({TypeCheckHint.record: record}));
}
}
}
extension LogRecordX on LogRecord {
Breadcrumb toBreadcrumb() {
return Breadcrumb(
category: 'log',
type: 'debug',
timestamp: time.toUtc(),
level: level.toSentryLevel(),
message: message,
data: {
if (object != null) 'LogRecord.object': object!,
if (error != null) 'LogRecord.error': error!,
if (stackTrace != null) 'LogRecord.stackTrace': stackTrace!,
'LogRecord.loggerName': loggerName,
'LogRecord.sequenceNumber': sequenceNumber,
},
);
}
SentryEvent toEvent() {
return SentryEvent(
timestamp: time.toUtc(),
logger: loggerName,
level: level.toSentryLevel(),
message: SentryMessage(message),
throwable: error,
// ignore: deprecated_member_use
extra: {
if (object != null) 'LogRecord.object': object!,
'LogRecord.sequenceNumber': sequenceNumber,
},
);
}
}
extension LogLevelX on LogLevel {
SentryLevel? toSentryLevel() => switch (this) {
LogLevel.all || LogLevel.debug => SentryLevel.debug,
LogLevel.info => SentryLevel.info,
LogLevel.warning => SentryLevel.warning,
LogLevel.error => SentryLevel.error,
_ => null,
};
}
================================================
FILE: lib/core/app_info/app_info_provider.dart
================================================
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:hiddify/core/model/app_info_entity.dart';
import 'package:hiddify/core/model/environment.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'app_info_provider.g.dart';
@Riverpod(keepAlive: true)
Environment environment(EnvironmentRef ref) => throw Exception("override environmentProvider");
@Riverpod(keepAlive: true)
class AppInfo extends _$AppInfo {
@override
Future build() async {
final packageInfo = await PackageInfo.fromPlatform();
final environment = ref.watch(environmentProvider);
return AppInfoEntity(
name: packageInfo.appName,
version: packageInfo.version,
buildNumber: packageInfo.buildNumber,
release: Release.read(),
operatingSystem: kIsWeb ? "web" : Platform.operatingSystem,
operatingSystemVersion: kIsWeb ? "web" : Platform.operatingSystemVersion,
environment: environment,
);
}
}
================================================
FILE: lib/core/db/converters/duration_converter.dart
================================================
import 'package:drift/drift.dart';
class DurationTypeConverter extends TypeConverter {
@override
Duration fromSql(int fromDb) {
return Duration(seconds: fromDb);
}
@override
int toSql(Duration value) {
return value.inSeconds;
}
}
================================================
FILE: lib/core/db/db.dart
================================================
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:hiddify/core/db/converters/duration_converter.dart';
import 'package:hiddify/core/db/db.steps.dart';
import 'package:hiddify/core/directories/directories_provider.dart';
import 'package:hiddify/features/per_app_proxy/model/per_app_proxy_mode.dart';
import 'package:hiddify/features/profile/model/profile_entity.dart';
import 'package:hiddify/utils/custom_loggers.dart';
part 'db.g.dart';
@DriftDatabase(tables: [ProfileEntries, AppProxyEntries])
class Db extends _$Db with InfraLogger {
Db([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 5;
static QueryExecutor _openConnection() {
return LazyDatabase(
() => driftDatabase(
name: "db",
native: const DriftNativeOptions(databaseDirectory: AppDirectories.getDatabaseDirectory),
web: DriftWebOptions(sqlite3Wasm: Uri.parse('sqlite3.wasm'), driftWorker: Uri.parse('drift_worker.js')),
),
);
}
@override
MigrationStrategy get migration {
return MigrationStrategy(
onCreate: (Migrator m) async {
await m.createAll();
},
onUpgrade: stepByStep(
from1To2: (m, schema) async {
await m.alterTable(
TableMigration(
schema.profileEntries,
columnTransformer: {schema.profileEntries.type: const Constant("remote")},
newColumns: [schema.profileEntries.type],
),
);
},
from2To3: (m, schema) async {
await m.createTable(schema.geoAssetEntries);
},
from3To4: (m, schema) async {
final testUrlExists = await _columnExists(
schema.profileEntries.actualTableName,
schema.profileEntries.testUrl.name,
);
if (!testUrlExists) {
await m.addColumn(schema.profileEntries, schema.profileEntries.testUrl);
}
},
from4To5: (m, schema) async {
await m.deleteTable('geo_asset_entries');
await m.renameColumn(schema.profileEntries, 'test_url', schema.profileEntries.profileOverride);
final userOverrideExists = await _columnExists(
schema.profileEntries.actualTableName,
schema.profileEntries.userOverride.name,
);
if (!userOverrideExists) {
await m.addColumn(schema.profileEntries, schema.profileEntries.userOverride);
}
final populatedHeadersExists = await _columnExists(
schema.profileEntries.actualTableName,
schema.profileEntries.populatedHeaders.name,
);
if (!populatedHeadersExists) {
await m.addColumn(schema.profileEntries, schema.profileEntries.populatedHeaders);
}
await m.createTable(schema.appProxyEntries);
},
),
);
}
Future _columnExists(String table, String column) async {
final result = await customSelect('PRAGMA table_info($table);').get();
return result.any((row) => row.data['name'] == column);
}
}
@DataClassName('ProfileEntry')
class ProfileEntries extends Table {
TextColumn get id => text()();
TextColumn get type => textEnum()();
BoolColumn get active => boolean()();
TextColumn get name => text().withLength(min: 1)();
TextColumn get url => text().nullable()();
DateTimeColumn get lastUpdate => dateTime()();
IntColumn get updateInterval => integer().nullable().map(DurationTypeConverter())();
IntColumn get upload => integer().nullable()();
IntColumn get download => integer().nullable()();
IntColumn get total => integer().nullable()();
DateTimeColumn get expire => dateTime().nullable()();
TextColumn get webPageUrl => text().nullable()();
TextColumn get supportUrl => text().nullable()();
TextColumn get populatedHeaders => text().nullable()();
TextColumn get profileOverride => text().nullable()();
TextColumn get userOverride => text().nullable()();
@override
Set get primaryKey => {id};
}
@DataClassName('AppProxyEntry')
class AppProxyEntries extends Table {
TextColumn get mode => textEnum()();
TextColumn get pkgName => text()();
IntColumn get flags => integer().withDefault(const Constant(0))();
@override
Set get primaryKey => {mode, pkgName};
}
================================================
FILE: lib/core/db/db.steps.dart
================================================
// dart format width=80
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
// GENERATED BY drift_dev, DO NOT MODIFY.
final class Schema2 extends i0.VersionedSchema {
Schema2({required super.database}) : super(version: 2);
@override
late final List entities = [profileEntries];
late final Shape0 profileEntries = Shape0(
source: i0.VersionedTable(
entityName: 'profile_entries',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
_column_6,
_column_7,
_column_8,
_column_9,
_column_10,
_column_11,
_column_12,
],
attachedDatabase: database,
),
alias: null,
);
}
class Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn get id =>
columnsByName['id']! as i1.GeneratedColumn;
i1.GeneratedColumn get type =>
columnsByName['type']! as i1.GeneratedColumn;
i1.GeneratedColumn get active =>
columnsByName['active']! as i1.GeneratedColumn;
i1.GeneratedColumn get name =>
columnsByName['name']! as i1.GeneratedColumn;
i1.GeneratedColumn get url =>
columnsByName['url']! as i1.GeneratedColumn;
i1.GeneratedColumn get lastUpdate =>
columnsByName['last_update']! as i1.GeneratedColumn;
i1.GeneratedColumn get updateInterval =>
columnsByName['update_interval']! as i1.GeneratedColumn;
i1.GeneratedColumn get upload =>
columnsByName['upload']! as i1.GeneratedColumn;
i1.GeneratedColumn get download =>
columnsByName['download']! as i1.GeneratedColumn;
i1.GeneratedColumn get total =>
columnsByName['total']! as i1.GeneratedColumn;
i1.GeneratedColumn get expire =>
columnsByName['expire']! as i1.GeneratedColumn;
i1.GeneratedColumn get webPageUrl =>
columnsByName['web_page_url']! as i1.GeneratedColumn;
i1.GeneratedColumn get supportUrl =>
columnsByName['support_url']! as i1.GeneratedColumn;
}
i1.GeneratedColumn _column_0(String aliasedName) =>
i1.GeneratedColumn(
'id',
aliasedName,
false,
type: i1.DriftSqlType.string,
);
i1.GeneratedColumn _column_1(String aliasedName) =>
i1.GeneratedColumn(
'type',
aliasedName,
false,
type: i1.DriftSqlType.string,
);
i1.GeneratedColumn _column_2(String aliasedName) =>
i1.GeneratedColumn(
'active',
aliasedName,
false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("active" IN (0, 1))',
),
);
i1.GeneratedColumn _column_3(String aliasedName) =>
i1.GeneratedColumn(
'name',
aliasedName,
false,
additionalChecks: i1.GeneratedColumn.checkTextLength(minTextLength: 1),
type: i1.DriftSqlType.string,
);
i1.GeneratedColumn _column_4(String aliasedName) =>
i1.GeneratedColumn(
'url',
aliasedName,
true,
type: i1.DriftSqlType.string,
);
i1.GeneratedColumn _column_5(String aliasedName) =>
i1.GeneratedColumn(
'last_update',
aliasedName,
false,
type: i1.DriftSqlType.dateTime,
);
i1.GeneratedColumn _column_6(String aliasedName) =>
i1.GeneratedColumn(
'update_interval',
aliasedName,
true,
type: i1.DriftSqlType.int,
);
i1.GeneratedColumn _column_7(String aliasedName) =>
i1.GeneratedColumn(
'upload',
aliasedName,
true,
type: i1.DriftSqlType.int,
);
i1.GeneratedColumn _column_8(String aliasedName) =>
i1.GeneratedColumn(
'download',
aliasedName,
true,
type: i1.DriftSqlType.int,
);
i1.GeneratedColumn _column_9(String aliasedName) =>
i1.GeneratedColumn(
'total',
aliasedName,
true,
type: i1.DriftSqlType.int,
);
i1.GeneratedColumn _column_10(String aliasedName) =>
i1.GeneratedColumn(
'expire',
aliasedName,
true,
type: i1.DriftSqlType.dateTime,
);
i1.GeneratedColumn _column_11(String aliasedName) =>
i1.GeneratedColumn(
'web_page_url',
aliasedName,
true,
type: i1.DriftSqlType.string,
);
i1.GeneratedColumn _column_12(String aliasedName) =>
i1.GeneratedColumn(
'support_url',
aliasedName,
true,
type: i1.DriftSqlType.string,
);
final class Schema3 extends i0.VersionedSchema {
Schema3({required super.database}) : super(version: 3);
@override
late final List entities = [
profileEntries,
geoAssetEntries,
];
late final Shape0 profileEntries = Shape0(
source: i0.VersionedTable(
entityName: 'profile_entries',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
_column_6,
_column_7,
_column_8,
_column_9,
_column_10,
_column_11,
_column_12,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape1 geoAssetEntries = Shape1(
source: i0.VersionedTable(
entityName: 'geo_asset_entries',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(id)', 'UNIQUE(name, provider_name)'],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_13,
_column_14,
_column_15,
],
attachedDatabase: database,
),
alias: null,
);
}
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn get id =>
columnsByName['id']! as i1.GeneratedColumn;
i1.GeneratedColumn get type =>
columnsByName['type']! as i1.GeneratedColumn