This commit is contained in:
SongSongSo
2023-05-30 19:02:09 +08:00
41 changed files with 385 additions and 125 deletions
@@ -26,9 +26,9 @@
<Compile Include="..\ImplicitUsings.Nito.cs">
<LinkBase>Properties</LinkBase>
</Compile>
<!--<Compile Include="..\ImplicitUsings.Enums.cs">
<Compile Include="..\ImplicitUsings.Enums.cs">
<LinkBase>Properties</LinkBase>
</Compile>-->
</Compile>
<Compile Include="..\BD.WTTS.Client\Services\IPC\IPCSubProcessModuleService.cs">
<LinkBase>Services</LinkBase>
</Compile>
@@ -38,6 +38,9 @@
<Compile Include="..\BD.WTTS.Client\Services\UI\Widgets\IPC\*.cs">
<LinkBase>Services\UI\Widgets</LinkBase>
</Compile>
<Compile Include="..\BD.WTTS.Client\Enums\CommandExitCode.cs">
<LinkBase>Enums</LinkBase>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -46,6 +49,10 @@
<PackageReference Include="dotnetCampus.Ipc" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="..\TFM_NET7_WITH_ALL.props" />
</Project>
@@ -24,11 +24,6 @@ public interface IPCSubProcessService : IDisposable
/// <returns></returns>
T? GetService<T>() where T : class;
const int ExitCode_EmptyArrayArgs = 4001;
const int ExitCode_EmptyPipeName = 4002;
const int ExitCode_EmptyMainProcessId = 4003;
const int ExitCode_NotFoundMainProcessId = 4004;
private static IPCSubProcessServiceImpl? iPCSubProcessServiceImpl;
static IPCSubProcessService Instance => iPCSubProcessServiceImpl.ThrowIsNull();
@@ -37,8 +32,8 @@ public interface IPCSubProcessService : IDisposable
/// 子进程 IPC 程序启动通用函数
/// </summary>
/// <param name="moduleName"></param>
/// <param name="configureServices"></param>
/// <param name="configureIpcProvider"></param>
/// <param name="configureServices">配置子进程的依赖注入服务</param>
/// <param name="configureIpcProvider">配置 IPC 服务</param>
/// <param name="args"></param>
/// <returns></returns>
static async Task<int> MainAsync(
@@ -48,15 +43,15 @@ public interface IPCSubProcessService : IDisposable
params string[] args)
{
if (args.Length < 2)
return ExitCode_EmptyArrayArgs;
return (int)CommandExitCode.EmptyArrayArgs;
var pipeName = args[0];
if (string.IsNullOrWhiteSpace(pipeName))
return ExitCode_EmptyPipeName;
return (int)CommandExitCode.EmptyPipeName;
if (!int.TryParse(args[1], out var pid))
return ExitCode_EmptyMainProcessId;
return (int)CommandExitCode.EmptyMainProcessId;
var mainProcess = Process.GetProcessById(pid);
if (mainProcess == null)
return ExitCode_NotFoundMainProcessId;
return (int)CommandExitCode.NotFoundMainProcessId;
TaskCompletionSource tcs = new();
mainProcess.EnableRaisingEvents = true;
@@ -31,6 +31,12 @@
<Compile Include="..\ImplicitUsings.Nito.cs">
<LinkBase>Properties</LinkBase>
</Compile>
<Compile Include="..\ImplicitUsings.Models.cs">
<LinkBase>Properties</LinkBase>
</Compile>
<Compile Include="..\ImplicitUsings.Enums.cs">
<LinkBase>Properties</LinkBase>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -4,7 +4,7 @@
using System.Net.NetworkInformation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Models;
namespace BD.WTTS.Models.Abstractions;
partial interface IReverseProxyConfig
{
@@ -2,7 +2,7 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.Configuration/FastGithubConfig.cs
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Models;
namespace BD.WTTS.Models.Abstractions;
public partial interface IReverseProxyConfig
{
@@ -3,6 +3,9 @@ using IHttpClientFactory_Common = System.Net.Http.Client.IHttpClientFactory;
using IHttpClientFactory_Extensions_Http = System.Net.Http.IHttpClientFactory;
const string moduleName = "Accelerator";
#if DEBUG
Console.WriteLine($"This: {moduleName} / Program.Start");
#endif
try
{
var exitCode = await IPCSubProcessService.MainAsync(moduleName, ConfigureServices, static ipcProvider =>
@@ -1,10 +0,0 @@
// C# 10 定义全局 using
#pragma warning disable IDE0079 // 请删除不必要的忽略
#pragma warning disable IDE0005
#pragma warning disable SA1209 // Using alias directives should be placed after other using directives
#pragma warning disable SA1211 // Using alias directives should be ordered alphabetically by alias name
global using BD.WTTS.Enums;
global using BD.WTTS.Models;
@@ -13,7 +13,7 @@ sealed class CertService
readonly ILogger<CertService> logger;
readonly IReverseProxyConfig reverseProxyConfig;
IReverseProxyService ReverseProxyService => reverseProxyConfig.Service;
ReverseProxyServiceImpl ReverseProxyService => reverseProxyConfig.Service;
/// <summary>
/// 获取 CER 证书文件路径
@@ -9,7 +9,7 @@ sealed class LazyReverseProxyServiceImpl : IReverseProxyService
static IReverseProxyService instance() => Ioc.Get<IReverseProxyService>();
#pragma warning restore IDE1006 // 命名样式
public ICertificateManager CertificateManager => instance().CertificateManager;
//public ICertificateManager CertificateManager => instance().CertificateManager;
public bool ProxyRunning => instance().ProxyRunning;
@@ -2,7 +2,7 @@
#if WINDOWS
using static BD.WTTS.Models.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
@@ -2,7 +2,7 @@
#if WINDOWS
using static BD.WTTS.Models.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
@@ -2,7 +2,7 @@
#if WINDOWS
using static BD.WTTS.Models.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
@@ -2,7 +2,7 @@
#if WINDOWS
using static BD.WTTS.Models.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
@@ -2,7 +2,7 @@
#if WINDOWS
using static BD.WTTS.Models.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
@@ -6,7 +6,7 @@ namespace BD.WTTS.Services;
/// <summary>
/// 反向代理服务
/// </summary>
[IpcPublic(Timeout = 55000, IgnoresIpcException = false)]
[IpcPublic(Timeout = 9000, IgnoresIpcException = false)]
public partial interface IReverseProxyService : IDisposable, IReverseProxySettings
{
/// <summary>
@@ -52,6 +52,13 @@ public partial interface IReverseProxyService : IDisposable, IReverseProxySettin
/// </summary>
/// <returns></returns>
ValueTask StopProxyAsync();
#if DEBUG
string GetDebugString()
{
return $"Pid: {Environment.ProcessId}, Exe: {Environment.ProcessPath}, Asm: {Assembly.GetAssembly(GetType())?.FullName}";
}
#endif
}
/// <summary>
@@ -3,8 +3,6 @@ namespace BD.WTTS.Services;
partial interface IReverseProxyService
{
ICertificateManager CertificateManager { get; }
/// <summary>
/// 将 PEM 证书公钥写入 GOG GALAXY
/// </summary>
@@ -71,6 +71,9 @@
<Compile Include="..\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\Services\IReverseProxyService.Constants.cs">
<LinkBase>Services</LinkBase>
</Compile>
<Compile Include="..\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\Services\IReverseProxyService.cs">
<LinkBase>Services</LinkBase>
</Compile>
<Compile Include="..\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\Enums\ExternalProxyType.cs">
<LinkBase>Enums</LinkBase>
</Compile>
@@ -70,6 +70,17 @@ sealed class Plugin : PluginBase<Plugin>
// 从子进程中获取 IPC 远程服务
reverseProxyService = await ipc.GetServiceAsync<IReverseProxyService>(moduleName);
#if DEBUG
try
{
var debugStringIPC = $"Pid: {Environment.ProcessId}, Exe: {Environment.ProcessPath}, Asm: {Assembly.GetAssembly(GetType())?.FullName}{Environment.NewLine}{reverseProxyService?.GetDebugString()}";
Console.WriteLine($"DebugString/IReverseProxyService: {debugStringIPC}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
#endif
if (ResourceService.IsChineseSimplified)
{
+3
View File
@@ -34,6 +34,9 @@
<Compile Include="..\ImplicitUsings.Models.cs">
<LinkBase>Properties</LinkBase>
</Compile>
<Compile Include="..\ImplicitUsings.Enums.cs">
<LinkBase>Properties</LinkBase>
</Compile>
<Compile Include="..\ImplicitUsings.UI.cs">
<LinkBase>Properties</LinkBase>
</Compile>
@@ -0,0 +1,56 @@
namespace BD.WTTS.Enums;
public enum CommandExitCode
{
#region 100 ~ 5xx HttpStatusCode
/// <inheritdoc cref="HttpStatusCode.OK"/>
HttpStatusCodeOk = 200,
/// <inheritdoc cref="HttpStatusCode.BadRequest"/>
HttpStatusBadRequest = 400,
/// <inheritdoc cref="HttpStatusCode.InternalServerError"/>
HttpStatusCodeInternalServerError = 500,
#endregion
#region xxxx 4 位数通用错误码
/// <summary>
/// 参数不能为空数组
/// </summary>
EmptyArrayArgs = 4001,
/// <summary>
/// IPC 管道名称不能为空
/// </summary>
EmptyPipeName = 4002,
/// <summary>
/// 主进程 Id 不能为空
/// </summary>
EmptyMainProcessId = 4003,
/// <summary>
/// 主进程 Id 找不到
/// </summary>
NotFoundMainProcessId = 4004,
/// <summary>
/// 获取一组插件失败
/// </summary>
GetPluginsFail = 4040,
/// <summary>
/// 获取单个插件失败
/// </summary>
GetPluginFail = 4041,
/// <summary>
/// 获取子进程启动配置失败
/// </summary>
GetSubProcessBootConfigurationFail = 4042,
#endregion
}
+7 -2
View File
@@ -123,6 +123,10 @@ public sealed partial class AppSettings : IMicroServiceClient.ISettings
public static AppSettings Instance { get; } = GetAppSettings();
#if DEBUG
internal static bool UseLocalhostApiBaseUrl { get; set; }
#endif
static AppSettings GetAppSettings()
{
var s = new AppSettings()
@@ -136,8 +140,9 @@ public sealed partial class AppSettings : IMicroServiceClient.ISettings
#endif
};
#if DEBUG
s.ApiBaseUrl = Constants.Urls.BaseUrl_API_Debug;
//s.ApiBaseUrl = Constants.Urls.BaseUrl_API_Development;
s.ApiBaseUrl = UseLocalhostApiBaseUrl ?
Constants.Urls.BaseUrl_API_Debug :
Constants.Urls.BaseUrl_API_Development;
#else
s.ApiBaseUrl = Constants.Urls.BaseUrl_API_Production;
#endif
@@ -1,3 +1,5 @@
using dotnetCampus.Ipc.Pipes;
namespace BD.WTTS.Plugins.Abstractions;
/// <summary>
@@ -69,4 +71,14 @@ public interface IPlugin : IExplicitHasValue
void OnUnhandledException(Exception ex, string name, bool? isTerminating = null);
ValueTask OnExit();
/// <summary>
/// 启动子进程 IPC 程序的 Main 函数
/// </summary>
/// <param name="moduleName"></param>
/// <param name="pipeName"></param>
/// <param name="processId"></param>
/// <param name="encodedArgs"></param>
/// <returns></returns>
Task<int> RunSubProcessMainAsync(string moduleName, string pipeName, string processId, string encodedArgs);
}
@@ -1,3 +1,5 @@
using dotnetCampus.Ipc.Pipes;
namespace BD.WTTS.Plugins.Abstractions;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
@@ -68,4 +70,45 @@ public abstract class PluginBase<TPlugin> : IPlugin where TPlugin : PluginBase<T
{
return ValueTask.CompletedTask;
}
/// <summary>
/// 将参数解析为字符串
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static string DecodeArgs(string args)
=> HttpUtility.UrlDecode(args);
/// <summary>
/// 将参数解析为字符串数组
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static string[] DecodeToArrayArgs(string args)
=> HttpUtility.UrlDecode(args).Split(' ', StringSplitOptions.RemoveEmptyEntries);
public virtual async Task<int> RunSubProcessMainAsync(string moduleName, string pipeName, string processId, string encodedArgs)
{
var subProcessBootConfiguration = GetSubProcessBootConfiguration(encodedArgs ?? string.Empty);
if (subProcessBootConfiguration == default)
return (int)CommandExitCode.GetSubProcessBootConfigurationFail;
var exitCode = await IPCSubProcessService.MainAsync(moduleName,
subProcessBootConfiguration.configureServices,
subProcessBootConfiguration.configureIpcProvider,
new[] { pipeName, processId });
return exitCode;
}
/// <summary>
/// 获取子进程 IPC 程序启动配置
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
protected virtual (Action<IServiceCollection>? configureServices, Action<IpcProvider>? configureIpcProvider) GetSubProcessBootConfiguration(string args)
{
return default;
}
}
+47 -17
View File
@@ -7,20 +7,28 @@ public static class PluginsCore
static readonly HashSet<string> ignoreDirNames = new(StringComparer.OrdinalIgnoreCase)
{
"Update",
Update,
};
internal static HashSet<Assembly>? LoadAssemblies()
public const string Accelerator = "Accelerator";
public const string GameAccount = "GameAccount";
public const string GameList = "GameList";
public const string ArchiSteamFarmPlus = "ArchiSteamFarmPlus";
public const string Authenticator = "Authenticator";
public const string GameTools = "GameTools";
public const string Update = "Update";
internal static HashSet<Assembly>? LoadAssemblies(params string[] loadModules)
{
HashSet<Assembly>? assemblies = null;
#if DEBUG // DEBUG 模式遍历项目查找模块
var modules = new[] {
"Accelerator",
"GameAccount",
"GameList",
"ArchiSteamFarmPlus",
"Authenticator",
"GameTools",
var modules = loadModules.Any_Nullable() ? loadModules : new[] {
Accelerator,
GameAccount,
GameList,
ArchiSteamFarmPlus,
Authenticator,
GameTools,
};
foreach (var item in modules)
{
@@ -70,12 +78,12 @@ public static class PluginsCore
var pluginsPath = Path.Combine(AppContext.BaseDirectory, "modules");
if (Directory.Exists(pluginsPath))
{
var directories = Directory.EnumerateDirectories(pluginsPath);
foreach (var directory in directories)
bool EachDirectories(string directory, string? dirName = null)
{
var dirName = Path.GetDirectoryName(directory);
if (dirName == null) continue;
if (ignoreDirNames.Contains(dirName)) continue;
dirName ??= Path.GetDirectoryName(directory);
if (dirName == null) return true;
if (ignoreDirNames.Contains(dirName))
return true;
var dllPath = Path.Combine(directory,
$"Steam++.Plugins.{dirName}.dll");
if (File.Exists(dllPath))
@@ -90,13 +98,35 @@ public static class PluginsCore
catch (Exception e)
{
Log.Error(TAG, e, $"AssemblyLoadFrom fail, assemblyPath: {assemblyPath}");
continue;
return true;
}
assemblies ??= new();
assemblies.Add(assembly);
}
}
return false;
}
if (loadModules.Any_Nullable())
{
foreach (var loadModule in loadModules)
{
var directory = Path.Combine(pluginsPath, loadModule);
if (!Directory.Exists(directory))
continue;
if (EachDirectories(directory, loadModule))
continue;
}
}
else
{
var directories = Directory.EnumerateDirectories(pluginsPath);
foreach (var directory in directories)
{
if (EachDirectories(directory))
continue;
}
}
}
#endif
@@ -122,9 +152,9 @@ public static class PluginsCore
}
}
internal static HashSet<IPlugin>? InitPlugins()
internal static HashSet<IPlugin>? InitPlugins(params string[] loadModules)
{
HashSet<Assembly>? assemblies = LoadAssemblies();
HashSet<Assembly>? assemblies = LoadAssemblies(loadModules);
if (!assemblies.Any_Nullable()) return null;
var assemblies_ = VerifyAssemblies(assemblies).ToArray();
@@ -2,6 +2,7 @@ using dotnetCampus.Ipc.CompilerServices.GeneratedProxies;
using dotnetCampus.Ipc.Context;
using dotnetCampus.Ipc.Pipes;
using dotnetCampus.Ipc.Utils.Logging;
using Polly;
using IPCLogLevel = dotnetCampus.Ipc.Utils.Logging.LogLevel;
using MSEXLogLevel = Microsoft.Extensions.Logging.LogLevel;
@@ -178,24 +179,26 @@ public sealed partial class IPCMainProcessServiceImpl : IPCMainProcessService
#if WINDOWS
// 启动管理员权限服务进程
if (IPlatformService.Instance is WindowsPlatformServiceImpl windowsPlatformService)
Task2.InBackground(async () =>
{
Task2.InBackground(async () =>
var processPath = Environment.ProcessPath;
processPath.ThrowIsNull();
var pipeName = ipcProvider.ThrowIsNull().IpcContext.PipeName;
const string arguments_ =
$"-clt {IPlatformService.IPCRoot.CommandName} {IPlatformService.IPCRoot.args_PipeName} {{0}} {IPlatformService.IPCRoot.args_ProcessId} {{1}}";
var arguments = string.Format(arguments_, pipeName, pid);
await AddDaemonWithStartSubProcessAsync(IPlatformService.IPCRoot.moduleName, async _ =>
{
var processPath = Environment.ProcessPath;
processPath.ThrowIsNull();
var pipeName = ipcProvider.ThrowIsNull().IpcContext.PipeName;
const string arguments_ =
$"-clt {IPlatformService.IPCRoot.CommandName} {IPlatformService.IPCRoot.args_PipeName} {{0}} {IPlatformService.IPCRoot.args_ProcessId} {{1}}";
var arguments = string.Format(arguments_, pipeName, pid);
await AddDaemonWithStartSubProcessAsync(IPlatformService.IPCRoot.moduleName, async _ =>
return await Policy.HandleResult<Process?>(x => x == null)
.RetryAsync(3)
.ExecuteAsync(async () =>
{
var process = await windowsPlatformService.StartAsAdministrator(processPath, arguments);
var process = await WindowsPlatformServiceImpl.StartAsAdministrator(processPath, arguments);
return process;
});
await IPlatformService.IPCRoot.SetIPC(this);
});
}
await IPlatformService.IPCRoot.SetIPC(this);
});
#endif
}
@@ -497,7 +497,7 @@ internal sealed class HostsFileServiceImpl
{
try
{
s.WriteDefaultHostsContent(new FileStream(s.HostsFilePath, FileMode.OpenOrCreate));
s.WriteDefaultHostsContent();
Toast.Show(AppResources.CommunityFix_ResetHostsFileOk);
}
catch (Exception e)
@@ -13,14 +13,13 @@ partial class ScheduledTaskServiceImpl
/// 使用 PowerShell 实现的开机启动
/// <para>https://docs.microsoft.com/en-us/powershell/module/scheduledtasks</para>
/// </summary>
/// <param name="platformService"></param>
/// <param name="isAutoStart"></param>
/// <param name="name"></param>
/// <param name="userId"></param>
/// <param name="userName"></param>
/// <param name="tdName"></param>
/// <param name="programName"></param>
static async void SetBootAutoStartByPowerShell(IPlatformService platformService, bool isAutoStart, string name, string userId, string userName, string tdName, string programName)
static async void SetBootAutoStartByPowerShell(bool isAutoStart, string name, string userId, string userName, string tdName, string programName)
{
if (isAutoStart)
{
@@ -32,7 +31,7 @@ partial class ScheduledTaskServiceImpl
if (string.IsNullOrWhiteSpace(userName)) userName = userId;
else userName = SecurityElement.Escape(userName);
var description = SecurityElement.Escape(GetDescription(name));
var xml = $"<?xml version=\"1.0\" encoding=\"UTF-16\"?><Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\"><RegistrationInfo><Description>{description}</Description></RegistrationInfo><Triggers><LogonTrigger><Enabled>true</Enabled><UserId>{userName}</UserId></LogonTrigger></Triggers><Principals><Principal id=\"Author\"><UserId>{userId}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>{(platformService.IsAdministrator ? "HighestAvailable" : "LeastPrivilege")}</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>false</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><RunOnlyIfIdle>false</RunOnlyIfIdle><WakeToRun>false</WakeToRun><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>5</Priority></Settings><Actions Context=\"Author\"><Exec><Command>{programName}</Command><Arguments>{arguments}</Arguments><WorkingDirectory>{workingDirectory}</WorkingDirectory></Exec></Actions></Task>";
var xml = $"<?xml version=\"1.0\" encoding=\"UTF-16\"?><Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\"><RegistrationInfo><Description>{description}</Description></RegistrationInfo><Triggers><LogonTrigger><Enabled>true</Enabled><UserId>{userName}</UserId></LogonTrigger></Triggers><Principals><Principal id=\"Author\"><UserId>{userId}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>{(IPlatformService._IsAdministrator.Value ? "HighestAvailable" : "LeastPrivilege")}</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>false</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><RunOnlyIfIdle>false</RunOnlyIfIdle><WakeToRun>false</WakeToRun><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>5</Priority></Settings><Actions Context=\"Author\"><Exec><Command>{programName}</Command><Arguments>{arguments}</Arguments><WorkingDirectory>{workingDirectory}</WorkingDirectory></Exec></Actions></Task>";
await RunPowerShellSinglePipeAsync($"Register-ScheduledTask -Force -TaskName '{Escape(tdName)}' -Xml '{Escape(xml)}';exit");
}
else
@@ -6,7 +6,7 @@ namespace BD.WTTS.Services.Implementation;
sealed partial class ScheduledTaskServiceImpl : IScheduledTaskService
{
void IScheduledTaskService.SetBootAutoStart(IPlatformService platformService, bool isAutoStart, string name)
void IScheduledTaskService.SetBootAutoStart(bool isAutoStart, string name)
{
try
{
@@ -16,7 +16,7 @@ sealed partial class ScheduledTaskServiceImpl : IScheduledTaskService
var userId = hasSid ? identity.User!.ToString() : userName;
var tdName = hasSid ? userId : userId.Replace(Path.DirectorySeparatorChar, '_');
tdName = $"{name}_{{{tdName}}}";
SetBootAutoStartByPowerShell(platformService, isAutoStart, name, userId, userName, tdName, IApplication.ProgramName);
SetBootAutoStartByPowerShell(isAutoStart, name, userId, userName, tdName, IApplication.ProgramName);
}
catch (Exception e)
{
@@ -44,7 +44,7 @@ partial class WindowsPlatformServiceImpl
/// <param name="arguments"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task<Process?> StartAsAdministratorByBypassUAC(string fileName, string? arguments = null)
internal static async Task<Process?> StartAsAdministratorByBypassUAC(string fileName, string? arguments = null)
{
/** 33. Author: winscripting.blog
* https://github.com/hfiref0x/UACME
@@ -68,6 +68,15 @@ partial class WindowsPlatformServiceImpl
if (!File.Exists(targetName))
return null;
var processPath = Environment.ProcessPath;
processPath.ThrowIsNull();
if (fileName != processPath) // 仅允许启动自己
throw new ArgumentOutOfRangeException(nameof(fileName));
var processName = Path.GetFileNameWithoutExtension(fileName);
if (string.IsNullOrWhiteSpace(processName))
throw new ArgumentNullException(nameof(processName));
classes = Registry.CurrentUser.OpenSubKey(@"Software\Classes", true);
using var command = classes!.CreateSubKey(@"ms-settings\Shell\Open\command", true);
command.SetValue("DelegateExecute", "");
@@ -80,19 +89,21 @@ partial class WindowsPlatformServiceImpl
WindowStyle = ProcessWindowStyle.Hidden,
FileName = targetName,
};
var processs_start_before = new HashSet<int>(
Process.GetProcessesByName(processName).Select(x => x.Id));
var process = Process.Start(psi);
process!.WaitForExit(6000);
var processName = Path.GetFileNameWithoutExtension(fileName);
for (int i = 0; i < 7; i++)
{
var processs = Process.GetProcessesByName(processName);
foreach (var item in processs)
var processs_start_after = Process.GetProcessesByName(processName);
foreach (var item in processs_start_after)
{
if (processs_start_before.Contains(item.Id))
continue;
if (IsProcessElevated(item))
{
return item;
}
}
await Task.Delay(800);
@@ -128,24 +139,31 @@ partial class WindowsPlatformServiceImpl
/// <param name="fileName"></param>
/// <param name="arguments"></param>
/// <returns></returns>
public async ValueTask<Process?> StartAsAdministrator(string fileName, string? arguments = null)
internal static async ValueTask<Process?> StartAsAdministrator(string fileName, string? arguments = null)
{
IPlatformService thiz = this;
if (thiz.IsAdministrator)
if (IPlatformService._IsAdministrator.Value)
return Process2.Start(fileName, arguments);
var process = await StartAsAdministratorByBypassUAC(fileName, arguments);
if (process != null)
return process;
ProcessStartInfo psi = new()
try
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = true,
Verb = "runas",
};
return Process.Start(psi);
ProcessStartInfo psi = new()
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = true,
Verb = "runas",
};
var process_runas = Process.Start(psi);
return process_runas;
}
catch
{
return null;
}
}
}
#endif
@@ -51,7 +51,7 @@ partial class WindowsPlatformServiceImpl
{
var scheduledTaskService = IScheduledTaskService.Instance;
scheduledTaskService.ThrowIsNull();
scheduledTaskService.SetBootAutoStart(this, isAutoStart, name);
scheduledTaskService.SetBootAutoStart(isAutoStart, name);
}
}
}
@@ -122,8 +122,7 @@ sealed partial class WindowsPlatformServiceImpl : IPlatformService
public Process? StartAsInvoker(string fileName, string? arguments = null)
{
IPlatformService thiz = this;
if (thiz.IsAdministrator)
if (IPlatformService._IsAdministrator.Value)
{
// runas /trustlevel:0x20000 没有真正的降权,只是作为具有限制特权,使用 explorer 最好,但不接受参数,可以创建一个临时cmd脚本启动
//return StartAsInvokerByRunas(fileName, arguments);
@@ -42,7 +42,7 @@ sealed class SteamServiceImpl2 : SteamServiceBaseImpl, ISteamConnectService
protected override Dictionary<uint, string?>? HideGameList => serviceProvider.GetService<IGameLibrarySettings>()?.HideGameList;
//protected override string? GetString(string name) => AppResources.ResourceManager.GetString(name);
protected override string? GetString(string name) => AppResources.ResourceManager.GetString(name);
protected override Process? StartAsInvoker(string fileName, string? arguments = null)
{
@@ -6,8 +6,13 @@ namespace BD.WTTS.Services;
/// <summary>
/// 用于 IPC 的平台实现的服务
/// </summary>
[IpcPublic(Timeout = 55000, IgnoresIpcException = false)]
[IpcPublic(Timeout = 9000, IgnoresIpcException = false)]
public partial interface IPCPlatformService
{
#if DEBUG
string GetDebugString()
{
return $"Pid: {Environment.ProcessId}, Exe: {Environment.ProcessPath}, Asm: {Assembly.GetAssembly(GetType())?.FullName}";
}
#endif
}
@@ -3,21 +3,21 @@ namespace BD.WTTS.Services;
partial interface IPlatformService
{
internal static readonly Lazy<bool> _IsAdministrator = new(() =>
{
#if WINDOWS
using WindowsIdentity identity = WindowsIdentity.GetCurrent();
return identity.IsSystem || new WindowsPrincipal(identity)
.IsInRole(WindowsBuiltInRole.Administrator);
#elif MACCATALYST || MACOS || LINUX || IOS || ANDROID
return Interop.Libc.GetEUID() == 0;
#else
throw new PlatformNotSupportedException();
#endif
});
/// <summary>
/// 当前程序是否以 Administrator/System(Windows) 或 Root(FreeBSD/Linux/MacOS/Android/iOS) 权限运行
/// </summary>
bool IsAdministrator
{
get
{
#if WINDOWS
using WindowsIdentity identity = WindowsIdentity.GetCurrent();
return identity.IsSystem || new WindowsPrincipal(identity)
.IsInRole(WindowsBuiltInRole.Administrator);
#elif MACCATALYST || MACOS || LINUX || IOS || ANDROID
return Interop.Libc.GetEUID() == 0;
throw new PlatformNotSupportedException();
#endif
}
}
bool IsAdministrator => _IsAdministrator.Value;
}
@@ -27,6 +27,18 @@ partial interface IPlatformService
[Obsolete("use WriteDefaultHostsContent(Stream", true)]
string DefaultHostsContent => string.Empty;
/// <summary>
/// 写入默认 hosts 文件内容
/// </summary>
void WriteDefaultHostsContent()
{
var fileStream = new FileStream(HostsFilePath,
FileMode.OpenOrCreate,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete);
WriteDefaultHostsContent(fileStream);
}
/// <summary>
/// 写入默认 hosts 文件内容
/// </summary>
@@ -33,6 +33,17 @@ public partial interface IPlatformService : IPCPlatformService
{
var iPCPlatformService = await ipc.GetServiceAsync<IPCPlatformService>(moduleName);
iPCPlatformService.ThrowIsNull();
#if DEBUG
try
{
var debugStringIPC = $"Pid: {Environment.ProcessId}, Exe: {Environment.ProcessPath}, Asm: {Assembly.GetAssembly(typeof(IPlatformService))?.FullName}{Environment.NewLine}{iPCPlatformService.GetDebugString()}";
Console.WriteLine($"DebugString/IPCPlatformService: {debugStringIPC}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
#endif
tcs.TrySetResult(iPCPlatformService);
}
catch (Exception ex)
@@ -12,9 +12,8 @@ public interface IScheduledTaskService
/// <summary>
/// 开机启动使用 taskschd.msc 实现
/// </summary>
/// <param name="platformService"></param>
/// <param name="isAutoStart"></param>
/// <param name="name"></param>
void SetBootAutoStart(IPlatformService platformService, bool isAutoStart, string name);
void SetBootAutoStart(bool isAutoStart, string name);
}
#endif
+52 -10
View File
@@ -2,12 +2,14 @@
using dotnetCampus.Ipc.CompilerServices.GeneratedProxies;
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
#endif
// ReSharper disable once CheckNamespace
namespace BD.WTTS;
partial class Startup // 自定义控制台命令参数
{
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ConfigureCommands(RootCommand rootCommand)
{
@@ -44,8 +46,12 @@ partial class Startup // 自定义控制台命令参数
var devtools = new Command("devtools");
devtools.AddOption(new Option<bool>("-disable_gpu", () => false, "禁用 GPU 硬件加速"));
devtools.AddOption(new Option<bool>("-use_wgl", () => false, "使用 Native OpenGL(仅 Windows)"));
devtools.Handler = CommandHandler.Create((bool disable_gpu, bool use_wgl) =>
devtools.AddOption(new Option<bool>("-use_localhost", () => false, "使用本机服务端"));
devtools.Handler = CommandHandler.Create((bool disable_gpu, bool use_wgl, bool use_localhost) =>
{
#if DEBUG
AppSettings.UseLocalhostApiBaseUrl = use_localhost;
#endif
IsMainProcess = true;
IsConsoleLineToolProcess = false;
@@ -303,6 +309,13 @@ partial class Startup // 自定义控制台命令参数
{
Handler = CommandHandler.Create(async (string n, string p) =>
{
if (string.IsNullOrWhiteSpace(n) ||
string.IsNullOrWhiteSpace(p))
return 400;
if (!IPlatformService._IsAdministrator.Value)
return 401;
RunUIApplication(AppServicesLevel.IPCRoot | AppServicesLevel.Hosts);
await WaitConfiguredServices;
@@ -329,14 +342,35 @@ partial class Startup // 自定义控制台命令参数
rootCommand.AddCommand(sudo);
#endif
// -clt plugins {插件名} {插件需要解析的参数}
// -clt plugins -l {AppServicesLevel} -m {插件名} -n {PipeName} -p {ProcessId} -a {插件需要解析的参数}
var plugins = new Command("plugins", "插件使用的 IPC 服务进程")
{
Handler = CommandHandler.Create(() =>
Handler = CommandHandler.Create(async (uint l, string m, string n, string p, string a) =>
{
// TODO
if (string.IsNullOrWhiteSpace(n) ||
string.IsNullOrWhiteSpace(p))
return (int)CommandExitCode.HttpStatusBadRequest;
var level = (AppServicesLevel)l;
RunUIApplication(level, loadModules: m);
await WaitConfiguredServices;
if (!TryGetPlugins(out var plugins))
return (int)CommandExitCode.GetPluginsFail;
var plugin = plugins.FirstOrDefault(x => x.Name == m);
if (plugin == null)
return (int)CommandExitCode.GetPluginFail;
var exitCode = await plugin.RunSubProcessMainAsync(m, n, p, a);
return exitCode;
}),
};
plugins.AddOption(new Option<uint>("-l", "AppServicesLevel"));
plugins.AddOption(new Option<string>("-m", "需要加载的模块名称"));
plugins.AddOption(new Option<string>("-a", "需要解析的参数,使用 HttpUtility.UrlDecode 解码"));
plugins.AddOption(new Option<string>(IPlatformService.IPCRoot.args_PipeName, "IPC 管道名"));
plugins.AddOption(new Option<string>(IPlatformService.IPCRoot.args_ProcessId, "主进程 Id"));
rootCommand.AddCommand(plugins);
// -clt types
@@ -345,8 +379,16 @@ partial class Startup // 自定义控制台命令参数
Handler = CommandHandler.Create(() =>
{
int exitCode = default;
using var assemblies_stream = new FileStream(Path.Combine(IOPath.CacheDirectory, "assemblies.txt"), FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
using var types_stream = new FileStream(Path.Combine(IOPath.CacheDirectory, "types.txt"), FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
using var assemblies_stream = new FileStream(Path.Combine(
IOPath.CacheDirectory, "assemblies.txt"),
FileMode.OpenOrCreate,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete);
using var types_stream = new FileStream(Path.Combine(
IOPath.CacheDirectory, "types.txt"),
FileMode.OpenOrCreate,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete);
try
{
var assemblies_ = AppDomain.CurrentDomain.GetAssemblies();
@@ -386,12 +428,12 @@ partial class Startup // 自定义控制台命令参数
}
}
}
return exitCode = 200;
return exitCode = (int)CommandExitCode.HttpStatusCodeOk;
}
catch (Exception ex)
{
Console.WriteLine(ex);
return exitCode = 500;
return exitCode = (int)CommandExitCode.HttpStatusCodeInternalServerError;
}
finally
{
@@ -410,5 +452,5 @@ partial class Startup // 自定义控制台命令参数
};
rootCommand.AddCommand(types);
}
}
#endif
#endif
}
+5 -2
View File
@@ -6,7 +6,10 @@ namespace BD.WTTS;
partial class Startup // 配置 Host
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void RunUIApplication(AppServicesLevel? level = null, Func<string>? sendMessage = null)
protected void RunUIApplication(
AppServicesLevel? level = null,
Func<string>? sendMessage = null,
params string[] loadModules)
{
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Start();
@@ -28,7 +31,7 @@ partial class Startup // 配置 Host
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
if (IsMainProcess)
{
plugins = PluginsCore.InitPlugins();
plugins = PluginsCore.InitPlugins(loadModules);
HasPlugins = plugins.Any_Nullable();
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("InitPlugins");
@@ -31,13 +31,13 @@ partial class Startup // OnStartup
if (IsMainProcess)
{
Task.Run(async () =>
Task2.InBackground(async () =>
{
await ActiveUserRecordAsync(ActiveUserAnonymousStatisticType.OnStartup);
});
if (GeneralSettings.AutoCheckAppUpdate.Value)
{
Task.Run(async () =>
Task2.InBackground(async () =>
{
await IApplicationUpdateService.Instance
.CheckUpdateAsync(showIsExistUpdateFalse: false);