Compare commits

..
91 Commits
Author SHA1 Message Date
lxy829 42464db1bc 🎨 ResxXamlBinding 2023-12-12 10:29:41 +08:00
RMBGAME 9e4c4836c0 🚧 Misc 2023-11-16 20:47:38 +08:00
RMBGAME d34a5d3347 🚧 改进主页数据加载逻辑 2023-11-16 20:47:38 +08:00
RMBGAME fd119ae0e1 ✨ 账号切换数据改为启动时加载避免等待 2023-11-16 20:47:37 +08:00
Mossimo 3d59c24dec 🐛 Misc 移除调试代码。添加注释。 2023-11-16 17:16:34 +08:00
Mossimo ea315a9b44 🐛 修复版本迁移导致的脚本失效。 2023-11-16 17:06:45 +08:00
RMBGAME d5592b4bc2 🐛 fix 2023-11-15 19:48:57 +08:00
RMBGAME 2df32c67d0 🐛 fix 2023-11-15 19:28:16 +08:00
RMBGAME fdaf795202 🐛 fix 2023-11-15 14:49:09 +08:00
john.jiang a7572ecd50 🎨 asf 路径修改启动问题修复 2023-11-15 14:43:05 +08:00
RMBGAME d433d60d29 ⬆️ Submodules 2023-11-15 14:42:54 +08:00
RMBGAME 3fe8f931a2 ✨ 令牌搜索功能 2023-11-15 14:42:21 +08:00
john.jiang a69d97fcdb 🎨 ASF fixs 2023-11-14 19:09:47 +08:00
RMBGAME aa63e3ab9f 🐛 fix UserIdleInfo 2023-11-13 19:15:20 +08:00
RMBGAME a56fc36380 fix Steam令牌某些情况下闪退问题 2023-11-13 16:08:43 +08:00
john.jiang 4c5428be5f 🎨 fixs 2023-11-11 19:54:59 +08:00
john.jiang e6953e50b8 🎨 idle fixs 2023-11-11 19:53:53 +08:00
ff m a961f68242 🐛 TrimStart(Constants.UrlSchemes.Login) -> Substring(Constants.UrlSchemes.Login.Length, loginUrl.Length - Constants.UrlSchemes.Login.Length) 2023-11-11 16:43:46 +08:00
ff m 72f301d526 🐛 MacOS Substring内存溢出 改用 TrimStart 已测试通过 2023-11-11 16:07:31 +08:00
john.jiang 2b18e03098 🎨 fixs 2023-11-10 18:36:35 +08:00
john.jiang 9fffe8458a 🎨 ASF Support 2023-11-10 18:32:20 +08:00
RMBGAME ea7282f290 🚧 Misc 2023-11-10 18:13:04 +08:00
RMBGAME 3714710dd8 fix 加速列表加载缓存数据时丢失选中状态问题 2023-11-04 21:27:25 +08:00
RMBGAME 00c228abd8 🐛 fix CI 2023-11-02 17:40:25 +08:00
Mossimo 54fc1e3593 ✨ Mac 支持 UrlSchemes 协议登录 2023-10-28 19:15:46 +08:00
RMBGAME d2d04a37cf ⬆️ Bump 2023-10-24 18:59:41 +08:00
RMBGAME 0600a5ca9f 🚧 Misc 2023-10-24 18:59:41 +08:00
Mossimo 01dc528e21 🐛 修改 install.sh -> online_install.sh 避免用户理解错误 添加 init_desktop.sh 添加桌面 desktop 文件 2023-10-19 17:48:27 +08:00
ff m 9014198aeb 🐛 修复 VDF Json 读取问题 2023-10-19 12:55:44 +08:00
RMBGAME bfd6b82a95 🚧 🐛 修复账号切换某些情况下切换后仍需要登录问题 2023-10-18 19:48:06 +08:00
RMBGAME a6a45e5324 🚧 Misc 2023-10-13 19:46:08 +08:00
RMBGAME 0ed0416f69 🐛 Fix 421 Misdirected Request 2023-10-06 21:12:10 +08:00
RMBGAME ce6fd9a4d6 ✨ 账号切换 Steam 离线模式启动 2023-10-06 18:33:40 +08:00
Aigio Liu 7f06198bb6 ⬆️ DirectoryPackages 2023-09-25 14:15:25 +08:00
Aigio Liu 8202df3a65 🚧 Misc 2023-09-25 13:40:19 +08:00
Aigio Liu 5033261a64 ⬆️ DirectoryPackages 2023-09-25 13:27:41 +08:00
Aigio Liu 191c957a23 🚧 3.0.0-rc.3 2023-09-25 13:21:55 +08:00
Aigio Liu 9eb5def6f0 🚧 Misc 2023-09-23 16:40:51 +08:00
Mossimo 2467ce9bb4 🐛 Fix macOS 加速错误 等待输入密码 过长导致 IPC 异常闪退 2023-09-23 15:41:33 +08:00
Aigio Liu f968f3f66a 🐛 SetBootAutoStart PrivilegedProcess 2023-09-23 13:50:08 +08:00
Aigio Liu 511a2fa366 🚧 Migrations.MoveAccelerator 2023-09-23 13:24:30 +08:00
RMBGAME 5ed54bd72e 🚧 托盘 2023-09-22 17:19:32 +08:00
Aigio Liu 91ecfa342a 🚧 ReverseProxy 2023-09-22 15:38:42 +08:00
RMBGAME be1481d063 ✨ 托盘改进 2023-09-22 15:18:59 +08:00
Aigio Liu fce80b18ee 🐛 GameAccount 2023-09-22 10:30:28 +08:00
Aigio Liu 211daa428a 🎨 Authenticator 2023-09-21 19:19:06 +08:00
Aigio Liu 781fd9489f 🐛 Authenticator 2023-09-21 18:52:45 +08:00
Aigio Liu 680586b6e9 🐛 InitializeScriptAsync 2023-09-21 18:14:59 +08:00
Aigio Liu 35ee526efc 🐛 -clt steam -account 2023-09-21 15:08:35 +08:00
Aigio Liu 2f0090d571 ⬆️ DirectoryPackages 2023-09-21 14:52:26 +08:00
Aigio Liu 2f6ad43382 🐛 Misc 2023-09-21 14:31:57 +08:00
Aigio Liu 20056583a3 🚧 ASF 2023-09-20 16:50:11 +08:00
Aigio Liu 77dc0abd2a 🐛 Accelerator 2023-09-20 14:25:31 +08:00
Aigio Liu ddf6486af6 🚧 ASF 2023-09-20 14:25:31 +08:00
Aigio Liu 0229003e19 🐛 Cert 2023-09-19 16:37:43 +08:00
Aigio Liu 8486edf6b8 🎨 Cert 2023-09-19 16:01:03 +08:00
RMBGAME fe941e1271 ✨ 托盘改进 2023-09-19 15:44:40 +08:00
RMBGAME 81129afc8f 🚧 托盘菜单 2023-09-18 18:51:08 +08:00
Aigio Liu 566cc4c017 🚧 Misc 2023-09-18 17:56:06 +08:00
Aigio Liu 0925ec9104 🐛 DoH 2023-09-16 18:03:02 +08:00
Aigio Liu cac4a9370a 🧪 DoH 2023-09-16 17:42:27 +08:00
Aigio Liu e32fade0ea 🚧 DNS over HTTPS 2023-09-16 15:52:42 +08:00
Aigio Liu f6554a3b21 🎨 IDnsAnalysisService 2023-09-16 12:20:45 +08:00
Aigio Liu d00572d97d 💄 DNS over HTTPS 2023-09-16 11:44:05 +08:00
Aigio Liu 56e3563634 ✨ DNS over HTTPS 2023-09-15 19:42:06 +08:00
Aigio Liu 9355c317ac 🔥 StartAsAdministratorByBypassUAC 2023-09-15 14:22:03 +08:00
Aigio Liu 35b5edd09a 🚧 Misc 2023-09-14 16:31:05 +08:00
Aigio Liu ab22ea1113 🐛 GameAccount 2023-09-14 16:07:45 +08:00
Aigio Liu 62c015ad0b 🚧 Misc 2023-09-14 15:38:57 +08:00
Aigio Liu 195f5269be ⬆️ .NET 7.0.11 & SDK 7.0.401 & VS 17.7.4 2023-09-14 14:32:43 +08:00
Aigio Liu b1fa5b4a4d 🐛 AppUpdate 2023-09-14 12:06:58 +08:00
Mossimo 7778854007 🐛修复迁移后脚本缓存路径问题 2023-09-13 18:00:25 +08:00
Aigio Liu 68bbd8b3e3 🐛 macOS AppCenter 2023-09-13 17:33:15 +08:00
Aigio Liu 137a2dcde9 🚧 Misc 2023-09-12 19:08:44 +08:00
Aigio Liu ef239c5dc2 🐛 SetBootAutoStart 2023-09-12 19:03:23 +08:00
ff m 1fd4d4e35a 🐛 修复 macOS 代码签名 2023-09-12 16:41:12 +08:00
Aigio Liu a71ff13d49 🐛 Misc 2023-09-12 14:46:24 +08:00
Aigio Liu 56f9be173a 🐛 dotnetfx 2023-09-12 14:36:44 +08:00
Aigio Liu 6e3e84c9c3 🐛 2023-09-12 14:27:04 +08:00
RMBGAME a55e28600f 🐛 SDA某些情况下导入错误修复 2023-09-12 13:56:34 +08:00
RMBGAME 31dd805a6d 修改启动时加速UI 2023-09-11 21:49:50 +08:00
Aigio Liu aeac99fc1c 🎨 IPCMainProcessServiceImpl / IpcLogger 2023-09-09 10:36:07 +08:00
RMBGAME 5b9bc9e00b 🚑 Misc 2023-09-09 03:17:27 +08:00
RMBGAME 5c3ab25271 🐛 修复因 HTTP/2 导致 discord websocket 验证失败问题 2023-09-09 03:14:37 +08:00
RMBGAME 1e8d373df5 🐛 修复最小化启动无效 2023-09-08 17:55:10 +08:00
Aigio Liu f226aeb8a9 🐛 fix #2830 2023-09-08 17:41:20 +08:00
Aigio Liu 3f0422cfbb ✨ pub hash command 2023-09-08 17:05:05 +08:00
john.jiang af89dddc4c 🎨 doh 2023-09-08 16:19:25 +08:00
Aigio Liu 55f2c519b2 🚧 3.0.207.0 2023-09-08 10:32:22 +08:00
Aigio Liu 527f5fb8a4 🐛 AvaloniaApplicationUpdateServiceImpl 2023-09-08 10:27:04 +08:00
Aigio Liu 8139a7414d 🚧 3.0.0-rc.2 2023-09-08 10:10:16 +08:00
290 changed files with 12761 additions and 3124 deletions
+23 -52
View File
@@ -89,22 +89,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TFM", "TFM", "{3B0CFC2A-F61
src\TFM_NETX_WITH_WINDOWS.props = src\TFM_NETX_WITH_WINDOWS.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm.Library", "ref\ArchiSteamFarm\ArchiSteamFarm.Library\ArchiSteamFarm.Library.csproj", "{6981D5CA-837C-47F8-9568-E3BD451A6C68}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibAPNG", "ref\Avalonia.Image2\LibAPNG\LibAPNG.csproj", "{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gif", "ref\Avalonia.Image2\Avalonia.Gif\Avalonia.Gif.csproj", "{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArchiSteamFarm", "ArchiSteamFarm", "{A3A703CC-F22E-40CE-9064-6DF3E55DD556}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Avalonia", "Avalonia", "{E5732454-7455-4078-88A2-E727DCC64D26}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReverseProxy", "ReverseProxy", "{4FA9B783-ADAE-46F6-9AAD-ED32F1839F13}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Steam", "Steam", "{99D97BD4-3F51-48BC-8BD2-647F6D268BB0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "ref\Titanium-Web-Proxy\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{826716DC-65C2-456A-BB6D-5FDEC423E4E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameloop.Vdf", "ref\Gameloop.Vdf\Gameloop.Vdf\Gameloop.Vdf.csproj", "{FE91B979-FE45-43F3-B9C4-430F2360FDDF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steam4NET", "ref\Steam4NET\Steam4NET\Steam4NET.csproj", "{C8792BC7-33C0-41D4-97F6-684AD4B0B6A5}"
@@ -224,6 +216,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steam++", "src\BD.WTTS.Clie
EndProject
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "BD.WTTS.Client.AppHost.Bridge.Package", "src\BD.WTTS.Client.AppHost.Bridge.Package\BD.WTTS.Client.AppHost.Bridge.Package.wapproj", "{27795C79-2994-4514-8656-BA34FCEE8901}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm", "ref\ArchiSteamFarm\ArchiSteamFarm\ArchiSteamFarm.csproj", "{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -238,26 +232,6 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM64.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x64.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x64.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x86.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x86.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|Any CPU.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM64.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM64.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x64.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x64.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x86.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x86.Build.0 = Release|Any CPU
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -298,26 +272,6 @@ Global
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x64.Build.0 = Release|Any CPU
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x86.ActiveCfg = Release|Any CPU
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x86.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM64.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x64.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x64.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x86.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x86.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|Any CPU.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM64.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM64.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x64.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x64.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x86.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x86.Build.0 = Release|Any CPU
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -1308,6 +1262,26 @@ Global
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.ActiveCfg = Release|x86
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Build.0 = Release|x86
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Deploy.0 = Release|x86
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1317,14 +1291,10 @@ Global
{202026AE-894E-4E18-9846-B0739CDBE3E9} = {46259E8D-32A4-4065-953D-D6F835B57A28}
{F776BA87-0B9B-4C3C-B04F-688DCC021BF1} = {202026AE-894E-4E18-9846-B0739CDBE3E9}
{3B0CFC2A-F61B-4000-AC12-6E0AFB5C475C} = {46259E8D-32A4-4065-953D-D6F835B57A28}
{6981D5CA-837C-47F8-9568-E3BD451A6C68} = {A3A703CC-F22E-40CE-9064-6DF3E55DD556}
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3} = {E5732454-7455-4078-88A2-E727DCC64D26}
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29} = {E5732454-7455-4078-88A2-E727DCC64D26}
{A3A703CC-F22E-40CE-9064-6DF3E55DD556} = {90E88965-DB90-4B49-94F5-6D2965491988}
{E5732454-7455-4078-88A2-E727DCC64D26} = {90E88965-DB90-4B49-94F5-6D2965491988}
{4FA9B783-ADAE-46F6-9AAD-ED32F1839F13} = {90E88965-DB90-4B49-94F5-6D2965491988}
{99D97BD4-3F51-48BC-8BD2-647F6D268BB0} = {90E88965-DB90-4B49-94F5-6D2965491988}
{826716DC-65C2-456A-BB6D-5FDEC423E4E6} = {4FA9B783-ADAE-46F6-9AAD-ED32F1839F13}
{FE91B979-FE45-43F3-B9C4-430F2360FDDF} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
{C8792BC7-33C0-41D4-97F6-684AD4B0B6A5} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
{123F532B-4178-4D03-A4EE-2A8F3E01433F} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
@@ -1378,6 +1348,7 @@ Global
{35676E23-8E54-4785-A75B-98EB0650596B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
{26D8AA24-2ACE-4167-BC2B-117B9BB76AA4} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
{27795C79-2994-4514-8656-BA34FCEE8901} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {567F588F-E95C-4286-8B30-DB3B2200221C}
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
#添加桌面文件
run_path=$(dirname "$0")
base_path="$(dirname "$run_path")"
exec_name="Steam++"
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
echo "#!/usr/bin/env xdg-open
[Desktop Entry]
Name=Watt Toolkit
Exec=$base_path/$exec_name.sh
Icon=$base_path/Icons/Watt-Toolkit.png
Terminal=false
Type=Application
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
exit 0
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
Name=°²×° Watt Toolkit
Exec=curl https://steampp.net/pkg/latest/steamdeck/install.sh | bash -s --
Icon=steamdeck-gaming-return
Terminal=true
Type=Application
StartupNotify=false
+23 -8
View File
@@ -10,11 +10,12 @@ Base_Path="/Users/moxiansheng/Documents/GitHub/SteamTools"
App_Path="$Base_Path/src/BD.WTTS.Client.Avalonia.App"
Publish_Path="bin/Release/Publish/assemblies"
Plugins_Release_Path="bin/Release"
CodesignKey_Name="mossimo CodesignKey"
CodesignKey_Name="mossimo code"
App_Modules_Path="$App_Path/$Publish_Path/modules"
echo "编译主程序"
cd "$App_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:CreatePackage=false -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
rm -r "$App_Modules_Path"
mkdir -p "$App_Modules_Path"
echo "编译插件"
@@ -36,34 +37,34 @@ Plugins_Accelerator_ReverseProxy_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Acc
cd "$Plugins_Accelerator_ReverseProxy_Path"
# 根据 Build_Mode 执行不同逻辑
if [ "$Build_Mode" = "any" ]; then
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
lipo -create "$Publish_Path/arm64/Steam++.Accelerator" "$Publish_Path/x64/Steam++.Accelerator" -output "$Publish_Path/Steam++.Accelerator"
Copy_Plugins "$Publish_Path" "Accelerator" "Steam++.Accelerator"
elif [ "$Build_Mode" = "arm64" ]; then
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Publish_Path/arm64" "Accelerator" "Steam++.Accelerator"
elif [ "$Build_Mode" = "x64" ]; then
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Publish_Path/x64" "Accelerator" "Steam++.Accelerator"
fi
echo "编译令牌插件"
Plugins_Authenticator_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Authenticator"
cd "$Plugins_Authenticator_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "Authenticator" "BD.WTTS.Client.Plugins.Authenticator.dll"
echo "编译账户插件"
Plugins_GameAccount_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameAccount"
cd "$Plugins_GameAccount_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameAccount" "BD.WTTS.Client.Plugins.GameAccount.dll"
echo "编译游戏库插件"
Plugins_GameList_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameList"
cd "$Plugins_GameList_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameList" "BD.WTTS.Client.Plugins.GameList.dll"
echo "复制插件到程序目录"
@@ -79,3 +80,17 @@ elif [ "$Build_Mode" = "x64" ]; then
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/osx-x64/Steam++.app/Contents/MonoBundle"
cp -r "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents"
fi
echo "代码签名重新手动签名"
App_Publish_Path="$App_Path/bin/Release/net7.0-macos/Steam++.app"
# #代码签名重新手动签名
codesign --remove-signature "$App_Publish_Path"
codesign --remove-signature "$App_Publish_Path/Contents/MacOS/Steam++"
dylibs=$(find "$App_Publish_Path/Contents/MonoBundle" -type f -name "*.dylib")
for dylib in $dylibs; do
codesign --remove-signature "$dylib"
codesign -f -s "$CodesignKey_Name" "$dylib"
done
codesign -f -s "$CodesignKey_Name" "$App_Publish_Path/Contents/MacOS/Steam++"
codesign -f -s "$CodesignKey_Name" "$App_Publish_Path"
sudo xattr -rd com.apple.quarantine "$App_Publish_Path"
+19 -1
View File
@@ -498,7 +498,7 @@
{
"TypeName": "bool",
"PropertyName": "GameInstalledFilter",
"DefaultValue": "true",
"DefaultValue": "false",
"DefaultValueIsConst": true,
"Summary": "已安装游戏筛选",
"IsRegionOrEndregion": null,
@@ -943,6 +943,24 @@
},
{
"Sharp": "endif"
},
{
"TypeName": "bool",
"PropertyName": "UseDoh",
"DefaultValue": "true",
"DefaultValueIsConst": true,
"Summary": "启用 DNS over HTTPS",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "string",
"PropertyName": "CustomDohAddres",
"DefaultValue": "null",
"DefaultValueIsConst": true,
"Summary": "自定义 DNS over HTTPS 地址",
"IsRegionOrEndregion": null,
"IsValueType": null
}
],
"Usings": ""
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

+5 -3
View File
@@ -5,11 +5,13 @@ namespace BD.WTTS;
/// </summary>
public static partial class AssemblyInfo
{
const string Version2 = "3.0";
/// <summary>
/// 语义化应用程序版本
/// https://semver.org/lang/zh-CN/
/// </summary>
public const string Version = "3.0.0";
public const string Version = $"{Version2}.0";
/// <summary>
/// 预览版本号,范围 1~9,只增不减
@@ -19,9 +21,9 @@ public static partial class AssemblyInfo
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "1";
const string ver_for_rc = "3";
public const string FileVersion = $"{Version}.{ver_for_rc}0{ver_for_preview}";
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
//public const string InformationalVersion = Version;
//public const string InformationalVersion = $"{Version}-preview.{ver_for_preview}";
@@ -11,7 +11,7 @@
<Identity
Name="4651ED44255E.47979655102CE"
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
Version="3.0.0.0" />
Version="3.0.207.0" />
<Properties>
<DisplayName>Watt Toolkit</DisplayName>
@@ -62,9 +62,9 @@
<Extensions>
<desktop2:Extension Category="windows.firewallRules">
<desktop2:FirewallRules Executable="Steam++\Steam++.exe">
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all" />
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all" />
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
</desktop2:FirewallRules>
</desktop2:Extension>
</Extensions>
+1 -1
View File
@@ -9,6 +9,6 @@
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
<appSettings>
<add key="d" value="7.0.10"/>
<add key="d" value="7.0.11"/>
</appSettings>
</configuration>
+1 -1
View File
@@ -10,6 +10,6 @@
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
<appSettings>
<add key="d" value="7.0.10"/>
<add key="d" value="7.0.11"/>
</appSettings>
</configuration>
+2 -2
View File
@@ -18,7 +18,7 @@ static unsafe partial class Program
{
dotnet_version_major = "7";
dotnet_version_minor = "0";
dotnet_version_build = "10";
dotnet_version_build = "11";
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
}
@@ -27,7 +27,7 @@ static unsafe partial class Program
//#else
// public const string dotnet_version_major = "7";
// public const string dotnet_version_minor = "0";
// public const string dotnet_version_build = "10";
// public const string dotnet_version_build = "11";
// public const string dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
//#endif
@@ -1,8 +1,7 @@
{
"profiles": {
"BD.WTTS.Client.Avalonia.App": {
"commandName": "Project",
"commandLineArgs": "-clt devtools -steamrun"
"commandName": "Project"
}
}
}
+1 -1
View File
@@ -248,7 +248,7 @@ sealed partial class Program : Startup
sealed class Essentials_AppVerS : IApplicationVersionService
{
string IApplicationVersionService.ApplicationVersion => AssemblyInfo.Version;
string IApplicationVersionService.ApplicationVersion => AssemblyInfo.FileVersion;
string IApplicationVersionService.AssemblyTrademark => AssemblyInfo.Trademark;
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BD.WTTS.Converters;
public sealed class StringOmitConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string s = value.ToString();
int leng;
if (int.TryParse(parameter.ToString(), out leng))
{
if (s.Length <= leng)
return s;
else
return s[..leng] + "...";
}
else
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -5,46 +5,19 @@ namespace BD.WTTS.Services.Implementation;
/// <inheritdoc cref="IAppUpdateService"/>
sealed class AvaloniaApplicationUpdateServiceImpl : AppUpdateServiceBaseImpl
{
readonly IWindowManager windowManager;
public AvaloniaApplicationUpdateServiceImpl(
IApplication application,
INotificationService notificationService,
IWindowManager windowManager,
IToast toast,
IMicroServiceClient client,
IOptions<AppSettings> options) : base(application, notificationService, toast, client, options)
{
this.windowManager = windowManager;
}
protected override async Task ShowNewVersionWindowAsync()
protected override bool HasActiveWindow()
{
await windowManager.ShowAsync(typeof(object), AppEndPoint.NewVersion, isParent: false);
}
public override async void OnMainOpenTryShowNewVersionWindow()
{
if (ShowNewVersionWindowOnMainOpen)
{
ShowNewVersionWindowOnMainOpen = false;
await ShowNewVersionWindowAsync();
}
}
protected override async void OnExistNewVersion()
{
base.OnExistNewVersion();
var hasActiveWindow = App.Instance.HasActiveWindow();
if (hasActiveWindow)
{
await ShowNewVersionWindowAsync();
}
else
{
ShowNewVersionWindowOnMainOpen = true;
notification.Notify(AppResources.NewVersionUpdateNotifyText_.Format(NewVersionInfo?.Version), NotificationType.NewVersion);
}
return hasActiveWindow;
}
}
@@ -10,7 +10,9 @@ public sealed class NavigationService : INavigationService
{
public static NavigationService Instance { get; } = (NavigationService)Ioc.Get<INavigationService>();
public Control? PreviousPage { get; set; }
//public Type? PreviousPage { get; set; }
public Type? CurrnetPage => _frame?.CurrentSourcePageType;
public object? GetViewModelToPageContent(object viewModel, bool isCreateInstance = true)
{
@@ -54,7 +56,8 @@ public sealed class NavigationService : INavigationService
public void Navigate(Type? t, NavigationTransitionEffect effect = NavigationTransitionEffect.None, bool useCache = true)
{
_frame.ThrowIsNull();
if (_frame == null)
return;
var navOptions = new FrameNavigationOptions
{
+111 -43
View File
@@ -1,12 +1,16 @@
using Avalonia.Controls;
using BD.SteamClient.Services;
using BD.WTTS.Client.Resources;
using BD.WTTS.Converters;
using System.Collections.Specialized;
using static BD.WTTS.Services.INotificationService;
namespace BD.WTTS.UI;
partial class App
{
public Dictionary<string, TrayMenuItem> TrayMenus { get; } = new Dictionary<string, TrayMenuItem>();
public TrayIcons TrayIcons { get; init; } = new TrayIcons();
readonly Dictionary<string, ICommand> trayIconMenus = new();
@@ -32,51 +36,11 @@ partial class App
ToolTipText = AssemblyInfo.Trademark,
Command = ReactiveCommand.Create(RestoreMainWindow),
//[!TrayIcon.IsVisibleProperty] = new Binding { Path = "Value", Source = GeneralSettings.IsEnableTrayIcon, Mode = BindingMode.OneWay },
Menu = new NativeMenu
{
//new NativeMenuItem
//{
// Header = "Test",
//},
//new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new MultiBinding {
Bindings = new List<Avalonia.Data.IBinding>()
{
new Binding { Path = "IsRunningSteamProcess", Source = SteamConnectService.Current, Mode = BindingMode.OneWay },
new Binding { Path = "Res.CloseSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
new Binding { Path = "Res.StartSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
},
Converter = (VisbleStringConverter)App.Instance.FindResource("VisbleStringConverter")!,
},
Command = ReactiveCommand.Create(() =>
{
if(ISteamService.Instance.IsRunningSteamProcess)
{
ISteamService.Instance.TryKillSteamProcess();
}
else
{
ISteamService.Instance.StartSteamWithParameter();
}
})
},
new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.OpenMainWindow", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(RestoreMainWindow)
},
new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.Exit", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(() => { Shutdown(); })
}
}
Menu = new NativeMenu(),
});
UpdateMenuItems();
//IViewModelManager.Instance.InitTaskBarWindowViewModel();
//NotifyIconHelper.Init(this,
// notifyIconClick: (_, _) => RestoreMainWindow());
@@ -102,4 +66,108 @@ partial class App
}
}
public void UpdateMenuItems()
{
MainThread2.BeginInvokeOnMainThread(() =>
{
var menus = TrayIcons.FirstOrDefault()?.Menu;
if (menus != null)
{
menus.Items.Clear();
var defaultTrayMenus = new List<NativeMenuItemBase>()
{
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new MultiBinding {
Bindings = new List<Avalonia.Data.IBinding>()
{
new Binding { Path = "IsRunningSteamProcess", Source = SteamConnectService.Current, Mode = BindingMode.OneWay },
new Binding { Path = "Res.CloseSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
new Binding { Path = "Res.StartSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
},
Converter = (VisbleStringConverter)App.Instance.FindResource("VisbleStringConverter")!,
},
Command = ReactiveCommand.Create(async () =>
{
if(ISteamService.Instance.IsRunningSteamProcess)
{
await ISteamService.Instance.TryKillSteamProcess();
}
else
{
ISteamService.Instance.StartSteamWithParameter();
}
}),
},
//new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.OpenMainWindow", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(RestoreMainWindow),
},
new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.Exit", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(() => { Shutdown(); })
},
};
if (TrayMenus.Any_Nullable())
{
foreach (var item in TrayMenus)
{
NativeMenu? subMenu = null;
if (item.Value.Items.Any_Nullable())
{
subMenu = new NativeMenu();
foreach (var sub in item.Value.Items)
subMenu.Add(new NativeMenuItem
{
Header = sub.Name,
Command = sub.Command,
CommandParameter = sub.CommandParameter,
});
menus.Add(new NativeMenuItem
{
Header = item.Value.Name,
Menu = subMenu,
});
}
else
{
menus.Add(new NativeMenuItem
{
Header = item.Value.Name,
Command = item.Value.Command,
CommandParameter = item.Value.CommandParameter,
});
}
}
menus.Add(new NativeMenuItemSeparator());
}
foreach (var item in defaultTrayMenus)
{
menus.Add(item);
}
}
});
}
public void UpdateMenuItems(string menuKey, TrayMenuItem trayMenuItem)
{
if (TrayMenus != null)
{
if (TrayMenus.ContainsKey(menuKey))
TrayMenus[menuKey] = trayMenuItem;
else
TrayMenus.Add(menuKey, trayMenuItem);
UpdateMenuItems();
}
}
}
+1
View File
@@ -18,6 +18,7 @@
<spp:StringEqualsConverter x:Key="StringEqualsConverter" />
<spp:EnumLocalizationNameConverter x:Key="EnumLocalizationNameConverter" />
<spp:VisbleStringConverter x:Key="VisbleStringConverter" />
<spp:StringOmitConverter x:Key="StringOmitConverter" />
</Application.Resources>
<Application.Styles>
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="True" />
+13 -2
View File
@@ -44,6 +44,8 @@ public sealed partial class App : Application
AvaloniaXamlLoader.Load(this);
#if WINDOWS || LINUX || MACOS
if (GeneralSettings.MinimizeOnStartup.Value)
Startup.Instance.IsMinimize = true;
if (Startup.Instance.IsSteamRun)
{
try
@@ -90,8 +92,17 @@ public sealed partial class App : Application
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
MainWindow = InitializeMainWindow?.Invoke(this);
desktop.MainWindow = MainWindow ??= new MainWindow();
MainWindow = InitializeMainWindow?.Invoke(this) ?? new MainWindow();
desktop.MainWindow =
#if !UI_DEMO
Startup.Instance.IsMinimize ? null :
#endif
MainWindow;
if (Startup.Instance.IsMinimize && MainWindow is AppWindow appWindow)
{
Task2.InBackground(() => appWindow.SplashScreen.RunTasks(CancellationToken.None));
}
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
{
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ms="https://steampp.net/settings"
xmlns:sys="using:System"
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://steampp.net/services"
x:CompileBindings="True">
@@ -16,7 +17,7 @@
<!-- AutoCompleteBox -->
<Style Selector="AutoCompleteBox.Search /template/ TextBox#PART_TextBox">
<Setter Property="Watermark" Value="{ReflectionBinding Path=Res.Search, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="Watermark" Value="{ReflectionBinding Path=Res.Search, Mode=OneWay, Source={x:Static spp:Strings.Search}}" />
<Setter Property="InnerRightContent">
<Template>
<StackPanel Orientation="Horizontal">
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:CompileBindings="True">
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
@@ -29,7 +29,7 @@
Stretch="UniformToFill" />
<TextBlock
HorizontalAlignment="Center"
Text="{Binding Path=Res.GameList_IsLoading, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.GameList_IsLoading, Mode=OneWay, Source={x:Static spp:Strings.GameList_IsLoading}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock
HorizontalAlignment="Center"
@@ -51,7 +51,7 @@
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{ReflectionBinding Path=Res.CustomFilePicker_Tip,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Source={x:Static spp:Strings.CustomFilePicker_Tip}}"
TextAlignment="Center"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
</StackPanel>
@@ -76,4 +76,4 @@
<Style Selector="spp|CustomFilePicker:pointerover /template/ Panel#FilePickerTipBorder">
<Setter Property="IsVisible" Value="True" />
</Style>
</Styles>
</Styles>
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:CompileBindings="True">
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
@@ -17,10 +17,7 @@
Classes="Circle"
Value="60" />
<ProgressBar Width="150" IsIndeterminate="True" />
<ProgressBar
Width="150"
Classes="notransitions"
Value="50" />
<ProgressBar Width="150" Value="50" />
<TextBlock Text="Material Progress" />
<ProgressBar
@@ -31,7 +28,6 @@
<ProgressBar
Width="150"
Height="5"
Classes="Material notransitions"
Value="50" />
</StackPanel>
</Border>
@@ -58,7 +54,7 @@
<Setter Property="Width" Value="{DynamicResource ProgressBarMinHeight}" />
</Style>
<Style Selector="ProgressBar:not(.notransitions) /template/ Border#PART_Indicator">
<Style Selector="ProgressBar[IsIndeterminate=False] /template/ Border#PART_Indicator">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition
@@ -86,4 +86,4 @@
</Transitions>
</Setter>
</Style>
</Styles>
</Styles>
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
@@ -1,5 +1,6 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:CompileBindings="True">
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
@@ -1,5 +1,6 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ms="https://steampp.net/settings"
x:CompileBindings="True">
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://steampp.net/services"
xmlns:ui="using:FluentAvalonia.UI.Controls"
@@ -27,26 +28,26 @@
<MenuItem
x:Name="TextBoxContextMenuCutItem"
Command="{Binding $parent[TextBox].Cut}"
Header="{Binding Path=Res.Cut, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Cut, Mode=OneWay, Source={x:Static spp:Strings.Cut}}"
InputGesture="{x:Static TextBox.CutGesture}"
IsEnabled="{Binding $parent[TextBox].CanCut}" />
<MenuItem
x:Name="TextBoxContextMenuCopyItem"
Command="{Binding $parent[TextBox].Copy}"
Header="{Binding Path=Res.Copy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Copy, Mode=OneWay, Source={x:Static spp:Strings.Copy}}"
InputGesture="{x:Static TextBox.CopyGesture}"
IsEnabled="{Binding $parent[TextBox].CanCopy}" />
<MenuItem
x:Name="TextBoxContextMenuPasteItem"
Command="{Binding $parent[TextBox].Paste}"
Header="{Binding Path=Res.Paste, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Paste, Mode=OneWay, Source={x:Static spp:Strings.Paste}}"
InputGesture="{x:Static TextBox.PasteGesture}"
IsEnabled="{Binding $parent[TextBox].CanPaste}" />
<MenuItem Header="-" />
<MenuItem
x:Name="TextBoxContextMenuSelectAllItem"
Command="{Binding $parent[TextBox].SelectAll}"
Header="{Binding Path=Res.SelectAll, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.SelectAll, Mode=OneWay, Source={x:Static spp:Strings.SelectAll}}"
InputGesture="Ctrl + A"
IsEnabled="{Binding $parent[TextBox].Text, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
</MenuFlyout>
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://steampp.net/services"
xmlns:ui="using:FluentAvalonia.UI.Controls"
@@ -296,8 +297,8 @@
</Styles.Resources>
<Style Selector="ToggleSwitch">
<Setter Property="OffContent" Value="{ReflectionBinding Path=Res.Off, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="OnContent" Value="{ReflectionBinding Path=Res.On, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="OffContent" Value="{ReflectionBinding Path=Res.Off, Mode=OneWay, Source={x:Static spp:Strings.Off}}" />
<Setter Property="OnContent" Value="{ReflectionBinding Path=Res.On, Mode=OneWay, Source={x:Static spp:Strings.On}}" />
<Setter Property="MinWidth" Value="0" />
</Style>
@@ -1,5 +1,6 @@
<Styles
x:Class="BD.WTTS.UI.Styling.CustomTheme"
xmlns:spp="https://steampp.net/ui"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
@@ -1,5 +1,6 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System"
xmlns:ui="https://steampp.net/ui"
@@ -1,5 +1,6 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:CompileBindings="True">
@@ -1,5 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System"
xmlns:ui="using:FluentAvalonia.UI.Controls"
@@ -1,4 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Add Resources Here -->
<LinearGradientBrush x:Key="HeaderOpacityMaskBrush" StartPoint="0,0" EndPoint="0%,100%">
<GradientStop Offset="0" Color="#FF000000" />
@@ -1,4 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Add Resources Here -->
<LinearGradientBrush x:Key="HeaderOpacityMaskBrush" StartPoint="0,0" EndPoint="0%,100%">
<GradientStop Color="#00000000" />
@@ -1,4 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:spp="https://steampp.net/ui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Add Resources Here -->
<LinearGradientBrush x:Key="HeaderOpacityMaskBrush" StartPoint="0,0" EndPoint="0%,100%">
<GradientStop Offset="0" Color="#FF000000" />
@@ -97,27 +97,27 @@
Content="&#xE921;"
Theme="{StaticResource FA_SystemCaptionButton}"
ToolTip.Placement="Bottom"
ToolTip.Tip="{Binding Path=Res.Minimize, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
ToolTip.Tip="{Binding Path=Res.Minimize, Mode=OneWay, Source={x:Static spp:Strings.Minimize}}" />
<Button
Name="MaxRestoreButton"
Content="&#xE922;"
Theme="{StaticResource FA_SystemCaptionButton}"
ToolTip.Placement="Bottom"
ToolTip.Tip="{Binding Path=Res.Maximize, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
ToolTip.Tip="{Binding Path=Res.Maximize, Mode=OneWay, Source={x:Static spp:Strings.Maximize}}" />
<Button
Name="CloseButton"
Classes="Close"
Content="{StaticResource Close}"
Theme="{StaticResource FA_SystemCaptionButton}"
ToolTip.Placement="Bottom"
ToolTip.Tip="{Binding Path=Res.Close, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
ToolTip.Tip="{Binding Path=Res.Close, Mode=OneWay, Source={x:Static spp:Strings.Close}}" />
</StackPanel>
</ControlTemplate>
</Setter>
<Style Selector="^:maximized /template/ Button#MaxRestoreButton">
<Setter Property="Content" Value="&#xE923;" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Restore, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Restore, Mode=OneWay, Source={x:Static spp:Strings.Restore}}" />
</Style>
</ControlTheme>
@@ -41,7 +41,7 @@
Foreground="White"
Text="{ReflectionBinding Path=Res.Advertisement,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
Source={x:Static spp:Strings.Advertisement}}" />
<Button
Name="CloseAdBtn"
HorizontalAlignment="Right"
@@ -49,7 +49,7 @@
Background="Transparent"
Content="{ReflectionBinding Path=Res.Advertisement_Close,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Source={x:Static spp:Strings.Advertisement_Close}}"
Cursor="Hand"
Foreground="White" />
</Panel>
@@ -8,8 +8,8 @@ public class OptionsDisplayItem : TemplatedControl
public static readonly StyledProperty<string> DescriptionProperty =
AvaloniaProperty.Register<OptionsDisplayItem, string>(nameof(Description));
public static readonly StyledProperty<FAIconElement> IconProperty =
AvaloniaProperty.Register<OptionsDisplayItem, FAIconElement>(nameof(Icon));
public static readonly StyledProperty<object?> IconProperty =
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Icon));
public static readonly StyledProperty<bool> NavigatesProperty =
AvaloniaProperty.Register<OptionsDisplayItem, bool>(nameof(Navigates));
@@ -41,7 +41,7 @@ public class OptionsDisplayItem : TemplatedControl
set => SetValue(DescriptionProperty, value);
}
public FAIconElement Icon
public object? Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
@@ -1,5 +1,6 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Controls.SplashScreen"
xmlns:spp="https://steampp.net/ui"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -15,9 +15,7 @@
<StackPanel Spacing="{StaticResource SettingsSpacing}">
<TextBlock
Margin="0,0,0,5"
Text="{ReflectionBinding Path=Res.About,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.About, Mode=OneWay, Source={x:Static spp:Strings.About}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<spp:OptionsDisplayItem
@@ -140,9 +138,7 @@
Margin="12,4,0,0"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{ReflectionBinding Path=Res.Link,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.Link, Mode=OneWay, Source={x:Static spp:Strings.Link}}" />
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding Hyperlinks, Mode=OneTime}">
<ItemsRepeater.Layout>
<UniformGridLayout MinItemWidth="166" />
@@ -165,9 +161,7 @@
Margin="12,4,0,0"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{ReflectionBinding Path=Res.OpenSourceLibrary,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.OpenSourceLibrary, Mode=OneWay, Source={x:Static spp:Strings.OpenSourceLibrary}}" />
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding OSL, Mode=OneTime}">
<ItemsRepeater.Layout>
<UniformGridLayout MinItemWidth="290" />
@@ -122,7 +122,7 @@
<TextBox
Name="CommandTextBox"
Grid.Row="1"
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static spp:Strings.Debug_InstructionPlaceholder}}" />
</Grid>
</Border>
</spp:PageBase>
@@ -1,5 +1,6 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Pages.ErrorPage"
xmlns:spp="https://steampp.net/ui"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -15,12 +16,12 @@
VerticalAlignment="Center"
FontSize="16"
Foreground="White"
Text="{Binding Path=Res.Error_PageFault, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
Text="{Binding Path=Res.Error_PageFault, Mode=OneWay, Source={x:Static spp:Strings.Error_PageFault}}" />-->
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"
Foreground="White"
Text="{Binding Path=Res.NewFeatureDevelopment, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.NewFeatureDevelopment, Mode=OneWay, Source={x:Static spp:Strings.NewFeatureDevelopment}}" />
</Panel>
</UserControl>
</UserControl>
@@ -73,7 +73,7 @@
MaxWidth="1080"
Spacing="20">
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static spp:Strings.Notice_Tray_Title}}"
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
@@ -81,7 +81,7 @@
Message="当前使用的程序版本为测试版本,可能会有各种各样不可言明的 Bug,遇到问题请理性反馈,谢谢。"
Severity="Warning" />
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static spp:Strings.Notice_Tray_Title}}"
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
@@ -119,7 +119,7 @@
Theme="{StaticResource SubtitleTextBlockStyle}" />
</WrapPanel>
<TextBlock VerticalAlignment="Center" Theme="{StaticResource SubtitleTextBlockStyle}">
<Run Text="{Binding Path=Res.Welcome_User_, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Run Text="{Binding Path=Res.Welcome_User_, Mode=OneWay, Source={x:Static spp:Strings.Welcome_User_}}" />
</TextBlock>
</WrapPanel>
@@ -7,7 +7,7 @@ public partial class HomePage : ReactiveUserControl<HomePageViewModel>
public HomePage()
{
InitializeComponent();
this.SetViewModel<HomePageViewModel>(false);
this.SetViewModel<HomePageViewModel>(true);
//ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
}
@@ -212,13 +212,13 @@
<TextBlock
VerticalAlignment="Center"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}}"
Text="{Binding Path=Res.User_UnLogin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.User_UnLogin, Mode=OneWay, Source={x:Static spp:Strings.User_UnLogin}}" />
<StackPanel VerticalAlignment="Center" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
<TextBlock Text="{Binding Path=User.NickName, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
<Border VerticalAlignment="Center" IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
<TextBlock FontSize="10" Text="{Binding Path=Res.User_Sponsor, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}❤️{0}}" />
<TextBlock FontSize="10" Text="{Binding Path=Res.User_Sponsor, Mode=OneWay, Source={x:Static spp:Strings.User_Sponsor}, StringFormat={}❤️{0}}" />
</Border>
</StackPanel>
@@ -243,14 +243,14 @@
<Button
Margin="5,0"
Command="{Binding Path=SignInAsync, Mode=OneWay, Source={x:Static s:UserService.Current}}"
Content="{Binding Path=Res.Sign, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Sign, Mode=OneWay, Source={x:Static spp:Strings.Sign}}"
FontSize="10"
IsVisible="{Binding Path=!User.IsSignIn, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
<Button
Margin="5,0"
FontSize="10"
IsVisible="{Binding Path=User.IsSignIn, Mode=OneWay, Source={x:Static s:UserService.Current}}"
ToolTip.Tip="{Binding Path=Res.Signed, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
ToolTip.Tip="{Binding Path=Res.Signed, Mode=OneWay, Source={x:Static spp:Strings.Signed}}">
<Button.Content>
<MultiBinding StringFormat="{}{0} : {1}">
<CompiledBinding Path="Res.User_EngineOil" Source="{x:Static s:ResourceService.Current}" />
@@ -266,24 +266,24 @@
<MenuItem
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="https://steampp.net/account/center/info"
Header="{Binding Path=Res.PersonalCenter, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.PersonalCenter, Mode=OneWay, Source={x:Static spp:Strings.PersonalCenter}}"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static spp:Strings.MyOrder}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
<MenuItem
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="https://steampp.net/account/center/bind"
Header="{Binding Path=Res.BindThirdPartyAccount, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.BindThirdPartyAccount, Mode=OneWay, Source={x:Static spp:Strings.BindThirdPartyAccount}}"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="https://steampp.net/account/center/safe"
Header="{Binding Path=Res.PersonalSafe, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.PersonalSafe, Mode=OneWay, Source={x:Static spp:Strings.PersonalSafe}}"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
Command="{Binding Path=SignOut, Mode=OneWay, Source={x:Static s:UserService.Current}}"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
Header="{Binding Path=Res.SignOut, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.SignOut, Mode=OneWay, Source={x:Static spp:Strings.SignOut}}"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<!--<MenuItem
Command="{Binding Path=ShowWindow,
@@ -330,7 +330,7 @@
</Style>
<Style Selector="Grid#PaneToggleButtonGrid">
<Setter Property="Margin" Value="5 5 0 0" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static spp:Strings.Back}}" />
<Setter Property="ToolTip.Placement" Value="Bottom" />
</Style>
</ui:NavigationView.Styles>
@@ -73,15 +73,19 @@ public sealed partial class MainView : ReactiveUserControl<MainWindowViewModel>
NavView.SelectedItem = null;
foreach (var nvi in NavView.MenuItemsSource)
{
SetSelectedItem(nvi);
}
var menuItemsSource = NavView.MenuItemsSource;
if (menuItemsSource != null)
foreach (var nvi in menuItemsSource)
{
SetSelectedItem(nvi);
}
foreach (var nvi in NavView.FooterMenuItemsSource)
{
SetSelectedItem(nvi);
}
var footerMenuItemsSource = NavView.FooterMenuItemsSource;
if (footerMenuItemsSource != null)
foreach (var nvi in footerMenuItemsSource)
{
SetSelectedItem(nvi);
}
if (FrameView.BackStackDepth > 0 && !NavView.IsBackButtonVisible)
{
@@ -26,11 +26,11 @@
CornerRadius="{StaticResource ControlCornerRadius}"
ItemContainerTheme="{StaticResource TabItemStyle}"
Theme="{StaticResource TabStripStyle}">
<TabStripItem Content="{Binding Path=Res.Settings_General, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Tag="Settings_General" />
<TabStripItem Content="{Binding Path=Res.Settings_UI, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Tag="Settings_UI" />
<TabStripItem Content="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Tag="Settings_Plugin" />
<TabStripItem Content="{Binding Path=Res.Settings_Steam, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Tag="Settings_Steam" />
<TabStripItem Content="{Binding Path=Res.About, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Tag="AboutPage" />
<TabStripItem Content="{Binding Path=Res.Settings_General, Mode=OneWay, Source={x:Static spp:Strings.Settings_General}}" Tag="Settings_General" />
<TabStripItem Content="{Binding Path=Res.Settings_UI, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI}}" Tag="Settings_UI" />
<TabStripItem Content="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static spp:Strings.Settings_Plugin}}" Tag="Settings_Plugin" />
<TabStripItem Content="{Binding Path=Res.Settings_Steam, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam}}" Tag="Settings_Steam" />
<TabStripItem Content="{Binding Path=Res.About, Mode=OneWay, Source={x:Static spp:Strings.About}}" Tag="AboutPage" />
</TabStrip>
</spp:PageBase.TabContent>
<!--<spp:PageBase.ActionContent>
@@ -17,9 +17,9 @@
<StackPanel Spacing="{StaticResource SettingsSpacing}">
<TextBlock
Margin="0,0,0,10"
Text="{Binding Path=Res.Settings_General, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.Settings_General, Mode=OneWay, Source={x:Static spp:Strings.Settings_General}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_StartupAutoRun, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Header="{Binding Path=Res.Settings_General_StartAutoRun, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_StartupAutoRun, Mode=OneWay, Source={x:Static spp:Strings.Settings_StartupAutoRun}}" Header="{Binding Path=Res.Settings_General_StartAutoRun, Mode=OneWay, Source={x:Static spp:Strings.Settings_StartupAutoRun}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE770;" />
</spp:OptionsDisplayItem.Icon>
@@ -28,7 +28,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_StartupNoShowMainWindow, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Header="{Binding Path=Res.Settings_General_RuningMinimize, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_StartupNoShowMainWindow, Mode=OneWay, Source={x:Static spp:Strings.Settings_StartupNoShowMainWindow}}" Header="{Binding Path=Res.Settings_General_RuningMinimize, Mode=OneWay, Source={x:Static spp:Strings.Settings_StartupNoShowMainWindow}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xEA6C;" />
</spp:OptionsDisplayItem.Icon>
@@ -37,7 +37,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_General_EnableTrayIcon_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Header="{Binding Path=Res.Settings_General_EnableTrayIcon, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_General_EnableTrayIcon_Tip, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_EnableTrayIcon_Tip}}" Header="{Binding Path=Res.Settings_General_EnableTrayIcon, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_EnableTrayIcon_Tip}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE9E9;" />
</spp:OptionsDisplayItem.Icon>
@@ -46,7 +46,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_General_AutoCheckUpdate, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_General_AutoCheckUpdate, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_AutoCheckUpdate}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE895;" />
</spp:OptionsDisplayItem.Icon>
@@ -55,7 +55,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_General_UpdateChannel, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{spp:OnPlatform OfficialRelease}">
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_General_UpdateChannel, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_UpdateChannel}}" IsVisible="{spp:OnPlatform OfficialRelease}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE71D;" />
</spp:OptionsDisplayItem.Icon>
@@ -75,7 +75,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_RestartEffective, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Header="{Binding Path=Res.Settings_General_UseGPURendering, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_RestartEffective, Mode=OneWay, Source={x:Static spp:Strings.Settings_RestartEffective}}" Header="{Binding Path=Res.Settings_General_UseGPURendering, Mode=OneWay, Source={x:Static spp:Strings.Settings_RestartEffective}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xE950;" />
</spp:OptionsDisplayItem.Icon>
@@ -94,7 +94,7 @@
</spp:OptionsDisplayItem>
<ui:SettingsExpander
Header="{Binding Path=Res.Settings_General_StorageSpace, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_General_StorageSpace, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_StorageSpace}}"
IconSource="Globe"
IsClickEnabled="False"
IsExpanded="True">
@@ -105,23 +105,23 @@
<Button
Command="{Binding OpenFolder_Click}"
CommandParameter="{x:Null}"
Content="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}, Converter={StaticResource StringFormatConverter}, ConverterParameter=App}" />
Content="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_BrowseCustomFolder}, Converter={StaticResource StringFormatConverter}, ConverterParameter=App}" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem
ActionIconSource="Open"
Command="{Binding OpenFolder_Click}"
CommandParameter="AppData"
Content="{Binding Path=Res.Settings_General_AppData, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Settings_General_AppData, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_AppData}}"
IsClickEnabled="True"
ToolTip.Tip="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}, Converter={StaticResource StringFormatConverter}, ConverterParameter=AppData}" />
ToolTip.Tip="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_BrowseCustomFolder}, Converter={StaticResource StringFormatConverter}, ConverterParameter=AppData}" />
<ui:SettingsExpanderItem
ActionIconSource="Open"
Command="{Binding OpenFolder_Click}"
CommandParameter="Cache"
Content="{Binding Path=Res.Settings_General_CacheData, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Settings_General_CacheData, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_CacheData}}"
IsClickEnabled="True"
ToolTip.Tip="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}, Converter={StaticResource StringFormatConverter}, ConverterParameter=Cache}">
ToolTip.Tip="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_BrowseCustomFolder}, Converter={StaticResource StringFormatConverter}, ConverterParameter=Cache}">
<ui:SettingsExpanderItem.Footer>
<TextBlock
Name="CacheSize"
@@ -134,9 +134,9 @@
ActionIconSource="Open"
Command="{Binding OpenFolder_Click}"
CommandParameter="Logs"
Content="{Binding Path=Res.Settings_General_LogsData, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Settings_General_LogsData, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_LogsData}}"
IsClickEnabled="True"
ToolTip.Tip="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}, Converter={StaticResource StringFormatConverter}, ConverterParameter=Logs}">
ToolTip.Tip="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static spp:Strings.Settings_General_BrowseCustomFolder}, Converter={StaticResource StringFormatConverter}, ConverterParameter=Logs}">
<ui:SettingsExpanderItem.Footer>
<TextBlock
Name="LogSize"
@@ -16,10 +16,10 @@
<StackPanel Spacing="{StaticResource SettingsSpacing}">
<TextBlock
Margin="0,0,0,10"
Text="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static spp:Strings.Settings_Plugin}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<ui:SettingsExpander Description="{Binding Path=Res.Plugin_OnlyLoadOfficePlugins, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Header="{Binding Path=Res.Plugin_SafeMode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<ui:SettingsExpander Description="{Binding Path=Res.Plugin_OnlyLoadOfficePlugins, Mode=OneWay, Source={x:Static spp:Strings.Plugin_OnlyLoadOfficePlugins}}" Header="{Binding Path=Res.Plugin_SafeMode, Mode=OneWay, Source={x:Static spp:Strings.Plugin_OnlyLoadOfficePlugins}}">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="{StaticResource DefenderApp}" />
</ui:SettingsExpander.IconSource>
@@ -35,14 +35,14 @@
<CompiledBinding
Mode="OneWay"
Path="Res.Plugin_InstalledPlugins"
Source="{x:Static s:ResourceService.Current}" />
Source="{x:Static spp:Strings.Plugin_InstalledPlugins}" />
<CompiledBinding
Mode="OneWay"
Path="MainWindow2.PluginCount"
Source="{x:Static s:IViewModelManager.Instance}" />
Source="{x:Static spp:Strings.IViewModelManager.Instance}" />
</MultiBinding>
</TextBlock.Text>
<!--<Run Text="{Binding Path=Res.Plugin_InstalledPlugins, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<!--<Run Text="{Binding Path=Res.Plugin_InstalledPlugins, Mode=OneWay, Source={x:Static spp:Strings.Plugin_InstalledPlugins}}" />
<Run Text="(" />
<Run Text="{Binding MainWindow2.PluginCount, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}" />
<Run Text=")" />-->
@@ -73,14 +73,14 @@
<ui:SettingsExpanderItem
ActionIconSource="Refresh"
Command="{Binding $parent[spp:Settings_Plugin].((spp:SettingsPageViewModel)DataContext).CheckUpdate_Click}"
Content="{Binding Path=Res.CheckUpdate, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.CheckUpdate, Mode=OneWay, Source={x:Static spp:Strings.CheckUpdate}}"
IsClickEnabled="True">
<ui:SettingsExpanderItem.Description>
<MultiBinding StringFormat="{}{0}{1}">
<CompiledBinding
Mode="OneWay"
Path="Res.Plugin_Version"
Source="{x:Static s:ResourceService.Current}" />
Source="{x:Static spp:Strings.Plugin_Version}" />
<CompiledBinding Mode="OneWay" Path="Data.Version" />
</MultiBinding>
</ui:SettingsExpanderItem.Description>
@@ -89,19 +89,19 @@
ActionIconSource="Open"
Command="{Binding $parent[spp:Settings_Plugin].((spp:SettingsPageViewModel)DataContext).OpenPluginDirectory_Click}"
CommandParameter="{Binding Data.AssemblyLocation}"
Content="{Binding Path=Res.Plugin_OpenFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Plugin_OpenFolder, Mode=OneWay, Source={x:Static spp:Strings.Plugin_OpenFolder}}"
IsClickEnabled="True" />
<ui:SettingsExpanderItem
ActionIconSource="Open"
Command="{Binding $parent[spp:Settings_Plugin].((spp:SettingsPageViewModel)DataContext).OpenPluginCacheDirectory_Click}"
CommandParameter="{Binding Data.CacheDirectory}"
Content="{Binding Path=Res.Plugin_OpenCacheFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Plugin_OpenCacheFolder, Mode=OneWay, Source={x:Static spp:Strings.Plugin_OpenCacheFolder}}"
IsClickEnabled="True" />
<ui:SettingsExpanderItem
ActionIconSource="Delete"
Command="{Binding $parent[spp:Settings_Plugin].((spp:SettingsPageViewModel)DataContext).DeletePlugin_Click}"
CommandParameter="{Binding Data}"
Content="{Binding Path=Res.Plugin_Delete, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Plugin_Delete, Mode=OneWay, Source={x:Static spp:Strings.Plugin_Delete}}"
IsClickEnabled="True" />
</ui:SettingsExpander>
</DataTemplate>
@@ -111,17 +111,17 @@
<!--<ui:SettingsExpander
Description="{Binding Path=Res.Plugin_ManageAllInstalledPlugins,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Source={x:Static spp:Strings.Plugin_ManageAllInstalledPlugins}}"
Header="{Binding Path=Res.Plugin_InstalledPlugins,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Source={x:Static spp:Strings.Plugin_InstalledPlugins}}"
IsExpanded="True"
ItemsSource="{Binding Plugins}">
<ui:SettingsExpander.IconSource>
<ui:SymbolIconSource Symbol="Calculator" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button Content="{Binding Path=Res.Plugin_OpenFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Button Content="{Binding Path=Res.Plugin_OpenFolder, Mode=OneWay, Source={x:Static spp:Strings.Plugin_OpenFolder}}" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpander.ItemTemplate>
<DataTemplate>
@@ -148,21 +148,21 @@
Theme="{StaticResource TransparentButton}">
<DropDownButton.Flyout>
<MenuFlyout>
<MenuItem Command="{Binding Path=DataContext.CheckUpdate_Click, RelativeSource={RelativeSource AncestorType=spp:Settings_Plugin}}" Header="{Binding Path=Res.CheckUpdate, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<MenuItem Command="{Binding Path=DataContext.CheckUpdate_Click, RelativeSource={RelativeSource AncestorType=spp:Settings_Plugin}}" Header="{Binding Path=Res.CheckUpdate, Mode=OneWay, Source={x:Static spp:Strings.CheckUpdate}}" />
<MenuItem
Command="{Binding Path=DataContext.OpenPluginDirectory_Click,
RelativeSource={RelativeSource AncestorType=spp:Settings_Plugin}}"
CommandParameter="{Binding Data.AssemblyLocation}"
Header="{Binding Path=Res.Plugin_OpenFolder,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
Source={x:Static spp:Strings.Plugin_OpenFolder}}" />
<MenuItem
Command="{Binding Path=DataContext.OpenPluginCacheDirectory_Click,
RelativeSource={RelativeSource AncestorType=spp:Settings_Plugin}}"
CommandParameter="{Binding Data.CacheDirectory}"
Header="{Binding Path=Res.Plugin_OpenCacheFolder,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
Source={x:Static spp:Strings.Plugin_OpenCacheFolder}}" />
<MenuItem Header="-" />
<MenuItem
Command="{Binding Path=DataContext.DeletePlugin_Click,
@@ -170,7 +170,7 @@
CommandParameter="{Binding Data}"
Header="{Binding Path=Res.Plugin_Delete,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
Source={x:Static spp:Strings.Plugin_Delete}}" />
</MenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
@@ -16,18 +16,19 @@
<StackPanel Spacing="{StaticResource SettingsSpacing}">
<TextBlock
Margin="0,0,0,10"
Text="{Binding Path=Res.Settings_Steam, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.Settings_Steam, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<spp:OptionsDisplayItem Description="{Binding Source={x:Static ms:SteamSettings.SteamProgramPath}, Path=Value, Mode=OneWay}" Header="{Binding Path=Res.Settings_Steam_Path, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Source={x:Static ms:SteamSettings.SteamProgramPath}, Path=Value, Mode=OneWay}"
Header="{Binding Path=Res.Settings_Steam_Path, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_Path}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xE756;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button Command="{Binding SelectSteamProgramLocation}" Content="{Binding Path=Res.LocalAuth_ExportAuth_SelectPath, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Button Command="{Binding SelectSteamProgramLocation}" Content="{Binding Path=Res.LocalAuth_ExportAuth_SelectPath, Mode=OneWay, Source={x:Static spp:Strings.LocalAuth_ExportAuth_SelectPath}}" />
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_Steam_AutoRunSteam, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_Steam_AutoRunSteam, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_AutoRunSteam}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE770;" />
</spp:OptionsDisplayItem.Icon>
@@ -37,8 +38,8 @@
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem
Description="{Binding Path=Res.Settings_Steam_AdminRunPrompt, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_Steam_RuningAdmin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Description="{Binding Path=Res.Settings_Steam_AdminRunPrompt, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_AdminRunPrompt}}"
Header="{Binding Path=Res.Settings_Steam_RuningAdmin, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_RuningAdmin}}"
IsVisible="{spp:OnPlatform Windows}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xE7EF;" />
@@ -48,7 +49,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_Steam_RuningMinimize, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_Steam_RuningMinimize, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_RuningMinimize}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE9E9;" />
</spp:OptionsDisplayItem.Icon>
@@ -57,7 +58,8 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_Steam_NoAutoUpdate, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Header="{Binding Path=Res.Settings_Steam_NotCheckUpdate, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_Steam_NoAutoUpdate, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_NoAutoUpdate}}"
Header="{Binding Path=Res.Settings_Steam_NotCheckUpdate, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_NotCheckUpdate}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE895;" />
</spp:OptionsDisplayItem.Icon>
@@ -66,7 +68,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_Steam_RunSteamChina, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_Steam_RunSteamChina, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_RunSteamChina}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE9E9;" />
</spp:OptionsDisplayItem.Icon>
@@ -75,7 +77,8 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_Steam_AutoPopupMessage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Header="{Binding Path=Res.Settings_Steam_CheckSteamRuningNotification, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Path=Res.Settings_Steam_AutoPopupMessage, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_AutoPopupMessage}}"
Header="{Binding Path=Res.Settings_Steam_CheckSteamRuningNotification, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_CheckSteamRuningNotification}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE7E7;" />
</spp:OptionsDisplayItem.Icon>
@@ -84,12 +87,13 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Description="{Binding Source={x:Static ms:SteamSettings.SteamStratParameter}, Path=Value, Mode=OneWay}" Header="{Binding Path=Res.Settings_Steam_SteamStratParameter, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:OptionsDisplayItem Description="{Binding Source={x:Static ms:SteamSettings.SteamStratParameter}, Path=Value, Mode=OneWay}"
Header="{Binding Path=Res.Settings_Steam_SteamStratParameter, Mode=OneWay, Source={x:Static spp:Strings.Settings_Steam_SteamStratParameter}}">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xE756;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button Command="{Binding EditSteamParameter}" Content="{Binding Path=Res.Edit, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Button Command="{Binding EditSteamParameter}" Content="{Binding Path=Res.Edit, Mode=OneWay, Source={x:Static spp:Strings.Edit}}" />
</spp:OptionsDisplayItem.ActionButton>
<!--<spp:OptionsDisplayItem.Content>
<TextBox Classes="clearButton"
@@ -19,13 +19,13 @@
<StackPanel Spacing="{StaticResource SettingsSpacing}">
<TextBlock
Margin="0,0,0,10"
Text="{Binding Path=Res.Settings_UI, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.Settings_UI, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<Border IsEnabled="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
<ui:SettingsExpander
Description="{Binding Path=Res.Advertisement_NoShow_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Advertisement_NoShow, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Description="{Binding Path=Res.Advertisement_NoShow_Tip, Mode=OneWay, Source={x:Static spp:Strings.Advertisement_NoShow_Tip}}"
Header="{Binding Path=Res.Advertisement_NoShow, Mode=OneWay, Source={x:Static spp:Strings.Advertisement_NoShow}}"
IsEnabled="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{StaticResource HeartBroken}" />
@@ -37,8 +37,8 @@
</Border>
<spp:OptionsDisplayItem
Description="{Binding Path=Res.Settings_UI_ThemeStyle, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_Theme, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Description="{Binding Path=Res.Settings_UI_ThemeStyle, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_ThemeStyle}}"
Header="{Binding Path=Res.Settings_Theme, Mode=OneWay, Source={x:Static spp:Strings.Settings_Theme}}"
Icon="DarkTheme">
<spp:OptionsDisplayItem.ActionButton>
<ComboBox SelectedItem="{Binding Source={x:Static ms:UISettings.Theme}, Path=Value, Mode=TwoWay, Converter={StaticResource EnumConverter}, ConverterParameter={x:Type spp:AppTheme}}">
@@ -56,17 +56,17 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem
Description="{Binding Path=Res.Settings_UI_AccentColor, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Description="{Binding Path=Res.Settings_UI_AccentColor, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_AccentColor}}"
Expands="True"
Header="{Binding Path=Res.Settings_ThemeAccent, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_ThemeAccent, Mode=OneWay, Source={x:Static spp:Strings.Settings_ThemeAccent}}"
Icon="ColorLine"
IsExpanded="{Binding Path=IsChecked, ElementName=AccentCustom, Mode=OneWay}">
<spp:OptionsDisplayItem.ActionButton>
<StackPanel Orientation="Horizontal" Spacing="10">
<RadioButton Content="{Binding Path=Res.Settings_UI_SystemDefault, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsChecked="{Binding Source={x:Static ms:UISettings.UseSystemThemeAccent}, Path=Value, Mode=TwoWay}" />
<RadioButton Content="{Binding Path=Res.Settings_UI_SystemDefault, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_SystemDefault}}" IsChecked="{Binding Source={x:Static ms:UISettings.UseSystemThemeAccent}, Path=Value, Mode=TwoWay}" />
<RadioButton
x:Name="AccentCustom"
Content="{Binding Path=Res.Settings_UI_Custom, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Settings_UI_Custom, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_Custom}}"
IsChecked="{Binding Source={x:Static ms:UISettings.UseSystemThemeAccent}, Path=!Value, Mode=OneWay}" />
</StackPanel>
</spp:OptionsDisplayItem.ActionButton>
@@ -76,7 +76,7 @@
<TextBlock
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="{Binding Path=Res.Settings_UI_PreviewColor, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.Settings_UI_PreviewColor, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_PreviewColor}}" />
<Grid
HorizontalAlignment="Left"
@@ -252,7 +252,7 @@
<TextBlock
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="{Binding Path=Res.Settings_UI_CustomColor, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.Settings_UI_CustomColor, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_CustomColor}}" />
<ui:ColorPickerButton
DockPanel.Dock="Right"
@@ -270,8 +270,8 @@
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem
Description="{Binding Path=Res.Settings_UI_DefaultLanguage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_Language, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Description="{Binding Path=Res.Settings_UI_DefaultLanguage, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_DefaultLanguage}}"
Header="{Binding Path=Res.Settings_Language, Mode=OneWay, Source={x:Static spp:Strings.Settings_Language}}"
Navigates="False">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xF2B7;" />
@@ -291,8 +291,8 @@
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem
Description="{Binding Path=Res.Settings_UI_DefaultFonts, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_Fonts, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Description="{Binding Path=Res.Settings_UI_DefaultFonts, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_DefaultFonts}}"
Header="{Binding Path=Res.Settings_Fonts, Mode=OneWay, Source={x:Static spp:Strings.Settings_Fonts}}"
Icon="Font"
Navigates="False">
<spp:OptionsDisplayItem.ActionButton>
@@ -314,8 +314,8 @@
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem
Description="{Binding Path=Res.Settings_UI_WindowBackgroundStyle, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_UI_WindowTransparencyMaterial, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Description="{Binding Path=Res.Settings_UI_WindowBackgroundStyle, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_WindowBackgroundStyle}}"
Header="{Binding Path=Res.Settings_UI_WindowTransparencyMaterial, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_WindowTransparencyMaterial}}"
Navigates="False">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xF4A5;" />
@@ -336,7 +336,7 @@
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_UI_BackgroundOpacity, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Navigates="False">
<spp:OptionsDisplayItem Header="{Binding Path=Res.Settings_UI_BackgroundOpacity, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_BackgroundOpacity}}" Navigates="False">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE706;" />
</spp:OptionsDisplayItem.Icon>
@@ -351,7 +351,7 @@
<spp:OptionsDisplayItem
Expands="True"
Header="{Binding Path=Res.Settings_UI_CustomBackgroundImage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.Settings_UI_CustomBackgroundImage, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_CustomBackgroundImage}}"
IsExpanded="{Binding Source={x:Static ms:UISettings.WindowBackgroundCustomImage}, Path=Value, Mode=OneWay}"
Navigates="False">
<spp:OptionsDisplayItem.Icon>
@@ -367,7 +367,7 @@
<TextBlock
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="{Binding Path=Res.PreviewPictures, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.PreviewPictures, Mode=OneWay, Source={x:Static spp:Strings.PreviewPictures}}" />
<Image
Width="250"
@@ -389,7 +389,7 @@
<TextBlock
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="{Binding Path=Res.Settings_UI_BackgroundImageOpacity, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.Settings_UI_BackgroundImageOpacity, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_BackgroundImageOpacity}}" />
<Slider
MinWidth="250"
DockPanel.Dock="Right"
@@ -408,11 +408,11 @@
<TextBlock
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="{Binding Path=Res.SelectPicture, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.SelectPicture, Mode=OneWay, Source={x:Static spp:Strings.SelectPicture}}" />
<Button
MinWidth="150"
Command="{Binding SelectImage_Click}"
Content="{Binding Path=Res.Settings_UI_SelectImage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Settings_UI_SelectImage, Mode=OneWay, Source={x:Static spp:Strings.Settings_UI_SelectImage}}"
DockPanel.Dock="Right" />
</DockPanel>
@@ -425,7 +425,7 @@
<TextBlock
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="{Binding Path=Res.PictureZoom, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.PictureZoom, Mode=OneWay, Source={x:Static spp:Strings.PictureZoom}}" />
<ComboBox
MinWidth="150"
DockPanel.Dock="Right"
@@ -45,7 +45,7 @@
<StackPanel Classes="Mid">
<TextBlock
HorizontalAlignment="Left"
Text="{Binding Path=Res.User_FastLoginTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.User_FastLoginTip, Mode=OneWay, Source={x:Static spp:Strings.User_FastLoginTip}}"
Theme="{StaticResource CaptionTextBlockStyle}" />
<ItemsControl Background="Transparent" ItemsSource="{Binding ExternalLoginChannels}">
<ItemsControl.Styles>
@@ -107,7 +107,7 @@
MaxLength="{x:Static com:PhoneNumberHelper.ChineseMainlandPhoneNumberLength}"
Text="{Binding PhoneNumber}"
TextAlignment="Left"
Watermark="{Binding Path=Res.User_Phone, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Watermark="{Binding Path=Res.User_Phone, Mode=OneWay, Source={x:Static spp:Strings.User_Phone}}" />
<Border HorizontalAlignment="Left" Cursor="Hand">
<DropDownButton
Margin="0"
@@ -139,7 +139,7 @@
VerticalContentAlignment="Center"
MaxLength="{x:Static columns:MaxLengths.SMS_CAPTCHA}"
Text="{Binding SmsCode}"
Watermark="{Binding Path=Res.User_SMSCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Watermark="{Binding Path=Res.User_SMSCode, Mode=OneWay, Source={x:Static spp:Strings.User_SMSCode}}" />
<Border
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
@@ -162,22 +162,22 @@
HorizontalContentAlignment="Center"
Classes="accent"
Command="{Binding Submit}"
Content="{Binding Path=Res.LoginAndRegister, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
Content="{Binding Path=Res.LoginAndRegister, Mode=OneWay, Source={x:Static spp:Strings.LoginAndRegister}}" />
</StackPanel>
<!-- 2 加载中状态 -->
<StackPanel Classes="Mid">
<TextBlock Text="{Binding Path=Res.User_WaitFastLoginTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" TextWrapping="Wrap" />
<TextBlock Text="{Binding Path=Res.User_WaitFastLoginTip, Mode=OneWay, Source={x:Static spp:Strings.User_WaitFastLoginTip}}" TextWrapping="Wrap" />
<WrapPanel Margin="0,20" HorizontalAlignment="Center">
<TextBlock Text="{Binding Path=Res.User_LoginUnexpectedPrompt, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<TextBlock Text="{Binding Path=Res.User_LoginUnexpectedPrompt, Mode=OneWay, Source={x:Static spp:Strings.User_LoginUnexpectedPrompt}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<ui:HyperlinkButton
Margin="5,0"
Padding="0"
HorizontalContentAlignment="Center"
Command="{Binding ManualLogin}">
<TextBlock Text="{Binding Path=Res.Login_ManualLogin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<TextBlock Text="{Binding Path=Res.Login_ManualLogin, Mode=OneWay, Source={x:Static spp:Strings.Login_ManualLogin}}" Theme="{StaticResource CaptionTextBlockStyle}" />
</ui:HyperlinkButton>
</WrapPanel>
@@ -197,7 +197,7 @@
<Button
HorizontalContentAlignment="Center"
Command="{Binding ChangeState}"
Content="{Binding Path=Res.Cancel, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Cancel, Mode=OneWay, Source={x:Static spp:Strings.Cancel}}"
Theme="{StaticResource TransparentButton}">
<Button.CommandParameter>
<x:Int16>0</x:Int16>
@@ -207,7 +207,7 @@
HorizontalContentAlignment="Center"
Command="{Binding ChooseChannel}"
CommandParameter="{Binding CurrentSelectChannel}"
Content="{Binding Path=Res.Retry, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Retry, Mode=OneWay, Source={x:Static spp:Strings.Retry}}"
Theme="{StaticResource TransparentButton}" />
</StackPanel>
</StackPanel>
@@ -220,7 +220,7 @@
HorizontalAlignment="Center">
<TextBlock
FontSize="12"
Text="{Binding Path=Res.User_RegisterAgreed, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.User_RegisterAgreed, Mode=OneWay, Source={x:Static spp:Strings.User_RegisterAgreed}}"
Theme="{StaticResource CaptionTextBlockStyle}" />
<ui:HyperlinkButton
Margin="5,0"
@@ -228,16 +228,16 @@
Command="{Binding OpenHyperlink}"
CommandParameter="Agreement"
Cursor="Hand">
<TextBlock Text="{Binding Path=Res.User_Agreement, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<TextBlock Text="{Binding Path=Res.User_Agreement, Mode=OneWay, Source={x:Static spp:Strings.User_Agreement}}" Theme="{StaticResource CaptionTextBlockStyle}" />
</ui:HyperlinkButton>
<TextBlock Text="{Binding Path=Res.And, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<TextBlock Text="{Binding Path=Res.And, Mode=OneWay, Source={x:Static spp:Strings.And}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<ui:HyperlinkButton
Margin="5,0"
Padding="0"
Command="{Binding OpenHyperlink}"
CommandParameter="Privacy"
Cursor="Hand">
<TextBlock Text="{Binding Path=Res.User_Privacy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<TextBlock Text="{Binding Path=Res.User_Privacy, Mode=OneWay, Source={x:Static spp:Strings.User_Privacy}}" Theme="{StaticResource CaptionTextBlockStyle}" />
</ui:HyperlinkButton>
</WrapPanel>
@@ -255,7 +255,7 @@
<Button.CommandParameter>
<x:Int16>0</x:Int16>
</Button.CommandParameter>
<TextBlock Text="{Binding Path=Res.User_FastLogin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<TextBlock Text="{Binding Path=Res.User_FastLogin, Mode=OneWay, Source={x:Static spp:Strings.User_FastLogin}}" />
</Button>
<Separator
Height="18"
@@ -272,7 +272,7 @@
<Button.CommandParameter>
<x:Int16>1</x:Int16>
</Button.CommandParameter>
<TextBlock Text="{Binding Path=Res.User_PhoneLogin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<TextBlock Text="{Binding Path=Res.User_PhoneLogin, Mode=OneWay, Source={x:Static spp:Strings.User_PhoneLogin}}" />
</Button>
</StackPanel>
</Grid>
@@ -45,6 +45,18 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
this.ViewModel.Close();
}
}
Application.Current!.UrlsOpened += Current_UrlsOpened;
}
private async void Current_UrlsOpened(object? sender, UrlOpenedEventArgs e)
{
var loginUrl = e.Urls.Where(x => x.StartsWith(Constants.UrlSchemes.Login)).FirstOrDefault();
if (loginUrl != null)
{
//var token = loginUrl.TrimStart(Constants.UrlSchemes.Login);
var token = loginUrl.Substring(Constants.UrlSchemes.Login.Length, loginUrl.Length - Constants.UrlSchemes.Login.Length);
await ThirdPartyLoginHelper.OnMessageAsync(token);
}
}
protected override void OnDataContextChanged(EventArgs e)
@@ -59,6 +71,7 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
Application.Current!.UrlsOpened -= Current_UrlsOpened;
base.OnDetachedFromVisualTree(e);
if (DataContext is LoginOrRegisterWindowViewModel vm)
{
@@ -13,6 +13,7 @@
mc:Ignorable="d">
<Panel>
<TextBlock
Margin="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding Path=!!!Notices.Count, Mode=OneWay, Source={x:Static s:NoticeService.Current}}"
@@ -21,7 +22,7 @@
<DockPanel>
<!--<Button
Margin="0,0,0,5"
Content="{Binding Path=Res.Notice_AllMarkHasRead,Mode=OneWay,Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Notice_AllMarkHasRead,Mode=OneWay,Source={x:Static spp:Strings.Notice_AllMarkHasRead}}"
Cursor="Hand"
DockPanel.Dock="Top"
Theme="{StaticResource HyperlinkButton}" />-->
@@ -46,11 +46,24 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
}
if (AppSplashScreen.IsInitialized)
{
if (NavigationService.Instance.CurrnetPage == null)
{
INavigationService.Instance.Navigate(typeof(HomePage));
}
Task2.InBackground(async () =>
{
IViewModelManager.Instance.Get<HomePageViewModel>().GetServerContent();
await AdvertiseService.Current.RefrshAdvertiseAsync();
NoticeService.Current.GetNewsAsync();
});
try // 在主窗口显示时调用此函数检查是否需要显示新版本通知窗口
{
Ioc.Get_Nullable<IAppUpdateService>()?.OnMainOpenTryShowNewVersionWindow();
}
catch
{
}
}
}
}
@@ -78,134 +91,137 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
{
return Task.Run(
async () =>
{
{
if (!AppSplashScreen.IsInitialized)
{
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Start();
WatchTrace.Start();
#endif
var s = Instance;
if (s.IsMainProcess)
{
VersionTracking2.Track();
var s = Instance;
if (s.IsMainProcess)
{
VersionTracking2.Track();
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("VersionTracking2.Track");
WatchTrace.Record("VersionTracking2.Track");
#endif
Migrations.Up();
Migrations.Up();
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("Migrations.Up");
WatchTrace.Record("Migrations.Up");
#endif
// 仅在主进程中启动 IPC 服务端
IPCMainProcessService.Instance.Run();
// 仅在主进程中启动 IPC 服务端
IPCMainProcessService.Instance.Run();
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("IPC.StartServer");
WatchTrace.Record("IPC.StartServer");
#endif
}
}
LiveCharts.Configure(config =>
{
config
// registers SkiaSharp as the library backend
// REQUIRED unless you build your own
.AddSkiaSharp();
// adds the default supported types
// OPTIONAL but highly recommend
//.AddDefaultMappers()
LiveCharts.Configure(config =>
{
config
// registers SkiaSharp as the library backend
// REQUIRED unless you build your own
.AddSkiaSharp();
// adds the default supported types
// OPTIONAL but highly recommend
//.AddDefaultMappers()
// select a theme, default is Light
// OPTIONAL
//.AddDarkTheme()
// select a theme, default is Light
// OPTIONAL
//.AddDarkTheme()
// In case you need a non-Latin based font, you must register a typeface for SkiaSharp
config.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('汉')); // <- Chinese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('أ')) // <- Arabic // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('あ')) // <- Japanese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('헬')) // <- Korean // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('Ж')) // <- Russian // mark
// In case you need a non-Latin based font, you must register a typeface for SkiaSharp
config.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('汉')); // <- Chinese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('أ')) // <- Arabic // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('あ')) // <- Japanese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('헬')) // <- Korean // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('Ж')) // <- Russian // mark
if (App.Instance.Theme != AppTheme.FollowingSystem)
{
if (App.Instance.Theme == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
else
{
var dps = IPlatformService.Instance;
var isLightOrDarkTheme = dps.IsLightOrDarkTheme;
if (isLightOrDarkTheme.HasValue)
{
var mThemeFS = IApplication.GetAppThemeByIsLightOrDarkTheme(isLightOrDarkTheme.Value);
if (mThemeFS == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
}
});
if (App.Instance.Theme != AppTheme.FollowingSystem)
{
if (App.Instance.Theme == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
else
{
var dps = IPlatformService.Instance;
var isLightOrDarkTheme = dps.IsLightOrDarkTheme;
if (isLightOrDarkTheme.HasValue)
{
var mThemeFS = IApplication.GetAppThemeByIsLightOrDarkTheme(isLightOrDarkTheme.Value);
if (mThemeFS == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
}
});
AdvertiseService.Current.InitAdvertise();
NoticeService.Current.GetNewsAsync();
AdvertiseService.Current.InitAdvertise();
NoticeService.Current.GetNewsAsync();
var mainWindow = App.Instance.MainWindow;
mainWindow.ThrowIsNull();
var mainWindow = App.Instance.MainWindow;
mainWindow.ThrowIsNull();
#pragma warning disable SA1114 // Parameter list should follow declaration
IViewModelManager.Instance.InitViewModels(new TabItemViewModel[]
{
IViewModelManager.Instance.InitViewModels(new TabItemViewModel[]
{
new MenuTabItemViewModel("Welcome")
{
PageType = typeof(HomePage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
},
}, ImmutableArray.Create<TabItemViewModel>(
}, ImmutableArray.Create<TabItemViewModel>(
#if DEBUG
new MenuTabItemViewModel("Debug")
{
PageType = typeof(DebugPage),
IsResourceGet = false,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/bug.ico",
},
#endif
new MenuTabItemViewModel("Plugin_Store")
{
PageType = null,
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
},
new MenuTabItemViewModel("Settings")
{
PageType = typeof(SettingsPage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/settings.ico",
}));
new MenuTabItemViewModel("Debug")
{
PageType = typeof(DebugPage),
IsResourceGet = false,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/bug.ico",
},
#endif
new MenuTabItemViewModel("Plugin_Store")
{
PageType = null,
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
},
new MenuTabItemViewModel("Settings")
{
PageType = typeof(SettingsPage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/settings.ico",
}));
#pragma warning restore SA1114 // Parameter list should follow declaration
IViewModelManager.Instance.MainWindow.ThrowIsNull();
IViewModelManager.Instance.MainWindow.ThrowIsNull();
await Dispatcher.UIThread.InvokeAsync(() =>
{
mainWindow.DataContext = IViewModelManager.Instance.MainWindow;
s.InitSettingSubscribe();
await Dispatcher.UIThread.InvokeAsync(() =>
{
mainWindow.DataContext = IViewModelManager.Instance.MainWindow;
s.InitSettingSubscribe();
INavigationService.Instance.Navigate(typeof(HomePage));
});
INavigationService.Instance.Navigate(typeof(HomePage));
});
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("InitMainWindowViewModel");
WatchTrace.Record("InitMainWindowViewModel");
#endif
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Stop();
WatchTrace.Stop();
#endif
s.OnStartup();
await IViewModelManager.Instance.MainWindow.Initialize();
s.OnStartup();
await IViewModelManager.Instance.MainWindow.Initialize();
App.Instance.CompositeDisposable.Add(IViewModelManager.Instance.MainWindow);
App.Instance.CompositeDisposable.Add(SteamConnectService.Current.Dispose);
App.Instance.CompositeDisposable.Add(IViewModelManager.Instance.MainWindow);
App.Instance.CompositeDisposable.Add(SteamConnectService.Current.Dispose);
IsInitialized = true;
}, cancellationToken: token);
IsInitialized = true;
}
}, cancellationToken: token);
}
}
@@ -101,6 +101,9 @@
<PackageReference Include="Aigio.Any.AppCenter" />
<PackageReference Include="Aigio.Any.AppCenter.Analytics" />
<PackageReference Include="Aigio.Any.AppCenter.Crashes" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Runtime.Caching" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
@@ -6,8 +6,8 @@ public static partial class ServiceCollectionExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IServiceCollection AddDnsAnalysisService(this IServiceCollection services)
{
//services.AddSingleton<IDnsAnalysisService, DnsDohAnalysisService>();
services.AddSingleton<IDnsAnalysisService, DnsAnalysisServiceImpl>();
services.AddSingleton<DnsDohAnalysisService>();
services.AddSingleton<DnsAnalysisServiceImpl>();
return services;
}
}
@@ -89,6 +89,7 @@ static void ConfigureServices(IServiceCollection services)
services.AddHttpClient();
services.AddCommonHttpClientFactory();
services.AddSingleton<IHttpPlatformHelperService, HttpPlatformHelperConsoleService>();
services.AddDnsAnalysisService();
// 添加反向代理服务(子进程实现)
@@ -147,4 +148,11 @@ sealed class RepositorySecureStorage : Repository<KeyValuePair, string>, ISecure
var item = await FirstOrDefaultAsync(x => x.Id == key);
return item != null;
}
}
sealed class HttpPlatformHelperConsoleService : HttpPlatformHelperService
{
new const string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.51";
public override string UserAgent => DefaultUserAgent;
}
@@ -23,6 +23,20 @@ sealed partial class CertificateManagerImpl : ICertificateManager
public X509CertificatePackable RootCertificatePackable { get; set; }
readonly object lock_RootCertificatePackable = new();
byte[]? ICertificateManager.RootCertificatePackable
{
get
{
lock (lock_RootCertificatePackable)
{
RootCertificate ??= LoadRootCertificate();
}
return RootCertificate == default ? default : Serializable.SMP2(RootCertificatePackable);
}
}
/// <inheritdoc cref="ICertificateManager.PfxPassword"/>
public byte[]? PfxPassword { get; set; }
@@ -74,6 +88,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
}
}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
void SharedTrustRootCertificate()
{
if (RootCertificate == null)
@@ -180,7 +195,12 @@ sealed partial class CertificateManagerImpl : ICertificateManager
{
if (!File.Exists(filePath))
{
if (!GenerateCertificateUnlock(filePath)) return null;
if (!GenerateCertificateUnlock(filePath))
return null;
}
else if (RootCertificate == null)
{
RootCertificate = LoadRootCertificate();
}
return filePath;
}
@@ -222,44 +242,66 @@ sealed partial class CertificateManagerImpl : ICertificateManager
}
}
public void TrustRootCertificate()
bool? ICertificateManager.GenerateCertificate()
{
try
{
if (OperatingSystem.IsWindows())
{
try
{
SharedTrustRootCertificate();
}
#if DEBUG
catch (Exception e)
{
Log.Error(TAG, e, "SharedTrustRootCertificate Error");
}
#else
catch { }
#endif
}
else if (OperatingSystem.IsMacOS())
{
TrustRootCertificateMacOS();
}
else if (OperatingSystem.IsLinux() && !OperatingSystem.IsAndroid())
{
TrustRootCertificateLinux();
}
}
#if DEBUG
catch (Exception e)
{
e.LogAndShowT(TAG, msg: "PlatformTrustRootCertificateGuide Error");
}
#else
catch { }
#endif
return GenerateCertificate(null);
}
// public void TrustRootCertificate()
// {
// try
// {
// if (OperatingSystem.IsWindows())
// {
// try
// {
// SharedTrustRootCertificate();
// }
//#if DEBUG
// catch (Exception e)
// {
// Log.Error(TAG, e, "SharedTrustRootCertificate Error");
// }
//#else
// catch { }
//#endif
// }
// else if (OperatingSystem.IsMacOS())
// {
// TrustRootCertificateMacOS();
// }
// else if (OperatingSystem.IsLinux() && !OperatingSystem.IsAndroid())
// {
// TrustRootCertificateLinux();
// }
// }
//#if DEBUG
// catch (Exception e)
// {
// e.LogAndShowT(TAG, msg: "PlatformTrustRootCertificateGuide Error");
// }
//#else
// catch { }
//#endif
// }
public void TrustRootCertificate()
{
if (RootCertificate == null)
{
GenerateCertificate();
}
if (RootCertificate == null)
{
throw new ApplicationException(
"Could not install certificate as it is null or empty.");
}
ICertificateManager.Constants.TrustRootCertificate(GetCerFilePathGeneratedWhenNoFileExists, platformService, RootCertificate);
}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
[SupportedOSPlatform("macOS")]
void TrustRootCertificateMacOS()
{
@@ -271,6 +313,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
TrustRootCertificateMacOS();
}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
[SupportedOSPlatform("Linux")]
void TrustRootCertificateLinux()
{
@@ -390,12 +433,13 @@ sealed partial class CertificateManagerImpl : ICertificateManager
{
get
{
if (RootCertificate == null)
if (GetCerFilePathGeneratedWhenNoFileExists() == null) return false;
return IsCertificateInstalled(RootCertificatePackable);
var result = ICertificateManager.Constants.IsRootCertificateInstalled(this, platformService, RootCertificatePackable);
return result;
}
}
bool? ICertificateManager.IsRootCertificateInstalled2 => IsRootCertificateInstalled;
/// <summary>
/// 检查证书是否已安装并信任
/// </summary>
@@ -403,21 +447,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
/// <returns></returns>
bool IsCertificateInstalled(X509CertificatePackable packable)
{
X509Certificate2? certificate2 = packable;
if (certificate2 == null)
return false;
if (certificate2.NotAfter <= DateTime.Now)
return false;
if (OperatingSystem.IsAndroid() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return platformService.IsCertificateInstalled(packable);
}
else
{
using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
return store.Certificates.Contains(certificate2);
}
var result = ICertificateManager.Constants.IsCertificateInstalled(platformService, packable);
return result;
}
}
@@ -14,10 +14,18 @@ sealed class LazyCertificateManager : ICertificateManager
public byte[]? PfxPassword => impl().PfxPassword;
public X509CertificatePackable RootCertificatePackable { get => impl().RootCertificatePackable; set => impl().RootCertificatePackable = value; }
public byte[]? RootCertificatePackable { get => impl().RootCertificatePackable; }
[Obsolete]
public bool IsRootCertificateInstalled => impl().IsRootCertificateInstalled;
public bool? IsRootCertificateInstalled2 => impl().IsRootCertificateInstalled2;
public bool? GenerateCertificate()
{
return impl().GenerateCertificate();
}
public string? GetCerFilePathGeneratedWhenNoFileExists()
{
return impl().GetCerFilePathGeneratedWhenNoFileExists();
@@ -20,7 +20,7 @@ sealed class LifetimeHttpHandlerCleaner
/// <summary>
/// 获取或设置清理的时间间隔
/// 默认10s
/// 默认 10s
/// </summary>
public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromSeconds(10d);
@@ -14,7 +14,7 @@ sealed class RequestContext
public bool IsHttps { get; }
/// <summary>
/// 获取或设置Sni值
/// 获取或设置 Sni 值
/// </summary>
public TlsSniPattern TlsSniValue { get; }
@@ -24,11 +24,8 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
/// <returns></returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var uri = request.RequestUri;
if (uri == null)
{
var uri = request.RequestUri ??
throw new ApplicationException("The requested URI must be specified.");
}
// 请求上下文信息
var isHttps = uri.Scheme == Uri.UriSchemeHttps;
@@ -52,7 +49,7 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
/// 创建转发代理的 <see cref="HttpMessageHandler"/>
/// </summary>
/// <returns></returns>
SocketsHttpHandler CreateSocketsHttpHandler() => new()
HttpHandlerType CreateSocketsHttpHandler() => new()
{
Proxy = HttpNoProxy.Instance,
UseProxy = false,
@@ -20,12 +20,12 @@ sealed class CertService
/// <summary>
/// 获取 CER 证书文件路径
/// </summary>
public string CaCerFilePath => ReverseProxyService.CertificateManager.CerFilePath;
public string CaCerFilePath => ((ICertificateManager)ReverseProxyService.CertificateManager).CerFilePath;
/// <summary>
/// 获取 PFX 证书文件路径
/// </summary>
public string CaPfxFilePath => ReverseProxyService.CertificateManager.PfxFilePath;
public string CaPfxFilePath => ((ICertificateManager)ReverseProxyService.CertificateManager).PfxFilePath;
public CertService(
IMemoryCache serverCertCache,
@@ -69,10 +69,7 @@ sealed class CertService
/// <returns></returns>
public X509Certificate2? GetOrCreateServerCert(string? domain)
{
if (this.caCert == null)
{
this.caCert = new X509Certificate2(fileName: this.CaPfxFilePath, password: default(string));
}
caCert ??= new X509Certificate2(fileName: CaPfxFilePath, password: default(string));
var key = $"{nameof(CertService)}:{domain}";
return serverCertCache.GetOrCreate(key, GetOrCreateCert);
@@ -86,7 +83,7 @@ sealed class CertService
entry.SetAbsoluteExpiration(notAfter);
var subjectName = new X500DistinguishedName($"CN={domain}");
using var serverCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, GetDomains(), notBefore, notAfter);
using var serverCert = CertGenerator.CreateEndCertificate(caCert, subjectName, GetDomains(), notBefore, notAfter);
var serverCertPfx = serverCert.Export(X509ContentType.Pfx);
// 将生成的证书导出后重新创建一个
return new X509Certificate2(serverCertPfx);
@@ -96,14 +96,54 @@ sealed class HttpLocalRequestMiddleware
{
try
{
if (string.IsNullOrEmpty(context.Request.QueryString.Value))
var url = context.Request.QueryString.Value;
if (string.IsNullOrEmpty(url))
return;
url = HttpUtility.UrlDecode(url.Replace("?request=", ""));
Uri requestUri;
try
{
requestUri = new Uri(url);
}
catch
{
return;
}
var method = context.Request.Method;
var methodObj = method switch
{
"GET" => HttpMethod.Get,
"PUT" => HttpMethod.Put,
"POST" => HttpMethod.Post,
"DELETE" => HttpMethod.Delete,
"HEAD" => HttpMethod.Head,
"OPTIONS" => HttpMethod.Options,
"TRACE" => HttpMethod.Trace,
"PATCH" => HttpMethod.Patch,
"CONNECT" => HttpMethod.Connect,
_ => TryParse(method),
};
if (methodObj == null)
return;
var url = HttpUtility.UrlDecode(context.Request.QueryString.Value.Replace("?request=", ""));
string? cookie = context.Request.Headers["cookie-steamTool"];
if (string.IsNullOrEmpty(cookie))
static HttpMethod? TryParse(string method)
{
try
{
return new HttpMethod(method.ToUpperInvariant());
}
catch
{
}
return null;
}
StringValues cookie = context.Request.Headers["cookie-steamTool"];
if (StringValues.IsNullOrEmpty(cookie))
cookie = context.Request.Headers["Cookie"];
string? referer = context.Request.Headers["Referer-steamTool"];
StringValues referer = context.Request.Headers["Referer-steamTool"];
context.Response.Headers.AccessControlAllowOrigin = context.Request.Headers.Origin.Count == 0 ? "*" : context.Request.Headers.Origin;
context.Response.Headers.AccessControlAllowHeaders = "*";
@@ -119,26 +159,37 @@ sealed class HttpLocalRequestMiddleware
async Task SendAsync()
{
var method = context.Request.Method;
var hasContent = method != HttpMethods.Get && context.Request.ContentLength > 0;
var hasReqContent = !HttpMethods.IsGet(method) && context.Request.ContentLength > 0;
var req = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = new(method),
Content = hasContent ? new StreamContent(context.Request.Body) : null,
RequestUri = requestUri,
Method = methodObj,
Content = hasReqContent ? new StreamContent(context.Request.Body) : null,
};
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
if (cookie != null)
if (!StringValues.IsNullOrEmpty(cookie))
{
CookieHttpClient.CookieContainer.Add(new Cookie
{
CommentUri = req.RequestUri,
Domain = req.RequestUri.Host,
CommentUri = requestUri,
Domain = requestUri.Host,
Value = cookie,
});
// req.Headers.Add("Cookie", cookie);
}
if (referer != null) req.Headers.Referrer = new Uri(referer);
if (hasContent)
if (!StringValues.IsNullOrEmpty(referer))
{
try
{
var refererUri = new Uri(referer.ToString());
req.Headers.Referrer = refererUri;
}
catch
{
}
}
if (hasReqContent)
{
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType);
req.Content.Headers.ContentLength = context.Request.ContentLength;
@@ -157,12 +208,12 @@ sealed class HttpLocalRequestMiddleware
}
}
if (context.Request.Method == HttpMethods.Get)
if (HttpMethods.IsGet(context.Request.Method))
{
await SendAsync();
return;
}
else if (context.Request.Method == HttpMethods.Post)
else if (HttpMethods.IsPost(context.Request.Method))
{
if (context.Request.ContentLength > 0)
{
@@ -8,9 +8,7 @@ namespace BD.WTTS.Services.Implementation;
/// </summary>
sealed class HttpProxyMiddleware
{
private readonly HttpParser<HttpRequestHandler> httpParser = new();
private static readonly byte[] http200 = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n");
private static readonly byte[] http400 = Encoding.ASCII.GetBytes("HTTP/1.1 400 Bad Request\r\n\r\n");
readonly HttpParser<HttpRequestHandler> httpParser = new();
/// <summary>
/// 执行中间件
@@ -39,6 +37,7 @@ sealed class HttpProxyMiddleware
if (request.ProxyProtocol == ProxyProtocol.TunnelProxy)
{
input.AdvanceTo(consumed);
ReadOnlyMemory<byte> http200 = "HTTP/1.1 200 Connection Established\r\n\r\n"u8.ToArray();
await output.WriteAsync(http200, context.ConnectionClosed);
}
else
@@ -63,6 +62,7 @@ sealed class HttpProxyMiddleware
}
catch (Exception)
{
ReadOnlyMemory<byte> http400 = "HTTP/1.1 400 Bad Request\r\n\r\n"u8.ToArray();
await output.WriteAsync(http400, context.ConnectionClosed);
break;
}
@@ -120,7 +120,7 @@ sealed class HttpProxyMiddleware
void IHttpRequestLineHandler.OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
{
method = versionAndMethod.Method;
var host = Encoding.ASCII.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));
var host = Encoding.UTF8.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));
if (versionAndMethod.Method == AspNetCoreHttpMethod.Connect)
{
ProxyHost = HostString.FromUriComponent(host);
@@ -87,7 +87,12 @@ sealed partial class HttpReverseProxyMiddleware
context.Request.Headers.UserAgent = domainConfig.UserAgent.Replace("${origin}", context.Request.Headers.UserAgent, StringComparison.OrdinalIgnoreCase);
}
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty);
var forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = context.Request.Protocol.StartsWith("HTTP/2") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
};
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
if (error != ForwarderError.None)
{
@@ -282,8 +287,8 @@ sealed partial class HttpReverseProxyMiddleware
// var html_start_string = encoding.GetString(html_start.Span);
//#endif
await bodyCoreWriter.WriteAsync(html_start);
var script_xml_start = "<script type=\"text/javascript\" src=\"https://local.steampp.net/"u8.ToArray();
var script_xml_end = "\"></script>"u8.ToArray();
ReadOnlyMemory<byte> script_xml_start = "<script type=\"text/javascript\" src=\"https://local.steampp.net/"u8.ToArray();
ReadOnlyMemory<byte> script_xml_end = "\"></script>"u8.ToArray();
foreach (var script in scripts)
{
await bodyCoreWriter.WriteAsync(script_xml_start);
@@ -9,9 +9,9 @@ namespace BD.WTTS.Services.Implementation;
/// </summary>
abstract class TcpReverseProxyHandler : ConnectionHandler
{
private readonly IDomainResolver domainResolver;
private readonly DnsEndPoint endPoint;
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
readonly IDomainResolver domainResolver;
readonly DnsEndPoint endPoint;
readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
public TcpReverseProxyHandler(IDomainResolver domainResolver, DnsEndPoint endPoint)
{
@@ -5,9 +5,9 @@ using static BD.WTTS.Services.IDnsAnalysisService;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
sealed class DnsAnalysisServiceImpl
{
static readonly LookupClient lookupClient = new();
readonly LookupClient lookupClient = new();
public async Task<int> AnalysisHostnameTimeAsync(string url, CancellationToken cancellationToken = default)
{
@@ -24,7 +24,7 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
return 0;
}
public static IEnumerable<ARecord>? GetPingHostNameData(string url)
public IEnumerable<ARecord>? GetPingHostNameData(string url)
{
if (!string.IsNullOrEmpty(url))
{
@@ -119,11 +119,18 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
{
Retries = 1,
Timeout = TimeSpan.FromSeconds(1),
UseCache = false
UseCache = false,
};
var client = new LookupClient(options);
var response = await client.QueryServerAsync(new[] { IPAddress.Parse(PrimaryDNS_IPV6_Ali) }, IPV6_TESTDOMAIN, QueryType.AAAA, QueryClass.IN);
if (response.Answers.AaaaRecords().Any(s => s.Address.Equals(IPAddress.Parse(IPV6_TESTDOMAIN_SUCCESS))))
var response = await client.QueryServerAsync(
new[] {
IPAddress.Parse(PrimaryDNS_IPV6_Ali),
},
IPV6_TESTDOMAIN,
QueryType.AAAA,
QueryClass.IN);
if (response.Answers.AaaaRecords().Any(s =>
s.Address.Equals(IPAddress.Parse(IPV6_TESTDOMAIN_SUCCESS))))
{
return true;
}
@@ -133,9 +140,4 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
}
return false;
}
public Task<IPAddress?> GetHostIpv6AddresAsync()
{
return Task.FromResult<IPAddress?>(null);
}
}
@@ -0,0 +1,61 @@
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
sealed class DnsAnalysisServiceSwitchImpl : IDnsAnalysisService
{
readonly DnsAnalysisServiceImpl dnsAnalysisServiceImpl;
readonly DnsDohAnalysisService dnsDohAnalysisService;
readonly IReverseProxySettings settings;
public DnsAnalysisServiceSwitchImpl(
IReverseProxySettings settings,
DnsAnalysisServiceImpl dnsAnalysisServiceImpl,
DnsDohAnalysisService dnsDohAnalysisService)
{
this.settings = settings;
this.dnsAnalysisServiceImpl = dnsAnalysisServiceImpl;
this.dnsDohAnalysisService = dnsDohAnalysisService;
}
public async Task<int> AnalysisHostnameTimeAsync(string url, CancellationToken cancellationToken = default)
{
int r;
if (settings.UseDoh)
{
r = await dnsDohAnalysisService.DohAnalysisHostnameTimeAsync(
settings.CustomDohAddres, url, cancellationToken);
}
else
{
r = await dnsAnalysisServiceImpl.AnalysisHostnameTimeAsync(url, cancellationToken);
}
return r;
}
public IAsyncEnumerable<IPAddress> AnalysisDomainIpAsync(string hostNameOrAddress, IPAddress[]? dnsServers, bool isIPv6, CancellationToken cancellationToken = default)
{
IAsyncEnumerable<IPAddress> r;
if (settings.UseDoh)
{
r = dnsDohAnalysisService.DohAnalysisDomainIpAsync(settings.CustomDohAddres, hostNameOrAddress, isIPv6, true, cancellationToken);
}
else
{
r = dnsAnalysisServiceImpl.AnalysisDomainIpAsync(hostNameOrAddress, dnsServers, isIPv6, cancellationToken);
}
return r;
}
public async Task<string?> GetHostByIPAddressAsync(IPAddress ip)
{
var r = await dnsAnalysisServiceImpl.GetHostByIPAddressAsync(ip);
return r;
}
public async Task<bool> GetIsIpv6SupportAsync()
{
var r = await dnsAnalysisServiceImpl.GetIsIpv6SupportAsync();
return r;
}
}
@@ -7,12 +7,15 @@ using DnsQueryType = Ae.Dns.Protocol.Enums.DnsQueryType;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
public sealed class DnsDohAnalysisService : GeneralHttpClientFactory, IDnsAnalysisService
sealed class DnsDohAnalysisService : GeneralHttpClientFactory
{
const string TAG = "DnsDohAnalysisS";
protected override string? DefaultClientName => TAG;
readonly ConcurrentDictionary<(Uri dohAddresUri, string hostNameOrAddressWithQueryType), IPAddressResult> Cache = new();
readonly ConcurrentDictionary<Uri, DnsHttpClient> dnsClients = new();
public DnsDohAnalysisService(
ILoggerFactory loggerFactory,
IHttpPlatformHelperService http_helper,
@@ -21,13 +24,54 @@ public sealed class DnsDohAnalysisService : GeneralHttpClientFactory, IDnsAnalys
{
}
public async Task<int> AnalysisHostnameTimeAsync(string url, CancellationToken cancellationToken = default)
public const string DefaultDohAddres = Dnspod_DohAddres;
static Uri GetDohAddres(string? dohAddres)
{
if (string.IsNullOrWhiteSpace(dohAddres))
return new Uri(DefaultDohAddres);
Uri dohAddresUri;
try
{
dohAddresUri = new Uri(dohAddres);
}
catch
{
dohAddresUri = new Uri(DefaultDohAddres);
}
return dohAddresUri;
}
DnsHttpClient GetDnsHttpClient(string? dohAddres)
{
var dohAddresUri = GetDohAddres(dohAddres);
return GetDnsHttpClient(dohAddresUri);
}
DnsHttpClient GetDnsHttpClient(Uri dohAddresUri)
{
if (dnsClients.TryGetValue(dohAddresUri, out var value))
return value;
var client = CreateClient($"{TAG}_{dohAddresUri}", HttpHandlerCategory.Default);
//var handler = new HttpClientHandler
//{
// UseCookies = false,
// UseProxy = false,
// Proxy = HttpNoProxy.Instance,
//};
//var client = new HttpClient(handler);
client.BaseAddress = dohAddresUri;
var dnsClient = new DnsHttpClient(client);
dnsClients.TryAdd(dohAddresUri, dnsClient);
return dnsClient;
}
public async Task<int> DohAnalysisHostnameTimeAsync(string? dohAddres, string url, CancellationToken cancellationToken = default)
{
if (!string.IsNullOrEmpty(url))
{
var client = CreateClient(null, HttpHandlerCategory.Default);
client.BaseAddress = new Uri(Dnspod_DohAddres);
IDnsClient dnsClient = new DnsHttpClient(client);
var dnsClient = GetDnsHttpClient(dohAddres);
var queryType = DnsQueryType.A;
var query = DnsQueryFactory.CreateQuery(url, queryType);
@@ -39,60 +83,123 @@ public sealed class DnsDohAnalysisService : GeneralHttpClientFactory, IDnsAnalys
return 0;
}
/// <summary>
/// 解析域名 IP 地址
/// </summary>
/// <param name="hostNameOrAddress">要解析的主机名或 IP 地址</param>
/// <param name="isIPv6"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public IAsyncEnumerable<IPAddress> AnalysisDomainIpAsync(string hostNameOrAddress, bool isIPv6, CancellationToken cancellationToken = default)
=> DohAnalysisDomainIpAsync(hostNameOrAddress, default, isIPv6, cancellationToken);
///// <summary>
///// DNS 解析查询最大递归查询次数
///// </summary>
//const byte max_recursion_dns_query = 20;
static readonly TimeSpan TTL = TimeSpan.FromMinutes(9.9D);
/// <summary>
/// 解析域名 IP 地址
/// DNS 解析查询
/// </summary>
/// <param name="hostNameOrAddress">要解析的主机名或 IP 地址</param>
/// <param name="cancellationToken"></param>
/// <param name="dohAddresUri">DoH 地址</param>
/// <param name="hostNameOrAddress">解析的地址</param>
/// <param name="queryType">查询类型</param>
/// <param name="count">当前递归的次数</param>
/// <param name="domainHistory">返回的历史域名,避免死循环</param>
/// <param name="cancellationToken">取消标记</param>
/// <returns></returns>
public IAsyncEnumerable<IPAddress> AnalysisDomainIpAsync(string hostNameOrAddress, CancellationToken cancellationToken = default)
=> DohAnalysisDomainIpAsync(hostNameOrAddress, default, default, cancellationToken);
async Task<IPAddress[]> Query(
Uri dohAddresUri,
string hostNameOrAddress,
DnsQueryType queryType = DnsQueryType.A,
//byte count = 1,
//HashSet<string>? domainHistory = null,
CancellationToken cancellationToken = default)
{
//if (count <= max_recursion_dns_query)
//{
// domainHistory ??= new();
var query = DnsQueryFactory.CreateQuery(hostNameOrAddress, queryType);
var dnsClient = GetDnsHttpClient(dohAddresUri);
var answer = await dnsClient.Query(query, cancellationToken);
/// <summary>
/// 解析域名 IP 地址
/// </summary>
/// <param name="hostNameOrAddress">要解析的主机名或 IP 地址</param>
/// <param name="dnsServers">自定义 DNS 服务器,可选的值有 <see cref="DNS_Alis"/>, <see cref="DNS_114s"/>, <see cref="DNS_Cloudflares"/>, <see cref="DNS_Dnspods"/>, <see cref="DNS_Googles"/></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public IAsyncEnumerable<IPAddress> AnalysisDomainIpAsync(string hostNameOrAddress, string? dnsServers, CancellationToken cancellationToken = default)
=> DohAnalysisDomainIpAsync(hostNameOrAddress, dnsServers, default, cancellationToken);
var dnsResources = answer.Answers.Select(x => x.Resource);
var iPAddresses = dnsResources.OfType<DnsIpAddressResource>().Select(x => x.IPAddress).ToArray();
if (iPAddresses.Any())
return iPAddresses;
//var domainRes = dnsResources.OfType<DnsDomainResource>().FirstOrDefault();
//if (domainRes != null)
//{
// var domain = domainRes.Domain;
// if (!string.IsNullOrWhiteSpace(domain))
// {
// //if (domainHistory.Add(domain))
// //{
// // CNAME 递归解析
// iPAddresses = await Query(dohAddresUri, domain, queryType,/* ++count, domainHistory,*/ cancellationToken);
// //}
// }
//}
////}
return Array.Empty<IPAddress>();
}
readonly record struct IPAddressResult
{
public IPAddress[] IPAddresses { get; init; }
public DateTimeOffset CreationTime { get; init; }
}
/// <summary>
/// DOH 解析域名 IP 地址
/// </summary>
/// <param name="hostNameOrAddress">要解析的主机名或 IP 地址</param>
/// <param name="dnsServers">自定义 DNS 服务器,可选的值有 <see cref="DNS_Alis"/>, <see cref="DNS_114s"/>, <see cref="DNS_Cloudflares"/>, <see cref="DNS_Dnspods"/>, <see cref="DNS_Googles"/></param>
/// <param name="isIPv6"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<IPAddress> DohAnalysisDomainIpAsync(string hostNameOrAddress, string? dnsServers, bool isIPv6, [EnumeratorCancellation] CancellationToken cancellationToken = default)
public async IAsyncEnumerable<IPAddress> DohAnalysisDomainIpAsync(string? dohAddres, string hostNameOrAddress, bool isIPv6, bool useCache = true, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var client = CreateClient();
client.BaseAddress = new Uri(string.IsNullOrEmpty(dnsServers) ? Dnspod_DohAddres : dnsServers);
IDnsClient dnsClient = new DnsHttpClient(client);
var queryType = isIPv6 ? DnsQueryType.AAAA : DnsQueryType.A;
var query = DnsQueryFactory.CreateQuery(hostNameOrAddress, queryType);
var dohAddresUri = GetDohAddres(dohAddres);
var answer = await dnsClient.Query(query, cancellationToken);
foreach (var x in answer.Answers)
(Uri dohAddresUri, string hostNameOrAddressWithQueryType) cacheKey = useCache ? GetCacheKey() : default;
(Uri dohAddresUri, string hostNameOrAddressWithQueryType) GetCacheKey()
{
if (x.Resource is DnsIpAddressResource ipAddressResource)
var hostNameOrAddressWithQueryType = string.Concat(hostNameOrAddress, ":", (short)queryType);
var cacheKey = (dohAddresUri, hostNameOrAddressWithQueryType);
return cacheKey;
}
if (useCache)
{
if (Cache.TryGetValue(cacheKey, out var dns_list))
{
yield return ipAddressResource.IPAddress;
if ((DateTimeOffset.Now - dns_list.CreationTime) <= TTL)
{
foreach (var item in dns_list.IPAddresses)
{
yield return item;
}
}
}
}
var result = await Query(dohAddresUri, hostNameOrAddress, queryType, cancellationToken: cancellationToken);
if (isIPv6 && !result.Any())
{
// IPv6 返回空时使用 IPv4 解析
result = await Query(dohAddresUri, hostNameOrAddress, DnsQueryType.A, cancellationToken: cancellationToken);
}
if (useCache)
{
Cache[cacheKey] = new()
{
IPAddresses = result,
CreationTime = DateTimeOffset.Now,
};
}
foreach (var item in result)
{
yield return item;
}
}
}
@@ -6,14 +6,15 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
protected const string TAG = "ReverseProxyS";
public ReverseProxyServiceImpl(
IDnsAnalysisService dnsAnalyses)
DnsAnalysisServiceImpl dnsAnalysisServiceImpl,
DnsDohAnalysisService dnsDohAnalysisService)
{
DnsAnalysis = dnsAnalyses;
DnsAnalysis = new DnsAnalysisServiceSwitchImpl(this, dnsAnalysisServiceImpl, dnsDohAnalysisService);
}
public IDnsAnalysisService DnsAnalysis { get; }
public abstract ICertificateManager CertificateManager { get; }
public abstract CertificateManagerImpl CertificateManager { get; }
/// <summary>
/// 获取或设置当前根证书
@@ -84,6 +85,10 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
public bool IsSupportIpv6 { get; set; }
public bool UseDoh { get; set; }
public string? CustomDohAddres { get; set; }
/// <summary>
/// 获取一个随机的未使用的端口
/// </summary>
@@ -164,20 +169,18 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
}
}
CheckRootCertificate();
reverseProxySettings.SetValue(this);
if (!CertificateManager.IsRootCertificateInstalled)
{
var isOk = CertificateManager.SetupRootCertificate();
if (!isOk)
{
return StartProxyResultCode.SetupRootCertificateFail;
}
}
return await StartProxyImpl();
}
protected virtual void CheckRootCertificate()
{
}
protected abstract Task<StartProxyResult> StartProxyImpl();
// IDisposable
@@ -15,13 +15,14 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
public YarpReverseProxyServiceImpl(
IPCSubProcessService ipc,
IDnsAnalysisService dnsAnalysis,
ICertificateManager certificateManager) : base(dnsAnalysis)
DnsAnalysisServiceImpl dnsAnalysisServiceImpl,
DnsDohAnalysisService dnsDohAnalysisService,
ICertificateManager certificateManager) : base(dnsAnalysisServiceImpl, dnsDohAnalysisService)
{
this.ipc = ipc;
platformService = GetIPCService<IPCPlatformService>(nameof(platformService));
toast = GetIPCService<IPCToastService>(nameof(toast));
CertificateManager = certificateManager;
CertificateManager = (CertificateManagerImpl)certificateManager;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -35,10 +36,20 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
return ipcService;
}
public override ICertificateManager CertificateManager { get; }
public override CertificateManagerImpl CertificateManager { get; }
public override bool ProxyRunning => app != null;
protected override void CheckRootCertificate()
{
if (OperatingSystem.IsWindows())
{
ICertificateManager.Constants.CheckRootCertificate(
platformService,
CertificateManager);
}
}
protected override Task<StartProxyResult> StartProxyImpl() => Task.FromResult(StartProxyCore());
StartProxyResult StartProxyCore()
@@ -1,6 +1,7 @@
// ReSharper disable once CheckNamespace
using dotnetCampus.Ipc.CompilerServices.Attributes;
using Const = BD.WTTS.Constants;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services;
/// <summary>
@@ -12,6 +13,198 @@ public interface ICertificateManager
static class Constants
{
public static ICertificateManager Instance => Ioc.Get<ICertificateManager>(); // 因为 Ipc 服务接口的原因,不能将此属性放在非嵌套类上
internal static bool IsCertificateInstalled(
IPCPlatformService platformService,
X509CertificatePackable packable)
{
X509Certificate2? certificate2 = packable;
if (certificate2 == null)
return false;
if (certificate2.NotAfter <= DateTime.Now)
return false;
if (OperatingSystem.IsAndroid() ||
OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
{
return platformService.IsCertificateInstalled(packable);
}
else
{
using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
return store.Certificates.Contains(certificate2);
}
}
internal static bool IsRootCertificateInstalled(
ICertificateManager certificateManager,
IPCPlatformService platformService,
X509CertificatePackable packable)
{
if (EqualityComparer<X509CertificatePackable>.Default.Equals(packable, default))
{
var filePath = certificateManager.GetCerFilePathGeneratedWhenNoFileExists();
if (filePath == null)
return false;
}
var isInstalled = IsCertificateInstalled(platformService, packable);
return isInstalled;
}
internal static void TrustRootCertificate(
Func<string?> getCerFilePath,
IPCPlatformService platformService,
X509Certificate2 certificate2)
{
if (OperatingSystem.IsWindows())
{
using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadWrite);
var findCerts = store.Certificates.Find(X509FindType.FindByThumbprint, certificate2.Thumbprint, true);
if (!findCerts.Any())
{
store.Add(certificate2);
}
}
catch (Exception e)
{
Log.Error(nameof(ICertificateManager), e,
"Please manually install the CA certificate to a trusted root certificate authority.");
}
}
else if (OperatingSystem.IsMacOS())
{
var cerFilePath = getCerFilePath();
if (cerFilePath == null)
return;
void TrustRootCertificateMacOS()
{
var result = platformService.TrustRootCertificateAsync(cerFilePath);
if (result.HasValue && !result.Value)
{
TrustRootCertificateMacOS();
}
}
TrustRootCertificateMacOS();
}
else if (OperatingSystem.IsLinux())
{
var cerFilePath = getCerFilePath();
if (cerFilePath == null)
return;
void TrustRootCertificateLinux()
{
var result = platformService.TrustRootCertificateAsync(cerFilePath);
try
{
// 部分系统还是只能手动导入浏览器
Browser2.Open(Const.Urls.OfficialWebsite_LiunxSetupCer);
}
catch
{
}
if (result.HasValue && !result.Value)
getCerFilePath();
}
TrustRootCertificateLinux();
}
}
/// <summary>
/// 检查根证书,生成,信任,减少 Ipc 往返次数
/// </summary>
/// <param name="certificateManager"></param>
/// <returns></returns>
internal static StartProxyResultCode CheckRootCertificate(
IPCPlatformService platformService,
ICertificateManager certificateManager)
{
string? cerFilePathLazy = null;
string? GetCerFilePath()
{
if (cerFilePathLazy != null)
return cerFilePathLazy;
cerFilePathLazy = certificateManager.GetCerFilePathGeneratedWhenNoFileExists();
return cerFilePathLazy;
}
X509CertificatePackable GetRootCertificatePackable()
{
#if APP_REVERSE_PROXY
return ((CertificateManagerImpl)certificateManager).RootCertificatePackable;
#else
try
{
var packableBytes = certificateManager.RootCertificatePackable;
if (packableBytes.Any_Nullable())
{
var result = Serializable.DMP2<X509CertificatePackable>(packableBytes);
return result;
}
}
catch
{
}
return default;
#endif
}
// 获取证书数据
var packable = GetRootCertificatePackable();
var packable_eq = EqualityComparer<X509CertificatePackable>.Default;
if (packable_eq.Equals(packable, default)) // 证书为默认值时
{
// 生成证书
var cerFilePath = GetCerFilePath();
if (cerFilePath == null)
return StartProxyResultCode.GenerateCerFilePathFail; // 生成证书 Cer 文件路径失败
// 再次获取证书检查是否为默认值
packable = GetRootCertificatePackable();
if (packable_eq.Equals(packable, default))
{
return StartProxyResultCode.GetCertificatePackableFail; // 获取证书数据失败
}
}
X509Certificate2? certificate2 = packable;
if (certificate2 == null)
return StartProxyResultCode.GetX509Certificate2Fail;
bool IsCertificateInstalled()
{
// 直接传递平台服务,避免 IPC 调用开销
var result = ICertificateManager.Constants.IsCertificateInstalled(
platformService,
packable);
return result;
}
var isRootCertificateInstalled = IsCertificateInstalled();
if (!isRootCertificateInstalled)
{
// 安装证书
ICertificateManager.Constants.TrustRootCertificate(
GetCerFilePath, platformService, certificate2);
// 安装后检查证书是否已成功安装
isRootCertificateInstalled = IsCertificateInstalled();
if (!isRootCertificateInstalled)
return StartProxyResultCode.TrustRootCertificateFail;
}
return StartProxyResultCode.Ok;
}
}
/// <summary>
@@ -36,7 +229,7 @@ public interface ICertificateManager
/// <summary>
/// 获取当前 Root 证书,<see cref="X509CertificatePackable"/> 类型可隐式转换为 <see cref="X509Certificate2"/>
/// </summary>
X509CertificatePackable RootCertificatePackable { get; set; }
byte[]? RootCertificatePackable { get; }
/// <summary>
/// 获取 Cer 证书路径,当不存在时生成文件后返回路径
@@ -64,5 +257,18 @@ public interface ICertificateManager
/// <summary>
/// 当前根证书是否已安装并信任
/// </summary>
[Obsolete("use IsRootCertificateInstalled2")]
bool IsRootCertificateInstalled { get; }
/// <summary>
/// 当前根证书是否已安装并信任
/// </summary>
bool? IsRootCertificateInstalled2 { get; }
/// <summary>
/// (✔️🔒)生成 Root 证书
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
bool? GenerateCertificate();
}
@@ -163,6 +163,10 @@ public partial interface IReverseProxySettings
/// 是否支持 IPv6
/// </summary>
bool IsSupportIpv6 { get; set; }
bool UseDoh { get; set; }
string? CustomDohAddres { get; set; }
}
[MP2Obj(SerializeLayout.Explicit)]
@@ -206,7 +210,11 @@ public readonly partial record struct ReverseProxySettings(
[property:MP2Key(18)]
string? ProxyDNS,
[property:MP2Key(19)]
bool IsSupportIpv6)
bool IsSupportIpv6,
[property:MP2Key(20)]
bool UseDoh,
[property:MP2Key(21)]
string? CustomDohAddres)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal IPAddress GetProxyIp() => GetProxyIp(ProxyIp);
@@ -257,6 +265,8 @@ public readonly partial record struct ReverseProxySettings(
settings.TwoLevelAgentPassword = TwoLevelAgentPassword;
settings.ProxyDNS = GetProxyDNS();
settings.IsSupportIpv6 = IsSupportIpv6;
settings.UseDoh = UseDoh;
settings.CustomDohAddres = CustomDohAddres;
}
#endif
}
@@ -271,14 +281,37 @@ public enum StartProxyResultCode : byte
/// </summary>
IpcCallFailOrDefault = default,
/// <summary>
/// 生成证书失败
/// </summary>
GenerateCertificateFail,
/// <summary>
/// 生成 Cer 文件路径失败
/// </summary>
GenerateCerFilePathFail,
/// <summary>
/// 获取证书数据失败
/// </summary>
GetCertificatePackableFail,
/// <summary>
/// 信任证书失败
/// </summary>
TrustRootCertificateFail,
GetX509Certificate2Fail,
/// <summary>
/// 成功
/// </summary>
Ok = 121,
/// <summary>
/// 证书安装失败,或未信任
/// 证书安装或生成失败,或未信任
/// </summary>
[Obsolete("use GenerateCertificateFail/TrustRootCertificateFail")]
SetupRootCertificateFail,
/// <summary>
@@ -294,7 +327,7 @@ public enum StartProxyResultCode : byte
/// <summary>
/// 绑定端口错误
/// </summary>
BindPortError
BindPortError,
}
/// <summary>
@@ -9,11 +9,19 @@ public partial interface IDnsAnalysisService // 公共常量定义
#region DNS 常量
const string DNS_Ali_DohAddres = "https://dns.alidns.com";
const string DNS_Ali_DohAddres2 = "https://223.6.6.6/dns-query";
const string DNS_Ali_DohAddres3 = "https://223.5.5.5/dns-query";
const string Dnspod_DohAddres = "https://1.12.12.12";
const string Dnspod_DohAddres2 = "https://doh.pub";
const string Dnspod_DohAddres3 = "https://120.53.53.53/dns-query";
const string Google_DohAddres = "https://dns.google";
const string Cloudflare_DohAddres = "https://cloudflare-dns.com";
const string DohAddres_360 = "https://doh.360.cn/dns-query";
const string TUNA_DohAddres = "https://101.6.6.6:8443/dns-query";
protected const string PrimaryDNS_IPV6_Ali = "2400:3200::1";
const string PrimaryDNS_IPV6_Ali = "2400:3200::1";
const string PrimaryDNS_Ali = "223.5.5.5";
const string SecondaryDNS_Ali = "223.6.6.6";
@@ -38,6 +46,9 @@ public partial interface IDnsAnalysisService // 公共常量定义
static readonly IPAddress[] DNS_Googles = new[] { NameServer.GooglePublicDns.Address, NameServer.GooglePublicDns2.Address };
static readonly IPAddress[] DNS_Cloudflares = new[] { NameServer.Cloudflare.Address, NameServer.Cloudflare2.Address };
static readonly Uri[] DoH_Alis = new[] { new Uri(DNS_Ali_DohAddres), new Uri(DNS_Ali_DohAddres2), new Uri(DNS_Ali_DohAddres3) };
static readonly Uri[] DoH_Dnspods = new[] { new Uri(Dnspod_DohAddres), new Uri(Dnspod_DohAddres2), new Uri(Dnspod_DohAddres3) };
private static class NameServer
{
public static readonly IPEndPoint GooglePublicDns = new(IPAddress.Parse(SecondaryDNS_Google), 53);
@@ -7,10 +7,8 @@ partial interface IDnsAnalysisService // 服务函数与 protected 定义
{
protected const string TAG = "DnsAnalysisS";
static IDnsAnalysisService Instance => Ioc.Get<IDnsAnalysisService>();
protected const string IPV6_TESTDOMAIN = "ipv6.rmbgame.net";
protected const string IPV6_TESTDOMAIN_SUCCESS = PrimaryDNS_IPV6_Ali;
const string IPV6_TESTDOMAIN = "ipv6.rmbgame.net";
const string IPV6_TESTDOMAIN_SUCCESS = PrimaryDNS_IPV6_Ali;
async Task<long> PingHostnameAsync(string url)
{
@@ -76,19 +74,6 @@ partial interface IDnsAnalysisService // 服务函数与 protected 定义
}
}
/// <summary>
/// DOH 解析域名 IP 地址
/// </summary>
/// <param name="hostNameOrAddress">要解析的主机名或 IP 地址</param>
/// <param name="dnsServers">自定义 DNS 服务器,可选的值有 <see cref="DNS_Alis"/>, <see cref="DNS_114s"/>, <see cref="DNS_Cloudflares"/>, <see cref="DNS_Dnspods"/>, <see cref="DNS_Googles"/></param>
/// <param name="isIPv6"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IAsyncEnumerable<IPAddress>? DohAnalysisDomainIpAsync(string hostNameOrAddress, string? dnsServers, bool isIPv6, CancellationToken cancellationToken = default)
{
return null;
}
async Task<string?> GetHostByIPAddressAsync(IPAddress ip)
{
var hostEntry = await Dns.GetHostEntryAsync(ip);
@@ -11,6 +11,7 @@ public sealed class Script : IEntity<int>
public const string TableName = "1B2D820C";
public const string ColumnName_Id = "E386BC36";
public const string ColumnName_Enable = "6DB11594";
public const string ColumnName_CachePath = "997C6B59";
string DebuggerDisplay() => $"{Name}, {Id}";
@@ -58,7 +59,7 @@ public sealed class Script : IEntity<int>
/// <summary>
/// 缓存文件地址
/// </summary>
[Column("997C6B59")]
[Column(ColumnName_CachePath)]
[SQLiteNotNull]
public string CachePath { get; set; } = string.Empty;
@@ -15,4 +15,6 @@ public interface IScriptRepository : IRepository<Script, int>
Task<IList<Script>> GetAllAsync(CancellationToken cancellationToken = default);
Task SaveScriptEnableAsync(ScriptDTO item, CancellationToken cancellationToken = default);
Task SaveScriptCachePathAsync(ScriptDTO item, CancellationToken cancellationToken);
}
@@ -38,6 +38,22 @@ internal sealed class ScriptRepository : Repository<Script, int>, IScriptReposit
}, cancellationToken: cancellationToken).ConfigureAwait(false);
}
public async Task SaveScriptCachePathAsync(ScriptDTO item, CancellationToken cancellationToken)
{
var dbConnection = await GetDbConnection().ConfigureAwait(false);
await AttemptAndRetry(async t =>
{
t.ThrowIfCancellationRequested();
const string sql_ =
$"{SQLStrings.Update}[{Script.TableName}] " +
$"set [{Script.ColumnName_CachePath}] = '{{0}}' " +
$"where [{Script.ColumnName_Id}] = {{1}}";
var sql = string.Format(sql_, item.CachePath, item.LocalId);
var r = await dbConnection.ExecuteAsync(sql);
return r;
}, cancellationToken: cancellationToken).ConfigureAwait(false);
}
public async Task<IList<Script>> GetAllAsync(CancellationToken cancellationToken)
{
var dbConnection = await GetDbConnection().ConfigureAwait(false);
@@ -366,11 +366,30 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
static IApiRsp OK_Script_DeleteSuccess() => ApiRspHelper.Ok(AppResources.Script_DeleteSuccess);
}
/// <summary>
/// 版本升级 检查数据库 数据 是否正确 不正确删除重新打包缓存文件
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
public async Task<IEnumerable<ScriptDTO>> CheckFiles(IEnumerable<ScriptDTO> list)
{
var scripts = list.ToList();
foreach (var item in list)
{
//检查缓存文件夹如果不是 IScriptManager.DirName_Build 替换成 IScriptManager.DirName_Build 开头
if (!item.CachePath.StartsWith($"{IScriptManager.DirName_Build}{Path.DirectorySeparatorChar}"))
{
item.CachePath = Path.Combine(IScriptManager.DirName_Build, item.FileName!);
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
if (File.Exists(cachePath))
{
File.Delete(cachePath);
}
await TryReadFileAsync(item, true);
//清理 脚本内容 后续 IPC 传递 插件进程读取。
item.Content = string.Empty;
await scriptRepository.SaveScriptCachePathAsync(item, default);
}
if (!CheckFile(item))
{
var temp = await DeleteScriptAsync(item);
@@ -399,8 +418,9 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
/// 修改为仅尝试判断文件是否存在
/// </summary>
/// <param name="item"></param>
/// <param name="isReadContent"></param>
/// <returns></returns>
public async Task<ScriptDTO> TryReadFileAsync(ScriptDTO item)
public async Task<ScriptDTO> TryReadFileAsync(ScriptDTO item, bool isReadContent = false)
{
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
if (File.Exists(cachePath))
@@ -413,7 +433,10 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
var infoPath = Path.Combine(Plugin.Instance.AppDataDirectory, item.FilePath);
if (File.Exists(infoPath))
{
//item.Content = File.ReadAllText(infoPath);
if (isReadContent)
{
item.Content = File.ReadAllText(infoPath);
}
if (!await BuildScriptAsync(item, fileInfo, item.IsCompile))
{
toast.Show(ToastIcon.Error, AppResources.Script_ReadFileError_.Format(item.Name));
@@ -11,7 +11,21 @@ partial class ProxyService
/// 启动代理服务
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
async Task<OperateProxyServiceResult> StartProxyServiceAsync()
{
try
{
var result = await StartProxyServiceCoreAsync();
return result;
}
catch (Exception ex)
{
return ex;
}
}
async Task<OperateProxyServiceResult> StartProxyServiceCoreAsync()
{
IReadOnlyCollection<AccelerateProjectDTO>? proxyDomains = EnableProxyDomains;
@@ -47,6 +61,8 @@ partial class ProxyService
string? twoLevelAgentPassword = ProxySettings.TwoLevelAgentPassword.Value;
string? proxyDNS = ProxySettings.ProxyMasterDns.Value;
bool isSupportIpv6 = await RefreshIpv6Support();
bool useDoh = ProxySettings.UseDoh.Value;
string? customDohAddres = ProxySettings.CustomDohAddres.Value;
Lazy<IPAddress> proxyIp_ = new(() => ReverseProxySettings.GetProxyIp(proxyIp));
void SetProxyIp(IPAddress proxyIPAddress)
@@ -169,12 +185,23 @@ partial class ProxyService
break;
}
if (!OperatingSystem.IsWindows())
{
var checkRootCertificateCode = ICertificateManager.Constants.CheckRootCertificate(
platformService,
ICertificateManager.Constants.Instance);
if (checkRootCertificateCode != StartProxyResultCode.Ok)
{
return $"CheckRootCertificate, ErrCode: {checkRootCertificateCode}";
}
}
ReverseProxySettings reverseProxySettings = new(proxyDomains, scripts,
isEnableScript, isOnlyWorkSteamBrowser, proxyPort,
proxyIp, proxyMode, isProxyGOG, onlyEnableProxyScript,
enableHttpProxyToHttps, socks5ProxyEnable, socks5ProxyPortId,
twoLevelAgentEnable, twoLevelAgentProxyType, twoLevelAgentIp,
twoLevelAgentPortId, twoLevelAgentUserName, twoLevelAgentPassword, proxyDNS, isSupportIpv6);
twoLevelAgentPortId, twoLevelAgentUserName, twoLevelAgentPassword, proxyDNS, isSupportIpv6, useDoh, customDohAddres);
byte[] reverseProxySettings_ = Serializable.SMP2(reverseProxySettings);
var startProxyResult = await reverseProxyService.StartProxyAsync(reverseProxySettings_);
if (startProxyResult)
@@ -249,7 +276,21 @@ partial class ProxyService
/// <summary>
/// 停止代理服务
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
async Task<OperateProxyServiceResult> StopProxyServiceAsync(bool isExit = false)
{
try
{
var result = await StopProxyServiceCoreAsync(isExit);
return result;
}
catch (Exception ex)
{
return ex;
}
}
async Task<OperateProxyServiceResult> StopProxyServiceCoreAsync(bool isExit)
{
bool callSet = true;
#if WINDOWS
@@ -309,10 +350,12 @@ partial class ProxyService
/// 操作代理服务返回结果
/// </summary>
/// <param name="Error"></param>
readonly record struct OperateProxyServiceResult(string? Error)
readonly record struct OperateProxyServiceResult(string? Error, Exception? Exception = null)
{
public static implicit operator OperateProxyServiceResult(string error) => new(error);
public static implicit operator OperateProxyServiceResult(Exception exception) => new(null, exception);
/// <summary>
/// 将返回结果使用 Toast 显示并且返回当前应设置的代理状态
/// </summary>
@@ -326,6 +369,11 @@ partial class ProxyService
Toast.Show(ToastIcon.Success, Strings.CommunityFix_StartProxySuccess);
proxyStatus = true;
}
else if (Exception != null)
{
Exception.LogAndShowT(nameof(ProxyService));
proxyStatus = false;
}
else
{
Toast.Show(ToastIcon.Error, Error);
@@ -343,6 +391,11 @@ partial class ProxyService
Toast.Show(ToastIcon.Error, Error);
proxyStatus = true;
}
else if (Exception != null)
{
Exception.LogAndShowT(nameof(ProxyService));
proxyStatus = true;
}
return proxyStatus;
}
}
@@ -50,6 +50,8 @@ public sealed partial class ProxyService
if (proxyStatusLeft != proxyStatusRight)
{
ProxyStatus = proxyStatusRight;
//UpdateProxyTrayMenuItems();
//if (Steamworks.SteamClient.IsValid)
//{
// if (ProxyStatus)
@@ -229,34 +231,87 @@ public sealed partial class ProxyService
public async Task InitializeAsync()
{
//reverseProxyService.StopProxy();
await InitializeAccelerateAsync();
await InitializeScriptAsync();
await RefreshIpv6Support();
if (IsProgramStartupRunProxy())
try
{
if (platformService.UsePlatformForegroundService)
await InitializeAccelerateAsync();
}
catch (Exception ex)
{
Toast.LogAndShowT(ex, nameof(ProxyService),
msg: "Accelerate init fail.");
return; // 加速项目初始化失败时,中止初始化
}
try
{
await InitializeScriptAsync();
}
catch (Exception ex)
{
Toast.LogAndShowT(ex, nameof(ProxyService),
msg: "Script init fail.");
return; // 脚本数据初始化失败时,中止初始化
}
try
{
await RefreshIpv6Support();
}
catch (Exception ex)
{
Toast.LogAndShowT(ex, nameof(ProxyService),
msg: "IPv6 refresh fail.");
// Ipv6 支持刷新失败时,可忽略
}
try
{
if (IsProgramStartupRunProxy())
{
await platformService.StartOrStopForegroundServiceAsync(nameof(ProxyService), true);
}
else
{
ProxyStatus = true;
if (platformService.UsePlatformForegroundService)
{
await platformService.StartOrStopForegroundServiceAsync(nameof(ProxyService), true);
}
else
{
ProxyStatus = true;
}
}
}
catch (Exception ex)
{
Toast.LogAndShowT(ex, nameof(ProxyService),
msg: "Program startup run proxy fail.");
// 程序启动时启动加速服务失败,可忽略
}
UpdateProxyTrayMenuItems();
}
/// <summary>
/// 是否使用 <see cref="IHttpService"/> 加载确认物品图片 <see cref="Stream"/>
/// </summary>
static bool IsLoadImage
private void UpdateProxyTrayMenuItems()
{
get
try
{
// 此页面当前使用 Square.Picasso 库加载图片
if (OperatingSystem.IsAndroid()) return false;
return true;
IApplication.Instance.UpdateMenuItems(Plugin.Instance.UniqueEnglishName, new TrayMenuItem
{
Name = Plugin.Instance.Name,
Items = new List<TrayMenuItem>
{
new TrayMenuItem
{
Name = "启动/停止",
Command = ReactiveCommand.Create(() =>
{
ProxyStatus = !ProxyStatus;
}),
},
},
});
}
catch (Exception ex)
{
ex.LogAndShowT();
//托盘菜单添加异常
}
}
@@ -275,23 +330,24 @@ public sealed partial class ProxyService
#endif
if (result.IsSuccess)
{
if (ProxySettings.SupportProxyServicesStatus.Value.Any_Nullable() && result.Content.Any_Nullable())
{
var items = result.Content!.SelectMany(s => s.Items!);
foreach (var item in items)
{
if (ProxySettings.SupportProxyServicesStatus.Value.Contains(item.Id.ToString()))
{
item.ThreeStateEnable = true;
}
}
}
ProxyDomains.AddOrUpdate(result.Content!);
}
LoadOrSaveLocalAccelerate();
if (ProxySettings.SupportProxyServicesStatus.Value.Any_Nullable() && ProxyDomains.Items.Any_Nullable())
{
var items = ProxyDomains.Items!.SelectMany(s => s.Items!);
var enableItems = ProxySettings.SupportProxyServicesStatus.Value;
foreach (var item in items)
{
if (enableItems.Contains(item.Id.ToString()))
{
item.ThreeStateEnable = true;
}
}
}
//if (IsLoadImage && ProxyDomains.Items.Any_Nullable())
//{
// foreach (var item in ProxyDomains.Items)
@@ -305,10 +361,13 @@ public sealed partial class ProxyService
private void LoadOrSaveLocalAccelerate()
{
var filepath = Path.Combine(IOPath.AppDataDirectory, "LOCAL_ACCELERATE.json");
var localAccelerateFilePath = Path.Combine(Plugin.Instance.AppDataDirectory,
"LOCAL_ACCELERATE.json");
if (ProxyDomains.Items.Any_Nullable())
{
if (IOPath.TryOpen(filepath, FileMode.Create, FileAccess.Write, FileShare.Read, out var fileStream, out var _))
if (IOPath.TryOpen(localAccelerateFilePath,
FileMode.Create, FileAccess.Write, FileShare.Read,
out var fileStream, out var _))
{
using var stream = fileStream;
MessagePackSerializer.Serialize(stream, ProxyDomains.Items, options: Serializable.lz4Options);
@@ -316,7 +375,9 @@ public sealed partial class ProxyService
}
else
{
if (File.Exists(filepath) && IOPath.TryOpenRead(filepath, out var fileStream, out var _))
if (File.Exists(localAccelerateFilePath) &&
IOPath.TryOpenRead(localAccelerateFilePath,
out var fileStream, out var _))
{
using var stream = fileStream;
ProxyDomains.Clear();
@@ -330,19 +391,19 @@ public sealed partial class ProxyService
Log.Error(nameof(ProxyService), ex, nameof(LoadOrSaveLocalAccelerate));
}
if (accelerates.Any_Nullable())
{
ProxyDomains.AddOrUpdate(accelerates!);
}
}
}
}
private Timer? timer;
Timer? timer;
public void StartTimer()
{
if (timer == null)
{
timer = new Timer(_ => AccelerateTime = DateTimeOffset.Now - _StartAccelerateTime, nameof(AccelerateTime), 0, 1000);
}
timer ??= new Timer(_ => AccelerateTime = DateTimeOffset.Now - _StartAccelerateTime,
nameof(AccelerateTime), 0, 1000);
}
public void StopTimer()
@@ -378,7 +439,8 @@ public sealed partial class ProxyService
// 加载脚本数据
var scriptList = await scriptManager.GetAllScriptAsync();
ProxyScripts.AddRange(await scriptManager.CheckFiles(scriptList));
var scriptList2 = await scriptManager.CheckFiles(scriptList);
ProxyScripts.AddRange(scriptList2);
//拉取 GM.js
await BasicsInfoAsync();

Some files were not shown because too many files have changed in this diff Show More