Compare commits

...
85 Commits
Author SHA1 Message Date
Mossimo d04213147e 🔧 修复离线安装脚本 dotnet_path 参数缺失导致脚本错误 2026-08-31 18:08:47 +08:00
ff m 4310f72872 🌐 本地化代理启动错误提示
将代理启动时的硬编码错误字符串替换为本地化资源:
- ProxyService.Operate 中证书校验失败、端口绑定失败改用多语言资源字符串
- 新增 Error_CheckRootCertificateFailed_、Error_BindPortError_ 及各语言资源翻译
2026-08-23 22:22:01 +08:00
ff m fefaedb54f ✨ 新增 linux-x64 发布配置 2026-08-23 22:01:05 +08:00
LY 9e25ff7460 🔖 TBD v3.1.10001.0 2026-08-22 22:40:10 +08:00
RMBGAME 3e870daeaf 🔧 修复rxapp编译错误 2026-08-22 19:53:54 +08:00
LY 8f16d3aa66 引入源码方式引用 dotnetCampus.Ipc 并增强进程校验
- 用 ProjectReference 替换 NuGet 包方式引用 dotnetCampus.Ipc,并调整解决方案和项目文件
- IPC 服务实现中增加命名管道连接进程校验,确保仅允许自身或特定模块连接
- 新增 TryGetProcessById、CheckBePid、ValidateNamedPipeClientConnection 等方法用于进程和连接安全校验
- 启动流程中增加主进程 PID 合法性校验,失败时返回新错误码 MainProcessIdIncorrect(4043)
- 日志增加调试信息,便于追踪进程校验流程
- WattToolkit.slnx 新增 dotnetCampus.Ipc 项目引用路径
- 更新 dotnetCampus.Ipc 子模块提交引用
2026-08-22 00:53:04 +08:00
LY 9374231e9f 新增子模块 ref/dotnetCampus.Ipc
添加了指向 https://github.com/BeyondDimension/dotnetCampus.Ipc 的子模块,并在 dotnetCampus.Ipc 目录下记录了对应的提交哈希。
2026-08-21 23:36:38 +08:00
LY 2c0bf8b2d6 重构跨平台 Shell 执行接口及实现
统一各平台 Shell 执行方式,将相关方法改为静态调用,简化接口实现。注释并保留过时方法代码,推荐统一入口。完善方法注释,预留扩展点,提升代码一致性和可维护性。
2026-08-21 23:17:57 +08:00
LY 048fb345ed 🛡️ 安全修复 path 特权任意路径文件覆盖 2026-08-21 21:14:04 +08:00
LY 5726f27445 ↑ .NET 11 PRE 7 & Avalonia 11.3.20 2026-08-21 21:02:53 +08:00
LY 2c3aba6709 还原 Ava 11 2026-08-21 18:53:19 +08:00
LY 137ff46ed9 升级目标框架至 .NET 10/11 并同步子模块
本次提交将项目目标框架升级为 net10.0 和 net11.0,更新了多个 Avalonia 相关 csproj 文件的 <TargetFrameworks> 配置。Directory.Build.props 默认 DotNet_Version 升级为 11.0,并为 net11.0 增加 runtime-async=on 特性。各子模块同步更新引用,指向新 commit。
2026-08-21 18:44:49 +08:00
RMBGAME c16ffa08e0 ⬆️ Avalonia 12.1.0 2026-07-17 18:27:31 +08:00
Mossimo 86122510b4 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2026-03-11 19:32:23 +08:00
Mossimo 67f047735f Update Startup.cs
Macos Dot DisposeAsync
2026-03-11 19:31:46 +08:00
RMBGAME 8ee7e09ecf Steam编辑数据支持导入导出
修复Steam新成就格式无限加载问题
2026-03-11 19:12:56 +08:00
LY e464f285e2 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2026-03-11 04:08:32 +08:00
LY 68d1435ade 🔧 更新预览版本号至 5
将 AssemblyInfo.Constants.cs 中 AssemblyInfo 类的 ver_for_preview 常量从 "3" 提升至 "5",以反映最新的预览版本进度。
2026-03-11 04:07:53 +08:00
RMBGAME d42f6ba07a 😭 没有办法只能特殊处理github脚本注入问题 2026-03-10 20:55:31 +08:00
RMBGAME 662a5aca6f 🔨 优化脚本注入路径为同源,修复github加速在启用脚本的情况下出现异常 2026-03-09 23:33:38 +08:00
RMBGAME f422c76805 挂卡支持黑名单游戏自动排除 2026-03-09 18:28:30 +08:00
LY cd85ff7f17 🔧 升级预览版本号,优化登录注册异常处理
- 将预览版本号提升至 3
- 注释掉短信验证码校验方法 CanSubmit
- 登录/注册流程移除验证码校验,增加异常捕获与提示,确保 IsLoading 状态正确重置
2026-03-07 23:51:53 +08:00
LY bd30f0dcc2 🔧 更新 Common 子模块及预览版本号
更新 Common 子模块至最新提交(799d7da),并将 AssemblyInfo.Constants.cs 中的预览版本号(ver_for_preview)从 1 升级为 2。
2026-03-05 16:25:54 +08:00
LY 5c4564931c 🚀 增加 macOS 发布支持并优化资源释放
新增 macOS ARM64 发布配置,发布命令支持自包含包;注释模块退出异步调用,提升资源释放安全性。
2026-02-13 19:15:08 +08:00
Mossimo 923c553da8 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2026-02-13 17:55:57 +08:00
LY de36e33c5b 🛠️ 增强异常处理,记录反序列化失败信息
在 ReverseProxyServiceImpl 抽象类中,将反序列化 ReverseProxySettings 的 catch 语句改为 catch (Exception e),并在捕获异常时调用 OnException(e) 进行异常处理。这样可以在反序列化失败时记录异常信息,提高代码健壮性和可维护性。
2026-02-13 17:11:53 +08:00
Mossimo d95c797afa Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2026-02-13 15:43:40 +08:00
Mossimo f217902424 Update Info.plist 2026-02-13 15:42:48 +08:00
rmbadmin d6e6bd6cc3 🛠️ 修复图片流类型与文件名判断逻辑 2026-02-13 13:22:39 +08:00
LY 8b521ff7c2 🔄 更新子模块引用至最新提交
更新了 Common 和 DirectoryPackages 子模块,指向各自的最新提交版本,以保持依赖同步和代码库的最新状态。
2026-02-13 10:19:07 +08:00
rmbadmin ed061db526 🛠️ 更新子模块 2026-02-12 15:49:55 +08:00
rmbadmin 880d88749f Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2026-02-12 15:31:31 +08:00
rmbadmin 01dee6f61a 🧹 头像加载逻辑 2026-02-12 15:31:17 +08:00
LY 3267624951 🔧 调整 FileVersion 格式以兼容 msix 要求
将 AssemblyInfo.Constants.cs 中的 FileVersion 常量格式修改为始终包含四位,确保第四位为 0,满足 msix 安装包对版本号的格式要求。
2026-02-12 12:10:49 +08:00
rmbadmin 5bb8b2bde8 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2026-02-12 11:36:10 +08:00
rmbadmin 736e9c405b 🔼 升级 .NET 版本至 10.0.3 2026-02-12 11:35:56 +08:00
LY 36619868e5 🗑️ 删除解决方案文件并增强证书处理健壮性
本次提交包括以下变更:
- 完全删除 WattToolkit.sln 解决方案文件,移除项目结构和配置。
- 优化 MSIXHelper.cs 中签名证书(pfxFilePath)处理逻辑,新增证书文件存在性判断,缺失时跳过签名流程,提升自动化构建容错性。
2026-02-12 11:17:59 +08:00
LY 13ad0c7142 🔒 优化代码签名密码文件处理逻辑
优化了代码签名时对密码文件(.txt)不存在的处理方式。现在仅在密码文件存在时才读取和解密密码,并动态调整 signtool 命令行参数,避免无谓操作和异常。相关目录与熵文件的读取也仅在需要时进行。
2026-02-12 11:06:43 +08:00
LY 2155c6a91e 🛠️ 优化 GC 配置,改进符号文件处理及签名流程
重构了 BD.WTTS.Client.Avalonia.App.csproj 的 GC 配置方式,新增 runtimeconfig.json 的 GC 高内存占比设置。IDotNetPublishCommand.cs 现支持同步移动 .pdb 符号文件,提升调试体验。MSIXHelper.cs 注释掉了签名证书路径的限制,支持更多签名场景。
2026-02-12 10:52:44 +08:00
LY 40893ce6b1 ✨ 新增 WattToolkit.slnx 并优化解决方案查找
新增自定义解决方案文件 WattToolkit.slnx,完善项目结构描述。更新 Common、DirectoryPackages、appcenter-sdk-dotnet 子模块引用。优化 ProjectUtils.GetProjectPath 方法,支持同时查找 .sln 和 .slnx 文件,提升兼容性。
2026-02-12 10:31:23 +08:00
LY 023d5a98b6 🔄 更新子模块引用至最新提交
本次提交将 Common、DirectoryPackages、SteamClient 及 WTTS.MicroServices.ClientSDK 四个子模块指向了各自的新提交哈希。具体代码变更请参考各子模块仓库,可能包含功能优化、缺陷修复等内容。
2026-02-11 19:17:25 +08:00
LY e501f46aa7 ✨ 新增 -silence 参数支持挂运行服务
增加了 -silence 命令行参数,允许运行 Steam 应用时选择挂运行服务模式(不加载窗口,内存占用更小)。该参数默认值为 false,并已集成到 app 命令参数列表及执行逻辑中。
2026-02-11 19:07:14 +08:00
rmbadmin c96c30fa40 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2026-02-09 19:55:02 +08:00
rmbadmin b76f77c017 ✨ 更新子模块并优化图片流处理 2026-02-09 19:54:46 +08:00
LY 74b2c3b3e7 🗑️ 移除 SAM.API 项目并重置版本号为 0
本次提交移除了 WattToolkit.sln 解决方案中的 SAM.API 项目及其所有相关配置,同时将 AssemblyInfo.Constants.cs 中的 VersionBuild 从 1 修改为 0,使程序集版本号回退。
2026-02-09 18:31:14 +08:00
rmbadmin be6b58f7f0 🔄 更新子模块引用及标记本地更改
更新了 Fusillade、Titanium-Web-Proxy 和 dotnet-packaging 子模块的提交状态,添加了 -dirty 标记,表示存在未提交的本地更改。同时将 SteamClient 子模块引用从 6370e99 更新为 460d55c,指向了新的上游提交。
2026-02-09 18:17:16 +08:00
LY 651a229a24 🔄 更新 DirectoryPackages 子模块版本
将 DirectoryPackages 子模块从 d28073cee219c8a54d2cec38a314da24b22d85db 更新至 96eb03bd07beaf475584841434df07d53f0d4d1b,以同步最新依赖或修复。
2026-02-09 17:36:42 +08:00
LY 6db92b60ce 🔼 升级 .NET 版本及相关配置
升级 .NET 版本至 10.0.2,更新 Version2 为 3.1,替换 HSM 代码签名证书指纹,标记 DirectoryPackages 子项目为 dirty。
2026-02-09 17:05:22 +08:00
LY b25c682e55 迁移动图控件至BD.Avalonia8.Image2.Compat并引入关机检测
新增BD.Avalonia8.Image2.Compat项目,替换原Avalonia.Gif动图控件,支持Gif/Apng。引入OSShuttingDownHelper跨项目共享代码,统一操作系统关机检测逻辑,并在主程序及IPC服务中应用。调整解决方案结构和项目引用,优化构建配置,统一引入SourceLink,提升可维护性和一致性。同步部分依赖子模块。
2026-02-09 16:39:18 +08:00
LY c457037a09 新增 Avalonia8 子模块并锁定特定提交
新增 ref/Avalonia8 子模块,指向 https://github.com/BeyondDimension/Avalonia8,并锁定在提交 2a842298c87e1ff8b0fe80b28f45ca52a31cbf21,便于后续集成和管理。
2026-02-09 16:38:49 +08:00
LY c222ae2a57 新增虚拟文件夹项目并调整嵌套关系
本次提交新增了 "Image2" 和 "Avalonia8" 两个虚拟文件夹项目,调整了部分项目的嵌套关系,并更新了 Common、DirectoryPackages、SteamClient、WinAuth 四个子模块的 commit。
2026-02-09 15:17:29 +08:00
rmbadmin e2a5e7ef11 🛡️ 增加加速进程关机检测,防止关机时报错 2026-02-09 12:18:41 +08:00
LY 32d3fa92ca 🔄 更新子模块并增强ReverseProxySettings序列化兼容性
更新了多个子模块(Avalonia.Image2、DirectoryPackages、SteamClient、WTTS.MicroServices.ClientSDK、WinAuth)到最新提交。
在 IReverseProxyService.Constants.cs 中,将 ReverseProxySettings 的 MP2Obj 特性增加了 GenerateType.VersionTolerant 参数,使其支持版本容错序列化,提升了数据结构的兼容性和可扩展性。
2026-02-06 19:05:24 +08:00
rmbadmin db30652b22 ✨ 统一关闭 UI 动画并优化 XAML 属性顺序
本次提交主要做了以下改动:

- 全面将各页面 Border 控件的 spp:Animations.EnableAnimations 属性由 True 改为 False,关闭 UI 动画效果,提升性能与兼容性。
- 优化 HomePage.axaml 及相关 XAML 文件的属性顺序,提升代码可读性和一致性。
- 调整 HomePage.axaml 中 NavButtonStyle 等资源定义顺序,增强可维护性。
- ProxyService.Operate.cs 中,异常隐式转换现在会将 Exception.Message 作为 Error 字段,便于错误信息展示。
- 更新了 DirectoryPackages 和 SteamClient 子模块状态为 dirty,提示有本地未提交更改。
- 本次变更未涉及核心业务逻辑,仅为 UI 表现和异常信息优化。
2026-02-06 17:39:25 +08:00
rmbgame cb00861532 Fix Epic 19.0.1 Switch 2026-02-03 11:57:05 +08:00
LY 64f60440a3 ✨ 优化自动化打包流程与命令兼容性
本次提交包括以下更新:
- 优化 CommandCompat.cs 中 SetHandler 的反射调用逻辑,提升类型兼容性与健壮性
- INSISBuildCommand 支持 timestamp=latest 自动查找最新发布目录,简化打包流程
- MSIXHelper.cs 新增多项 Windows 版本枚举,便于后续适配
- launchSettings.json 增加多种一键发布/打包/签名启动配置,提升运维效率
- DirectoryPackages 子模块标记为 dirty,提示有本地未提交更改
2026-02-02 13:59:27 +08:00
LY 0465f6f578 🛠️ 命令行兼容性升级与依赖优化
本次提交主要内容如下:

- 升级开发环境文档,VS/Xcode 版本提升
- 多项目依赖由包引用切换为源码引用,便于本地开发
- 注释 Aigio.AppCenter 相关包,恢复 fusillade
- 新增 CommandCompat,统一命令行参数与 Handler 绑定方式,适配新版 System.CommandLine
- NoticeService 优化,确保 UI 线程安全
- 细节修正:特性标记、空值保护、无用节点清理等
- 更新子模块指向

为后续开发和维护提供更好的兼容性和可维护性。
2026-02-02 11:45:18 +08:00
LY a8701adbb4 🚀 CI 配置迁移及依赖与命名空间调整
本次提交包括以下主要变更:

- CI/CD 配置从 CI.yml 迁移至 CI.yml.bak,流程内容基本一致,仅启用 aigio-win10ltsc-vm 平台,release 步骤仍为注释状态。
- BD.WTTS.Client.csproj 中 fusillade 依赖切换为 BD.Common.fusillade,自有维护版本。
- 全面将 UI 相关代码中的 using Avalonia.ReactiveUI; 替换为 using ReactiveUI.Avalonia;,并同步更新全局 using。
- 更新了 Avalonia.Image2、Common、DirectoryPackages、WTTS.MicroServices.ClientSDK 四个子模块指向的 commit,依赖同步至新版本。
2026-01-30 19:04:03 +08:00
LY d954e99f12 🔗 新增 Fusillade 作为子模块依赖
在 .gitmodules 文件中添加了 ref/Fusillade 子模块,指向 https://github.com/reactiveui/Fusillade,并在项目中引入了指定提交的 Fusillade 依赖。这样项目可以直接使用 Fusillade 库的功能。
2026-01-30 18:44:59 +08:00
rmbgame ab1dcbdcaf 🎨 优化设置界面结构,替换自定义颜色选择器
本次提交主要内容如下:

- 新增 Avalonia.Controls.ColorPicker 依赖,替换原有自定义颜色选择器,简化颜色选择逻辑
- 统一和优化 Settings_UI.axaml 中控件属性顺序、缩进和结构,提升可读性与维护性
- 优化 Accent 颜色预览区域和图片相关设置项的布局与属性
- ComboBox ItemTemplate 类型声明规范化
- Tab 项模板结构调整
- 更新 Common 和 DirectoryPackages 子模块指向最新 commit

整体提升了 UI 代码规范性和可维护性,为后续功能扩展打下基础
2026-01-30 17:40:32 +08:00
LY 7e4319695f 🛠️ 重构命令行工具并优化依赖管理
本次提交包含以下主要更新:

- 解决方案结构调整,新增 BD.Common 相关源码引用,升级 VS 版本至 18
- 多项目依赖由 PackageReference 改为 ProjectReference,便于源码调试
- 命令行工具(CLT)全面重构,采用新版 System.CommandLine API,参数定义更规范,注册方式更现代化
- 移除 StyleCop.Analyzers,简化构建流程;多平台配置精简 TargetFrameworks
- 更新各子模块至最新 commit
- IPlugin 等接口参数支持 null,提升兼容性;AppSettings 属性 Obsolete 标记调整
- 其他细节优化及无用代码清理
2026-01-30 16:53:03 +08:00
LY 6a78e9aa71 🚀 升级 .NET 10 并优化版本号管理
本次提交包含以下主要变更:

- 升级 .NET 目标版本至 10.0,提升运行时和语言特性支持。
- 优化版本号管理逻辑,增加 VersionBuild 字段,调整 FileVersion 和 InformationalVersion 格式,便于区分不同版本阶段。
- 更新 Common、appcenter-sdk-dotnet 等子模块依赖,保持依赖库最新。
- DirectoryPackages 标记为 dirty,提示有本地未提交更改。
2026-01-30 12:02:56 +08:00
Aigio Liu ece1768b2a 🐛 BD.AppCenter 2025-05-06 10:37:12 +08:00
Aigio Liu c749fad3be 🎨 加速二级代理描述 2025-03-24 16:29:02 +08:00
k1mlka 25afcb2b3f 🐛 ChangePropertyAction Content -> Value 2025-03-21 18:03:06 +08:00
Aigio Liu 45683c78a8 ⬆️ Pkg 2025-03-21 17:46:01 +08:00
k1mlka a477cd87cf 💄 ChangePropertyAction 限定值的颜色类型 2025-03-21 17:34:00 +08:00
Aigio Liu a2cf95f0c1 🐛 NATCheckCommand 2025-03-21 13:31:58 +08:00
Aigio Liu 88ddc7a320 🔖 3.0.0-rc.16 2025-03-21 10:04:27 +08:00
RMBGAME e5463f44d2 ⬆️ FluentAvalonia 2.3.0 2025-03-19 00:42:45 +08:00
Aigio Liu 816d1c9b0c 🐛 TestStunClient3489Async 2025-03-17 14:16:50 +08:00
Aigio Liu 69296f9587 ✨ IcuTestFail 2025-03-15 22:39:59 +08:00
Aigio Liu 6577ed9fa0 🚧 GenerateServerSideProxyToken cts 2025-03-13 00:10:19 +08:00
Aigio Liu a54e988441 🐛 https://github.com/BeyondDimension/SteamTools/issues/3590 2025-03-11 04:51:58 +08:00
Aigio Liu db508a04f0 🐛 GetActiveUserRecord 2025-03-10 00:50:23 +08:00
RMBGAME eb065d7c7e 🐛 部分选中的加速效果存在选择问题 2025-03-08 16:58:14 +08:00
RMBGAME 1b639291e4 ✨ Http3 2025-03-05 15:51:50 +08:00
RMBGAME c8810f7926 ⬆️ 反代 http3 支持 2025-03-04 17:17:29 +08:00
RMBGAME 073b5885c6 ⬆️ Avalonia 11.2.5 2025-03-03 14:04:47 +08:00
ff m ed6df52998 🐛 修复 没登陆无法跳转问题 2025-02-26 12:51:35 +08:00
Aigio Liu 88b4aac622 🔖 3.0.0-rc.15 2025-02-13 00:42:17 +08:00
yingpanwang 9504413ff7 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2025-02-12 16:59:28 +08:00
yingpanwang 0f28de1e94 🐛 解决登陆时、确认报价时未携带Origin的请求头导致请求失败的问题 2025-02-12 16:58:27 +08:00
john.jiang ad11654cb4 🎨 修复挂卡登录缓存问题,增加本地储存 token 过期检查并生成新 token 2025-02-12 16:26:47 +08:00
ff m f43aa16c6a 🐛 fix Linux 不编译 ASF 2025-01-23 20:36:33 +08:00
210 changed files with 18225 additions and 17977 deletions
+1
View File
@@ -34,6 +34,7 @@ bld/
# Visual Studio 2015/2017 cache/options directory
.vs/
.vscode/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
+9
View File
@@ -40,3 +40,12 @@
[submodule "ref/appcenter-sdk-dotnet"]
path = ref/appcenter-sdk-dotnet
url = https://github.com/BeyondDimension/appcenter-sdk-dotnet.git
[submodule "ref/Fusillade"]
path = ref/Fusillade
url = https://github.com/reactiveui/Fusillade
[submodule "ref/Avalonia8"]
path = ref/Avalonia8
url = https://github.com/BeyondDimension/Avalonia8
[submodule "ref/dotnetCampus.Ipc"]
path = ref/dotnetCampus.Ipc
url = https://github.com/BeyondDimension/dotnetCampus.Ipc
+2 -3
View File
@@ -71,13 +71,12 @@ See details [./doc/download-guide.en.md](./doc/download-guide.en.md)
Read what we [milestones](https://github.com/BeyondDimension/SteamTools/milestones), and feel free to ask questions.
## ⌨️ Development Environment
[Visual Studio 2022](https://visualstudio.microsoft.com/vs)
[Visual Studio 2022 for Mac](https://visualstudio.microsoft.com/vs/mac)
[Visual Studio 2026](https://visualstudio.microsoft.com/vs)
[JetBrains Rider](https://www.jetbrains.com/rider)
[Visual Studio Code](https://code.visualstudio.com)
[OpenJDK 17](https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-17)
[Android Studio Electric Eel Or Higher](https://developer.android.com/studio)
[Xcode 14 Or Higher](https://developer.apple.com/xcode)
[Xcode 26 Or Higher](https://developer.apple.com/xcode)
## 🏗️ Project Structure
See details&nbsp;&nbsp;[./src/README.md](./src/README.md)
+2 -18
View File
@@ -93,28 +93,12 @@
查看这个 [milestones](https://github.com/BeyondDimension/SteamTools/milestones) 来了解我们下一步的开发计划,并随时提出问题。
## ⌨️ 开发环境
[Visual Studio 2022](https://visualstudio.microsoft.com/zh-hans/vs) 或 [Visual Studio 2022 for Mac](https://visualstudio.microsoft.com/zh-hans/vs/mac)
- 系统要求
- [Windows 11 版本 21H2 或更高版本:家庭版、专业版、专业教育版、专业工作站版、企业版和教育版](https://learn.microsoft.com/zh-cn/visualstudio/releases/2022/system-requirements)
- [macOS Big Sur 11.0 或更高版本](https://learn.microsoft.com/zh-cn/visualstudio/releases/2022/mac-system-requirements)
- 工作负荷
- Web 和云
- ASP.NET 和 Web 开发
- 桌面应用和移动应用
- 使用 .NET 的移动开发 / .NET Multi-platform App UI 开发
- .NET 桌面开发
- 通用 Windows 平台开发
- 单个组件
- GitHub Extension for Visual Studio(可选)
- [Visual Studio Marketplace](https://marketplace.visualstudio.com)
- [Avalonia for Visual Studio(可选)](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaVS)
- [NUnit VS Templates(可选)](https://marketplace.visualstudio.com/items?itemName=NUnitDevelopers.NUnitTemplatesforVisualStudio)
[Visual Studio 2026](https://visualstudio.microsoft.com/zh-hans/vs)
[JetBrains Rider](https://www.jetbrains.com/rider)
[Visual Studio Code](https://code.visualstudio.com)
[OpenJDK 17](https://learn.microsoft.com/zh-cn/java/openjdk/download#openjdk-17)
[Android Studio Electric Eel 或更高版本](https://developer.android.google.cn/studio)
[Xcode 14 或更高版本](https://developer.apple.com/xcode)
[Xcode 26 或更高版本](https://developer.apple.com/xcode)
## 🏗️ [项目结构](./src/README.md)
详见&nbsp;&nbsp;[./src/README.md](./src/README.md)
-1372
View File
File diff suppressed because it is too large Load Diff
+181
View File
@@ -0,0 +1,181 @@
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="ARM" />
<Platform Name="ARM64" />
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Folder Name="/0.Root/">
<File Path=".github/workflows/CI.yml" />
<File Path=".gitignore" />
<File Path=".gitmodules" />
<File Path="nuget.config" />
<File Path="README.en.md" />
<File Path="README.md" />
<File Path="ref/DirectoryPackages/Directory.Packages.props" />
<File Path="src/.editorconfig" />
<File Path="src/Directory.Build.props" />
</Folder>
<Folder Name="/0.Root/Build/">
<File Path="build/publish_apphost_linux_x64.sh" />
<File Path="build/settings_v4_app.json" />
</Folder>
<Folder Name="/0.Root/Document/">
<File Path="doc/file-system.md" />
<File Path="doc/open-source-library.md" />
<File Path="doc/openwrt.md" />
<File Path="doc/publish.md" />
<File Path="doc/release-keylol.md" />
</Folder>
<Folder Name="/0.Root/Document/Program File Structure/">
<File Path="doc/program-file-structure/Linux.md" />
<File Path="doc/program-file-structure/macOS.md" />
<File Path="doc/program-file-structure/Windows.md" />
</Folder>
<Folder Name="/0.Root/Source/">
<File Path="src/AssemblyInfo.Constants.cs" />
<File Path="src/AssemblyInfo.cs" />
<File Path="src/AssemblyInfo.Version.Max.cs" />
<File Path="src/README.md" />
<File Path="src/Utils.cs" />
</Folder>
<Folder Name="/0.Root/Source/ImplicitUsings/">
<File Path="src/ImplicitUsings.ArchiSteamFarm.cs" />
<File Path="src/ImplicitUsings.AspNetCore.cs" />
<File Path="src/ImplicitUsings.AutoMapper.cs" />
<File Path="src/ImplicitUsings.Avalonia.cs" />
<File Path="src/ImplicitUsings.BCL.cs" />
<File Path="src/ImplicitUsings.Common.cs" />
<File Path="src/ImplicitUsings.Controllers.cs" />
<File Path="src/ImplicitUsings.Data.cs" />
<File Path="src/ImplicitUsings.EntityFrameworkCore.cs" />
<File Path="src/ImplicitUsings.Identity.cs" />
<File Path="src/ImplicitUsings.Jobs.cs" />
<File Path="src/ImplicitUsings.JsonProperty.cs" />
<File Path="src/ImplicitUsings.JWT.cs" />
<File Path="src/ImplicitUsings.MessagePack.cs" />
<File Path="src/ImplicitUsings.Models.cs" />
<File Path="src/ImplicitUsings.MSEX.cs" />
<File Path="src/ImplicitUsings.Nito.cs" />
<File Path="src/ImplicitUsings.NLog.cs" />
<File Path="src/ImplicitUsings.Plugins.cs" />
<File Path="src/ImplicitUsings.Quartz.cs" />
<File Path="src/ImplicitUsings.ReactiveUI.cs" />
<File Path="src/ImplicitUsings.Repositories.cs" />
<File Path="src/ImplicitUsings.Services.cs" />
<File Path="src/ImplicitUsings.Settings.cs" />
<File Path="src/ImplicitUsings.SQLite.cs" />
<File Path="src/ImplicitUsings.Steam.cs" />
<File Path="src/ImplicitUsings.UI.cs" />
<File Path="src/ImplicitUsings.UI.ViewModels.cs" />
<File Path="src/ImplicitUsings.UI.Views.cs" />
<File Path="src/ImplicitUsings.UnitTest.cs" />
<File Path="src/ImplicitUsings.WTTS.cs" />
</Folder>
<Folder Name="/0.Root/TFM/">
<File Path="src/TFM_NETX.props" />
<File Path="src/TFM_NETX_SINGLE.props" />
<File Path="src/TFM_NETX_WINDOWS.props" />
<File Path="src/TFM_NETX_WITH_ALL.props" />
<File Path="src/TFM_NETX_WITH_DESKTOP.props" />
<File Path="src/TFM_NETX_WITH_WINDOWS.props" />
</Folder>
<Folder Name="/1.AppHost/">
<Project Path="src/BD.WTTS.Client.AppHost.Bridge/Steam++.csproj" />
<Project Path="src/BD.WTTS.Client.AppHost/BD.WTTS.Client.AppHost.csproj" />
</Folder>
<Folder Name="/2.Reference/" />
<Folder Name="/2.Reference/AppCenter/">
<Project Path="ref/appcenter-sdk-dotnet/SDK/AppCenter/BD.AppCenter.Any/BD.AppCenter.Any.csproj" />
<Project Path="ref/appcenter-sdk-dotnet/SDK/AppCenterAnalytics/BD.AppCenter.Analytics.Any/BD.AppCenter.Analytics.Any.csproj" />
</Folder>
<Folder Name="/2.Reference/Avalonia/">
<Project Path="ref/Avalonia.Image2/Avalonia.Gif/Avalonia.Gif.csproj" />
<Project Path="ref/Avalonia.Image2/LibAPNG/LibAPNG.csproj" />
</Folder>
<Folder Name="/2.Reference/Common/">
<Project Path="ref/Common/src/BD.Common.Area/BD.Common.Area.csproj" />
<Project Path="ref/Common/src/BD.Common.Essentials.Maui.Runtime/BD.Common.Essentials.Maui.Runtime.csproj" />
<Project Path="ref/Common/src/BD.Common.Essentials.Preferences.SQLite/BD.Common.Essentials.Preferences.SQLite.csproj" />
<Project Path="ref/Common/src/BD.Common.Essentials.Primitives/BD.Common.Essentials.Primitives.csproj" />
<Project Path="ref/Common/src/BD.Common.Essentials.Utils/BD.Common.Essentials.Utils.csproj" />
<Project Path="ref/Common/src/BD.Common.Essentials/BD.Common.Essentials.csproj" />
<Project Path="ref/Common/src/BD.Common.Mvvm.ReactiveUI/BD.Common.Mvvm.ReactiveUI.csproj" />
<Project Path="ref/Common/src/BD.Common.Mvvm/BD.Common.Mvvm.csproj" />
<Project Path="ref/Common/src/BD.Common.Pinyin.TinyPinyin/BD.Common.Pinyin.TinyPinyin.csproj" />
<Project Path="ref/Common/src/BD.Common.Pinyin/BD.Common.Pinyin.csproj" />
<Project Path="ref/Common/src/BD.Common.Primitives/BD.Common.Primitives.csproj" />
<Project Path="ref/Common/src/BD.Common.Repositories.SQLitePCL/BD.Common.Repositories.SQLitePCL.csproj" />
<Project Path="ref/Common/src/BD.Common.Security/BD.Common.Security.csproj" />
<Project Path="ref/Common/src/BD.Common.Settings/BD.Common.Settings.csproj" />
<Project Path="ref/Common/src/BD.Common/BD.Common.csproj" />
</Folder>
<Folder Name="/2.Reference/Ipc/">
<Project Path="ref/dotnetCampus.Ipc/src/dotnetCampus.Ipc.Analyzers/dotnetCampus.Ipc.Analyzers.csproj" />
<Project Path="ref/dotnetCampus.Ipc/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj" />
</Folder>
<Folder Name="/2.Reference/Steam/">
<Project Path="ref/Facepunch.Steamworks/Facepunch.Steamworks/Facepunch.Steamworks.Posix.csproj" />
<Project Path="ref/Facepunch.Steamworks/Facepunch.Steamworks/Facepunch.Steamworks.Win64.csproj" />
<Project Path="ref/Gameloop.Vdf/Gameloop.Vdf/Gameloop.Vdf.csproj" />
<Project Path="ref/Steam4NET/Steam4NET/Steam4NET.csproj" />
<Project Path="ref/SteamClient/ref/ValveKeyValue/ValveKeyValue/ValveKeyValue/ValveKeyValue.csproj" />
<Project Path="ref/SteamClient/src/BD.SteamClient.Models.Protobuf/BD.SteamClient.Models.Protobuf.csproj" />
<Project Path="ref/SteamClient/src/BD.SteamClient/BD.SteamClient.csproj" />
</Folder>
<Folder Name="/2.Reference/WinAuth/">
<Project Path="ref/WinAuth/src/WinAuth/WinAuth.csproj" />
</Folder>
<Folder Name="/3.Test/">
<Project Path="src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj" />
</Folder>
<Folder Name="/4.Shared/">
<Project Path="src/BD.Avalonia8.Image2.Compat/BD.Avalonia8.Image2.Compat.csproj" />
<Project Path="src/BD.WTTS.Client.Avalonia/BD.WTTS.Client.Avalonia.csproj" />
<Project Path="src/BD.WTTS.Client.IPC/BD.WTTS.Client.IPC.csproj" />
<Project Path="src/BD.WTTS.Client/BD.WTTS.Client.csproj" />
<Project Path="src/OSShuttingDownHelper/OSShuttingDownHelper.shproj" />
</Folder>
<Folder Name="/4.Shared/Avalonia.Ref/">
<Project Path="src/Avalonia.Base.Internals/Avalonia.Base.Internals.csproj" />
<Project Path="src/Avalonia.Base/Avalonia.Base.csproj" />
<Project Path="src/Avalonia.Controls.Internals/Avalonia.Controls.Internals.csproj" />
<Project Path="src/Avalonia.Desktop/Avalonia.Desktop.csproj" />
<Project Path="src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj" />
<Project Path="src/Avalonia.Native/Avalonia.Native.csproj" />
<Project Path="src/Avalonia.Skia.Internals/Avalonia.Skia.Internals.csproj" />
<Project Path="src/Avalonia.WebView2/Avalonia.WebView2.csproj" />
<Project Path="src/Avalonia.Win32/Avalonia.Win32.csproj" />
<Project Path="src/Avalonia.X11/Avalonia.X11.csproj" />
</Folder>
<Folder Name="/4.Shared/ClientSDK/">
<Project Path="ref/WTTS.MicroServices.ClientSDK/ref/WTTS.Public/src/BD.WTTS.MicroServices.Primitives.Resources/BD.WTTS.MicroServices.Primitives.Resources.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/ref/WTTS.Public/src/BD.WTTS.Primitives.Resources/BD.WTTS.Primitives.Resources.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/src/BD.WTTS.MicroServices.ClientSDK/BD.WTTS.MicroServices.ClientSDK.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/src/BD.WTTS.MicroServices.Primitives.Models/BD.WTTS.MicroServices.Primitives.Models.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/src/BD.WTTS.MicroServices.Primitives.ViewModels/BD.WTTS.MicroServices.Primitives.ViewModels.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/src/BD.WTTS.MicroServices.Primitives/BD.WTTS.MicroServices.Primitives.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/src/BD.WTTS.Primitives.Models/BD.WTTS.Primitives.Models.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/src/BD.WTTS.Primitives.ViewModels/BD.WTTS.Primitives.ViewModels.csproj" />
<Project Path="ref/WTTS.MicroServices.ClientSDK/src/BD.WTTS.Primitives/BD.WTTS.Primitives.csproj" />
</Folder>
<Folder Name="/5.Plugins/">
<Project Path="src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj" />
<Project Path="src/BD.WTTS.Client.Plugins.Accelerator/BD.WTTS.Client.Plugins.Accelerator.csproj" />
<Project Path="src/BD.WTTS.Client.Plugins.ArchiSteamFarmPlus/BD.WTTS.Client.Plugins.ArchiSteamFarmPlus.csproj" />
<Project Path="src/BD.WTTS.Client.Plugins.Authenticator/BD.WTTS.Client.Plugins.Authenticator.csproj" />
<Project Path="src/BD.WTTS.Client.Plugins.GameAccount/BD.WTTS.Client.Plugins.GameAccount.csproj" />
<Project Path="src/BD.WTTS.Client.Plugins.GameList/BD.WTTS.Client.Plugins.GameList.csproj" />
<Project Path="src/BD.WTTS.Client.Plugins.GameTools/BD.WTTS.Client.Plugins.GameTools.csproj" />
<Project Path="src/BD.WTTS.Client.Plugins.SteamIdleCard/BD.WTTS.Client.Plugins.SteamIdleCard.csproj" />
</Folder>
<Folder Name="/6.Tools/">
<Project Path="ref/Common/src/BD.Common.Settings.V4.SourceGenerator.Tools/BD.Common.Settings.V4.SourceGenerator.Tools.csproj" />
<Project Path="src/BD.WTTS.Client.Avalonia.Designer.HostApp/BD.WTTS.Client.Avalonia.Designer.HostApp.csproj" />
<Project Path="src/BD.WTTS.Client.Tools.HostsTest/BD.WTTS.Client.Tools.HostsTest.csproj" />
<Project Path="src/BD.WTTS.Client.Tools.OpenSourceLibraryList/BD.WTTS.Client.Tools.OpenSourceLibraryList.csproj" />
<Project Path="src/BD.WTTS.Client.Tools.Publish/BD.WTTS.Client.Tools.Publish.csproj" />
</Folder>
<Project Path="src/BD.WTTS.Client.Avalonia.App/BD.WTTS.Client.Avalonia.App.csproj" />
</Solution>
+1 -1
View File
@@ -163,7 +163,7 @@ Install_zenity
Install_jq
certutil_Init
Kill_Process
dotnet_path="$base_path/dotnet"
dotnet_exec="$dotnet_path/dotnet/dotnet"
if [ -x "$dotnet_exec" ]; then
echo "文件具有执行权限。"
+7
View File
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "11.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}
Submodule
+1
Submodule ref/Avalonia8 added at bdf87548bb
Submodule
+1
Submodule ref/Fusillade added at e53e10aef3
+46 -7
View File
@@ -5,29 +5,33 @@ namespace BD.WTTS;
/// </summary>
public static partial class AssemblyInfo
{
const string Version2 = "3.0";
const string Version2 = "3.1";
/// <summary>
/// 语义化应用程序版本
/// https://semver.org/lang/zh-CN/
/// </summary>
public const string Version = $"{Version2}.0";
public const string Version = $"{Version2}.{VersionBuild}";
const string VersionBuild = "1"; // 1~99
const string VersionBuild2 = $"{VersionBuild}0"; // VersionBuild为1位时,补0,两位
/// <summary>
/// 预览版本号,范围 1~9,只增不减
/// </summary>
const string ver_for_preview = "0";
const string ver_for_preview = "1";
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "14";
const string ver_for_rc = "0";
public const string FileVersion = $"{Version2}.{ver_for_rc}2{ver_for_preview}.0";
public const string FileVersion = $"{Version2}.{VersionBuild2}{ver_for_rc}0{ver_for_preview}.0"; // msix 需要保持第四位为0
//public const string InformationalVersion = Version;
public const string InformationalVersion = Version;
//public const string InformationalVersion = $"{Version}-preview.{ver_for_preview}";
public const string InformationalVersion = $"{Version}-rc.{ver_for_rc}";
//public const string InformationalVersion = $"{Version}-rc.{ver_for_rc}";
#if !APP_HOST
/// <summary>
@@ -63,6 +67,8 @@ public static partial class AssemblyInfo
/// </summary>
public const string Product = Trademark;
public const string Product_APP_REVERSE_PROXY = $"{Product} - Accelerator and script module sub-process";
/// <summary>
/// 与该应用程序关联的公司名称。
/// </summary>
@@ -146,4 +152,37 @@ false
#endif
/// <summary>
/// 验证程序集是否为合法程序集
/// </summary>
/// <param name="assemblyPath"></param>
/// <returns></returns>
public static bool ValidateAssembly(string assemblyPath)
{
var fvi = FileVersionInfo.GetVersionInfo(assemblyPath);
if (fvi.Comments != Description)
{
return false;
}
else if (fvi.CompanyName != Company)
{
return false;
}
else if (fvi.LegalCopyright != Copyright)
{
return false;
}
else if (fvi.LegalTrademarks != Trademark)
{
return false;
}
else if (fvi.ProductName != Product && fvi.ProductName != Product_APP_REVERSE_PROXY)
{
return false;
}
// TODO: Digital signature verification
return true;
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ using AssemblyInfo = BD.WTTS.AssemblyInfo;
[assembly: AssemblyTrademark(AssemblyInfo.Trademark)]
[assembly: AssemblyDescription(AssemblyInfo.Description)]
#if APP_REVERSE_PROXY
[assembly: AssemblyProduct($"{AssemblyInfo.Product} - Accelerator and script module sub-process")]
[assembly: AssemblyProduct(AssemblyInfo.Product_APP_REVERSE_PROXY)]
#else
[assembly: AssemblyProduct(AssemblyInfo.Product)]
#endif
@@ -23,4 +23,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
+4
View File
@@ -18,4 +18,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
@@ -21,4 +21,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
@@ -19,4 +19,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
@@ -23,4 +23,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
@@ -26,4 +26,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
@@ -23,4 +23,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
+4
View File
@@ -23,4 +23,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
+4
View File
@@ -23,4 +23,8 @@
<Import Project="..\TFM_NETX.props" />
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
</PropertyGroup>
</Project>
@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(DotNet_Version)</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>提供动图控件,支持 Gif/Apng</Description>
<AssemblyName>BD.Avalonia8.Image2</AssemblyName>
<RootNamespace>BD.Avalonia8.Image2</RootNamespace>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<GenerateAssemblyFileVersionAttribute>true</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>true</GenerateAssemblyInformationalVersionAttribute>
<GenerateAssemblyVersionAttribute>true</GenerateAssemblyVersionAttribute>
</PropertyGroup>
<!-- Avalonia -->
<PropertyGroup>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)..\..\ref\Avalonia8\src\Sdk\Links\LibAPNG.props" />
<ItemGroup>
<Compile Remove="..\ImplicitUsings.Common.cs" />
<Compile Remove="..\AssemblyInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\ref\Avalonia8\src\BD.Avalonia8.Image2\**\*.cs">
<LinkBase></LinkBase>
</Compile>
<Compile Remove="$(MSBuildThisFileDirectory)..\..\ref\Avalonia8\src\BD.Avalonia8.Image2\bin\**\*.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\..\ref\Avalonia8\src\BD.Avalonia8.Image2\obj\**\*.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="ReactiveUI" />
<PackageReference Include="ReactiveUI.Fody" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ref\Common\src\BD.Common.Mvvm\BD.Common.Mvvm.csproj" />
<ProjectReference Include="..\..\ref\Common\src\BD.Common\BD.Common.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,41 @@
// Common\src\BD.Common8.Bcl\Extensions\StreamExtensions.cs
namespace System.Extensions;
/// <summary>
/// 提供对 <see cref="Stream"/> 类型的扩展函数
/// </summary>
internal static partial class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadInt32(this Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(int)];
stream.ReadExactly(data);
return BitConverter.ToInt32(data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadUInt16(this Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(ushort)];
stream.ReadExactly(data);
return BitConverter.ToUInt16(data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(uint)];
stream.ReadExactly(data);
return BitConverter.ToUInt32(data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteUInt32(this Stream stream, uint value)
{
Span<byte> data = stackalloc byte[sizeof(uint)];
BitConverter.TryWriteBytes(data, value);
stream.Write(data);
}
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ReactiveUI />
</Weavers>
@@ -0,0 +1,4 @@
// steampp
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.Avalonia8.Image2")]
@@ -0,0 +1,24 @@
// <auto-generated/>
global using Avalonia;
global using Avalonia.Animation;
global using Avalonia.Controls;
global using Avalonia.Logging;
global using Avalonia.Media;
global using Avalonia.Media.Imaging;
global using Avalonia.Metadata;
global using Avalonia.Platform;
global using Avalonia.Rendering.Composition;
global using Avalonia.Threading;
global using Avalonia.VisualTree;
global using BD.Avalonia8.Image2;
global using BD.Avalonia8.Image2.Decoding;
global using System.Extensions;
global using System.Numerics;
global using System.Reactive.Linq;
global using AvaBitmap = Avalonia.Media.Imaging.Bitmap;
global using AvaPixelFormat = Avalonia.Platform.PixelFormat;
global using AvaPoint = Avalonia.Point;
global using AvaRect = Avalonia.Rect;
global using AvaSize = Avalonia.Size;
global using AvaVector = Avalonia.Vector;
global using CommonImageSource = BD.Common.UI.ViewModels.ImageSource.ClipStream;
@@ -0,0 +1,5 @@
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
[assembly: SuppressMessage("Style", "IDE0005:Using 指令是不需要的。", Justification = "<挂起>")]
+1 -1
View File
@@ -9,6 +9,6 @@
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
<appSettings>
<add key="d" value="7.0.11"/>
<add key="d" value="11.0.0-preview.7.26381.103"/>
</appSettings>
</configuration>
+1 -1
View File
@@ -10,6 +10,6 @@
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
<appSettings>
<add key="d" value="9.0.0"/>
<add key="d" value="11.0.0-preview.7.26381.103"/>
</appSettings>
</configuration>
+18 -7
View File
@@ -5,6 +5,7 @@
#if NETFRAMEWORK
using System.Configuration;
using System.Diagnostics;
#endif
using static BD.WTTS.AssemblyInfo;
using static BD.WTTS.Client.Resources.Strings;
@@ -16,14 +17,15 @@ static unsafe partial class Program
{
static Program()
{
dotnet_version_major = "9";
dotnet_version_major = "11";
dotnet_version_minor = "0";
dotnet_version_build = "0";
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
dotnet_version_preOrRcString = "-preview.7.26381.103"; // -字符开头!
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}{dotnet_version_preOrRcString}";
}
//#if NETFRAMEWORK
public static string dotnet_version_major, dotnet_version_minor, dotnet_version_build, dotnet_version;
public static string dotnet_version_major, dotnet_version_minor, dotnet_version_build, dotnet_version, dotnet_version_preOrRcString;
//#else
// public const string dotnet_version_major = "7";
// public const string dotnet_version_minor = "0";
@@ -239,7 +241,7 @@ static unsafe partial class Program
#if NETFRAMEWORK
try
{
var dotnet_version_ = ConfigurationManager.AppSettings["d"];
var dotnet_version_ = ConfigurationManager.AppSettings["d"]; // 由 App.config 中的配置键控制加载运行时版本
if (!
#if !NET35
string.
@@ -248,7 +250,7 @@ static unsafe partial class Program
dotnet_version_.Length < sbyte.MaxValue)
{
var dotnet_version__ = dotnet_version_.Split('.');
int major, minor, build;
int major, minor;
switch (dotnet_version__.Length)
{
case 1:
@@ -265,8 +267,17 @@ static unsafe partial class Program
case 3:
dotnet_version_major = (int.TryParse(dotnet_version__[0], out major) ? major : 0).ToString();
dotnet_version_minor = (int.TryParse(dotnet_version__[1], out minor) ? minor : 0).ToString();
dotnet_version_build = (int.TryParse(dotnet_version__[2], out build) ? build : 0).ToString();
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
// 支持 PRE/RC 版本号
string? preOrRcString = null;
var buildString = dotnet_version__[2];
var index = buildString.IndexOf('-');
if (index > 0)
{
preOrRcString = buildString.Substring(index);
buildString = buildString.Substring(0, index);
}
dotnet_version_build = (int.TryParse(buildString, out var build) ? build : 0).ToString();
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}{preOrRcString}";
break;
}
}
@@ -6,9 +6,6 @@
<RootNamespace>BD.WTTS</RootNamespace>
<DefineConstants>AVALONIA;$(DefineConstants)</DefineConstants>
<ApplicationId>net.steampp.app</ApplicationId>
<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<RetainVMGarbageCollection>false</RetainVMGarbageCollection>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
</PropertyGroup>
@@ -36,6 +33,18 @@
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<!-- 👇 GC 配置 MSBuild 属性 -->
<ServerGarbageCollection>false</ServerGarbageCollection>
<RetainVMGarbageCollection>false</RetainVMGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>
<ItemGroup>
<!-- 👇 GC 配置 runtimeconfig.json -->
<RuntimeHostConfigurationOption Include="System.GC.HighMemoryPercent" Value="75" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\ImplicitUsings.MessagePack.cs">
<LinkBase>Properties</LinkBase>
+2 -2
View File
@@ -7,9 +7,9 @@
<key>CFBundleDisplayName</key>
<string>Watt Toolkit</string>
<key>CFBundleShortVersionString</key>
<string>3.0.0</string>
<string>3.1.0</string>
<key>CFBundleVersion</key>
<string>3.0.1219.0</string>
<string>3.1.2021.0</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
+3 -1
View File
@@ -120,7 +120,9 @@ partial class Program
#endif
.UseSkia()
.LogToTrace()
.UseReactiveUI();
.UseReactiveUI(_ =>
{
});
builder.With(new FontManagerOptions
{
+33 -24
View File
@@ -295,33 +295,42 @@ sealed partial class Program : Startup
protected override ActiveUserRecordDTO GetActiveUserRecord()
{
#if !__MOBILE__ && !MAUI
var app = UI.App.Instance;
var window = app.GetFirstOrDefaultWindow();
var screens = window?.Screens;
#else
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
var mainDisplayInfoH = mainDisplayInfo.Height.ToInt32(NumberToInt32Format.Ceiling);
var mainDisplayInfoW = mainDisplayInfo.Width.ToInt32(NumberToInt32Format.Ceiling);
#endif
var result = new ActiveUserRecordDTO
{
#if __MOBILE__ || MAUI
ScreenCount = 1,
PrimaryScreenPixelDensity = mainDisplayInfo.Density,
PrimaryScreenWidth = mainDisplayInfoW,
PrimaryScreenHeight = mainDisplayInfoH,
SumScreenWidth = mainDisplayInfoW,
SumScreenHeight = mainDisplayInfoH,
#else
ScreenCount = screens?.ScreenCount ?? default,
PrimaryScreenPixelDensity = screens?.Primary?.Scaling ?? default,
PrimaryScreenWidth = screens?.Primary?.Bounds.Width ?? default,
PrimaryScreenHeight = screens?.Primary?.Bounds.Height ?? default,
SumScreenWidth = screens?.All.Sum(x => x.Bounds.Width) ?? default,
SumScreenHeight = screens?.All.Sum(x => x.Bounds.Height) ?? default,
#endif
};
SetScreen(result);
static void SetScreen(ActiveUserRecordDTO m)
{
try
{
#if !__MOBILE__ && !MAUI
var app = UI.App.Instance;
var window = app.GetFirstOrDefaultWindow();
var screens = window?.Screens;
m.ScreenCount = screens?.ScreenCount ?? default;
m.PrimaryScreenPixelDensity = screens?.Primary?.Scaling ?? default;
m.PrimaryScreenWidth = screens?.Primary?.Bounds.Width ?? default;
m.PrimaryScreenHeight = screens?.Primary?.Bounds.Height ?? default;
m.SumScreenWidth = screens?.All?.Sum(x => x.Bounds.Width) ?? default;
m.SumScreenHeight = screens?.All?.Sum(x => x.Bounds.Height) ?? default;
#else
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
var mainDisplayInfoH = mainDisplayInfo.Height.ToInt32(NumberToInt32Format.Ceiling);
var mainDisplayInfoW = mainDisplayInfo.Width.ToInt32(NumberToInt32Format.Ceiling);
m.ScreenCount = 1;
m.PrimaryScreenPixelDensity = mainDisplayInfo.Density;
m.PrimaryScreenWidth = mainDisplayInfoW;
m.PrimaryScreenHeight = mainDisplayInfoH;
m.SumScreenWidth = mainDisplayInfoW;
m.SumScreenHeight = mainDisplayInfoH;
#endif
}
catch
{
}
}
return result;
}
}
@@ -73,36 +73,34 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BD.Common" />
<PackageReference Include="BD.Common.Primitives" />
<PackageReference Include="BD.Common.Essentials" />
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="ReactiveUI.Avalonia" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" />
<PackageReference Include="Avalonia.Xaml.Behaviors" />
<PackageReference Include="Xaml.Behaviors.Avalonia" />
<PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="AvaloniaEdit.TextMate" />
<!--<PackageReference Include="Markdown.Avalonia" />-->
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="Avalonia.Controls.ColorPicker" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="AvaloniaUI.DiagnosticsSupport" />
<!--ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />-->
<ProjectReference Include="..\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\ref\Avalonia.Image2\Avalonia.Gif\Avalonia.Gif.csproj" />
<ProjectReference Include="..\BD.Avalonia8.Image2.Compat\BD.Avalonia8.Image2.Compat.csproj" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
<!-- Windows Only -->
<ProjectReference Include="..\Avalonia.X11\Avalonia.X11.csproj" />
<ProjectReference Include="..\Avalonia.Native\Avalonia.Native.csproj" />
<PackageReference Include="Aigio.Avalonia.AppCenter" />
<!--<PackageReference Include="Aigio.Avalonia.AppCenter" />
<PackageReference Include="Aigio.Avalonia.AppCenter.Analytics" />
<PackageReference Include="Aigio.Avalonia.AppCenter.Crashes" />
<PackageReference Include="Aigio.Avalonia.AppCenter.Crashes" />-->
<PackageReference Include="Avalonia.Win32" />
<PackageReference Include="BD.Common.Essentials.Maui.Runtime" />
<ProjectReference Include="..\..\ref\Common\src\BD.Common.Essentials.Maui.Runtime\BD.Common.Essentials.Maui.Runtime.csproj" />
<PackageReference Include="JumpLists.Win32.Avalonia" />
</ItemGroup>
@@ -111,7 +109,7 @@
<ProjectReference Include="..\Avalonia.X11\Avalonia.X11.csproj" />
<ProjectReference Include="..\Avalonia.Win32\Avalonia.Win32.csproj" />
<PackageReference Include="Avalonia.Native" />
<PackageReference Include="BD.Common.Essentials.Maui.Runtime" />
<ProjectReference Include="..\..\ref\Common\src\BD.Common.Essentials.Maui.Runtime\BD.Common.Essentials.Maui.Runtime.csproj" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == ''">
+1 -1
View File
@@ -45,7 +45,7 @@ public sealed partial class App : Application
{
if (ApplicationLifetime is ClassicDesktopStyleApplicationLifetime classicDesktopStyleApplicationLifetime)
{
window = classicDesktopStyleApplicationLifetime.Windows.FirstOrDefault(x => x != null);
window = classicDesktopStyleApplicationLifetime.Windows?.FirstOrDefault(x => x != null);
}
}
return window;
@@ -40,14 +40,6 @@
<Setter Property="FontFamily" Value="{Binding Source={x:Static ms:UISettings.FontName}, Path=Value, Mode=OneWay, Converter={StaticResource NameToFontFamilyConverter}}" />
</Style>
<Style Selector="ui|FontIcon">
<Setter Property="MinWidth" Value="16" />
</Style>
<Style Selector="ui|SymbolIcon">
<Setter Property="MinWidth" Value="16" />
</Style>
<!--<Style Selector="TextBlock">
<Setter Property="FontFamily" Value="{Binding Source={x:Static ms:UISettings.FontName}, Path=Value, Mode=OneWay, Converter={StaticResource NameToFontFamilyConverter}}" />
</Style>-->
@@ -1,18 +1,18 @@
<UserControl
d:DesignHeight="900"
d:DesignWidth="1080"
mc:Ignorable="d"
x:Class="BD.WTTS.UI.Views.Pages.HomePage"
x:CompileBindings="True"
x:DataType="spp:HomePageViewModel"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:col="using:System.Collections.Generic"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
xmlns:ui="using:FluentAvalonia.UI.Controls"
d:DesignHeight="900"
d:DesignWidth="1080"
x:CompileBindings="True"
x:DataType="spp:HomePageViewModel"
mc:Ignorable="d">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--<spp:PageBase.PreviewImage>
<ui:ImageIconSource Source="{Binding IconKey, Mode=OneWay, Converter={StaticResource BitmapAssetValueConverter}}" />
</spp:PageBase.PreviewImage>-->
@@ -70,87 +70,87 @@
<ScrollViewer Padding="20">
<!--<spp:ContentLoader IsLoading="False">-->
<StackPanel
x:Name="HomeRoot"
MaxWidth="1080"
Spacing="20">
Spacing="20"
x:Name="HomeRoot">
<spp:Snackbar
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
IsVisible="{spp:OnPlatform !GA}"
Message="当前使用的程序版本为测试版本,可能会有各种各样不可言明的 Bug,遇到问题请理性反馈,谢谢。"
Severity="Warning" />
Severity="Warning"
VerticalAlignment="Top" />
<spp:Snackbar
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
IsVisible="{spp:OnPlatform !GA}"
Message="如果您不喜欢当前主页为默认启动页面可以在【设置-界面设置-启动时默认打开页面】处更改" />
Message="如果您不喜欢当前主页为默认启动页面可以在【设置-界面设置-启动时默认打开页面】处更改"
VerticalAlignment="Top" />
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
IsVisible="{Binding !!!MainWindow2.PluginCount, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}"
Message="检测到你没有安装插件,请去下载插件即可使用更多功能。"
Severity="Warning" />
Severity="Warning"
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
VerticalAlignment="Top" />
<WrapPanel Margin="5,0">
<WrapPanel IsVisible="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
<spp:Image2
Width="36"
DecodeWidth="36"
Height="36"
Margin="0,0,5,0"
VerticalAlignment="Center"
DecodeWidth="36"
Source="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.AvatarMedium, Mode=OneWay}"
Stretch="Uniform">
Stretch="Uniform"
VerticalAlignment="Center"
Width="36">
<spp:Image2.Clip>
<EllipseGeometry Rect="0,0,36,36" />
</spp:Image2.Clip>
</spp:Image2>
<TextBlock
VerticalAlignment="Center"
Text="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.SteamNickName, Mode=OneWay}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
Theme="{StaticResource SubtitleTextBlockStyle}"
VerticalAlignment="Center" />
<TextBlock
VerticalAlignment="Center"
IsVisible="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.IPCountry, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
Text="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.IPCountry, Mode=OneWay, StringFormat={}({0})}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
Theme="{StaticResource SubtitleTextBlockStyle}"
VerticalAlignment="Center" />
<TextBlock
Margin="0,0,10,0"
VerticalAlignment="Center"
Text=","
Theme="{StaticResource SubtitleTextBlockStyle}" />
Theme="{StaticResource SubtitleTextBlockStyle}"
VerticalAlignment="Center" />
</WrapPanel>
<TextBlock VerticalAlignment="Center" Theme="{StaticResource SubtitleTextBlockStyle}">
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}" VerticalAlignment="Center">
<Run Text="{Binding Path=Res.Welcome_User_, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</TextBlock>
</WrapPanel>
<Grid Margin="0,-10,0,0" RowDefinitions="Auto,*">
<spp:CarouselBanner
MinHeight="350"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
ItemsSource="{Binding NavigationBanners}">
ItemsSource="{Binding NavigationBanners}"
MinHeight="350"
VerticalContentAlignment="Stretch">
<spp:CarouselBanner.ItemTemplate>
<DataTemplate DataType="spp:AdvertisementDTO">
<spp:AppItem
Title="{Binding Describe}"
Height="350"
Classes="Banner Scale"
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
ClickCommandParameter="{Binding Url}"
Description="{Binding Name}"
Height="350"
Title="{Binding Describe}"
ToolTip.Tip="{Binding Describe}">
<spp:AppItem.Status>
<Border
Padding="20,10"
Background="{DynamicResource AccentFillColorSelectedTextBackgroundBrush}"
ClipToBounds="True"
CornerRadius="{StaticResource ControlCornerRadius}">
CornerRadius="{StaticResource ControlCornerRadius}"
Padding="20,10">
<TextBlock FontSize="16" Text="阅读更多" />
</Border>
</spp:AppItem.Status>
@@ -176,22 +176,22 @@
Spacing="10">
<DockPanel>
<TextBlock
VerticalAlignment="Center"
Text="最新消息"
Theme="{StaticResource TitleTextBlockStyle}" />
Theme="{StaticResource TitleTextBlockStyle}"
VerticalAlignment="Center" />
<Button
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="{x:Static spp:HomePageViewModel.OfficialWebsite_Article}"
Content="查看更多"
DockPanel.Dock="Right"
Theme="{StaticResource HyperlinkButton}" />
HorizontalAlignment="Right"
Theme="{StaticResource HyperlinkButton}"
VerticalAlignment="Center" />
</DockPanel>
<Grid Margin="0,0,0,20" ColumnDefinitions="*,10,*,10,*">
<Grid ColumnDefinitions="*,10,*,10,*" Margin="0,0,0,20">
<Grid.Styles>
<Style x:DataType="spp:ArticleItemDTO" Selector="spp|AppItem">
<Style Selector="spp|AppItem" x:DataType="spp:ArticleItemDTO">
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Title" Value="{Binding Title}" />
<Setter Property="Description" Value="{Binding Introduction}" />
@@ -225,13 +225,13 @@
</Grid.Styles>
<spp:AppItem Classes="Vertical Scale" DataContext="{Binding Articles[0]}" />
<spp:AppItem
Grid.Column="2"
Classes="Vertical Scale"
DataContext="{Binding Articles[1]}" />
DataContext="{Binding Articles[1]}"
Grid.Column="2" />
<spp:AppItem
Grid.Column="4"
Classes="Vertical Scale"
DataContext="{Binding Articles[2]}" />
DataContext="{Binding Articles[2]}"
Grid.Column="4" />
</Grid>
<!--<spp:CarouselItems ItemsPerPage="3" ItemsSource="{Binding Articles}">
@@ -265,34 +265,34 @@
<DockPanel>
<TextBlock
VerticalAlignment="Center"
Text="精选"
Theme="{StaticResource TitleTextBlockStyle}" />
Theme="{StaticResource TitleTextBlockStyle}"
VerticalAlignment="Center" />
<Button
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="{x:Static spp:HomePageViewModel.WattGameUrl}"
Content="访问 WattGame 商城"
DockPanel.Dock="Right"
Theme="{StaticResource HyperlinkButton}" />
HorizontalAlignment="Right"
Theme="{StaticResource HyperlinkButton}"
VerticalAlignment="Center" />
</DockPanel>
<spp:CarouselItems
MinHeight="250"
ItemsPerPage="4"
ItemsSource="{Binding Shops}">
ItemsSource="{Binding Shops}"
MinHeight="250">
<spp:CarouselItems.ItemTemplate>
<DataTemplate x:DataType="spp:ShopRecommendGoodItem">
<!--<Border spp:Animations.EnableAnimations="True">-->
<spp:AppItem
Title="{Binding Name}"
MinHeight="250"
MaxWidth="260"
Classes="Vertical Scale"
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
Cursor="Hand"
MaxWidth="260"
MinHeight="250"
Tags="{Binding GoodsAllCategorysText}"
Title="{Binding Name}"
ToolTip.Tip="{Binding Brief}">
<spp:AppItem.ClickCommandParameter>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
@@ -310,31 +310,31 @@
<Border Classes="Status">
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
IsVisible="{Binding !!DiscountValue}"
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
TextDecorations="Strikethrough" />
TextDecorations="Strikethrough"
VerticalAlignment="Center" />
<TextBlock
VerticalAlignment="Center"
FontSize="14"
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
VerticalAlignment="Center" />
</StackPanel>
</Border>
</spp:AppItem.Status>
<spp:AppItem.ActionButton>
<Border
Height="24"
Padding="8,0"
Background="#4c6b22"
ClipToBounds="True"
CornerRadius="4"
IsVisible="{Binding !!DiscountValue}">
Height="24"
IsVisible="{Binding !!DiscountValue}"
Padding="8,0">
<TextBlock
VerticalAlignment="Center"
Foreground="#beee11"
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
Text="{Binding DiscountValue, StringFormat={}-{0}%}"
VerticalAlignment="Center" />
</Border>
</spp:AppItem.ActionButton>
</spp:AppItem>
@@ -522,9 +522,9 @@
<ItemsControl ItemsSource="{Binding MainWindow2.TabItems, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}">
<ItemsControl.Resources>
<ControlTheme
x:Key="NavButtonStyle"
BasedOn="{StaticResource {x:Type Button}}"
TargetType="Button">
TargetType="Button"
x:Key="NavButtonStyle">
<Setter Property="Width" Value="120" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="6" />
@@ -543,28 +543,28 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="spp:TabItemViewModel">
<Border spp:Animations.EnableAnimations="True">
<Border spp:Animations.EnableAnimations="False">
<Button
Command="{Binding $parent[spp:HomePage].((spp:HomePageViewModel)DataContext).NavgationToMenuPageCommand}"
CommandParameter="{Binding}"
Theme="{StaticResource NavButtonStyle}">
<Grid RowDefinitions="3*,*">
<Border
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="4 4 0 0"
RenderOptions.BitmapInterpolationMode="HighQuality">
HorizontalAlignment="Stretch"
RenderOptions.BitmapInterpolationMode="HighQuality"
VerticalAlignment="Stretch">
<spp:Image2
Width="96"
Height="96"
Margin="0,10,0,0"
Source="{Binding IconKey}"
Stretch="Uniform" />
Stretch="Uniform"
Width="96" />
</Border>
<TextBlock
FontWeight="SemiBold"
Grid.Row="1"
Margin="12"
FontWeight="SemiBold"
Text="{Binding Name}" />
</Grid>
</Button>
@@ -703,9 +703,9 @@
</TabControl>-->
<TextBlock
Margin="0,30"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HorizontalAlignment="Center"
Margin="0,30"
Text="不忘初心,方得始终。"
Theme="{StaticResource SubtitleTextBlockStyle}" />
</StackPanel>
@@ -16,48 +16,48 @@
x:CompileBindings="True"
x:DataType="spp:MainWindowViewModel"
mc:Ignorable="d">
<UserControl.Styles>
<Style Selector="ui|NavigationView.AppNav">
<Setter Property="IsPaneToggleButtonVisible" Value="False" />
<Setter Property="OpenPaneLength" Value="72" />
<Setter Property="IsPaneOpen" Value="True" />
</Style>
<UserControl.Styles>
<Style Selector="ui|NavigationView.AppNav">
<Setter Property="IsPaneToggleButtonVisible" Value="False" />
<Setter Property="OpenPaneLength" Value="72" />
<Setter Property="IsPaneOpen" Value="True" />
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=False] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 42 0 0" />
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=True] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 2 0 0" />
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=False] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 42 0 0" />
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=True] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 2 0 0" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav">
<Setter Property="Transitions">
<Setter.Value>
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.05" />
</Transitions>
</Setter.Value>
</Setter>
</Style>
<Style Selector="ui|NavigationViewItem.AppNav">
<Setter Property="Transitions">
<Setter.Value>
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.05" />
</Transitions>
</Setter.Value>
</Setter>
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter">
<Setter Property="Width" Value="72" />
<Setter Property="MinHeight" Value="75" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
<Setter Property="Template">
<ControlTemplate>
<Border
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter">
<Setter Property="Width" Value="72" />
<Setter Property="MinHeight" Value="75" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
<Setter Property="Template">
<ControlTemplate>
<Border
Name="LayoutRoot"
Margin="4,6"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
TemplatedControl.IsTemplateFocusTarget="True">
<Panel>
<Panel HorizontalAlignment="Left" VerticalAlignment="Center">
<Panel>
<Panel HorizontalAlignment="Left" VerticalAlignment="Center">
<Border
<Border
Name="SelectionIndicator"
Width="3"
Height="20"
@@ -65,11 +65,11 @@
Background="{DynamicResource NavigationViewSelectionIndicatorForeground}"
CornerRadius="{StaticResource ControlCornerRadius}"
Opacity="0" />
</Panel>
</Panel>
<DockPanel>
<ContentPresenter
<DockPanel>
<ContentPresenter
Name="ContentPresenter"
Grid.Row="1"
Margin="0,-15,0,3"
@@ -80,80 +80,80 @@
ContentTemplate="{TemplateBinding ContentTemplate}"
DockPanel.Dock="Bottom"
FontSize="10">
<ContentPresenter.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</ContentPresenter.Styles>
</ContentPresenter>
<ContentPresenter.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</ContentPresenter.Styles>
</ContentPresenter>
<Viewbox
<Viewbox
Name="IconBox"
Height="28"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter Name="Icon" Content="{Binding TemplateSettings.Icon, RelativeSource={RelativeSource TemplatedParent}}" />
</Viewbox>
<ContentPresenter Name="Icon" Content="{Binding TemplateSettings.Icon, RelativeSource={RelativeSource TemplatedParent}}" />
</Viewbox>
</DockPanel>
</Panel>
</Border>
</ControlTemplate>
</Setter>
</Style>
</DockPanel>
</Panel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pointerover /template/ ContentPresenter#Icon">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pointerover /template/ ContentPresenter#Icon">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pressed /template/ ContentPresenter#Icon">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:pressed /template/ ContentPresenter#Icon">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:selected /template/ ContentPresenter#ContentPresenter">
<Setter Property="IsVisible" Value="False" />
<Setter Property="Foreground" Value="{DynamicResource AccentFillColorDefaultBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:selected /template/ ContentPresenter#ContentPresenter">
<Setter Property="IsVisible" Value="False" />
<Setter Property="Foreground" Value="{DynamicResource AccentFillColorDefaultBrush}" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:selected /template/ ContentPresenter#Icon">
<Setter Property="Foreground" Value="{DynamicResource AccentFillColorDefaultBrush}" />
</Style>
</UserControl.Styles>
<Style Selector="ui|NavigationViewItem.AppNav uip|NavigationViewItemPresenter:selected /template/ ContentPresenter#Icon">
<Setter Property="Foreground" Value="{DynamicResource AccentFillColorDefaultBrush}" />
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto, *">
<spp:TitleBar Name="TitleBarHost">
<spp:TitleBar.ActionContent>
<StackPanel Orientation="Horizontal" Spacing="8">
<DropDownButton
<Grid RowDefinitions="Auto, *">
<spp:TitleBar Name="TitleBarHost">
<spp:TitleBar.ActionContent>
<StackPanel Orientation="Horizontal" Spacing="8">
<DropDownButton
Width="40"
Height="40"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding Path=MarkNotificationsHasRead, Mode=OneWay, Source={x:Static s:NoticeService.Current}}"
Theme="{DynamicResource TransparentButton}">
<Panel>
<ui:InfoBadge
<Panel>
<ui:InfoBadge
Margin="0,-3,-3,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Classes="Attention Dot"
IsVisible="{Binding Path=!!UnreadNotificationsCount, Mode=OneWay, Source={x:Static s:NoticeService.Current}}" />
<Viewbox
<Viewbox
Width="22"
Height="22"
VerticalAlignment="Center">
<ui:SymbolIcon Symbol="Alert" />
</Viewbox>
</Panel>
<ui:SymbolIcon Symbol="Alert" />
</Viewbox>
</Panel>
<DropDownButton.Flyout>
<!--<ui:FAMenuFlyout
<DropDownButton.Flyout>
<!--<ui:FAMenuFlyout
FlyoutPresenterClasses="NoPadding"
ItemsSource="{Binding Path=Notices, Mode=OneWay, Source={x:Static s:NoticeService.Current}}"
Placement="BottomEdgeAlignedRight">
@@ -166,79 +166,81 @@
</DataTemplate>
</ui:FAMenuFlyout.ItemTemplate>
</ui:FAMenuFlyout>-->
<Flyout FlyoutPresenterClasses="NoPadding" Placement="BottomEdgeAlignedRight">
<spp:NoticeFlyout Margin="5" />
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>
<Flyout FlyoutPresenterClasses="NoPadding" Placement="BottomEdgeAlignedRight">
<spp:NoticeFlyout Margin="5" />
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>
<DropDownButton Padding="4" Theme="{DynamicResource TransparentButton}">
<Border
<DropDownButton Padding="4" Theme="{DynamicResource TransparentButton}">
<Border
Width="32"
Height="32"
Background="{DynamicResource ControlOnImageFillColorDefaultBrush}"
ClipToBounds="True"
CornerRadius="30">
<spp:Image2
<spp:Image2
DecodeWidth="36"
FallbackSource="{StaticResource DefaultAvatar}"
RenderOptions.BitmapInterpolationMode="HighQuality"
KeepSourceStreamOpen="True"
Source="{Binding Path=AvatarPath, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
</Border>
<DropDownButton.Flyout>
<MenuFlyout FlyoutPresenterClasses="NoPadding" Placement="BottomEdgeAlignedRight">
<MenuItem
</Border>
<DropDownButton.Flyout>
<MenuFlyout FlyoutPresenterClasses="NoPadding" Placement="BottomEdgeAlignedRight">
<MenuItem
Command="{Binding Path=ShowWindow, Source={x:Static s:UserService.Current}}"
CommandParameter="{x:Static spp:AppEndPoint.LoginOrRegister}"
Cursor="Hand"
IsHitTestVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}}">
<MenuItem.Header>
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Border
<MenuItem.Header>
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Border
Width="64"
Height="64"
Margin="0,0,8,0"
ClipToBounds="True"
CornerRadius="64"
DockPanel.Dock="Left">
<spp:Image2
<spp:Image2
DecodeWidth="64"
FallbackSource="{StaticResource DefaultAvatar}"
RenderOptions.BitmapInterpolationMode="HighQuality"
KeepSourceStreamOpen="True"
Source="{Binding Path=AvatarPath, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
</Border>
<Panel>
<TextBlock
</Border>
<Panel>
<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}}" />
<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
<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
Padding="5,1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
CornerRadius="2"
IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#453a94" />
<GradientStop Offset="1.0" Color="#f43b47" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#453a94" />
<GradientStop Offset="1.0" Color="#f43b47" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
VerticalAlignment="Center"
FontSize="10"
Foreground="White"
Text="{Binding Path=Res.User_Sponsor, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}❤️{0}}" />
</Border>
</Border>
</StackPanel>
</Panel>
</DockPanel>
<!--<WrapPanel HorizontalAlignment="Center" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
</StackPanel>
</Panel>
</DockPanel>
<!--<WrapPanel HorizontalAlignment="Center" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
<Button Margin="5,0" FontSize="10">
<ToolTip.Tip>
<MultiBinding StringFormat="{}{0} : {1} / {2}">
@@ -273,33 +275,33 @@
</Button.Content>
</Button>
</WrapPanel>-->
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
CommandParameter="https://steampp.net/account/center/info"
Header="{Binding Path=Res.PersonalCenter, 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 s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
<MenuItem
<!--<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
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
CommandParameter="https://steampp.net/account/center/bind"
Header="{Binding Path=Res.BindThirdPartyAccount, 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
<MenuItem
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
CommandParameter="https://steampp.net/account/center/safe"
Header="{Binding Path=Res.PersonalSafe, 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="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
<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}}"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<!--<MenuItem
<!--<MenuItem
Command="{Binding Path=ShowWindow,
Source={x:Static s:UserService.Current}}"
CommandParameter="{x:Static spp:AppEndPoint.LoginOrRegister}"
@@ -311,14 +313,14 @@
Mode=OneWay,
Source={x:Static s:UserService.Current},
Converter={StaticResource IsNullConverter}}" />-->
</MenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
</StackPanel>
</spp:TitleBar.ActionContent>
</spp:TitleBar>
</MenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
</StackPanel>
</spp:TitleBar.ActionContent>
</spp:TitleBar>
<ui:NavigationView
<ui:NavigationView
Name="NavView"
Grid.RowSpan="2"
AlwaysShowHeader="False"
@@ -330,35 +332,35 @@
IsSettingsVisible="False"
MenuItemsSource="{Binding TabItems}"
PaneDisplayMode="Left">
<ui:NavigationView.Resources>
<!-- Override this -->
<Thickness x:Key="NavigationViewContentMargin">0,50,0,0</Thickness>
</ui:NavigationView.Resources>
<ui:NavigationView.Styles>
<Style Selector="Button#NavigationViewBackButton">
<Setter Property="FontFamily" Value="{DynamicResource SymbolThemeFontFamily}" />
<Setter Property="Width" Value="36" />
<Setter Property="Height" Value="36" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Content" Value="&#xF0D5;" />
</Style>
<Style Selector="Grid#PaneToggleButtonGrid">
<Setter Property="Margin" Value="5 0 0 0" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="ToolTip.Placement" Value="Bottom" />
</Style>
</ui:NavigationView.Styles>
<ui:NavigationView.MenuItemTemplate>
<DataTemplate DataType="spp:TabItemViewModel">
<ui:NavigationViewItem Classes="AppNav" Content="{Binding Name}">
<ui:NavigationViewItem.IconSource>
<!--<ui:FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{Binding IconKey, Converter={StaticResource ResourceKeyValueConverter}, ConverterParameter=fonticon}" />-->
<ui:ImageIconSource Source="{Binding IconKey, Converter={StaticResource BitmapAssetValueConverter}}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
</DataTemplate>
</ui:NavigationView.MenuItemTemplate>
<ui:Frame Name="FrameView" />
</ui:NavigationView>
</Grid>
<ui:NavigationView.Resources>
<!-- Override this -->
<Thickness x:Key="NavigationViewContentMargin">0,50,0,0</Thickness>
</ui:NavigationView.Resources>
<ui:NavigationView.Styles>
<Style Selector="Button#NavigationViewBackButton">
<Setter Property="FontFamily" Value="{DynamicResource SymbolThemeFontFamily}" />
<Setter Property="Width" Value="36" />
<Setter Property="Height" Value="36" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Content" Value="&#xF0D5;" />
</Style>
<Style Selector="Grid#PaneToggleButtonGrid">
<Setter Property="Margin" Value="5 0 0 0" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="ToolTip.Placement" Value="Bottom" />
</Style>
</ui:NavigationView.Styles>
<ui:NavigationView.MenuItemTemplate>
<DataTemplate DataType="spp:TabItemViewModel">
<ui:NavigationViewItem Classes="AppNav" Content="{Binding Name}">
<ui:NavigationViewItem.IconSource>
<!--<ui:FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{Binding IconKey, Converter={StaticResource ResourceKeyValueConverter}, ConverterParameter=fonticon}" />-->
<ui:ImageIconSource Source="{Binding IconKey, Converter={StaticResource BitmapAssetValueConverter}}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
</DataTemplate>
</ui:NavigationView.MenuItemTemplate>
<ui:Frame Name="FrameView" />
</ui:NavigationView>
</Grid>
</UserControl>
@@ -53,7 +53,7 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="spp:TabItemViewModel">
<Border spp:Animations.EnableAnimations="True">
<Border spp:Animations.EnableAnimations="False">
<Button
Command="{Binding $parent[spp:PluginStorePage].((spp:PluginStorePageViewModel)DataContext).NavgationToMenuPageCommand}"
CommandParameter="{Binding}"
@@ -1,7 +1,11 @@
<UserControl
d:DesignHeight="680"
d:DesignWidth="1080"
mc:Ignorable="d"
x:Class="BD.WTTS.UI.Views.Pages.Settings_UI"
x:CompileBindings="True"
x:DataType="spp:SettingsPageViewModel"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:com="https://steampp.net/common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:enum="using:BD.WTTS.Enums"
@@ -11,11 +15,7 @@
xmlns:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
xmlns:ui="using:FluentAvalonia.UI.Controls"
d:DesignHeight="680"
d:DesignWidth="1080"
x:CompileBindings="True"
x:DataType="spp:SettingsPageViewModel"
mc:Ignorable="d">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Spacing="{StaticResource SettingsSpacing}">
<TextBlock
Margin="0,0,0,10"
@@ -82,125 +82,125 @@
<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
x:Name="AccentCustom"
Content="{Binding Path=Res.Settings_UI_Custom, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
IsChecked="{Binding Source={x:Static ms:UISettings.UseSystemThemeAccent}, Path=!Value, Mode=OneWay}" />
IsChecked="{Binding Source={x:Static ms:UISettings.UseSystemThemeAccent}, Path=!Value, Mode=OneWay}"
x:Name="AccentCustom" />
</StackPanel>
</spp:OptionsDisplayItem.ActionButton>
<spp:OptionsDisplayItem.Content>
<StackPanel Spacing="4">
<DockPanel Margin="24,0,0,6" LastChildFill="False">
<DockPanel LastChildFill="False" Margin="24,0,0,6">
<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 s:ResourceService.Current}}"
VerticalAlignment="Center" />
<Grid
HorizontalAlignment="Left"
ColumnDefinitions="*,*"
DockPanel.Dock="Right"
HorizontalAlignment="Left"
RowDefinitions="*,*,*,*">
<Border
Background="{DynamicResource SystemAccentColor}"
Grid.ColumnSpan="2"
Height="40"
Background="{DynamicResource SystemAccentColor}">
Height="40">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Text="SystemAccentColor" />
HorizontalAlignment="Center"
Text="SystemAccentColor"
VerticalAlignment="Center" />
</Border>
<Border
Background="{DynamicResource SystemAccentColorLight1}"
Grid.Column="0"
Grid.Row="1"
Grid.Column="0"
Width="90"
Height="40"
Background="{DynamicResource SystemAccentColorLight1}">
Width="90">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Text="Light1" />
HorizontalAlignment="Center"
Text="Light1"
VerticalAlignment="Center" />
</Border>
<Border
Background="{DynamicResource SystemAccentColorLight2}"
Grid.Column="0"
Grid.Row="2"
Grid.Column="0"
Width="90"
Height="40"
Background="{DynamicResource SystemAccentColorLight2}">
Width="90">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Text="Light2" />
HorizontalAlignment="Center"
Text="Light2"
VerticalAlignment="Center" />
</Border>
<Border
Grid.Row="3"
Background="{DynamicResource SystemAccentColorLight3}"
Grid.Column="0"
Width="90"
Grid.Row="3"
Height="40"
Background="{DynamicResource SystemAccentColorLight3}">
Width="90">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Text="Light3" />
HorizontalAlignment="Center"
Text="Light3"
VerticalAlignment="Center" />
</Border>
<Border
Background="{DynamicResource SystemAccentColorDark1}"
Grid.Column="1"
Grid.Row="1"
Grid.Column="1"
Width="90"
Height="40"
Background="{DynamicResource SystemAccentColorDark1}">
Width="90">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Text="Dark1" />
HorizontalAlignment="Center"
Text="Dark1"
VerticalAlignment="Center" />
</Border>
<Border
Background="{DynamicResource SystemAccentColorDark2}"
Grid.Column="1"
Grid.Row="2"
Grid.Column="1"
Width="90"
Height="40"
Background="{DynamicResource SystemAccentColorDark2}">
Width="90">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Text="Dark2" />
HorizontalAlignment="Center"
Text="Dark2"
VerticalAlignment="Center" />
</Border>
<Border
Grid.Row="3"
Background="{DynamicResource SystemAccentColorDark3}"
Grid.Column="1"
Width="90"
Grid.Row="3"
Height="40"
Background="{DynamicResource SystemAccentColorDark3}">
Width="90">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Text="Dark3" />
HorizontalAlignment="Center"
Text="Dark3"
VerticalAlignment="Center" />
</Border>
</Grid>
</DockPanel>
<Border
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}"
Height="1"
Margin="-20"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />
Margin="-20" />
<TextBlock Margin="24,24,0,0" Text="Windows 颜色" />
<ListBox
Name="ColorListBox"
MaxWidth="441"
Margin="24,0,24,12"
HorizontalAlignment="Left"
AutoScrollToSelectedItem="False"
HorizontalAlignment="Left"
Margin="24,0,24,12"
MaxWidth="441"
Name="ColorListBox"
SelectedItem="{Binding Source={x:Static ms:UISettings.ThemeAccent}, Converter={StaticResource ColorStringToBrushConverter}, Path=Value, Mode=TwoWay}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
@@ -218,22 +218,22 @@
<ControlTemplate>
<Panel>
<Border
Name="Root"
BorderThickness="2"
CornerRadius="{StaticResource ControlCornerRadius}">
CornerRadius="{StaticResource ControlCornerRadius}"
Name="Root">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<Border
Name="Check"
Width="20"
Background="{DynamicResource FocusStrokeColorOuter}"
Height="20"
Margin="0,2,2,0"
HorizontalAlignment="Right"
Margin="0,2,2,0"
Name="Check"
VerticalAlignment="Top"
Background="{DynamicResource FocusStrokeColorOuter}">
Width="20">
<ui:SymbolIcon
FontSize="18"
Foreground="{DynamicResource SystemAccentColor}"
@@ -261,26 +261,17 @@
</ListBox>
<Border
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}"
Height="1"
Margin="-20"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />
Margin="-20" />
<DockPanel Margin="24,6,0,0" LastChildFill="False">
<DockPanel LastChildFill="False" Margin="24,6,0,0">
<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 s:ResourceService.Current}}"
VerticalAlignment="Center" />
<ui:ColorPickerButton
DockPanel.Dock="Right"
IsCompact="True"
IsMoreButtonVisible="True"
ShowAcceptDismissButtons="True"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
UseSpectrum="True"
Color="{Binding Source={x:Static ms:UISettings.ThemeAccent}, Converter={StaticResource ColorStringToBrushConverter}, Path=Value, Mode=TwoWay}" />
<ColorPicker Color="{Binding Source={x:Static ms:UISettings.ThemeAccent}, Converter={StaticResource ColorStringToBrushConverter}, Path=Value, Mode=TwoWay}" DockPanel.Dock="Right" />
</DockPanel>
</StackPanel>
</spp:OptionsDisplayItem.Content>
@@ -298,7 +289,7 @@
<ComboBox.ItemTemplate>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="x:String, x:String" TypeName="generic:KeyValuePair" />
<x:Type TypeName="generic:KeyValuePair" x:TypeArguments="x:String, x:String" />
</DataTemplate.DataType>
<TextBlock Text="{Binding Value, Mode=OneWay}" />
</DataTemplate>
@@ -321,7 +312,7 @@
<ComboBox.ItemTemplate>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="x:String, x:String" TypeName="generic:KeyValuePair" />
<x:Type TypeName="generic:KeyValuePair" x:TypeArguments="x:String, x:String" />
</DataTemplate.DataType>
<TextBlock FontFamily="{Binding Value, Mode=OneWay, Converter={StaticResource NameToFontFamilyConverter}}" Text="{Binding Key, Mode=OneWay}" />
</DataTemplate>
@@ -360,8 +351,8 @@
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Slider
MinWidth="250"
Maximum="1.0"
MinWidth="250"
Minimum="0"
Value="{Binding Value, Source={x:Static ms:UISettings.WindowBackgroundOpacity}, Mode=TwoWay}" />
</spp:OptionsDisplayItem.ActionButton>
@@ -381,72 +372,72 @@
<spp:OptionsDisplayItem.Content>
<StackPanel Spacing="4">
<DockPanel Margin="24,0,0,6" LastChildFill="False">
<DockPanel LastChildFill="False" Margin="24,0,0,6">
<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 s:ResourceService.Current}}"
VerticalAlignment="Center" />
<Image
Width="250"
DockPanel.Dock="Right"
Height="150"
HorizontalAlignment="Left"
DockPanel.Dock="Right"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding Source={x:Static ms:UISettings.WindowBackgroundCustomImagePath}, Path=Value, Mode=OneWay, Converter={StaticResource BitmapAssetValueConverter}}"
Stretch="{Binding Source={x:Static ms:UISettings.WindowBackgroundCustomImageStretch}, Path=Value, Mode=OneWay, Converter={StaticResource EnumConverter}, ConverterParameter={x:Type Stretch}}" />
Stretch="{Binding Source={x:Static ms:UISettings.WindowBackgroundCustomImageStretch}, Path=Value, Mode=OneWay, Converter={StaticResource EnumConverter}, ConverterParameter={x:Type Stretch}}"
Width="250" />
</DockPanel>
<Border
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}"
Height="1"
Margin="-20"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />
Margin="-20" />
<DockPanel Margin="24,6,0,6" LastChildFill="False">
<DockPanel LastChildFill="False" Margin="24,6,0,6">
<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 s:ResourceService.Current}}"
VerticalAlignment="Center" />
<Slider
MinWidth="250"
DockPanel.Dock="Right"
Maximum="1.0"
MinWidth="250"
Minimum="0"
Value="{Binding Value, Source={x:Static ms:UISettings.WindowBackgroundCustomImageOpacity}, Mode=TwoWay}" />
</DockPanel>
<Border
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}"
Height="1"
Margin="-20"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />
Margin="-20" />
<DockPanel Margin="24,6,0,6" LastChildFill="False">
<DockPanel LastChildFill="False" Margin="24,6,0,6">
<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 s:ResourceService.Current}}"
VerticalAlignment="Center" />
<Button
MinWidth="150"
Command="{Binding SelectImage_Click}"
Content="{Binding Path=Res.Settings_UI_SelectImage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
DockPanel.Dock="Right" />
DockPanel.Dock="Right"
MinWidth="150" />
</DockPanel>
<Border
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}"
Height="1"
Margin="-20"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />
Margin="-20" />
<DockPanel Margin="24,6,0,0" LastChildFill="False">
<DockPanel LastChildFill="False" Margin="24,6,0,0">
<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 s:ResourceService.Current}}"
VerticalAlignment="Center" />
<ComboBox
MinWidth="150"
DockPanel.Dock="Right"
MinWidth="150"
SelectedItem="{Binding Source={x:Static ms:UISettings.WindowBackgroundCustomImageStretch}, Path=Value, Mode=TwoWay}">
<ComboBox.Items>
<spp:XamlMediaStretch>Fill</spp:XamlMediaStretch>
@@ -472,21 +463,21 @@
<ListBoxItem>
<Grid ColumnDefinitions="Auto,*,Auto">
<ui:ImageIcon
Width="24"
Height="24"
Margin="8"
Source="{Binding IconKey, Converter={StaticResource BitmapAssetValueConverter}}"
VerticalAlignment="Center"
Source="{Binding IconKey, Converter={StaticResource BitmapAssetValueConverter}}" />
Width="24" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding Name}" />
Text="{Binding Name}"
VerticalAlignment="Center" />
<Viewbox
Grid.Column="2"
Width="18"
Height="18"
Margin="8,0"
VerticalAlignment="Center">
VerticalAlignment="Center"
Width="18">
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{StaticResource Move}" />
</Viewbox>
</Grid>
@@ -190,7 +190,7 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
});
AdvertiseService.Current.InitAdvertise();
await NoticeService.Current.GetNewsAsync();
NoticeService.Current.GetNews();
var mainWindow = App.Instance.MainWindow;
mainWindow.ThrowIsNull();
@@ -214,12 +214,12 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/bug.ico",
},
#endif
new MenuTabItemViewModel("Plugin_Store")
{
PageType = typeof(PluginStorePage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
},
// new MenuTabItemViewModel("Plugin_Store")
// {
// PageType = typeof(PluginStorePage),
// IsResourceGet = true,
// IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
// },
new MenuTabItemViewModel("Settings")
{
PageType = typeof(SettingsPage),
@@ -48,8 +48,9 @@
<ItemGroup>
<!-- PackageReference -->
<PackageReference Include="BD.Common" />
<PackageReference Include="dotnetCampus.Ipc" />
<ProjectReference Include="..\..\ref\Common\src\BD.Common\BD.Common.csproj" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc.Analyzers\dotnetCampus.Ipc.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc\dotnetCampus.Ipc.csproj" />
<PackageReference Include="NLog" />
<PackageReference Include="NLog.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
@@ -46,16 +46,113 @@ public sealed class IPCSubProcessServiceImpl : IPCSubProcessService
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool TryGetProcessById(int pid, [NotNullWhen(true)] out Process? process)
{
try
{
process = Process.GetProcessById(pid);
return true;
}
catch
{
// 异常不记录日志
process = null;
return false;
}
}
internal static bool CheckBePid(Process proc)
{
if (proc.Id == Environment.ProcessId)
{
return true;
}
var thisPath = Environment.ProcessPath;
ArgumentException.ThrowIfNullOrWhiteSpace(thisPath);
thisPath = Path.GetFullPath(thisPath);
var procPath = proc.TryGetMainModule()?.FileName;
procPath = procPath == null ? null : Path.GetFullPath(procPath);
if (procPath != null)
{
if (thisPath == procPath)
{
return true;
}
var accProPath = Path.GetFullPath(Path.Combine(thisPath, "..", "modules", "Accelerator",
#if WINDOWS
"Steam++.Accelerator.exe"
#else
"Steam++.Accelerator"
#endif
));
if (procPath == accProPath)
{
return true;
}
if (AssemblyInfo.ValidateAssembly(procPath))
{
return true;
}
}
return false;
}
internal static bool CheckBePid(int pid)
{
if (pid == Environment.ProcessId)
{
return true;
}
if (!TryGetProcessById(pid, out var proc))
{
return false;
}
return CheckBePid(proc);
}
internal static void ValidateNamedPipeClientConnection(object? s, NamedPipeClientConnectingEventArgs e)
{
#if WINDOWS
#if DEBUG
Log.Info(nameof(IPCSubProcessServiceImpl), "服务端正在校验命名管道客户端进程,{ClientProcessId}", e.ClientProcessId);
#endif
try
{
// 验证连接的客户端进程必须是自己的程序
if (!e.ClientProcessId.HasValue ||
!CheckBePid(e.ClientProcessId.Value))
{
e.AllowConnection = false;
return;
}
}
catch (Exception ex)
{
Log.Error(nameof(IPCSubProcessServiceImpl), ex, "IpcConfiguration.NamedPipeClientConnecting fail");
e.AllowConnection = false;
}
#endif
}
public async Task RunAsync(string moduleName, TaskCompletionSource tcs, string pipeName,
Action<IpcProvider>? configureIpcProvider = null)
{
this.tcs = tcs;
ipcProvider = new IpcProvider(
IPCSubProcessModuleService.Constants.GetClientPipeName(moduleName, pipeName),
new IpcConfiguration
{
IpcLoggerProvider = _ => new IpcLogger_(loggerFactory, nameof(IPCSubProcessServiceImpl)),
});
var ipcCfg = new IpcConfiguration
{
IpcLoggerProvider = _ => new IpcLogger_(loggerFactory, nameof(IPCSubProcessServiceImpl)),
};
ipcCfg.NamedPipeClientConnecting += ValidateNamedPipeClientConnection;
ipcProvider = new IpcProvider(IPCSubProcessModuleService.Constants.GetClientPipeName(moduleName, pipeName), ipcCfg);
ipcProvider.CreateIpcJoint<IPCSubProcessModuleService>(new IPCSubProcessModuleServiceImpl(this));
configureIpcProvider?.Invoke(ipcProvider);
ipcProvider.StartServer();
@@ -89,6 +89,14 @@ public interface IPCSubProcessService : IDisposable
if (!TryGetProcessById(pid, out var mainProcess))
return (int)CommandExitCode.NotFoundMainProcessId;
#if DEBUG
Log.Info(nameof(IPCSubProcessServiceImpl), "客户端正在校验服务端进程,{pid}", pid);
#endif
// 验证要连接的服务端进程是否为自己的程序
var ckPid = IPCSubProcessServiceImpl.CheckBePid(mainProcess);
if (!ckPid)
return (int)CommandExitCode.MainProcessIdIncorrect;
#if LIB_CLIENT_IPC
var nativeLibraryPath = Environment.GetEnvironmentVariable(EnvKey_NativeLibraryPath);
if (!string.IsNullOrWhiteSpace(nativeLibraryPath))
@@ -92,8 +92,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BD.Common" />
<PackageReference Include="BD.Common.Repositories.SQLitePCL" />
<!--<PackageReference Include="BD.Common" />
<PackageReference Include="BD.Common.Repositories.SQLitePCL" />-->
<ProjectReference Include="..\..\ref\Common\src\BD.Common.Repositories.SQLitePCL\BD.Common.Repositories.SQLitePCL.csproj" />
<ProjectReference Include="..\..\ref\Common\src\BD.Common\BD.Common.csproj" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc.Analyzers\dotnetCampus.Ipc.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc\dotnetCampus.Ipc.csproj" />
<PackageReference Include="NLog" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="Ae.DNS.Client" />
@@ -101,9 +105,9 @@
<PackageReference Include="DnsClient" />
<PackageReference Include="Yarp.ReverseProxy" />
<PackageReference Include="NLog.Web.AspNetCore" />
<PackageReference Include="Aigio.Any.AppCenter" />
<!--<PackageReference Include="Aigio.Any.AppCenter" />
<PackageReference Include="Aigio.Any.AppCenter.Analytics" />
<PackageReference Include="Aigio.Any.AppCenter.Crashes" />
<PackageReference Include="Aigio.Any.AppCenter.Crashes" />-->
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Runtime.Caching" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
@@ -119,6 +123,8 @@
<ProjectReference Include="..\BD.WTTS.Client.IPC\BD.WTTS.Client.IPC.csproj" />
</ItemGroup>
<Import Project="..\OSShuttingDownHelper\OSShuttingDownHelper.projitems" Label="Shared" />
<!-- 此项目不使用平台 TFM !!! -->
<Import Project="..\TFM_NETX.props" />
@@ -35,6 +35,7 @@ public static class KestrelServerOptionsExtensions
options.Listen(IReverseProxyService.Constants.Instance.ProxyIp, httpProxyPort, listen =>
{
listen.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
var proxyMiddleware = options.ApplicationServices.GetRequiredService<HttpProxyMiddleware>();
var tunnelMiddleware = options.ApplicationServices.GetRequiredService<TunnelMiddleware>();
@@ -123,7 +124,7 @@ public static class KestrelServerOptionsExtensions
var httpsPort = IReverseProxyConfig.HttpsPort;
options.Listen(IReverseProxyService.Constants.Instance.ProxyIp, httpsPort, listen =>
{
listen.Protocols = HttpProtocols.Http1AndHttp2;
listen.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
listen.UseFlowAnalyze();
listen.UseTls();
});
@@ -1,5 +1,6 @@
using BD.Common.Repositories.Abstractions;
using dotnetCampus.Ipc.CompilerServices.GeneratedProxies;
using Windows.Win32;
using KeyValuePair = BD.Common.Entities.KeyValuePair;
const string moduleName = AssemblyInfo.Accelerator;
@@ -35,6 +36,12 @@ static bool IsProcessElevated_DEBUG_Only()
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
#endif
if (OSShuttingDownHelper.IsSystemShuttingDown())
{
return 0;
}
try
{
var exitCode = await IPCSubProcessService.MainAsync(moduleName, pluginName, ConfigureServices, static ipcProvider =>
@@ -77,7 +84,7 @@ static void ConfigureServices(IServiceCollection services)
services.AddLogging(l =>
{
l.ClearProviders();
l.AddNLog(LogManager.Configuration); // 添加 NLog 日志
l.AddNLog(LogManager.Configuration!); // 添加 NLog 日志
#if DEBUG
l.AddConsole();
#endif
@@ -301,37 +301,37 @@ sealed partial class CertificateManagerImpl : ICertificateManager
ICertificateManager.Constants.TrustRootCertificate(GetCerFilePathGeneratedWhenNoFileExists, platformService, RootCertificate);
}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
[SupportedOSPlatform("macOS")]
void TrustRootCertificateMacOS()
{
var filePath = GetCerFilePathGeneratedWhenNoFileExists();
if (filePath == null) return;
var state = platformService.TrustRootCertificateAsync(filePath);
//await platformService.RunShellAsync($"security add-trusted-cert -d -r trustRoot -k /Users/{Environment.UserName}/Library/Keychains/login.keychain-db \\\"{filePath}\\\"", true);
if (state.HasValue && !state.Value)
TrustRootCertificateMacOS();
}
//[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
//[SupportedOSPlatform("macOS")]
//void TrustRootCertificateMacOS()
//{
// var filePath = GetCerFilePathGeneratedWhenNoFileExists();
// if (filePath == null) return;
// var state = platformService.TrustRootCertificateAsync(filePath);
// //await platformService.RunShellAsync($"security add-trusted-cert -d -r trustRoot -k /Users/{Environment.UserName}/Library/Keychains/login.keychain-db \\\"{filePath}\\\"", true);
// if (state.HasValue && !state.Value)
// TrustRootCertificateMacOS();
//}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
[SupportedOSPlatform("Linux")]
void TrustRootCertificateLinux()
{
var filePath = GetCerFilePathGeneratedWhenNoFileExists();
if (filePath == null) return;
var state = platformService.TrustRootCertificateAsync(filePath);
//部分系统还是只能手动导入浏览器
Browser2.Open(Constants.Urls.OfficialWebsite_LiunxSetupCer);
if (state.HasValue && !state.Value)
GetCerFilePathGeneratedWhenNoFileExists();
//全部屏蔽 Linux 浏览器全部不信任系统证书 只能手动导入 如需导入请手动操作
//var crtFile = $"{Path.Combine(IOPath.AppDataDirectory, $@"{ICertificateManager.CertificateName}.Certificate.crt")}";
////复制一份Crt导入系统用 ca-certificates 只识别Crt后缀
//platformService.RunShell($"cp -f \"{filePath}\" \"{crtFile}\"", false);
//platformService.RunShell($"cp -f \"{crtFile}\" \"/usr/local/share/ca-certificates\" && sudo update-ca-certificates", true);
//浏览器不信任系统证书列表
//Browser2.Open(Constants.Urls.OfficialWebsite_LiunxSetupCer);
}
//[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
//[SupportedOSPlatform("Linux")]
//void TrustRootCertificateLinux()
//{
// var filePath = GetCerFilePathGeneratedWhenNoFileExists();
// if (filePath == null) return;
// var state = platformService.TrustRootCertificateAsync(filePath);
// //部分系统还是只能手动导入浏览器
// Browser2.Open(Constants.Urls.OfficialWebsite_LiunxSetupCer);
// if (state.HasValue && !state.Value)
// GetCerFilePathGeneratedWhenNoFileExists();
// //全部屏蔽 Linux 浏览器全部不信任系统证书 只能手动导入 如需导入请手动操作
// //var crtFile = $"{Path.Combine(IOPath.AppDataDirectory, $@"{ICertificateManager.CertificateName}.Certificate.crt")}";
// ////复制一份Crt导入系统用 ca-certificates 只识别Crt后缀
// //platformService.RunShell($"cp -f \"{filePath}\" \"{crtFile}\"", false);
// //platformService.RunShell($"cp -f \"{crtFile}\" \"/usr/local/share/ca-certificates\" && sudo update-ca-certificates", true);
// //浏览器不信任系统证书列表
// //Browser2.Open(Constants.Urls.OfficialWebsite_LiunxSetupCer);
//}
/// <inheritdoc cref="ICertificateManager.SetupRootCertificate"/>
public bool SetupRootCertificate()
@@ -36,7 +36,7 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
// 设置请求头 host,因为连接中已带端口号故 修改协议为 http
request.Headers.Host = uri.Host;
request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
//request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
if (domainConfig.Timeout != null)
{
@@ -61,6 +61,7 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
AutomaticDecompression = DecompressionMethods.None,
ConnectCallback = ConnectCallback,
EnableMultipleHttp2Connections = true,
EnableMultipleHttp3Connections = true,
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8,
ResponseHeaderEncodingSelector = (_, _) => Encoding.UTF8,
};
@@ -30,6 +30,12 @@ sealed class HttpLocalRequestMiddleware
/// <returns></returns>
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (TryGetInjectScriptLocalId(context.Request.Path, out var injectScriptLid))
{
await HandleScriptRequestAsync(context, injectScriptLid);
return;
}
if (HttpMethods.IsOptions(context.Request.Method) && context.Request.Headers.ContainsKey("Access-Control-Request-Private-Network"))
{
// https://wicg.github.io/private-network-access/
@@ -60,34 +66,50 @@ sealed class HttpLocalRequestMiddleware
await HandleHttpRequestAsync(context);
return;
default: //默认处理脚本匹配
if (!int.TryParse(context.Request.Path.Value?.Trim('/'), out var lid) && lid <= 0)
{
await Handle404NotFoundAsync(context);
return;
}
// TODO: Scripts
if (reverseProxyConfig.TryGetScriptContent(lid, out string? content))
{
if (string.IsNullOrEmpty(content))
{
await Handle404NotFoundAsync(context);
return;
}
context.Response.Headers.ContentType = "text/javascript;charset=UTF-8";
await context.Response.WriteAsync(content);
return;
}
else
if (!int.TryParse(context.Request.Path.Value?.Trim('/'), out var lid) || lid <= 0)
{
await Handle404NotFoundAsync(context);
return;
}
await HandleScriptRequestAsync(context, lid);
return;
}
}
await next(context);
}
static bool TryGetInjectScriptLocalId(PathString path, out int localId)
{
localId = default;
var pathValue = path.Value;
if (string.IsNullOrEmpty(pathValue))
return false;
var prefix = IReverseProxyService.Constants.InjectScriptPathPrefix;
if (!pathValue.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
return false;
var idPart = pathValue[prefix.Length..];
if (!idPart.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
return false;
idPart = idPart[..^3];
return int.TryParse(idPart, out localId) && localId > 0;
}
async Task HandleScriptRequestAsync(HttpContext context, int lid)
{
if (reverseProxyConfig.TryGetScriptContent(lid, out var content) && !string.IsNullOrEmpty(content))
{
context.Response.Headers.ContentType = "application/javascript;charset=UTF-8";
await context.Response.WriteAsync(content);
return;
}
await Handle404NotFoundAsync(context);
}
/// <summary>
/// 处理脚本所需要的 HTTP 请求
/// </summary>
@@ -90,4 +90,122 @@ partial class HttpReverseProxyMiddleware
notfound: insertPosition = -1;
return false;
}
internal static bool FindScriptInjectInsertPositionForGithub(byte[] buffer_, Encoding encoding, out ReadOnlyMemory<byte> buffer, out int insertPosition)
{
buffer = buffer_.AsMemory();
ReadOnlySpan<byte> mark = "<script"u8;
var lastScriptWithSrcStart = -1;
var span = buffer_.AsSpan();
if (span.Length >= mark.Length)
{
for (var i = 0; i <= span.Length - mark.Length; i++)
{
if (!EqualsAsciiIgnoreCase(span.Slice(i, mark.Length), mark))
{
continue;
}
var afterMarkIndex = i + mark.Length;
if (afterMarkIndex < span.Length && IsHtmlAttributeNameChar(span[afterMarkIndex]))
{
continue;
}
var tagEndOffset = span.Slice(afterMarkIndex).IndexOf((byte)'>');
if (tagEndOffset < 0)
{
break;
}
var tagEndIndex = afterMarkIndex + tagEndOffset;
var scriptTag = span.Slice(i, tagEndIndex - i + 1);
if (ScriptTagHasSrcAttribute(scriptTag))
{
lastScriptWithSrcStart = i;
}
i = tagEndIndex;
}
}
if (lastScriptWithSrcStart >= 0)
{
insertPosition = lastScriptWithSrcStart;
return true;
}
return FindScriptInjectInsertPosition(buffer_, encoding, out buffer, out insertPosition);
}
static bool ScriptTagHasSrcAttribute(ReadOnlySpan<byte> scriptTag)
{
ReadOnlySpan<byte> src = "src"u8;
if (scriptTag.Length < src.Length)
return false;
for (var i = 0; i <= scriptTag.Length - src.Length; i++)
{
if (!EqualsAsciiIgnoreCase(scriptTag.Slice(i, src.Length), src))
continue;
if (i > 0 && IsHtmlAttributeNameChar(scriptTag[i - 1]))
continue;
var j = i + src.Length;
while (j < scriptTag.Length && IsAsciiWhitespace(scriptTag[j]))
{
j++;
}
if (j < scriptTag.Length && scriptTag[j] == (byte)'=')
{
return true;
}
}
return false;
}
static bool EqualsAsciiIgnoreCase(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
{
if (left.Length != right.Length)
return false;
for (var i = 0; i < left.Length; i++)
{
var l = ToUpperAscii(left[i]);
var r = ToUpperAscii(right[i]);
if (l != r)
return false;
}
return true;
}
static byte ToUpperAscii(byte value)
{
if (value is >= (byte)'a' and <= (byte)'z')
return (byte)(value - 32);
return value;
}
static bool IsAsciiWhitespace(byte value) =>
value == (byte)' ' ||
value == (byte)'\t' ||
value == (byte)'\r' ||
value == (byte)'\n' ||
value == (byte)'\f';
static bool IsHtmlAttributeNameChar(byte value) =>
(value is >= (byte)'a' and <= (byte)'z') ||
(value is >= (byte)'A' and <= (byte)'Z') ||
(value is >= (byte)'0' and <= (byte)'9') ||
value == (byte)'-' ||
value == (byte)'_' ||
value == (byte)':' ||
value == (byte)'.';
}
@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Http;
using Yarp.ReverseProxy.Forwarder;
using HttpVersion = System.Net.HttpVersion;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
@@ -30,6 +31,27 @@ sealed partial class HttpReverseProxyMiddleware
this.logger = logger;
}
static ArgumentOutOfRangeException GetUnknownHttpVersionException(string? actualValue, [CallerArgumentExpression(nameof(actualValue))] string? paramName = null) => new(
$"""
Version doesn't map to a known HTTP protocol. (Parameter '{paramName}')
Actual value was {actualValue}.
""");
static Version GetHttpVersion(string requestProtocol) =>
(requestProtocol != null && requestProtocol.Length >= 6) ? requestProtocol[5] switch
{
// 参考 Microsoft.AspNetCore.Http.HttpProtocol.GetHttpProtocol
'1' => requestProtocol.Length >= 8 ? requestProtocol[7] switch
{
'0' => HttpVersion.Version10,
'1' => HttpVersion.Version11,
_ => throw GetUnknownHttpVersionException(requestProtocol),
} : throw GetUnknownHttpVersionException(requestProtocol),
'2' => HttpVersion.Version20,
'3' => HttpVersion.Version30,
_ => throw GetUnknownHttpVersionException(requestProtocol),
} : throw GetUnknownHttpVersionException(requestProtocol);
/// <summary>
/// 处理请求
/// </summary>
@@ -60,12 +82,7 @@ sealed partial class HttpReverseProxyMiddleware
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, null);
var forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = context.Request.Protocol switch
{
var protocol when protocol.StartsWith("HTTP/2") => System.Net.HttpVersion.Version20,
var protocol when protocol.StartsWith("HTTP/3") => System.Net.HttpVersion.Version30,
_ => System.Net.HttpVersion.Version11,
},
Version = GetHttpVersion(context.Request.Protocol),
};
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
if (error != ForwarderError.None)
@@ -119,19 +136,23 @@ sealed partial class HttpReverseProxyMiddleware
context.Request.Headers.UserAgent = domainConfig.UserAgent.Replace("${origin}", context.Request.Headers.UserAgent, StringComparison.OrdinalIgnoreCase);
}
var forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = context.Request.Protocol switch
{
var protocol when protocol.StartsWith("HTTP/2") => System.Net.HttpVersion.Version20,
var protocol when protocol.StartsWith("HTTP/3") => System.Net.HttpVersion.Version30,
_ => System.Net.HttpVersion.Version11,
},
};
ForwarderRequestConfig forwarderRequestConfig;
if (domainConfig.IsServerSideProxy)
{
SetWattHeaders(context, reverseProxyConfig.Service.ServerSideProxyToken);
forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = HttpVersion.Version30,
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
}
else
{
forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = GetHttpVersion(context.Request.Protocol),
};
}
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
@@ -306,7 +327,12 @@ sealed partial class HttpReverseProxyMiddleware
}
}
if (FindScriptInjectInsertPosition(buffer_, encoding, out var buffer, out var position))
var isGithubHost = IsGithubHost(context.Request.Host.Host);
var isFindPosition = isGithubHost
? FindScriptInjectInsertPositionForGithub(buffer_, encoding, out var buffer, out var position)
: FindScriptInjectInsertPosition(buffer_, encoding, out buffer, out position);
if (isFindPosition)
{
using var bodyWriter = new MemoryStream();
using Stream? bodyCompress = GetStreamByContentCompression(bodyWriter, contentCompression, CompressionMode.Compress, true);
@@ -329,8 +355,8 @@ sealed partial class HttpReverseProxyMiddleware
// var html_start_string = encoding.GetString(html_start.Span);
//#endif
await bodyCoreWriter.WriteAsync(html_start);
ReadOnlyMemory<byte> script_xml_start = "<script type=\"text/javascript\" src=\"https://local.steampp.net/"u8.ToArray();
ReadOnlyMemory<byte> script_xml_end = "\"></script>"u8.ToArray();
var script_xml_start = Encoding.ASCII.GetBytes($"<script type=\"text/javascript\" src=\"{IReverseProxyService.Constants.InjectScriptPathPrefix}");
ReadOnlyMemory<byte> script_xml_end = ".js\"></script>"u8.ToArray();
foreach (var script in scripts)
{
await bodyCoreWriter.WriteAsync(script_xml_start);
@@ -377,6 +403,15 @@ sealed partial class HttpReverseProxyMiddleware
context.Response.Body = originalBody;
}
}
static bool IsGithubHost(string? host)
{
if (string.IsNullOrWhiteSpace(host))
return false;
return host.Equals("github.com", StringComparison.OrdinalIgnoreCase) ||
host.EndsWith(".github.com", StringComparison.OrdinalIgnoreCase);
}
}
static Stream? GetStreamByContentCompression(Stream stream, string contentCompression, CompressionMode mode, bool leaveOpen) => contentCompression switch
@@ -154,8 +154,9 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
{
reverseProxySettings = Serializable.DMP2<ReverseProxySettings>(reverseProxySettings_);
}
catch
catch (Exception e)
{
OnException(e);
return StartProxyResultCode.DeserializeReverseProxySettingsFail;
}
@@ -98,8 +98,58 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
protected override Task<StartProxyResult> StartProxyImpl() => Task.FromResult(StartProxyCore());
readonly string[] allowedHosts = ["*",];
#if WINDOWS
/// <summary>
/// https://learn.microsoft.com/zh-cn/dotnet/core/extensions/globalization-icu#determine-if-your-app-is-using-icu
/// </summary>
/// <returns></returns>
static bool ICUMode()
{
SortVersion sortVersion = CultureInfo.InvariantCulture.CompareInfo.Version;
byte[] bytes = sortVersion.SortId.ToByteArray();
int version = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0];
return version != 0 && version == sortVersion.FullVersion;
}
static bool IcuTest(IEnumerable<string> incoming)
{
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 18362) && ICUMode())
{
// https://learn.microsoft.com/zh-cn/dotnet/core/extensions/globalization-icu#icu-on-windows
// 与 .NET 6 和 .NET 5 相比,.NET 7 及更高版本能够在早期 Windows 版本上加载 ICU
// 通常问题出在 1703 ~ 1903 之间的 Windows 版本
try
{
// https://github.com/dotnet/aspnetcore/blob/v9.0.2/src/Middleware/HostFiltering/src/MiddlewareConfigurationManager.cs#L67
foreach (var entry in incoming)
{
// Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode.
var host = new HostString(entry).ToUriComponent();
}
return true;
}
catch
{
return false;
}
}
else
{
return true;
}
}
#endif
StartProxyResult StartProxyCore()
{
#if WINDOWS
if (!IcuTest(allowedHosts))
{
return StartProxyResultCode.IcuTestFail;
}
#endif
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub/Program.cs#L29
try
{
@@ -113,13 +163,10 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
builder.Logging.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(AssemblyInfo.Accelerator));
builder.Services.Configure<HostFilteringOptions>(static o =>
builder.Services.Configure<HostFilteringOptions>(o =>
{
o.AllowEmptyHosts = true;
o.AllowedHosts = new List<string>
{
"*",
};
o.AllowedHosts = allowedHosts;
});
builder.Host.UseNLog();
@@ -34,6 +34,11 @@ public partial interface IReverseProxyService : IDisposable
public const string LocalDomain = "local.steampp.net";
/// <summary>
/// 同源脚本注入路径前缀,避免使用外域 script src 造成页面脚本 publicPath 污染。
/// </summary>
public const string InjectScriptPathPrefix = "/WattToolkit_Inject/";
public static IPAddress DefaultProxyIp => IPAddress.Any;
}
@@ -179,7 +184,7 @@ public partial interface IReverseProxySettings
string? ServerSideProxyToken { get; set; }
}
[MP2Obj(SerializeLayout.Explicit)]
[MP2Obj(GenerateType.VersionTolerant, SerializeLayout.Explicit)]
public readonly partial record struct ReverseProxySettings(
[property: MP2Key(0)]
IReadOnlyCollection<AccelerateProjectDTO>? ProxyDomains,
@@ -341,6 +346,12 @@ public enum StartProxyResultCode : byte
/// 绑定端口错误
/// </summary>
BindPortError,
/// <summary>
/// 早期 Windows 版本且缺少 Windows 更新补丁,Icu 存在 Bug 导致,要么更新操作系统,要么更改配置使用 NLS 重启程序
/// <para>https://learn.microsoft.com/zh-cn/dotnet/core/extensions/globalization-icu#use-nls-instead-of-icu</para>
/// </summary>
IcuTestFail = 127, // 值与 Mobius 保持一致
}
/// <summary>
@@ -82,6 +82,8 @@
<ItemGroup>
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
<ProjectReference Include="..\BD.WTTS.Client.Avalonia\BD.WTTS.Client.Avalonia.csproj" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc.Analyzers\dotnetCampus.Ipc.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc\dotnetCampus.Ipc.csproj" />
</ItemGroup>
<ItemGroup>
@@ -9,7 +9,7 @@ internal partial class NetworkTestService : INetworkTestService
#region 常量
// 默认测试地址
const string DEFAULT_TESTSERVER_HOSTNAME = "stun.syncthing.net";
internal const string DEFAULT_TESTSERVER_HOSTNAME = "stun.miwifi.com";
const int DEFAULT_PORT = 3478;
const int DEFAULT_TLSPORT = 5349;
@@ -202,8 +202,6 @@ internal partial class NetworkTestService : INetworkTestService
#region RFC3489
StunClient3489? _stunClient;
/// <summary>
/// 测试 STUN 客户端 基于 RFC3489
/// </summary>
@@ -217,19 +215,17 @@ internal partial class NetworkTestService : INetworkTestService
string? testServerHostName = DEFAULT_TESTSERVER_HOSTNAME,
int? testServerPort = DEFAULT_TESTSTUN3489_PORT,
IPEndPoint? localIPEndPoint = null,
bool force = false,
CancellationToken cancellationToken = default
)
{
testServerHostName ??= DEFAULT_TESTSERVER_HOSTNAME;
testServerPort ??= DEFAULT_TESTSTUN3489_PORT;
if (_stunClient == null || force)
_stunClient = await GetStunClient3489Async(testServerHostName, testServerPort.Value, localIPEndPoint);
using var stunClient = await GetStunClient3489Async(testServerHostName, testServerPort.Value, localIPEndPoint);
try
{
await _stunClient.QueryAsync(cancellationToken);
await stunClient.QueryAsync(cancellationToken);
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
@@ -238,7 +234,7 @@ internal partial class NetworkTestService : INetworkTestService
return null;
}
return _stunClient.State;
return stunClient.State;
}
private async ValueTask<StunClient3489> GetStunClient3489Async(string serverHostName, int port, IPEndPoint? localIPEndPoint = null)
@@ -289,7 +285,7 @@ internal partial class NetworkTestService : INetworkTestService
testServerHostName ??= DEFAULT_TESTSERVER_HOSTNAME;
testServerPort ??= DEFAULT_TESTSTUN5389_PORT;
IStunClient5389 client = await GetStunClient5389Async(protocol, testServerHostName, testServerPort.Value, localIPEndPoint);
using var client = await GetStunClient5389Async(protocol, testServerHostName, testServerPort.Value, localIPEndPoint);
if (client == null)
return null;
@@ -91,14 +91,12 @@ internal interface IStunTestService
/// <param name="testServerHostName">STUN 服务地址</param>
/// <param name="testServerPort">STUN 服务端口</param>
/// <param name="localIPEndPoint">本机IP</param>
/// <param name="force">刷新测试 Client</param>
/// <remarks></remarks>
/// <returns></returns>
Task<ClassicStunResult?> TestStunClient3489Async(
string? testServerHostName = default,
int? testServerPort = default,
IPEndPoint? localIPEndPoint = default,
bool force = false,
CancellationToken cancellationToken = default
);
@@ -81,7 +81,7 @@ public sealed partial class GameAcceleratorService
if (XunYouSDK.IsSupported)
{
this.WhenValueChanged(x => x.XYAccelState, false)
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(x =>
{
if (x == null)
@@ -184,7 +184,7 @@ public sealed partial class GameAcceleratorService
});
UserService.Current.WhenAnyValue(static x => x.User)
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async x =>
{
string vipEndTimeString = VipEndTimeStringDef;
@@ -629,45 +629,9 @@ public sealed partial class GameAcceleratorService
};
var install = Ioc.Get<IAcceleratorService>().XY_Install(GameAcceleratorSettings.WattAcceleratorDirPath.Value!);
td.Opened += (s, e) =>
td.Opened += async (s, e) =>
{
DownloadCallbackAsync(td, install).Wait();
//await foreach (var item in install)
//{
// if (item.HandleUI(out var content))
// {
// switch (content)
// {
// case < 100:
// Dispatcher.UIThread.Post(() => { td.Content = $"正在下载 {item.Content}%"; });
// td.SetProgressBarState(item.Content, TaskDialogProgressState.Normal);
// break;
// case 100:
// td.SetProgressBarState(item.Content, TaskDialogProgressState.Indeterminate);
// Dispatcher.UIThread.Post(() => { td.Content = $"下载完成,正在安装..."; });
// break;
// case (int)XunYouDownLoadCode.安装成功:
// //处理成功
// //Dispatcher.UIThread.Post(() => { td.Content = $"安装完成"; });
// Dispatcher.UIThread.Post(() => { td.Hide(TaskDialogStandardResult.OK); });
// td.Hide();
// break;
// case int n when n > 101 && n < (int)XunYouDownLoadCode.启动安装程序失败:
// //处理失败
// break;
// // Code 和进度重叠 递进 1000 XunYouInstallOrStartCode.默认 XunYouInstallOrStartCode.已安装
// case 1000:
// Dispatcher.UIThread.Post(() => { td.Content = $"默认"; });
// // XunYouInstallOrStartCode.默认
// break;
// case 1001:
// Dispatcher.UIThread.Post(() => { td.Content = $"已安装"; });
// // XunYouInstallOrStartCode.已安装
// Dispatcher.UIThread.Post(() => { td.Hide(TaskDialogStandardResult.OK); });
// break;
// }
// }
//}
await DownloadCallbackAsync(td, install);
};
//_ = Task.Run(() => { XunYouSDK.InstallAsync(progress, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WattAccelerator")); });
@@ -74,6 +74,9 @@ partial class ProxyService
bool useDoh = ProxySettings.UseDoh.Value;
string? customDohAddres = ProxySettings.CustomDohAddres2.Value;
string? proxyToken = proxyDomains.Any_Nullable(s => s.ProxyType == ProxyType.ServerAccelerate || s.Items.Any_Nullable(x => x.ProxyType == ProxyType.ServerAccelerate)) ?
await TryRequestServerSideProxyToken() : null;
if (ProxySettings.ProxyBeforeDNSCheck.Value)
{
if (useDoh)
@@ -225,13 +228,10 @@ partial class ProxyService
ICertificateManager.Constants.Instance);
if (checkRootCertificateCode != StartProxyResultCode.Ok)
{
return $"CheckRootCertificate, ErrCode: {checkRootCertificateCode}";
return Strings.Error_CheckRootCertificateFailed_;
}
}
string? proxyToken = proxyDomains.Any_Nullable(s => s.ProxyType == ProxyType.ServerAccelerate || s.Items.Any_Nullable(x => x.ProxyType == ProxyType.ServerAccelerate)) ?
await TryRequestServerSideProxyToken() : null;
ReverseProxySettings reverseProxySettings = new(proxyDomains, scripts,
isEnableScript, isOnlyWorkSteamBrowser, proxyPort,
proxyIp, proxyMode, isProxyGOG, onlyEnableProxyScript,
@@ -295,14 +295,14 @@ partial class ProxyService
}
//新线程等待 IPC 错误返回后 Kill 自己 Linux 修改权限需要重新启动
reverseProxyService.Exit();
return "StartProxyFail: BindPortError";
return Strings.Error_BindPortError_.Format(proxyPort);
}
else
{
var errorString = startProxyResult.Code switch
{
StartProxyResultCode.Exception => startProxyResult.Exception?.ToString() ?? nameof(StartProxyResultCode.Exception),
_ => $"StartProxyFail, ErrCode: {startProxyResult.Code}"
_ => $"{Strings.Error_BindPortError_.Format(proxyPort)}StaartProxyErrorCode:${startProxyResult.Code}"
};
return errorString;
}
@@ -406,8 +406,10 @@ partial class ProxyService
if (!string.IsNullOrEmpty(proxyTokenCache))
return proxyTokenCache;
using CancellationTokenSource cts = new();
cts.CancelAfter(TimeSpan.FromSeconds(4.99D)); // 5 秒超时
var resp = await IMicroServiceClient.Instance.Account
.GenerateServerSideProxyToken();
.GenerateServerSideProxyToken(cts.Token);
if (resp != null && !string.IsNullOrEmpty(resp.Content?.AccessToken))
{
@@ -416,11 +418,14 @@ partial class ProxyService
return proxyTokenCache;
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
ex.LogAndShowT();
return null;
}
return null;
}
/// <summary>
@@ -431,7 +436,7 @@ partial class ProxyService
{
public static implicit operator OperateProxyServiceResult(string error) => new(error);
public static implicit operator OperateProxyServiceResult(Exception exception) => new(null, exception);
public static implicit operator OperateProxyServiceResult(Exception exception) => new(exception.Message, exception);
/// <summary>
/// 将返回结果使用 Toast 显示并且返回当前应设置的代理状态
@@ -28,13 +28,13 @@ public sealed partial class ProxyService
ProxyDomains
.Connect()
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Sort(SortExpressionComparer<AccelerateProjectGroupDTO>.Ascending(x => x.Order).ThenBy(x => x.Name))
.Bind(out _ProxyDomainsList)
.Subscribe(_ => SelectGroup = ProxyDomains.Items.FirstOrDefault());
//this.WhenValueChanged(x => x.ProxyStatus, false)
// .ObserveOn(RxApp.MainThreadScheduler)
// .ObserveOn(RxSchedulers.MainThreadScheduler)
// .Subscribe(async proxyStatusLeft =>
// {
// bool proxyStatusRight;
@@ -64,7 +64,7 @@ public sealed partial class ProxyService
// });
this.WhenAnyValue(v => v.ProxyDomainsList)
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(domain => domain?
.ToObservableChangeSet()
.AutoRefresh(x => x.ObservableItems)
@@ -78,7 +78,7 @@ public sealed partial class ProxyService
}));
this.WhenAnyValue(v => v.ProxyScripts)
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(script => script?
.Connect()
.AutoRefresh(x => x.Disable)
@@ -163,8 +163,35 @@ public sealed partial class ProxyService
return [];
var data = ProxyDomains.Items
.Where(x => x.Items != null)
.SelectMany(s => s.Items!.Where(w => w.ThreeStateEnable != false));
//return data.Concat(data.SelectMany(s => GetProxyDomainsItems(s)));
.SelectMany(s => s.Items!.Where(w => w.ThreeStateEnable != false))
.Select(item =>
{
//过滤部分选中的子项
if (item.Items.Any_Nullable())
{
return new AccelerateProjectDTO
{
Name = item.Name,
Port = item.Port,
MatchDomainNames = item.MatchDomainNames,
ForwardDomainNames = item.ForwardDomainNames,
IgnoreSSLCertVerification = item.IgnoreSSLCertVerification,
FakeServerName = item.FakeServerName,
ProxyType = item.ProxyType,
ListenDomainNames = item.ListenDomainNames,
Checked = item.Checked,
Id = item.Id,
Order = item.Order,
FakeUserAgent = item.FakeUserAgent,
Version = item.Version,
ThreeStateEnable = item.ThreeStateEnable,
// 递归过滤子项
Items = item.Items.Where(x => x.ThreeStateEnable != false).ToList()
};
}
return item;
});
return data;
}
@@ -11,7 +11,7 @@ internal partial class GameAcceleratorSettingsContext : JsonSerializerContext
=> instance ??= new GameAcceleratorSettingsContext(ISettings.GetDefaultOptions());
}
[MPObj, MP2Obj(SerializeLayout.Explicit)]
[MP2Obj(SerializeLayout.Explicit)]
public sealed partial class GameAcceleratorSettings_ : ISettings, ISettings<GameAcceleratorSettings_>
{
public const string Name = nameof(GameAcceleratorSettings);
@@ -21,16 +21,17 @@ public sealed partial class AcceleratorPageViewModel
{
// Create new ProxyEnableDomain for 加速服务 page
var enableGroupDomain = ProxyService.Current.ProxyDomainsList
.Where(list => list.ThreeStateEnable == true || list.ThreeStateEnable == null)
.Where(list => list.ThreeStateEnable != false)
.Select(list => new ProxyDomainGroupViewModel
{
Name = list.Name,
IconUrl = list.IconUrl ?? string.Empty,
EnableProxyDomainVMs = new(
list.Items!
.Where(i => i.ThreeStateEnable == true)
.Where(i => i.ThreeStateEnable != false)
.Select(i => new ProxyDomainViewModel(i.Name, i.ProxyType, "https://" + i.ListenDomainNames.Split(";")[0],
i.Items?
.Where(x => x.ThreeStateEnable != false)
.Select(c => new ProxyDomainViewModel(c.Name, c.ProxyType, "https://" + c.ListenDomainNames.Split(';')[0]))
.ToList()))
.ToList()),
@@ -9,7 +9,7 @@ public sealed partial class GameAcceleratorPageViewModel
GameAcceleratorService.Current.Games
.Connect()
.Sort(new LastAccelerateTimeComparer())
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Bind(out _Games)
.Subscribe();
}
@@ -13,7 +13,7 @@ public sealed partial class GameAcceleratorViewModel
GameAcceleratorService.Current.Games
.Connect()
.Sort(new LastAccelerateTimeComparer())
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Bind(out _Games)
.Subscribe();
@@ -31,10 +31,9 @@ public partial class NetworkCheckControlViewModel : ViewModelBase
public string[] STUNAddress { get; } =
[
"stun.syncthing.net",
NetworkTestService.DEFAULT_TESTSERVER_HOSTNAME,
"stun.hot-chilli.net",
"stun.fitauto.ru",
"stun.miwifi.com",
];
[ObservableAsProperty]
@@ -90,28 +89,7 @@ public partial class NetworkCheckControlViewModel : ViewModelBase
{
SelectedSTUNAddress = STUNAddress[0];
NATCheckCommand = ReactiveCommand.CreateFromTask(async () =>
{
var natCheckResult = await _networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown };
var (netSucc, _) = await _networkTestService.TestOpenUrlAsync("https://www.baidu.com");
var publicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown";
var localEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown";
var (natLevel, natTypeTip) = natCheckResult.NatType switch
{
// Open
NatType.OpenInternet or NatType.FullCone => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"),
// Moderate
NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"),
// Strict
NatType.Symmetric or NatType.UdpBlocked => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"),
// Unknown
NatType.Unknown or NatType.UnsupportedServer or _ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"),
};
return new NATFetchResult(publicEndPoint, localEndPoint, natLevel, natTypeTip, netSucc);
});
NATCheckCommand = ReactiveCommand.CreateFromTask(NATCheckAsync);
NATCheckCommand
.IsExecuting
.ToPropertyEx(this, x => x.IsNATChecking);
@@ -145,36 +123,7 @@ public partial class NetworkCheckControlViewModel : ViewModelBase
var canDNSCheck = this.WhenAnyValue(x => x.DomainPendingTest)
.Select(domain => domain == string.Empty || DomainRegExp().IsMatch(domain));
DNSCheckCommand = ReactiveCommand.CreateFromTask(async () =>
{
var testDomain = DomainPendingTest == string.Empty ? DefaultTestDomain : DomainPendingTest;
try
{
long delayMs;
IPAddress[] address;
if (ProxySettings.UseDoh)
{
var configDoh = ProxySettings.CustomDohAddres2.Value ?? ProxySettingsWindowViewModel.DohAddress.FirstOrDefault() ?? string.Empty;
(delayMs, address) = await _networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh);
}
else
{
var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty;
(delayMs, address) = await _networkTestService.TestDNSAsync(testDomain, configDns, 53);
}
if (address.Length == 0)
throw new Exception("Parsing failed. Return empty ip address.");
DNSTestDelay = delayMs + "ms ";
DNSTestResult = string.Empty + address.FirstOrDefault();
}
catch (Exception ex)
{
Log.Error(nameof(AcceleratorPageViewModel), ex.ToString());
DNSTestDelay = string.Empty;
DNSTestResult = "error";
}
}, canDNSCheck);
DNSCheckCommand = ReactiveCommand.CreateFromTask(DNSCheckAsync, canDNSCheck);
DNSCheckCommand
.IsExecuting
.ToPropertyEx(this, x => x.IsDNSChecking);
@@ -199,4 +148,70 @@ public partial class NetworkCheckControlViewModel : ViewModelBase
IPv6CheckCommand.Execute().Subscribe();
}
async Task<NATFetchResult> NATCheckAsync()
{
try
{
return await NATCheckCoreAsync();
}
catch
{
return new("", "", "", "", false);
}
}
async Task<NATFetchResult> NATCheckCoreAsync()
{
var natCheckResult = await _networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown };
var (netSucc, _) = await _networkTestService.TestOpenUrlAsync("https://www.baidu.com");
var publicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown";
var localEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown";
var (natLevel, natTypeTip) = natCheckResult.NatType switch
{
// Open
NatType.OpenInternet or NatType.FullCone => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"),
// Moderate
NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"),
// Strict
NatType.Symmetric or NatType.UdpBlocked => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"),
// Unknown
NatType.Unknown or NatType.UnsupportedServer or _ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"),
};
return new NATFetchResult(publicEndPoint, localEndPoint, natLevel, natTypeTip, netSucc);
}
async Task DNSCheckAsync()
{
var testDomain = DomainPendingTest == string.Empty ? DefaultTestDomain : DomainPendingTest;
try
{
long delayMs;
IPAddress[] address;
if (ProxySettings.UseDoh)
{
var configDoh = ProxySettings.CustomDohAddres2.Value ?? ProxySettingsWindowViewModel.DohAddress.FirstOrDefault() ?? string.Empty;
(delayMs, address) = await _networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh);
}
else
{
var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty;
(delayMs, address) = await _networkTestService.TestDNSAsync(testDomain, configDns, 53);
}
if (address.Length == 0)
throw new Exception("Parsing failed. Return empty ip address.");
DNSTestDelay = delayMs + "ms ";
DNSTestResult = string.Empty + address.FirstOrDefault();
}
catch (Exception ex)
{
Log.Error(nameof(AcceleratorPageViewModel), ex.ToString());
DNSTestDelay = string.Empty;
DNSTestResult = "error";
}
}
}
@@ -59,7 +59,7 @@ public partial class ScriptPageViewModel : TabItemViewModel
ProxyService.Current.ProxyScripts
.Connect()
//.Filter(scriptFilter)
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Sort(SortExpressionComparer<ScriptDTO>.Ascending(x => x.Order).ThenBy(x => x.Name))
.Bind(out _ProxyScripts)
.Subscribe(_ => this.RaisePropertyChanged(nameof(IsProxyScriptsEmpty)));
@@ -11,7 +11,7 @@ public partial class ScriptStorePageViewModel : ViewModelBase
_ScriptsSourceList
.Connect()
//.Filter(scriptFilter)
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Sort(SortExpressionComparer<ScriptDTO>.Ascending(x => x.Order).ThenBy(x => x.Name))
.Bind(out _Scripts)
.Subscribe(_ => this.RaisePropertyChanged(nameof(IsScriptsEmpty)));
@@ -1,6 +1,6 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
using BD.WTTS.Helpers;
namespace BD.WTTS.UI.Views.Controls;
@@ -1,6 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
namespace BD.WTTS.UI.Views.Controls;
@@ -213,27 +213,31 @@
Binding="{Binding PingValue}"
ComparisonCondition="GreaterThan"
Value="120">
<ia:ChangePropertyAction
PropertyName="Fill"
TargetObject="PingEllipse"
Value="#FF4F58" />
<ia:ChangePropertyAction
PropertyName="Foreground"
TargetObject="PingText"
Value="#FF4F58" />
<ia:ChangePropertyAction PropertyName="Fill" TargetObject="PingEllipse">
<ia:ChangePropertyAction.Value>
<SolidColorBrush Color="#FF4F58" />
</ia:ChangePropertyAction.Value>
</ia:ChangePropertyAction>
<ia:ChangePropertyAction PropertyName="Foreground" TargetObject="PingText">
<ia:ChangePropertyAction.Value>
<SolidColorBrush Color="#FF4F58" />
</ia:ChangePropertyAction.Value>
</ia:ChangePropertyAction>
</ia:DataTriggerBehavior>
<ia:DataTriggerBehavior
Binding="{Binding PingValue}"
ComparisonCondition="LessThanOrEqual"
Value="120">
<ia:ChangePropertyAction
PropertyName="Fill"
TargetObject="PingEllipse"
Value="#47A42B" />
<ia:ChangePropertyAction
PropertyName="Foreground"
TargetObject="PingText"
Value="#47A42B" />
<ia:ChangePropertyAction PropertyName="Fill" TargetObject="PingEllipse">
<ia:ChangePropertyAction.Value>
<SolidColorBrush Color="#47A42B" />
</ia:ChangePropertyAction.Value>
</ia:ChangePropertyAction>
<ia:ChangePropertyAction PropertyName="Foreground" TargetObject="PingText">
<ia:ChangePropertyAction.Value>
<SolidColorBrush Color="#47A42B" />
</ia:ChangePropertyAction.Value>
</ia:ChangePropertyAction>
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
<TextBlock
@@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
using System.CommandLine;
namespace BD.WTTS.UI.Views.Controls;
@@ -1,6 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
namespace BD.WTTS.UI.Views.Controls;
@@ -1,4 +1,4 @@
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
namespace BD.WTTS.UI.Views.Pages;
@@ -104,7 +104,7 @@
<Button Content="选择证书"/>
</DockPanel>-->
<ui:SettingsExpanderItem Description="*该选项勾选后会导致程序的加速完全不生效,请确认是配合二级代理实现仅需要脚本功能后再启用">
<ui:SettingsExpanderItem Description="*该选项勾选后会导致程序的加速失效,请确认是配合二级代理实现仅需要脚本功能后再启用">
<TextBlock Text="{Binding Path=Res.CommunityFix_ProxyOnlyOnScript, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.OnlyEnableProxyScript}, Path=Value, Mode=TwoWay}" />
@@ -114,7 +114,7 @@
<ui:SettingsExpander
Description="*改动后需重启加速才能生效"
Description="*请在有正确二级代理的情况下才考虑启用该选项,否则会导致程序的加速失效,改动后需重启加速才能生效"
Header="{Binding Path=Res.Settings_Proxy_TwoLevelAgent, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
IsExpanded="True">
<ui:SettingsExpander.Footer>
@@ -1,5 +1,5 @@
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
namespace BD.WTTS.UI.Views.Pages;
@@ -31,7 +31,7 @@
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="spp:ScriptDTO">
<Border spp:Animations.EnableAnimations="True">
<Border spp:Animations.EnableAnimations="False">
<spp:AppItem
Title="{Binding Name}"
MaxWidth="135"
@@ -1,4 +1,4 @@
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
namespace BD.WTTS.UI.Views.Pages;
@@ -117,6 +117,8 @@
<ItemGroup>
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
<ProjectReference Include="..\BD.WTTS.Client.Avalonia\BD.WTTS.Client.Avalonia.csproj" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc.Analyzers\dotnetCampus.Ipc.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc\dotnetCampus.Ipc.csproj" />
</ItemGroup>
<ItemGroup>
@@ -135,10 +137,6 @@
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Settings\" />
</ItemGroup>
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
</Project>
@@ -26,7 +26,7 @@ public partial class BotPageViewModel : ViewModelBase
ASFService.Current.SteamBotsSourceList
.Connect()
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Sort(SortExpressionComparer<BotViewModel>.Descending(x => x.Bot.BotName))
.Bind(out _SteamBots)
.Subscribe();
@@ -1,6 +1,6 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
using BD.WTTS.UI.Views.Controls;
namespace BD.WTTS.UI.Views.Pages;
@@ -70,6 +70,8 @@
<ProjectReference Include="..\..\ref\WinAuth\src\WinAuth\WinAuth.csproj" />
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
<ProjectReference Include="..\BD.WTTS.Client.Avalonia\BD.WTTS.Client.Avalonia.csproj" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc.Analyzers\dotnetCampus.Ipc.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\ref\dotnetCampus.Ipc\src\dotnetCampus.Ipc\dotnetCampus.Ipc.csproj" />
</ItemGroup>
<ItemGroup>
@@ -98,10 +100,6 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Services\" />
</ItemGroup>
<!--<Import Project="..\TFM_NETX_WITH_ALL.props" />-->
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
@@ -1,4 +1,4 @@
using Avalonia.Gif;
using BD.Avalonia8.Image2;
using BD.WTTS.UI.Views.Pages;
using System.Threading;
@@ -67,7 +67,7 @@ public sealed partial class AuthenticatorHomePageViewModel
this.AuthSource
.Connect()
.Filter(textFilter)
.ObserveOn(RxApp.MainThreadScheduler)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Sort(SortExpressionComparer<AuthenticatorItemModel>.Ascending(x => x.AuthData.Index).ThenBy(x => x.AuthName))
.Bind(out _Auths)
.Subscribe();
@@ -21,7 +21,7 @@
<Border
Width="200"
Margin="3"
spp:Animations.EnableAnimations="True">
spp:Animations.EnableAnimations="False">
<spp:AppItem
Title="{Binding Name}"
Classes="Vertical"
@@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;
namespace BD.WTTS.UI.Views.Pages;

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