Compare commits

...

22 Commits

Author SHA1 Message Date
74df08144f 解决开启关闭后UI界面状态不同步问题
Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
(cherry picked from commit 23a4a5ce2e46ffbd3b8188333dfa7f4559984e4c)
2025-10-31 00:20:29 -07:00
5c8f0ca1fc 1. Android: 组合多个硬件标识 + 序列号 + Build信息
2. iOS: 组合设备型号 + 系统版本 + identifierForVendor,持久化存储到钥匙串
  3. macOS: 使用硬件UUID + 序列号
  4. Windows: 使用主板UUID + CPU信息
  5. Linux: 使用machine-id + 硬件信息

(cherry picked from commit 1be3037f715548a1efa4cc5d7d204b989878557a)
2025-10-31 00:16:49 -07:00
c1c5f1a2e0 优化:需要在 kr_loginOut() 方法中添加逻辑:退出登录后,如果站点启用了设备登录,则自动调用设备登录接口。
(cherry picked from commit b5267ad753163223646d29cdc52669d24f158d8b)
2025-10-31 00:16:48 -07:00
d94e7fd44a 修正流量不统计等问题
(cherry picked from commit 9f94be27288b5cf8f23236706fa2e561abf967e2)
2025-10-31 00:13:53 -07:00
064a0a7402 修改flutter兼容版本,优化macos无法切换节点和协议不兼容等问题
(cherry picked from commit dcc07886f8ba73eb2630a14a81bda191468c7a1f)
2025-10-31 00:13:52 -07:00
8bba2441c2 Shadowsocks - 原本就能用 Trojan - 已修复,insecure: true VMess - 已修复,insecure: true VLESS -
已修复,insecure: true Hysteria - 新增支持 Hysteria2 - 已修复,insecure: true

(cherry picked from commit 7fae8a6f4d569a30c5a3606949154a57e5a597d6)
2025-10-31 00:13:42 -07:00
7011ba1551 添加 /FS 标志来允许多个 CL.EXE 进程写入同一个 PDB 文件
(cherry picked from commit c12510cbdea34463b411858a0034c52e6ded5c5f)
2025-10-31 00:13:42 -07:00
bba8acfe76 windows路径问题
(cherry picked from commit e226b8635d60a8c2fea8a99c151a5161a797aa52)
2025-10-31 00:13:42 -07:00
9c2f9be6c5 windows路径问题
(cherry picked from commit 9cefb1b9e009575ee7f6a5aef631cb344b6e1df8)
2025-10-31 00:13:42 -07:00
0067017ca6 修正Visual Studio 编译器的 PDB 文件锁冲突
(cherry picked from commit 62ab152d639509119db5edd742765bd3bec575bc)
2025-10-31 00:13:41 -07:00
1970bbb6fd 修正windows编译路径数据库问题
(cherry picked from commit 58f6dec2325bdf78689fb4894d76c0dd7b591df3)
2025-10-31 00:13:41 -07:00
fbdf4a2337 防止被多次初始化
(cherry picked from commit 77f1a8b30d2c30c03f0ec45fc32fe8eef9d2b4c7)
2025-10-31 00:13:41 -07:00
4250f61345 还是路径问题
(cherry picked from commit 0045992c45bcfd54db328f82d14b8bac33da9139)
2025-10-31 00:13:41 -07:00
ee20cc93e2 修正windwos打包路径问题
(cherry picked from commit 88df41fe3fd2bb3b0293dc0df178b1e6f83dad07)
2025-10-31 00:13:41 -07:00
01ea786ef0 新增 iOS 和 Apple TV 在线打包
(cherry picked from commit bdcd45999796408067a6e67600e9c98721969da3)
2025-10-31 00:13:29 -07:00
11c66d0314 修正windows打包路径问题
(cherry picked from commit 3db9d86bcd6f85cf6b81ee27e79a0d2a0f9287ab)
2025-10-31 00:13:29 -07:00
0449e04f42 修正windows打包路径问题
(cherry picked from commit 93ca88fa49d3bd3422b5f05243ec9dfad47c2a77)
2025-10-31 00:13:29 -07:00
41f85a6747 使用 GLib/GObject 的旧代码触发新版本 GLib 的弃用检查,修正使用最新的
(cherry picked from commit 61e8750169dd1f797f4cd607d72f4b6cd3d0705f)
2025-10-31 00:13:29 -07:00
3328919f7e 修正跨平台字符处理统一
(cherry picked from commit 6e986a9c9cacfd00102cbe91f695c2253598560b)
2025-10-31 00:13:29 -07:00
fde4bfa464 修正windows在线打包
(cherry picked from commit dae69f6c0e25bc1ba80fc1b52bdfbf255f1a5f51)
2025-10-31 00:13:29 -07:00
8204895199 修正在线打包
(cherry picked from commit 1af2aa64f26db5f7e3d41409e714be1789f17345)
2025-10-31 00:13:28 -07:00
773047838c 去掉调试模式并且修正在线打包windows
(cherry picked from commit 603afe3ca6ffc6838e83ff8e1980dcc9e615733b)
2025-10-31 00:13:28 -07:00
20 changed files with 2015 additions and 709 deletions

View File

@ -45,13 +45,64 @@ on:
required: true
default: 'https://xpp5.oss-ap-southeast-1.aliyuncs.com/bear1.txt'
type: string
encryption_key:
description: '加密密钥'
required: true
default: 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx'
type: string
platforms:
description: '构建平台 (多选: android,windows,macos,linux)'
description: '构建平台 (多选: android,windows,macos,linux,ios)'
required: true
default: 'android,windows,macos,linux'
type: string
jobs:
# ==================== 编译 libcore (iOS/tvOS) ====================
build-libcore-ios:
name: 编译 libcore (iOS/tvOS)
runs-on: macos-latest
if: contains(inputs.platforms || 'ios', 'ios')
steps:
- name: 📥 Checkout 代码
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: 🔧 设置 Go 环境
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
cache-dependency-path: libcore/go.sum
- name: 🔧 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 📦 编译 libcore.xcframework (支持 iOS/tvOS)
working-directory: libcore
run: |
echo "🚀 开始编译 iOS/tvOS libcore..."
make ios-full
if [ -d "bin/Libcore.xcframework" ]; then
echo "✅ iOS/tvOS libcore 编译成功"
ls -lh bin/
else
echo "❌ iOS/tvOS libcore 编译失败"
exit 1
fi
- name: 📤 上传 iOS libcore
uses: actions/upload-artifact@v4
with:
name: libcore-ios
path: libcore/bin/Libcore.xcframework
retention-days: 7
# ==================== 编译 libcore (Android) ====================
build-libcore-android:
name: 编译 libcore (Android)
@ -293,7 +344,7 @@ jobs:
name: libcore-android
path: android/app/libs/
- name: ⚙️ 配置 API 和 OSS
- name: ⚙️ 配置 API、OSS 和加密密钥
run: |
CONFIG_FILE="lib/app/common/app_config.dart"
API_DOMAIN="${{ inputs.api_domain || 'api.maodag.top' }}"
@ -301,16 +352,22 @@ jobs:
OSS_URL_2="${{ inputs.oss_url_2 || 'https://xgp3.oss-ap-northeast-1.aliyuncs.com/bear1.txt' }}"
OSS_URL_3="${{ inputs.oss_url_3 || 'https://xpp4.oss-ap-northeast-2.aliyuncs.com/bear1.txt' }}"
OSS_URL_4="${{ inputs.oss_url_4 || 'https://xpp5.oss-ap-southeast-1.aliyuncs.com/bear1.txt' }}"
ENCRYPTION_KEY="${{ inputs.encryption_key || 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx' }}"
echo "🔧 配置参数:"
echo " API: $API_DOMAIN"
echo " OSS: $OSS_URL_1"
echo " 密钥: ${ENCRYPTION_KEY:0:10}..."
# 转义密钥中的特殊字符(使用 perl 兼容所有平台)
ENCRYPTION_KEY_ESCAPED=$(echo "$ENCRYPTION_KEY" | perl -pe 's/([\[\].*^$()+?{|\\])/\\$1/g')
sed -i "s|api\.maodag\.top|$API_DOMAIN|g" "$CONFIG_FILE"
sed -i "s|https://ppp2\.oss-cn-hongkong\.aliyuncs\.com/bear1\.txt|$OSS_URL_1|g" "$CONFIG_FILE"
sed -i "s|https://xgp3\.oss-ap-northeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_2|g" "$CONFIG_FILE"
sed -i "s|https://xpp4\.oss-ap-northeast-2\.aliyuncs\.com/bear1\.txt|$OSS_URL_3|g" "$CONFIG_FILE"
sed -i "s|https://xpp5\.oss-ap-southeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_4|g" "$CONFIG_FILE"
sed -i "s|c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx|$ENCRYPTION_KEY_ESCAPED|g" "$CONFIG_FILE"
echo "✅ 配置完成"
@ -370,9 +427,70 @@ jobs:
uses: actions/download-artifact@v4
with:
name: libcore-windows
path: libcore/bin/
path: libcore_windows_temp
- name: 🔧 复制 libcore 文件到正确位置并重命名
run: |
Write-Host "📋 开始复制 libcore 文件..."
# 显示下载的文件结构
Write-Host "🔍 检查下载的文件结构:"
Get-ChildItem -Recurse libcore_windows_temp -ErrorAction SilentlyContinue | Format-Table Name, FullName
# 确保目标目录存在
New-Item -ItemType Directory -Force -Path "libcore\bin" | Out-Null
# 查找 libcore.dll可能在 libcore_windows_temp/bin/ 或 libcore_windows_temp/libcore/bin/
$dllFiles = Get-ChildItem -Path libcore_windows_temp -Recurse -Filter "libcore.dll" -ErrorAction SilentlyContinue
if ($dllFiles) {
$sourceDll = $dllFiles[0].FullName
Write-Host "✅ 找到 libcore.dll: $sourceDll"
Copy-Item $sourceDll "libcore\bin\libcore.dll" -Force
} else {
Write-Host "❌ 未找到 libcore.dll"
Write-Host "当前目录内容:"
Get-ChildItem -Path . -Recurse | Select-Object -First 20 | Format-Table Name, FullName
exit 1
}
# 查找并复制 HiddifyCli.exe重命名为 BearVPNCli.exe
$exeFiles = Get-ChildItem -Path libcore_windows_temp -Recurse -Filter "HiddifyCli.exe" -ErrorAction SilentlyContinue
if ($exeFiles) {
$sourceExe = $exeFiles[0].FullName
Write-Host "✅ 找到 HiddifyCli.exe: $sourceExe"
Write-Host "📝 复制并重命名为 BearVPNCli.exe"
Copy-Item $sourceExe "libcore\bin\BearVPNCli.exe" -Force
Write-Host "✅ 重命名完成HiddifyCli.exe → BearVPNCli.exe"
} else {
Write-Host "⚠️ 未找到 HiddifyCli.exe这不是致命错误"
}
# 复制 webui 目录
$webuiDir = Get-ChildItem -Path libcore_windows_temp -Recurse -Filter "webui" -Directory -ErrorAction SilentlyContinue
if ($webuiDir) {
Write-Host "✅ 找到 webui 目录: $($webuiDir[0].FullName)"
Copy-Item -Path $webuiDir[0].FullName -Destination "libcore\bin\webui" -Recurse -Force
} else {
Write-Host "⚠️ 未找到 webui 目录(这不是致命错误)"
}
Write-Host ""
Write-Host "📄 验证复制后的文件结构:"
if (Test-Path "libcore\bin") {
Get-ChildItem libcore\bin\ -Recurse | Format-Table Name, FullName, Length
} else {
Write-Host "❌ libcore\bin 目录不存在"
}
if (-not (Test-Path "libcore\bin\libcore.dll")) {
Write-Host "❌ libcore.dll 未正确复制到 libcore\bin\"
exit 1
}
Write-Host "✅ libcore 文件复制完成"
shell: pwsh
- name: ⚙️ 配置 API 和 OSS
- name: ⚙️ 配置 API、OSS 和加密密钥
shell: bash
run: |
CONFIG_FILE="lib/app/common/app_config.dart"
@ -381,12 +499,16 @@ jobs:
OSS_URL_2="${{ inputs.oss_url_2 || 'https://xgp3.oss-ap-northeast-1.aliyuncs.com/bear1.txt' }}"
OSS_URL_3="${{ inputs.oss_url_3 || 'https://xpp4.oss-ap-northeast-2.aliyuncs.com/bear1.txt' }}"
OSS_URL_4="${{ inputs.oss_url_4 || 'https://xpp5.oss-ap-southeast-1.aliyuncs.com/bear1.txt' }}"
ENCRYPTION_KEY="${{ inputs.encryption_key || 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx' }}"
ENCRYPTION_KEY_ESCAPED=$(echo "$ENCRYPTION_KEY" | perl -pe 's/([\[\].*^$()+?{|\\])/\\$1/g')
sed -i "s|api\.maodag\.top|$API_DOMAIN|g" "$CONFIG_FILE"
sed -i "s|https://ppp2\.oss-cn-hongkong\.aliyuncs\.com/bear1\.txt|$OSS_URL_1|g" "$CONFIG_FILE"
sed -i "s|https://xgp3\.oss-ap-northeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_2|g" "$CONFIG_FILE"
sed -i "s|https://xpp4\.oss-ap-northeast-2\.aliyuncs\.com/bear1\.txt|$OSS_URL_3|g" "$CONFIG_FILE"
sed -i "s|https://xpp5\.oss-ap-southeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_4|g" "$CONFIG_FILE"
sed -i "s|c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx|$ENCRYPTION_KEY_ESCAPED|g" "$CONFIG_FILE"
- name: 📦 安装 Flutter 依赖
run: |
@ -415,10 +537,70 @@ jobs:
}
shell: pwsh
- name: 🔧 验证 libcore 文件存在
run: |
Write-Host "📋 验证 libcore 文件是否存在..."
if (Test-Path "libcore\bin\libcore.dll") {
$dllInfo = Get-Item "libcore\bin\libcore.dll"
Write-Host "✅ libcore.dll 存在: $($dllInfo.FullName) - 大小: $($dllInfo.Length) bytes"
} else {
Write-Host "❌ libcore.dll 不存在"
Write-Host "当前 libcore\bin 目录内容:"
if (Test-Path "libcore\bin") {
Get-ChildItem "libcore\bin" | Format-Table Name, FullName, Length
} else {
Write-Host "libcore\bin 目录不存在"
}
exit 1
}
if (Test-Path "libcore\bin\BearVPNCli.exe") {
$exeInfo = Get-Item "libcore\bin\BearVPNCli.exe"
Write-Host "✅ BearVPNCli.exe 存在: $($exeInfo.FullName) - 大小: $($exeInfo.Length) bytes"
} else {
Write-Host "⚠️ BearVPNCli.exe 不存在"
}
shell: pwsh
- name: 🔨 构建 Windows (Release)
run: |
flutter build windows --release
- name: 🔍 验证 Windows 文件结构
run: |
Write-Host "📋 检查 Release 目录文件结构..."
$releaseDir = "build\windows\x64\runner\Release"
if (Test-Path $releaseDir) {
Write-Host "✅ Release 目录存在"
Write-Host ""
Write-Host "📄 文件列表:"
Get-ChildItem $releaseDir | Format-Table Name, Length, LastWriteTime
# 检查关键文件
if (Test-Path "$releaseDir\BearVPN.exe") {
Write-Host "✅ BearVPN.exe 存在"
} else {
Write-Host "❌ BearVPN.exe 不存在"
}
if (Test-Path "$releaseDir\BearVPNCli.exe") {
Write-Host "✅ BearVPNCli.exe 存在"
} else {
Write-Host "⚠️ BearVPNCli.exe 不存在"
}
if (Test-Path "$releaseDir\libcore.dll") {
Write-Host "✅ libcore.dll 存在"
} else {
Write-Host "❌ libcore.dll 不存在"
}
} else {
Write-Host "❌ Release 目录不存在"
}
shell: pwsh
- name: 📦 打包 Windows
shell: bash
run: |
@ -466,7 +648,7 @@ jobs:
run: |
chmod +x libcore/bin/HiddifyCli
- name: ⚙️ 配置 API 和 OSS
- name: ⚙️ 配置 API、OSS 和加密密钥
run: |
CONFIG_FILE="lib/app/common/app_config.dart"
API_DOMAIN="${{ inputs.api_domain || 'api.maodag.top' }}"
@ -474,12 +656,27 @@ jobs:
OSS_URL_2="${{ inputs.oss_url_2 || 'https://xgp3.oss-ap-northeast-1.aliyuncs.com/bear1.txt' }}"
OSS_URL_3="${{ inputs.oss_url_3 || 'https://xpp4.oss-ap-northeast-2.aliyuncs.com/bear1.txt' }}"
OSS_URL_4="${{ inputs.oss_url_4 || 'https://xpp5.oss-ap-southeast-1.aliyuncs.com/bear1.txt' }}"
ENCRYPTION_KEY="${{ inputs.encryption_key || 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx' }}"
# macOS 使用 BSD sed转义特殊字符
# 使用 perl 来转义特殊字符,更可靠
ENCRYPTION_KEY_ESCAPED=$(echo "$ENCRYPTION_KEY" | perl -pe 's/([\[\].*^$()+?{|\\])/\\$1/g')
echo "🔧 配置参数:"
echo " API: $API_DOMAIN"
echo " 密钥: ${ENCRYPTION_KEY:0:10}..."
# macOS sed 需要使用 -i '' 和正确的语法
sed -i '' "s|api\.maodag\.top|$API_DOMAIN|g" "$CONFIG_FILE"
sed -i '' "s|https://ppp2\.oss-cn-hongkong\.aliyuncs\.com/bear1\.txt|$OSS_URL_1|g" "$CONFIG_FILE"
sed -i '' "s|https://xgp3\.oss-ap-northeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_2|g" "$CONFIG_FILE"
sed -i '' "s|https://xpp4\.oss-ap-northeast-2\.aliyuncs\.com/bear1\.txt|$OSS_URL_3|g" "$CONFIG_FILE"
sed -i '' "s|https://xpp5\.oss-ap-southeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_4|g" "$CONFIG_FILE"
# 使用不同的分隔符避免转义问题
sed -i '' "s|c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx|$ENCRYPTION_KEY_ESCAPED|g" "$CONFIG_FILE"
echo "✅ 配置完成"
- name: 📦 安装 Flutter 依赖
run: |
@ -549,7 +746,7 @@ jobs:
run: |
chmod +x libcore/bin/HiddifyCli
- name: ⚙️ 配置 API 和 OSS
- name: ⚙️ 配置 API、OSS 和加密密钥
run: |
CONFIG_FILE="lib/app/common/app_config.dart"
API_DOMAIN="${{ inputs.api_domain || 'api.maodag.top' }}"
@ -557,12 +754,16 @@ jobs:
OSS_URL_2="${{ inputs.oss_url_2 || 'https://xgp3.oss-ap-northeast-1.aliyuncs.com/bear1.txt' }}"
OSS_URL_3="${{ inputs.oss_url_3 || 'https://xpp4.oss-ap-northeast-2.aliyuncs.com/bear1.txt' }}"
OSS_URL_4="${{ inputs.oss_url_4 || 'https://xpp5.oss-ap-southeast-1.aliyuncs.com/bear1.txt' }}"
ENCRYPTION_KEY="${{ inputs.encryption_key || 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx' }}"
ENCRYPTION_KEY_ESCAPED=$(echo "$ENCRYPTION_KEY" | perl -pe 's/([\[\].*^$()+?{|\\])/\\$1/g')
sed -i "s|api\.maodag\.top|$API_DOMAIN|g" "$CONFIG_FILE"
sed -i "s|https://ppp2\.oss-cn-hongkong\.aliyuncs\.com/bear1\.txt|$OSS_URL_1|g" "$CONFIG_FILE"
sed -i "s|https://xgp3\.oss-ap-northeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_2|g" "$CONFIG_FILE"
sed -i "s|https://xpp4\.oss-ap-northeast-2\.aliyuncs\.com/bear1\.txt|$OSS_URL_3|g" "$CONFIG_FILE"
sed -i "s|https://xpp5\.oss-ap-southeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_4|g" "$CONFIG_FILE"
sed -i "s|c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx|$ENCRYPTION_KEY_ESCAPED|g" "$CONFIG_FILE"
- name: 📦 安装 Flutter 依赖
run: |
@ -588,10 +789,90 @@ jobs:
path: BearVPN-linux-*.tar.gz
retention-days: 30
# ==================== iOS 构建 ====================
build-ios:
name: 构建 iOS
needs: build-libcore-ios
runs-on: macos-latest
if: contains(inputs.platforms || 'ios', 'ios')
steps:
- name: 📥 Checkout 代码
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: 🔧 设置 Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.5'
channel: 'stable'
cache: true
- name: 📥 下载 iOS libcore
uses: actions/download-artifact@v4
with:
name: libcore-ios
path: ios/Frameworks/
- name: ⚙️ 配置 API、OSS 和加密密钥
run: |
CONFIG_FILE="lib/app/common/app_config.dart"
API_DOMAIN="${{ inputs.api_domain || 'api.maodag.top' }}"
OSS_URL_1="${{ inputs.oss_url_1 || 'https://ppp2.oss-cn-hongkong.aliyuncs.com/bear1.txt' }}"
OSS_URL_2="${{ inputs.oss_url_2 || 'https://xgp3.oss-ap-northeast-1.aliyuncs.com/bear1.txt' }}"
OSS_URL_3="${{ inputs.oss_url_3 || 'https://xpp4.oss-ap-northeast-2.aliyuncs.com/bear1.txt' }}"
OSS_URL_4="${{ inputs.oss_url_4 || 'https://xpp5.oss-ap-southeast-1.aliyuncs.com/bear1.txt' }}"
ENCRYPTION_KEY="${{ inputs.encryption_key || 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx' }}"
# 使用 perl 转义特殊字符
ENCRYPTION_KEY_ESCAPED=$(echo "$ENCRYPTION_KEY" | perl -pe 's/([\[\].*^$()+?{|\\])/\\$1/g')
sed -i '' "s|api\.maodag\.top|$API_DOMAIN|g" "$CONFIG_FILE"
sed -i '' "s|https://ppp2\.oss-cn-hongkong\.aliyuncs\.com/bear1\.txt|$OSS_URL_1|g" "$CONFIG_FILE"
sed -i '' "s|https://xgp3\.oss-ap-northeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_2|g" "$CONFIG_FILE"
sed -i '' "s|https://xpp4\.oss-ap-northeast-2\.aliyuncs\.com/bear1\.txt|$OSS_URL_3|g" "$CONFIG_FILE"
sed -i '' "s|https://xpp5\.oss-ap-southeast-1\.aliyuncs\.com/bear1\.txt|$OSS_URL_4|g" "$CONFIG_FILE"
sed -i '' "s|c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx|$ENCRYPTION_KEY_ESCAPED|g" "$CONFIG_FILE"
- name: 📦 安装 Flutter 依赖
run: |
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
- name: 🔨 构建 iOS (Release)
run: |
flutter build ios --release --no-codesign
- name: 📦 打包 iOS
run: |
COMMIT_SHA=${GITHUB_SHA::7}
DATE=$(date '+%Y%m%d')
cd build/ios/iphoneos
# 创建 Payload 目录
mkdir -p Payload
cp -r Runner.app Payload/
# 创建 IPA 文件
zip -r "../../../../BearVPN-ios-release-${DATE}-${COMMIT_SHA}.ipa" Payload
echo "✅ iOS IPA 创建完成"
ls -lh BearVPN-ios-*.ipa
- name: 📤 上传 iOS
uses: actions/upload-artifact@v4
with:
name: ios-app
path: BearVPN-ios-*.ipa
retention-days: 30
# ==================== 创建 Release ====================
create-release:
name: 创建 Release
needs: [build-android, build-windows, build-macos, build-linux]
needs: [build-android, build-windows, build-macos, build-linux, build-ios]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
@ -614,6 +895,7 @@ jobs:
| **Android** | arm64-v8a | BearVPN-android-arm64-v8a-*.apk |
| **Android** | armeabi-v7a | BearVPN-android-armeabi-v7a-*.apk |
| **Android** | x86_64 | BearVPN-android-x86_64-*.apk |
| **iOS** | Universal | BearVPN-ios-*.ipa |
| **Windows** | x64 | BearVPN-windows-x64-*.zip |
| **macOS** | Universal | BearVPN-macos-*.zip |
| **Linux** | x64 | BearVPN-linux-x64-*.tar.gz |
@ -628,6 +910,7 @@ jobs:
### 📥 安装指南
**Android:** 下载 APK 直接安装
**iOS:** 下载 IPA 使用 AltStore/Sideloadly 安装
**Windows:** 解压 ZIP 运行 BearVPN.exe
**macOS:** 解压 ZIP 拖拽到应用程序
**Linux:** 解压 tar.gz 运行可执行文件
@ -650,6 +933,7 @@ jobs:
with:
files: |
artifacts/android-apk/*.apk
artifacts/ios-app/*.ipa
artifacts/windows-x64/*.zip
artifacts/macos-app/*.zip
artifacts/linux-x64/*.tar.gz

View File

@ -8,11 +8,108 @@ on:
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
# 先编译 libcore
build-libcore:
name: 编译 libcore (Windows)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 📥 Checkout 代码
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: 🔧 设置 Go 环境
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
cache-dependency-path: libcore/go.sum
- name: 🔧 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 🔧 安装 MinGW
run: |
sudo apt-get update
sudo apt-get install -y mingw-w64
- name: 📦 编译 libcore.dll
working-directory: libcore
run: |
echo "🚀 开始编译 Windows libcore..."
make windows-amd64
if [ -f "bin/libcore.dll" ] && [ -f "bin/HiddifyCli.exe" ]; then
echo "✅ Windows libcore 编译成功"
ls -lh bin/
else
echo "❌ Windows libcore 编译失败"
exit 1
fi
- name: 📤 上传 Windows libcore
uses: actions/upload-artifact@v4
with:
name: libcore-windows
path: |
libcore/bin/libcore.dll
libcore/bin/HiddifyCli.exe
libcore/bin/webui/**
retention-days: 7
# 构建 Windows 应用
build:
runs-on: windows-latest
needs: build-libcore
steps:
- name: 📥 Checkout 代码
uses: actions/checkout@v4
- name: 📥 下载 libcore
uses: actions/download-artifact@v4
with:
name: libcore-windows
path: .
- name: 🔧 复制 libcore 文件到正确位置并重命名
run: |
Write-Host "📋 复制 libcore 文件..."
# 创建目标目录
New-Item -ItemType Directory -Force -Path libcore\bin
# 查找并复制 HiddifyCli.exe重命名为 BearVPNCli.exe
$exeFiles = Get-ChildItem -Recurse -Filter "HiddifyCli.exe" -ErrorAction SilentlyContinue
if ($exeFiles) {
$sourceExe = $exeFiles[0].FullName
Write-Host "✅ 找到 HiddifyCli.exe: $sourceExe"
Write-Host "📝 复制并重命名为 BearVPNCli.exe"
Copy-Item $sourceExe libcore\bin\BearVPNCli.exe
Write-Host "✅ 重命名完成HiddifyCli.exe → BearVPNCli.exe"
} else {
Write-Host "⚠️ 未找到 HiddifyCli.exe"
}
# 复制 libcore.dll
$dllFiles = Get-ChildItem -Recurse -Filter "libcore.dll" -ErrorAction SilentlyContinue
if ($dllFiles) {
$sourceDll = $dllFiles[0].FullName
Write-Host "✅ 找到 libcore.dll: $sourceDll"
Copy-Item $sourceDll libcore\bin\libcore.dll
} else {
Write-Host "⚠️ 未找到 libcore.dll"
}
Write-Host ""
Write-Host "📄 验证文件:"
if (Test-Path libcore\bin) {
Get-ChildItem libcore\bin\ -ErrorAction SilentlyContinue | Format-Table Name, Length
}
- name: Setup Flutter
uses: subosito/flutter-action@v2
@ -26,6 +123,9 @@ jobs:
- name: Get dependencies
run: flutter pub get
- name: Generate code
run: dart run build_runner build --delete-conflicting-outputs
- name: Build Windows Debug
run: flutter build windows

9
.gitignore vendored
View File

@ -58,7 +58,6 @@ app.*.map.json
/android/app/profile
/android/app/release
/data
/.gradle/
@ -99,3 +98,11 @@ libcore/*.aar
# Android 编译产物
android/app/libs/*.aar
# FVM Version Cache
.fvm/
# Build scripts configuration files
scripts/*.json
!scripts/build_config.template.json
scripts/*.bak
lib/app/common/app_config.dart.bak

View File

@ -1135,18 +1135,9 @@ class AppConfig {
_isInitializing = true;
try {
// Debug 使
// if (kDebugMode) {
// KRLogUtil.kr_i('🐛 Debug 模式,使用固定 API 地址,跳过配置请求', tag: 'AppConfig');
// if (onSuccess != null) {
// await onSuccess();
// }
// return;
// }
if (onSuccess != null) {
await onSuccess();
}
// await _startAutoRetry(onSuccess);
//
KRLogUtil.kr_i('🚀 开始配置初始化', tag: 'AppConfig');
await _startAutoRetry(onSuccess);
} finally {
_isInitializing = false;
}

View File

@ -17,6 +17,7 @@ import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import '../services/api_service/kr_api.user.dart';
import '../services/kr_announcement_service.dart';
import '../services/singbox_imp/kr_sing_box_imp.dart';
import '../services/kr_site_config_service.dart';
import '../utils/kr_event_bus.dart';
import '../../singbox/model/singbox_status.dart';

View File

@ -92,7 +92,7 @@ class KROutboundItem {
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? false,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig["fingerprint"] ?? "chrome"
@ -123,7 +123,7 @@ class KROutboundItem {
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? false,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
@ -138,7 +138,9 @@ class KROutboundItem {
"password": nodeListItem.uuid
};
break;
case "hysteria":
case "hysteria2":
// "hysteria" Hysteria2
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
config = {
@ -156,8 +158,7 @@ class KROutboundItem {
"tls": {
"enabled": true,
"server_name": securityConfig["sni"] ?? "",
"insecure": securityConfig["allow_insecure"] ?? false,
"alpn": ["h3"]
"insecure": securityConfig["allow_insecure"] ?? true
}
};
break;
@ -181,7 +182,7 @@ class KROutboundItem {
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? false,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
@ -260,6 +261,10 @@ class KROutboundItem {
print('📄 完整配置: $config');
break;
case "vless":
// IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "vless",
"tag": nodeListItem.name,
@ -268,8 +273,8 @@ class KROutboundItem {
"uuid": nodeListItem.uuid,
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
if (isDomain) "server_name": nodeListItem.serverAddr,
"insecure": true,
"utls": {
"enabled": true,
"fingerprint": "chrome"
@ -280,6 +285,10 @@ class KROutboundItem {
print('📄 完整配置: $config');
break;
case "vmess":
// IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "vmess",
"tag": nodeListItem.name,
@ -290,8 +299,8 @@ class KROutboundItem {
"security": "auto",
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
if (isDomain) "server_name": nodeListItem.serverAddr,
"insecure": true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
@ -299,6 +308,10 @@ class KROutboundItem {
print('📄 完整配置: $config');
break;
case "trojan":
// IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "trojan",
"tag": nodeListItem.name,
@ -307,32 +320,39 @@ class KROutboundItem {
"password": nodeListItem.uuid,
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
if (isDomain) "server_name": nodeListItem.serverAddr,
"insecure": true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
case "hysteria":
case "hysteria2":
// "hysteria" Hysteria2
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
print(' - serverAddr: ${nodeListItem.serverAddr}');
print(' - port: ${nodeListItem.port}');
print(' - uuid: ${nodeListItem.uuid}');
//
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "hysteria2",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"password": nodeListItem.uuid,
"up_mbps": 100,
"down_mbps": 100,
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
"alpn": ["h3"]
if (isDomain) "server_name": nodeListItem.serverAddr,
}
};
print('✅ Hysteria2 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
print('✅ Hysteria2 节点配置构建成功');
print('📄 完整配置: ${jsonEncode(config)}');
break;
default:
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');

View File

@ -103,9 +103,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
//
final kr_lastMapCenter = LatLng(35.0, 105.0).obs;
//
bool kr_isSwitching = false;
// "闪连"Checkbox添加一个响应式变量 false
final isQuickConnectEnabled = false.obs;
@ -114,7 +111,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
//
static const String _quickConnectKey = 'kr_quick_connect_enabled';
//
final RxString currentSelectedCountry = ''.obs;
final RxBool isCountryReselectionEnabled = true.obs;
@ -206,7 +203,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
KRLogUtil.kr_i('开始执行闪连自动连接', tag: 'QuickConnect');
//
if (kr_isSwitching) {
if (KRSingBoxImp.instance.kr_status == SingboxStarted) {
KRLogUtil.kr_w('连接操作正在进行中,跳过自动连接', tag: 'QuickConnect');
return;
}
@ -503,6 +500,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
void _bindConnectionStatus() {
//
ever(KRSingBoxImp.instance.kr_status, (status) {
print('🔵 Controller 收到状态变化: ${status.runtimeType}');
KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController');
KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController');
@ -594,7 +592,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
// UI
update();
//
_checkCountryReselection(value);
} catch (e) {
@ -656,40 +654,79 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
***/
}
void kr_toggleSwitch(bool value) async {
//
if (kr_isSwitching) {
KRLogUtil.kr_i('正在切换中,忽略本次操作', tag: 'HomeController');
/// 🔧 : hiddify-app toggleConnection
Future<void> kr_toggleSwitch(bool value) async {
final currentStatus = KRSingBoxImp.instance.kr_status.value;
KRLogUtil.kr_i('🔵 toggleSwitch 被调用: value=$value, currentStatus=$currentStatus', tag: 'HomeController');
print('🔵 toggleSwitch: value=$value, currentStatus=$currentStatus');
// 🔧 : hiddify-app "switching status, debounce"
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
print('🔵 忽略操作:正在切换中');
return;
}
try {
kr_isSwitching = true;
if (value) {
//
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
print('🔵 执行 kr_start()');
await KRSingBoxImp.instance.kr_start();
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
print('🔵 kr_start() 完成');
// UI及时更新
Future.delayed(const Duration(milliseconds: 300), () {
kr_forceSyncConnectionStatus();
});
//
Future.delayed(const Duration(seconds: 2), () {
kr_forceSyncConnectionStatus();
});
// 🔧 : 3
await _waitForStatus(SingboxStarted, maxSeconds: 3);
} else {
await KRSingBoxImp.instance.kr_stop();
//
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
print('🔵 执行 kr_stop()');
await KRSingBoxImp.instance.kr_stop().timeout(
const Duration(seconds: 10),
onTimeout: () {
KRLogUtil.kr_e('⚠️ 停止操作超时', tag: 'HomeController');
throw TimeoutException('Stop operation timeout');
},
);
KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController');
print('🔵 kr_stop() 完成');
// 🔧 : 2
await _waitForStatus(SingboxStopped, maxSeconds: 2);
}
} catch (e) {
KRLogUtil.kr_e('切换失败: $e', tag: 'HomeController');
// VPN权限被拒绝
Future.delayed(const Duration(milliseconds: 100), () {
kr_forceSyncConnectionStatus();
});
} finally {
//
kr_isSwitching = false;
KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController');
print('🔵 切换失败: $e');
//
kr_forceSyncConnectionStatus();
}
print('🔵 toggleSwitch 完成,当前 kr_isConnected=${kr_isConnected.value}');
}
/// 🔧
Future<void> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
print('🔵 等待状态变为: $expectedType');
final startTime = DateTime.now();
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
final currentStatus = KRSingBoxImp.instance.kr_status.value;
print('🔵 当前状态: ${currentStatus.runtimeType}');
if (currentStatus.runtimeType == expectedType) {
print('🔵 状态已达到: $expectedType');
// kr_isConnected
kr_forceSyncConnectionStatus();
return;
}
await Future.delayed(const Duration(milliseconds: 100));
}
print('🔵 等待超时,强制同步状态');
kr_forceSyncConnectionStatus();
}
///
@ -900,35 +937,35 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
// 655350
return node.urlTestDelay.value < 65535 && node.urlTestDelay.value > 0;
}
///
void _checkCountryReselection(List<dynamic> activeGroups) {
//
if (!isCountryReselectionEnabled.value) {
return;
}
//
if (!kr_isConnected.value) {
return;
}
//
if (currentSelectedCountry.isEmpty) {
return;
}
try {
//
final currentNodeInfo = kr_getRealConnectedNodeInfo();
final currentDelay = currentNodeInfo['delay'] as int;
//
if (currentDelay > 0 && currentDelay < countryReselectionLatencyThreshold) {
//
return;
}
//
if (currentDelay >= countryReselectionLatencyThreshold || currentDelay <= 0) {
KRLogUtil.kr_w('🔄 当前节点延迟过高(${currentDelay}ms),尝试国家内重选', tag: 'HomeController');
@ -938,7 +975,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
KRLogUtil.kr_e('检查国家内重选时出错: $e', tag: 'HomeController');
}
}
///
void _performCountryReselection(String country) {
try {
@ -946,16 +983,16 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
final countryNodes = kr_subscribeService.allList
.where((node) => node.country == country && node.tag != 'auto')
.toList();
if (countryNodes.isEmpty) {
KRLogUtil.kr_w('⚠️ 国家 $country 内没有可用节点', tag: 'HomeController');
return;
}
//
String? bestNode;
int minDelay = 65535;
for (var node in countryNodes) {
final delay = node.urlTestDelay.value;
if (delay > 0 && delay < 65535 && delay < minDelay) {
@ -963,7 +1000,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
bestNode = node.tag;
}
}
if (bestNode != null && bestNode != kr_cutSeletedTag.value) {
KRLogUtil.kr_i('🎯 国家内重选: $bestNode (${minDelay}ms)', tag: 'HomeController');
kr_selectNode(bestNode);
@ -974,33 +1011,33 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
KRLogUtil.kr_e('执行国家内重选时出错: $e', tag: 'HomeController');
}
}
/// hi_node_list_controller调用
void setCurrentSelectedCountry(String country) {
currentSelectedCountry.value = country;
KRLogUtil.kr_i('🌍 设置当前选择国家: $country', tag: 'HomeController');
}
/// /
void setCountryReselectionEnabled(bool enabled) {
isCountryReselectionEnabled.value = enabled;
KRLogUtil.kr_i('🔄 国家内重选功能已${enabled ? "启用" : "禁用"}', tag: 'HomeController');
}
///
Map<String, dynamic> getCurrentCountryNodeStats() {
if (currentSelectedCountry.isEmpty) {
return {'error': '没有选择国家'};
}
final countryNodes = kr_subscribeService.allList
.where((node) => node.country == currentSelectedCountry.value && node.tag != 'auto')
.toList();
if (countryNodes.isEmpty) {
return {'error': '国家内没有节点'};
}
List<int> validDelays = [];
int totalNodes = countryNodes.length;
int validNodes = 0;
@ -1008,27 +1045,27 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
int maxDelay = 0;
String? fastestNode;
String? slowestNode;
for (var node in countryNodes) {
final delay = node.urlTestDelay.value;
if (delay > 0 && delay < 65535) {
validDelays.add(delay);
validNodes++;
if (delay < minDelay) {
minDelay = delay;
fastestNode = node.tag;
}
if (delay > maxDelay) {
maxDelay = delay;
slowestNode = node.tag;
}
}
}
double avgDelay = validDelays.isEmpty ? 0 : validDelays.reduce((a, b) => a + b) / validDelays.length;
return {
'country': currentSelectedCountry.value,
'totalNodes': totalNodes,
@ -1088,10 +1125,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
//
kr_updateConnectionInfo();
// 🔧
if (KRSingBoxImp.instance.kr_status.value == SingboxStarted()) {
KRSingBoxImp.instance.kr_selectOutbound(tag);
// 🔧
// 🔧 :()
Future.delayed(const Duration(milliseconds: 500), () {
if (kr_currentNodeLatency.value == -1 && kr_isConnected.value) {
KRLogUtil.kr_w('⚠️ 选择节点后延迟值未更新,尝试手动更新', tag: 'HomeController');
@ -1101,7 +1139,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}
});
} else {
KRSingBoxImp().kr_start();
// 🔧 ,,便VPN时应用
KRLogUtil.kr_i('💾 核心未启动,保存节点选择以便稍后应用: $tag', tag: 'HomeController');
KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag).then((_) {
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController');
}).catchError((e) {
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'HomeController');
});
}
//

View File

@ -7,6 +7,7 @@ import 'package:kaer_with_panels/app/widgets/kr_country_flag.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
import '../controllers/kr_home_controller.dart';
import '../models/kr_home_views_status.dart';
@ -209,13 +210,31 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
),
],
),
CupertinoSwitch(
value: controller.kr_isConnected.value,
onChanged: (bool value) {
controller.kr_toggleSwitch(value);
},
activeColor: Colors.blue,
),
// 🔧 : 使
Obx(() {
// 🔧 : observable
final _ = KRSingBoxImp.instance.kr_status.value; //
final isConnected = controller.kr_isConnected.value; // 使 controller
//
final status = KRSingBoxImp.instance.kr_status.value;
final isSwitching = status is SingboxStarting || status is SingboxStopping;
// 🔧
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
return CupertinoSwitch(
value: isConnected,
// 🔧 : onChanged nullSwitch
onChanged: isSwitching
? null
: (bool value) {
print('🔵 Switch onChanged 触发: 请求=$value, 当前状态=$status');
controller.kr_toggleSwitch(value);
},
activeColor: Colors.blue,
);
}),
],
),
],

View File

@ -72,7 +72,7 @@ abstract class Api {
// static const String kr_getInviteData = "/v1/public/invite/code";
///
static const String kr_config = "/v1/app/auth/config";
static const String kr_config = "/v1/common/site/config";
/// 线
static const String kr_getUserOnlineTimeStatistics =

View File

@ -123,7 +123,7 @@ class KRUserApi {
await HttpUtil.getInstance().request<KRConfigData>(
Api.kr_config,
data,
method: HttpMethod.POST,
method: HttpMethod.GET,
isShowLoading: false,
);
if (!baseResponse.isSuccess) {

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_udid/flutter_udid.dart';
import '../utils/kr_secure_storage.dart';
import '../utils/kr_log_util.dart';
import 'package:crypto/crypto.dart';
@ -51,37 +52,28 @@ class KRDeviceInfoService {
}
///
/// 使
Future<String> _getDeviceId() async {
try {
String? identifier;
String identifier;
if (Platform.isAndroid) {
final androidInfo = await _deviceInfo.androidInfo;
// Android使用androidId作为唯一标识
identifier = androidInfo.id;
identifier = await _getAndroidDeviceId();
} else if (Platform.isIOS) {
final iosInfo = await _deviceInfo.iosInfo;
// iOS使用identifierForVendor作为唯一标识
identifier = iosInfo.identifierForVendor;
identifier = await _getIOSDeviceId();
} else if (Platform.isMacOS) {
final macInfo = await _deviceInfo.macOsInfo;
// macOS使用systemGUID
identifier = macInfo.systemGUID;
identifier = await _getMacOSDeviceId();
} else if (Platform.isWindows) {
final windowsInfo = await _deviceInfo.windowsInfo;
// Windows使用计算机名作为唯一标识
identifier = windowsInfo.computerName;
identifier = await _getWindowsDeviceId();
} else if (Platform.isLinux) {
final linuxInfo = await _deviceInfo.linuxInfo;
// Linux使用machineId
identifier = linuxInfo.machineId;
identifier = await _getLinuxDeviceId();
} else {
// Web或其他平台使UUID
// Web或其他平台,使UUID
identifier = await _getOrCreateStoredDeviceId();
}
// 使ID
if (identifier == null || identifier.isEmpty) {
// ,使ID
if (identifier.isEmpty) {
identifier = await _getOrCreateStoredDeviceId();
}
@ -89,11 +81,201 @@ class KRDeviceInfoService {
} catch (e) {
print('❌ 获取设备ID失败: $e');
KRLogUtil.kr_e('❌ 获取设备ID失败 - $e', tag: 'KRDeviceInfoService');
// ID
// ,ID
return await _getOrCreateStoredDeviceId();
}
}
/// Android设备ID -
/// : AndroidID + + +
Future<String> _getAndroidDeviceId() async {
try {
final androidInfo = await _deviceInfo.androidInfo;
// 使 flutter_udid (Android标识获取方式)
String udid = await FlutterUdid.consistentUdid;
//
final factors = [
udid,
androidInfo.id, // Android ID
androidInfo.board, //
androidInfo.bootloader, // Bootloader
androidInfo.brand, //
androidInfo.device, //
androidInfo.fingerprint, //
androidInfo.hardware, //
androidInfo.manufacturer, //
androidInfo.model, //
androidInfo.product, //
];
//
final combined = factors
.where((f) => f != null && f.isNotEmpty)
.join('|');
// SHA256哈希
final bytes = utf8.encode(combined);
final hash = sha256.convert(bytes);
print('📱 Android多因子ID生成 - 因子数: ${factors.where((f) => f != null && f.isNotEmpty).length}');
KRLogUtil.kr_i('📱 Android多因子ID - $hash', tag: 'KRDeviceInfoService');
return hash.toString();
} catch (e) {
print('❌ Android设备ID获取失败: $e');
KRLogUtil.kr_e('❌ Android设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
return '';
}
}
/// iOS设备ID - 使identifierForVendor,
/// iOS限制较严,IMEI等敏感信息
Future<String> _getIOSDeviceId() async {
try {
final iosInfo = await _deviceInfo.iosInfo;
// 使 flutter_udid
String udid = await FlutterUdid.consistentUdid;
//
final factors = [
udid,
iosInfo.identifierForVendor ?? '', // Vendor标识
iosInfo.model, //
iosInfo.systemName, //
iosInfo.systemVersion, //
iosInfo.name, // ("iPhone 14 Pro")
iosInfo.utsname.machine, //
];
final combined = factors
.where((f) => f.isNotEmpty)
.join('|');
final bytes = utf8.encode(combined);
final hash = sha256.convert(bytes);
print('📱 iOS多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
KRLogUtil.kr_i('📱 iOS多因子ID - $hash', tag: 'KRDeviceInfoService');
return hash.toString();
} catch (e) {
print('❌ iOS设备ID获取失败: $e');
KRLogUtil.kr_e('❌ iOS设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
return '';
}
}
/// macOS设备ID - 使UUID
Future<String> _getMacOSDeviceId() async {
try {
final macInfo = await _deviceInfo.macOsInfo;
// 使 flutter_udid
String udid = await FlutterUdid.consistentUdid;
//
final factors = [
udid,
macInfo.systemGUID ?? '', // GUID ()
macInfo.model, //
macInfo.hostName, //
macInfo.arch, //
macInfo.kernelVersion, //
];
final combined = factors
.where((f) => f.isNotEmpty)
.join('|');
final bytes = utf8.encode(combined);
final hash = sha256.convert(bytes);
print('📱 macOS多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
KRLogUtil.kr_i('📱 macOS多因子ID - $hash', tag: 'KRDeviceInfoService');
return hash.toString();
} catch (e) {
print('❌ macOS设备ID获取失败: $e');
KRLogUtil.kr_e('❌ macOS设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
return '';
}
}
/// Windows设备ID - 使GUID
Future<String> _getWindowsDeviceId() async {
try {
final windowsInfo = await _deviceInfo.windowsInfo;
// 使 flutter_udid
String udid = await FlutterUdid.consistentUdid;
//
final factors = [
udid,
windowsInfo.deviceId, // ID
windowsInfo.computerName, //
windowsInfo.productName, //
windowsInfo.numberOfCores.toString(), // CPU核心数
windowsInfo.systemMemoryInMegabytes.toString(), //
];
final combined = factors
.where((f) => f.isNotEmpty)
.join('|');
final bytes = utf8.encode(combined);
final hash = sha256.convert(bytes);
print('📱 Windows多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
KRLogUtil.kr_i('📱 Windows多因子ID - $hash', tag: 'KRDeviceInfoService');
return hash.toString();
} catch (e) {
print('❌ Windows设备ID获取失败: $e');
KRLogUtil.kr_e('❌ Windows设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
return '';
}
}
/// Linux设备ID - 使machine-id
Future<String> _getLinuxDeviceId() async {
try {
final linuxInfo = await _deviceInfo.linuxInfo;
// 使 flutter_udid
String udid = await FlutterUdid.consistentUdid;
//
final factors = [
udid,
linuxInfo.machineId ?? '', // Machine ID ()
linuxInfo.id, // ID
linuxInfo.name, //
linuxInfo.version ?? '', //
linuxInfo.variant ?? '', //
];
final combined = factors
.where((f) => f.isNotEmpty)
.join('|');
final bytes = utf8.encode(combined);
final hash = sha256.convert(bytes);
print('📱 Linux多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
KRLogUtil.kr_i('📱 Linux多因子ID - $hash', tag: 'KRDeviceInfoService');
return hash.toString();
} catch (e) {
print('❌ Linux设备ID获取失败: $e');
KRLogUtil.kr_e('❌ Linux设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
return '';
}
}
/// ID
Future<String> _getOrCreateStoredDeviceId() async {
try {

File diff suppressed because it is too large Load Diff

View File

@ -23,17 +23,73 @@ class KRSecureStorage {
// Hive
Future<void> kr_initHive() async {
try {
if (Platform.isMacOS) {
//
if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
final baseDir = await getApplicationSupportDirectory();
KRLogUtil.kr_i('初始化 Hive路径: ${baseDir.path}', tag: 'SecureStorage');
//
if (!baseDir.existsSync()) {
await baseDir.create(recursive: true);
KRLogUtil.kr_i('已创建 Hive 目录: ${baseDir.path}', tag: 'SecureStorage');
}
await Hive.initFlutter(baseDir.path);
} else {
// Android iOS 使
await Hive.initFlutter();
}
// 使
final key = HiveAesCipher(_generateKey());
await Hive.openBox(_boxName, encryptionCipher: key);
} catch (e) {
KRLogUtil.kr_i('Hive 初始化成功', tag: 'SecureStorage');
} catch (e, stackTrace) {
KRLogUtil.kr_e('初始化 Hive 失败: $e', tag: 'SecureStorage');
KRLogUtil.kr_e('错误类型: ${e.runtimeType}', tag: 'SecureStorage');
KRLogUtil.kr_e('错误堆栈: $stackTrace', tag: 'SecureStorage');
// Windows Linux
if (Platform.isWindows || Platform.isLinux) {
try {
KRLogUtil.kr_i('尝试清理并重新初始化 Hive', tag: 'SecureStorage');
final baseDir = await getApplicationSupportDirectory();
final hiveDir = Directory(baseDir.path);
// Hive
if (hiveDir.existsSync()) {
final files = hiveDir.listSync();
for (var entity in files) {
if (entity is File) {
final fileName = entity.path.split(Platform.pathSeparator).last;
// Hive .hive .lock
if (fileName.startsWith(_boxName) ||
fileName.endsWith('.hive') ||
fileName.endsWith('.lock')) {
try {
await entity.delete();
KRLogUtil.kr_i('已删除文件: $fileName', tag: 'SecureStorage');
} catch (deleteError) {
KRLogUtil.kr_e('删除文件失败: $deleteError', tag: 'SecureStorage');
}
}
}
}
}
//
await Hive.initFlutter(baseDir.path);
final key = HiveAesCipher(_generateKey());
await Hive.openBox(_boxName, encryptionCipher: key);
KRLogUtil.kr_i('Hive 重新初始化成功', tag: 'SecureStorage');
} catch (retryError, retryStack) {
KRLogUtil.kr_e('重新初始化 Hive 仍然失败: $retryError', tag: 'SecureStorage');
KRLogUtil.kr_e('重试堆栈: $retryStack', tag: 'SecureStorage');
rethrow;
}
} else {
rethrow;
}
}
}

View File

@ -3,13 +3,11 @@ import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
// import 'package:combine/combine.dart'; // 使 Isolate.run
import 'package:combine/combine.dart';
import 'package:ffi/ffi.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kaer_with_panels/core/model/directories.dart';
import 'package:kaer_with_panels/gen/singbox_generated_bindings.dart';
import 'package:kaer_with_panels/singbox/model/singbox_config_option.dart';
import 'package:kaer_with_panels/singbox/model/singbox_outbound.dart';
import 'package:kaer_with_panels/singbox/model/singbox_stats.dart';
@ -50,14 +48,10 @@ class FFISingboxService with InfraLogger implements SingboxService {
}
@override
Future<void> init() async {
loggy.debug("initializing");
_statusReceiver = ReceivePort('service status receiver');
final source = _statusReceiver
.asBroadcastStream()
.map((event) => jsonDecode(event as String))
.map(SingboxStatus.fromEvent);
final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
_status = ValueConnectableStream.seeded(
source,
const SingboxStopped(),
@ -71,7 +65,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
) {
final port = _statusReceiver.sendPort.nativePort;
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
_box.setupOnce(NativeApi.initializeApiDLData);
final err = _box
@ -100,7 +94,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
bool debug,
) {
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final err = _box
.parse(
@ -122,13 +116,10 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final json = jsonEncode(options.toJson());
final err = _box
.changeHiddifyOptions(json.toNativeUtf8().cast())
.cast<Utf8>()
.toDartString();
final err = _box.changeHiddifyOptions(json.toNativeUtf8().cast()).cast<Utf8>().toDartString();
if (err.isNotEmpty) {
return left(err);
}
@ -143,7 +134,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
String path,
) {
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final response = _box
.generateConfig(
@ -168,7 +159,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
) {
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final err = _box
.start(
@ -189,7 +180,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> stop() {
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final err = _box.stop().cast<Utf8>().toDartString();
if (err.isNotEmpty) {
@ -209,7 +200,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
) {
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final err = _box
.restart(
@ -267,10 +258,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
},
);
final err = _box
.startCommandClient(1, receiver.sendPort.nativePort)
.cast<Utf8>()
.toDartString();
final err = _box.startCommandClient(1, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
if (err.isNotEmpty) {
loggy.error("error starting status command: $err");
throw err;
@ -312,10 +300,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
);
try {
final err = _box
.startCommandClient(5, receiver.sendPort.nativePort)
.cast<Utf8>()
.toDartString();
final err = _box.startCommandClient(5, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
if (err.isNotEmpty) {
logger.error("error starting group command: $err");
throw err;
@ -359,10 +344,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
);
try {
final err = _box
.startCommandClient(13, receiver.sendPort.nativePort)
.cast<Utf8>()
.toDartString();
final err = _box.startCommandClient(13, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
if (err.isNotEmpty) {
logger.error("error starting: $err");
throw err;
@ -378,7 +360,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final err = _box
.selectOutbound(
@ -399,12 +381,9 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> urlTest(String groupTag) {
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final err = _box
.urlTest(groupTag.toNativeUtf8().cast())
.cast<Utf8>()
.toDartString();
final err = _box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
if (err.isNotEmpty) {
return left(err);
}
@ -420,9 +399,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
Stream<List<String>> watchLogs(String path) async* {
yield await _readLogFile(File(path));
yield* Watcher(path, pollingDelay: const Duration(seconds: 1))
.events
.asyncMap((event) async {
yield* Watcher(path, pollingDelay: const Duration(seconds: 1)).events.asyncMap((event) async {
if (event.type == ChangeType.MODIFY) {
await _readLogFile(File(path));
}
@ -433,7 +410,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> clearLogs() {
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
_logBuffer.clear();
return right(unit);
@ -444,8 +421,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
Future<List<String>> _readLogFile(File file) async {
if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
final content =
await file.openRead(_logFilePosition).transform(utf8.decoder).join();
final content = await file.openRead(_logFilePosition).transform(utf8.decoder).join();
_logFilePosition = file.lengthSync();
final lines = const LineSplitter().convert(content);
if (lines.length > 300) {
@ -468,7 +444,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
}) {
loggy.debug("generating warp config");
return TaskEither(
() => Isolate.run(
() => CombineWorker().execute(
() {
final response = _box
.generateWarpConfig(

@ -1 +1 @@
Subproject commit f993a57755c37e08b02042037cbbf508c66c51f9
Subproject commit 4e7fe336554e87880be08fddbfc778cfc5f6b9f1

View File

@ -137,6 +137,6 @@ MyApplication *my_application_new()
{
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_FLAGS_NONE,
"flags", G_APPLICATION_DEFAULT_FLAGS,
nullptr));
}

View File

@ -82,7 +82,7 @@ SPEC CHECKSUMS:
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564

800
pubspec.lock Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,7 @@ dependencies:
fpdart: ^1.1.0
dartx: ^1.2.0
rxdart: ^0.27.7
# combine: ^0.5.8 # 暂时移除,使用 Isolate.run 替代
combine: 0.5.7 # 精确版本,兼容 Flutter 3.24.3(与 hiddify-app 相同)
encrypt: ^5.0.0
path: ^1.8.3
path_provider: ^2.1.1
@ -140,6 +140,7 @@ dev_dependencies:
flutter:
assets:
- assets/images/
- assets/geosite/
- assets/translations/strings_en.i18n.json
- assets/translations/strings_zh.i18n.json
- assets/translations/strings_es.i18n.json

View File

@ -2,6 +2,12 @@
cmake_minimum_required(VERSION 3.14)
project(BearVPN LANGUAGES CXX)
# CMake
# CMP0175: add_custom_command() flutter_inappwebview_windows
if(POLICY CMP0175)
cmake_policy(SET CMP0175 OLD)
endif()
#
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
@ -11,6 +17,8 @@ set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_PROFILE} /MT")
#
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819 /wd4244 /wd4458")
# /FS PDB 访 C1041
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FS")
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
@ -50,7 +58,7 @@ add_definitions(-DUNICODE -D_UNICODE)
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100" /wd"4819" /wd"4244" /wd"4458")
target_compile_options(${TARGET} PRIVATE /EHsc /utf-8)
target_compile_options(${TARGET} PRIVATE /EHsc /utf-8 /FS)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
# 使
@ -70,6 +78,14 @@ add_subdirectory("runner")
# them to the application.
include(flutter/generated_plugins.cmake)
# /FS PDB
# flutter_inappwebview_windows
foreach(plugin ${FLUTTER_PLUGIN_LIST})
if(TARGET ${plugin}_plugin)
target_compile_options(${plugin}_plugin PRIVATE /FS)
endif()
endforeach(plugin)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
@ -84,6 +100,8 @@ endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
# CLI BearVPNCli.exe
set(INSTALL_BUNDLE_CLI_DIR "${CMAKE_INSTALL_PREFIX}/cli")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
@ -94,14 +112,19 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# install(FILES "../libcore/bin/libcore.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
# COMPONENT Runtime)
# libcore.dll BearVPN.exe
install(FILES "../libcore/bin/libcore.dll"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime
OPTIONAL)
install(FILES "../libcore/bin/libcore.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime RENAME libcore.dll)
install(FILES "../libcore/bin/BearVPNCli.exe" DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime RENAME BearVPNCli.exe)
# BearVPNCli.exe libcore/bin
# libcore HiddifyCli.exe BearVPNCli.exe
# BearVPNCli.exe
install(FILES "../libcore/bin/BearVPNCli.exe"
DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime
OPTIONAL)
if(PLUGIN_BUNDLED_LIBRARIES)