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