mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-28 14:08:16 +08:00
🎨 msix publish
This commit is contained in:
@@ -87,6 +87,7 @@ false
|
||||
public const string GameListId = "00000000-0000-0000-0000-000000000003";
|
||||
|
||||
public const string ArchiSteamFarmPlus = "ArchiSteamFarmPlus";
|
||||
public const string ArchiSteamFarmPlusId = "00000000-0000-0000-0000-000000000004";
|
||||
|
||||
public const string Authenticator = "Authenticator";
|
||||
public const string AuthenticatorId = "00000000-0000-0000-0000-000000000005";
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
|
||||
public sealed override string Description => "提供一些游戏相关网站服务的加速及脚本注入功能。";
|
||||
|
||||
public sealed override string Author => "Steam++ 官方";
|
||||
protected sealed override string? AuthorOriginalString => null;
|
||||
|
||||
public sealed override string? Icon => "avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/accelerator.ico";
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
|
||||
public sealed override string Description => "提供多平台多账号令牌管理、加密、确认交易、云同步等功能";
|
||||
|
||||
public sealed override string Author => "Steam++ 官方";
|
||||
protected sealed override string? AuthorOriginalString => null;
|
||||
|
||||
public sealed override string? Icon => "avares://BD.WTTS.Client.Plugins.Authenticator/UI/Assets/authenticator.ico";
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
|
||||
public sealed override string Description => "可支持自行添加多平台账号快速切换功能,Steam 可自动读取账号信息,其它平台请手动添加账号信息。";
|
||||
|
||||
public sealed override string Author => "Steam++ 官方";
|
||||
protected sealed override string? AuthorOriginalString => null;
|
||||
|
||||
public sealed override string? Icon => "avares://BD.WTTS.Client.Plugins.GameAccount/UI/Assets/userswitcher.ico";
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
|
||||
public sealed override string Description => "管理库存游戏";
|
||||
|
||||
public sealed override string Author => "Steam++ 官方";
|
||||
protected sealed override string? AuthorOriginalString => null;
|
||||
|
||||
public sealed override string? Icon => "avares://BD.WTTS.Client.Plugins.GameList/UI/Assets/game.ico";
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
|
||||
public sealed override string Description => "游戏工具";
|
||||
|
||||
public sealed override string Author => "Steam++ 官方";
|
||||
protected sealed override string? AuthorOriginalString => null;
|
||||
|
||||
public sealed override string? Icon => "avares://BD.WTTS.Client.Plugins.GameTools/UI/Assets/toolbox.ico";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>pub</AssemblyName>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using BD.WTTS.Client.Tools.Publish.Helpers;
|
||||
using static BD.WTTS.Client.Tools.Publish.Helpers.DotNetCLIHelper;
|
||||
using static BD.WTTS.GlobalDllImportResolver;
|
||||
|
||||
@@ -65,7 +66,7 @@ interface IDotNetPublishCommand : ICommand
|
||||
Console.WriteLine(string.Join(' ', psi.ArgumentList));
|
||||
|
||||
var publishDir = Path.Combine(projRootPath, arg.PublishDir);
|
||||
var rootPublishDir = Path.Combine(publishDir, "..");
|
||||
var rootPublishDir = Path.GetFullPath(Path.Combine(publishDir, ".."));
|
||||
DirTryDelete(rootPublishDir);
|
||||
|
||||
// 发布主体
|
||||
@@ -100,12 +101,29 @@ interface IDotNetPublishCommand : ICommand
|
||||
// 复制运行时
|
||||
CopyRuntime(rootPublishDir, isWindows, info.Architecture);
|
||||
|
||||
if (OperatingSystem.IsWindows() && isWindows)
|
||||
{
|
||||
// 打包资源 images
|
||||
MSIXHelper.MakePri.Start(rootPublishDir);
|
||||
// 生成 msix 包
|
||||
MSIXHelper.MakeAppx.Start(rootPublishDir, GetVersion(), info.Architecture);
|
||||
// 签名 msix 包
|
||||
MSIXHelper.SignTool.Start(rootPublishDir);
|
||||
}
|
||||
|
||||
Console.WriteLine(rootPublishDir);
|
||||
}
|
||||
|
||||
Console.WriteLine("OK");
|
||||
}
|
||||
|
||||
static string GetVersion()
|
||||
{
|
||||
var v = new Version(AssemblyInfo.Version);
|
||||
static int GetInt32(int value) => value < 0 ? 0 : value;
|
||||
return $"{GetInt32(v.Major)}.{GetInt32(v.Minor)}.{GetInt32(v.Build)}.{GetInt32(v.Revision)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将运行时复制到发布根目录下
|
||||
/// </summary>
|
||||
@@ -630,37 +648,4 @@ interface IDotNetPublishCommand : ICommand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void CopyDirectory(string sourceDir, string destinationDir, bool recursive) // https://learn.microsoft.com/zh-cn/dotnet/standard/io/how-to-copy-directories
|
||||
{
|
||||
// Get information about the source directory
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
// Check if the source directory exists
|
||||
if (!dir.Exists)
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
|
||||
// Cache directories before we start copying
|
||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||
|
||||
// Create the destination directory
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
// Get the files in the source directory and copy to the destination directory
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||
file.CopyTo(targetFilePath, true);
|
||||
}
|
||||
|
||||
// If recursive and copying subdirectories, recursively call this method
|
||||
if (recursive)
|
||||
{
|
||||
foreach (DirectoryInfo subDir in dirs)
|
||||
{
|
||||
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
namespace BD.WTTS.Client.Tools.Publish.Commands;
|
||||
|
||||
interface IGenerateCaCertCommand : ICommand
|
||||
{
|
||||
const string commandName = "gcert";
|
||||
const string X500DistinguishedNameValue = $"C=CN, S=Hunan, L=Changsha, O=\u6C5F\u82CF\u84B8\u6C7D\u51E1\u661F\u79D1\u6280\u6709\u9650\u516C\u53F8, OU=\u6280\u672F\u90E8, CN=\u6C5F\u82CF\u84B8\u6C7D\u51E1\u661F\u79D1\u6280\u6709\u9650\u516C\u53F8\u4EE3\u7801\u7B7E\u540D\u8BC1\u4E66";
|
||||
|
||||
public const int CertificateValidDays = 3650;
|
||||
|
||||
public const int KEY_SIZE_BITS = 4096;
|
||||
|
||||
static Command ICommand.GetCommand()
|
||||
{
|
||||
var type = new Option<int>("--type",
|
||||
"Generate certificate type(1 code, 2 store-code)");
|
||||
var path = new Option<string>("--path",
|
||||
"Directory where certificate files need to be saved");
|
||||
var cn = new Option<string>("--cn", "X500DistinguishedNameValue");
|
||||
var password = new Option<string>("--pwd");
|
||||
var command = new Command(commandName, "Generate certificate")
|
||||
{
|
||||
type, path, cn, password,
|
||||
};
|
||||
command.SetHandler(Handler, type, path, cn, password);
|
||||
return command;
|
||||
}
|
||||
|
||||
// New-SelfSignedCertificate -Type Custom -Subject "CN=Contoso Software, O=Contoso Corporation, C=US" -KeyUsage DigitalSignature -FriendlyName "Your friendly name goes here" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")
|
||||
|
||||
/// <summary>
|
||||
/// 表示证书对代码签名有效。始终指定此值以限制证书的预期用途。
|
||||
/// https://oidref.com/1.3.6.1.5.5.7.3.3
|
||||
/// </summary>
|
||||
const string codeSigningOid = "1.3.6.1.5.5.7.3.3";
|
||||
|
||||
/// <summary>
|
||||
/// 表示证书遵循生存期签名。通常,如果签名带有时间戳,只要证书在时间戳时有效,即使证书过期,签名也仍然有效。此 EKU 强制签名过期,无论签名是否带有时间戳。
|
||||
/// https://oidref.com/1.3.6.1.4.1.311.10.3.13
|
||||
/// </summary>
|
||||
const string _13Oid = "1.3.6.1.4.1.311.10.3.13";
|
||||
|
||||
static byte[] GenerateCodeSigningCert(string x500DistinguishedNameValue, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
throw new ArgumentOutOfRangeException(nameof(password), password, null);
|
||||
|
||||
// https://learn.microsoft.com/zh-cn/windows/win32/appxpkg/how-to-create-a-package-signing-certificate
|
||||
|
||||
using var rsa = RSA.Create(KEY_SIZE_BITS);
|
||||
X500DistinguishedName subjectName = new(x500DistinguishedNameValue);
|
||||
CertificateRequest request = new(subjectName, rsa,
|
||||
HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
|
||||
|
||||
// 基本约束:此扩展指示证书是否为证书颁发机构 (CA) 。 对于自签名证书,
|
||||
// 此参数应包含扩展字符串 “2.5.29.19={text}”,指示证书是最终实体 (不是 CA) 。
|
||||
X509BasicConstraintsExtension basicConstraints = new(false, false, 0, true);
|
||||
request.CertificateExtensions.Add(basicConstraints);
|
||||
|
||||
X509KeyUsageExtension keyUsage = new(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.KeyCertSign, true);
|
||||
request.CertificateExtensions.Add(keyUsage);
|
||||
|
||||
var oids = new OidCollection { new(codeSigningOid), new(_13Oid), };
|
||||
var enhancedKeyUsage = new X509EnhancedKeyUsageExtension(oids, true);
|
||||
request.CertificateExtensions.Add(enhancedKeyUsage);
|
||||
|
||||
X509SubjectKeyIdentifierExtension subjectKeyId;
|
||||
|
||||
subjectKeyId = new(request.PublicKey, false);
|
||||
request.CertificateExtensions.Add(subjectKeyId);
|
||||
|
||||
var notBefore = DateTimeOffset.UtcNow;
|
||||
var notAfter = notBefore.AddDays(CertificateValidDays);
|
||||
using var cert = request.CreateSelfSigned(notBefore, notAfter);
|
||||
var pfx = cert.Export(X509ContentType.Pfx, password);
|
||||
return pfx;
|
||||
}
|
||||
|
||||
internal static void Handler(int type, string path, string cn, string password)
|
||||
{
|
||||
var bytes = type switch
|
||||
{
|
||||
// 生成 BeyondDimension 代码签名证书
|
||||
1 => GenerateCodeSigningCert(X500DistinguishedNameValue, password),
|
||||
// 生成 上传应用商店的 代码签名证书
|
||||
2 => GenerateCodeSigningCert($"CN={cn}", password),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
File.WriteAllBytes(path, bytes);
|
||||
}
|
||||
}
|
||||
@@ -181,4 +181,37 @@ interface Constants
|
||||
var download_link = $"{(no_cdn.Value ? feed_no_cdn : feed)}/aspnetcore/Runtime/{specific_version}/aspnetcore-runtime-{specific_version}-{osname}-{normalized_architecture}.{(osname == "win" ? "zip" : "tar.gz")}";
|
||||
return download_link;
|
||||
}
|
||||
|
||||
static void CopyDirectory(string sourceDir, string destinationDir, bool recursive) // https://learn.microsoft.com/zh-cn/dotnet/standard/io/how-to-copy-directories
|
||||
{
|
||||
// Get information about the source directory
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
// Check if the source directory exists
|
||||
if (!dir.Exists)
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
|
||||
// Cache directories before we start copying
|
||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||
|
||||
// Create the destination directory
|
||||
IOPath.DirCreateByNotExists(destinationDir);
|
||||
|
||||
// Get the files in the source directory and copy to the destination directory
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||
file.CopyTo(targetFilePath, true);
|
||||
}
|
||||
|
||||
// If recursive and copying subdirectories, recursively call this method
|
||||
if (recursive)
|
||||
{
|
||||
foreach (DirectoryInfo subDir in dirs)
|
||||
{
|
||||
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,84 +2,6 @@ namespace BD.WTTS.Client.Tools.Publish.Helpers;
|
||||
|
||||
static class MSIXHelper
|
||||
{
|
||||
static void W(Stream stream, string version)
|
||||
{
|
||||
// https://learn.microsoft.com/zh-cn/windows/msix/desktop/desktop-to-uwp-manual-conversion
|
||||
var appxmanifest =
|
||||
"""
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
|
||||
IgnorableNamespaces="uap rescap desktop desktop2">
|
||||
<Identity
|
||||
Name="4651ED44255E.47979655102CE"
|
||||
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
|
||||
Version="{0}"
|
||||
ProcessorArchitecture="x64" />
|
||||
<Properties>
|
||||
<DisplayName>Watt Toolkit</DisplayName>
|
||||
<PublisherDisplayName>软妹币玩家</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Watt Toolkit"
|
||||
Description="「Watt Toolkit」是一个开源跨平台的多功能游戏工具箱,此工具的大部分功能都是需要您下载安装 Steam 才能使用。"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png" ShortName="Steam++">
|
||||
<uap:ShowNameOnTiles>
|
||||
<uap:ShowOn Tile="square150x150Logo"/>
|
||||
<uap:ShowOn Tile="wide310x150Logo"/>
|
||||
<uap:ShowOn Tile="square310x310Logo"/>
|
||||
</uap:ShowNameOnTiles>
|
||||
</uap:DefaultTile >
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
<uap:InitialRotationPreference>
|
||||
<uap:Rotation Preference="landscape"/>
|
||||
</uap:InitialRotationPreference>
|
||||
<uap:LockScreen BadgeLogo="Images\BadgeLogo.png" Notification="badgeAndTileText"/>
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="Steam++\Steam++.exe" />
|
||||
<desktop:Extension Category="windows.startupTask" Executable="Steam++\Steam++.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<desktop:StartupTask TaskId="BootAutoStartTask" Enabled="true" DisplayName="Steam++ System Boot Run" />
|
||||
</desktop:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
<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>
|
||||
</desktop2:Extension>
|
||||
</Extensions>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="allowElevation" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
"""u8;
|
||||
}
|
||||
|
||||
enum WinVersion : ushort
|
||||
{
|
||||
W10_20H1 = 19041,
|
||||
@@ -87,7 +9,69 @@ static class MSIXHelper
|
||||
W11_22H2 = 22621,
|
||||
}
|
||||
|
||||
static class MakeAppx
|
||||
public static class MakePri
|
||||
{
|
||||
const string makepri_exe = "makepri.exe";
|
||||
|
||||
static string GetMakePriPath(WinVersion version)
|
||||
{
|
||||
var path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
||||
path = Path.Combine(path, "Windows Kits", "10", "bin", $"10.0.{(ushort)version}.0", RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(), makepri_exe);
|
||||
return path;
|
||||
}
|
||||
|
||||
static string GetMakePriPath()
|
||||
{
|
||||
var query = from m in Enum.GetValues<WinVersion>().OrderByDescending(x => (ushort)x)
|
||||
let p = GetMakePriPath(m)
|
||||
let exists = File.Exists(p)
|
||||
where exists
|
||||
select p;
|
||||
var path = query.FirstOrDefault();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new FileNotFoundException(makepri_exe);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void Start(string rootPublicPath)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/uwp/app-resources/makepri-exe-command-options
|
||||
|
||||
var xmlPath = @$"{rootPublicPath}\priconfig.xml";
|
||||
|
||||
IOPath.FileIfExistsItDelete(xmlPath);
|
||||
|
||||
var fileName = GetMakePriPath();
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
UseShellExecute = false,
|
||||
Arguments =
|
||||
$"""
|
||||
createconfig /cf "{xmlPath}" /dq lang-en-US /o /pv 10.0.0
|
||||
""",
|
||||
WorkingDirectory = rootPublicPath,
|
||||
};
|
||||
DotNetCLIHelper.StartProcessAndWaitForExit(psi);
|
||||
var prPath = $@"{ProjectUtils.ProjPath}\build\windows\makepri";
|
||||
CopyDirectory(prPath, rootPublicPath, true);
|
||||
psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
UseShellExecute = false,
|
||||
Arguments =
|
||||
$"""
|
||||
new /cf "{xmlPath}" /pr "{prPath}"
|
||||
""",
|
||||
WorkingDirectory = rootPublicPath,
|
||||
};
|
||||
DotNetCLIHelper.StartProcessAndWaitForExit(psi);
|
||||
|
||||
IOPath.FileIfExistsItDelete(xmlPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MakeAppx
|
||||
{
|
||||
// https://learn.microsoft.com/zh-cn/windows/msix/package/create-app-package-with-makeappx-tool
|
||||
// 使用 MakeAppx.exe 创建 MSIX 包或捆绑包
|
||||
@@ -114,13 +98,176 @@ static class MSIXHelper
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void Start(string arguments)
|
||||
public static void Start(
|
||||
string rootPublicPath,
|
||||
string version4,
|
||||
Architecture processorArchitecture)
|
||||
{
|
||||
GenerateAppxManifestXml(rootPublicPath, version4, processorArchitecture);
|
||||
|
||||
var msixPath = $"{rootPublicPath}.msix";
|
||||
IOPath.FileIfExistsItDelete(msixPath);
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = GetMakeAppxPath(),
|
||||
UseShellExecute = false,
|
||||
Arguments = arguments,
|
||||
Arguments =
|
||||
$"""
|
||||
pack /v /h SHA256 /d "{rootPublicPath}" /p "{msixPath}"
|
||||
""",
|
||||
};
|
||||
DotNetCLIHelper.StartProcessAndWaitForExit(psi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成位于根目录的 AppxManifest.xml
|
||||
/// </summary>
|
||||
/// <param name="rootPublicPath"></param>
|
||||
/// <param name="version4"></param>
|
||||
/// <param name="processorArchitecture"></param>
|
||||
static void GenerateAppxManifestXml(
|
||||
string rootPublicPath,
|
||||
string version4,
|
||||
Architecture processorArchitecture)
|
||||
{
|
||||
// https://learn.microsoft.com/zh-cn/windows/msix/desktop/desktop-to-uwp-manual-conversion
|
||||
|
||||
var xmlString =
|
||||
$"""
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<Package IgnorableNamespaces="uap rescap desktop desktop2 build" xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2" xmlns:build="http://schemas.microsoft.com/developer/appx/2015/build">
|
||||
<Identity Name="4651ED44255E.47979655102CE" Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
|
||||
Version="{version4}" ProcessorArchitecture="{processorArchitecture.ToString().ToLowerInvariant()}"/>
|
||||
<Properties>
|
||||
<DisplayName>Watt Toolkit</DisplayName>
|
||||
<PublisherDisplayName>软妹币玩家</PublisherDisplayName>
|
||||
<Logo>images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0"/>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0"/>
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language="ZH-CN"/>
|
||||
<Resource uap:Scale="200"/>
|
||||
</Resources>
|
||||
<Applications>
|
||||
<Application Id="App" Executable="Steam++.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements DisplayName="Watt Toolkit" Description="「Watt Toolkit」是一个开源跨平台的多功能游戏工具箱,此工具的大部分功能都是需要您下载安装 Steam 才能使用。" BackgroundColor="transparent" Square150x150Logo="images\Square150x150Logo.png" Square44x44Logo="images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="images\Wide310x150Logo.png" Square71x71Logo="images\SmallTile.png" Square310x310Logo="images\LargeTile.png" ShortName="Steam++">
|
||||
<uap:ShowNameOnTiles>
|
||||
<uap:ShowOn Tile="square150x150Logo"/>
|
||||
<uap:ShowOn Tile="wide310x150Logo"/>
|
||||
<uap:ShowOn Tile="square310x310Logo"/>
|
||||
</uap:ShowNameOnTiles>
|
||||
</uap:DefaultTile>
|
||||
<uap:SplashScreen Image="images\SplashScreen.png"/>
|
||||
<uap:InitialRotationPreference>
|
||||
<uap:Rotation Preference="landscape"/>
|
||||
</uap:InitialRotationPreference>
|
||||
<uap:LockScreen BadgeLogo="images\BadgeLogo.png" Notification="badgeAndTileText"/>
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="Steam++.exe"/>
|
||||
<desktop:Extension Category="windows.startupTask" Executable="Steam++.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<desktop:StartupTask TaskId="BootAutoStartTask" Enabled="true" DisplayName="Steam++ System Boot Run"/>
|
||||
</desktop:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Extensions>
|
||||
<desktop2:Extension Category="windows.firewallRules">
|
||||
<desktop2:FirewallRules Executable="Steam++.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
|
||||
</desktop2:FirewallRules>
|
||||
</desktop2:Extension>
|
||||
</Extensions>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient"/>
|
||||
<rescap:Capability Name="runFullTrust"/>
|
||||
<rescap:Capability Name="allowElevation"/>
|
||||
</Capabilities>
|
||||
</Package>
|
||||
""";
|
||||
var xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(xmlString);
|
||||
var xmlStringMini = xmlDoc.InnerXml;
|
||||
var xmlFilePath = Path.Combine(rootPublicPath, "AppxManifest.xml");
|
||||
File.WriteAllText(xmlFilePath, xmlStringMini);
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// 生成打包布局
|
||||
// /// </summary>
|
||||
// static void GeneratePackagingLayoutXml(
|
||||
// string rootPublicPath,
|
||||
// string appxManifestXmlPath,
|
||||
// Architecture processorArchitecture)
|
||||
// {
|
||||
// // 使用打包布局创建包
|
||||
// // https://learn.microsoft.com/zh-cn/windows/msix/package/packaging-layout
|
||||
|
||||
// var xmlString =
|
||||
//"""
|
||||
//<PackagingLayout xmlns="http://schemas.microsoft.com/appx/makeappx/2017">
|
||||
// <PackageFamily ID="MyGame" FlatBundle="true" ManifestPath="{0}" ResourceManager="false">
|
||||
// <!-- {1} code package-->
|
||||
// <Package ID="{1}" ProcessorArchitecture="{1}">
|
||||
// <Files>
|
||||
// <File DestinationPath="*" SourcePath="{2}"/>
|
||||
// </Files>
|
||||
// </Package>
|
||||
// </PackageFamily>
|
||||
//</PackagingLayout>
|
||||
//"""u8;
|
||||
// Stream stream = new MemoryStream();
|
||||
// stream.WriteFormat(xmlString, new object?[]
|
||||
// {
|
||||
// appxManifestXmlPath,
|
||||
// processorArchitecture.ToString().ToLowerInvariant(),
|
||||
// rootPublicPath,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
public static class SignTool
|
||||
{
|
||||
const string SignTool_exe = "SignTool.exe";
|
||||
|
||||
static string GetSignToolPath(WinVersion version)
|
||||
{
|
||||
var path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
||||
path = Path.Combine(path, "Windows Kits", "10", "bin", $"10.0.{(ushort)version}.0", RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(), SignTool_exe);
|
||||
return path;
|
||||
}
|
||||
|
||||
static string GetSignToolPath()
|
||||
{
|
||||
var query = from m in Enum.GetValues<WinVersion>().OrderByDescending(x => (ushort)x)
|
||||
let p = GetSignToolPath(m)
|
||||
let exists = File.Exists(p)
|
||||
where exists
|
||||
select p;
|
||||
var path = query.FirstOrDefault();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new FileNotFoundException(SignTool_exe);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void Start(
|
||||
string rootPublicPath)
|
||||
{
|
||||
var pwd = File.ReadAllText(@"C:\MSStore_CodeSigning.txt");
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = GetSignToolPath(),
|
||||
UseShellExecute = false,
|
||||
Arguments =
|
||||
$"""
|
||||
sign /a /fd SHA384 /f "C:\MSStore_CodeSigning.pfx" /p {pwd} "{rootPublicPath}.msix"
|
||||
""",
|
||||
};
|
||||
DotNetCLIHelper.StartProcessAndWaitForExit(psi);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
IGenerateCaCertCommand.GenerateCodeSigningCert(IGenerateCaCertCommand.X500DistinguishedNameValue, "aaa");
|
||||
|
||||
var rootCommand = new RootCommand($"{AssemblyInfo.Product} Publish Tools");
|
||||
var interfaceType = typeof(ICommand);
|
||||
var addMethod = interfaceType.GetMethod(nameof(ICommand.AddCommand), BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace BD.WTTS;
|
||||
|
||||
static class ReflectionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为内部调用,防止动态反射调用
|
||||
/// </summary>
|
||||
/// <param name="thisAssembly"></param>
|
||||
/// <param name="stackTrace"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsInternalCall(Assembly thisAssembly, StackTrace stackTrace)
|
||||
{
|
||||
if (stackTrace.FrameCount >= 1)
|
||||
{
|
||||
for (int i = 1; i < stackTrace.FrameCount; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = stackTrace.GetFrame(i)?.GetMethod();
|
||||
var assembly = method?.Module.Assembly;
|
||||
if (assembly == thisAssembly)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
if (i == 2)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsInternalCall(Assembly, StackTrace)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsInternalCall<T>(StackTrace stackTrace)
|
||||
=> IsInternalCall(typeof(T).Assembly, stackTrace);
|
||||
}
|
||||
@@ -81,4 +81,9 @@ partial interface IPlugin
|
||||
/// 插件安装时间
|
||||
/// </summary>
|
||||
DateTimeOffset InstallTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件发布时间
|
||||
/// </summary>
|
||||
DateTimeOffset ReleaseTime { get; }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,51 @@ partial class PluginBase
|
||||
|
||||
public virtual Type? SettingsPageViewType { get; }
|
||||
|
||||
public virtual string? Author { get; }
|
||||
const string OfficialAuthor = "Steam++ 官方";
|
||||
const string IllegalAuthor = "存在非法字符";
|
||||
const string UnknownAuthor = "未知";
|
||||
|
||||
/// <summary>
|
||||
/// 作者名字符串中是否存在非法字符
|
||||
/// </summary>
|
||||
/// <param name="author"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static bool IsIllegalAuthor(string author)
|
||||
{
|
||||
if (author.Contains("Steam++", StringComparison.OrdinalIgnoreCase) ||
|
||||
author.Contains("Steam", StringComparison.OrdinalIgnoreCase) ||
|
||||
author.Contains("steampp", StringComparison.OrdinalIgnoreCase) ||
|
||||
author.Contains("Watt", StringComparison.OrdinalIgnoreCase) ||
|
||||
author.Contains("WattToolkit", StringComparison.OrdinalIgnoreCase) ||
|
||||
author.Contains("\u6B21\u5143\u8D85\u8D8A", StringComparison.OrdinalIgnoreCase) ||
|
||||
author.Contains("\u51E1\u661F", StringComparison.OrdinalIgnoreCase) ||
|
||||
author.Contains("\u7E41\u661F", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public string? Author
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsOfficial)
|
||||
return OfficialAuthor;
|
||||
var author = AuthorOriginalString;
|
||||
if (string.IsNullOrWhiteSpace(author))
|
||||
return UnknownAuthor;
|
||||
if (IsIllegalAuthor(author))
|
||||
return IllegalAuthor;
|
||||
return author;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者名(由插件程序集重写此属性填写)
|
||||
/// </summary>
|
||||
protected virtual string? AuthorOriginalString { get; }
|
||||
|
||||
public virtual string AuthorStoreUrl =>
|
||||
$"https://steampp.net/store/plugins/authors/{WebUtility.UrlEncode(Author)}";
|
||||
@@ -36,63 +80,109 @@ partial class PluginBase
|
||||
|
||||
public string CacheDirectory => mCacheDirectory.Value;
|
||||
|
||||
//static byte[][] OfficialPluginHashDatas => Array.Empty<byte[]>();
|
||||
///// <summary>
|
||||
///// 官方插件的哈希值 SHA256 数据,可使用 API 调用服务端获取
|
||||
///// </summary>
|
||||
//internal static SecurityCriticalOfficialPluginHashDatas OfficialPluginHashDatas { private get; set; }
|
||||
|
||||
bool? mIsOfficial;
|
||||
//internal readonly record struct SecurityCriticalOfficialPluginHashDatas
|
||||
//{
|
||||
// internal SecurityCriticalOfficialPluginHashDatas(ImmutableArray<byte[]> value)
|
||||
// {
|
||||
// StackTrace stackTrace = new();
|
||||
// var isInternalCall = ReflectionHelper.IsInternalCall<SecurityCriticalOfficialPluginHashDatas>(stackTrace);
|
||||
// if (!isInternalCall)
|
||||
// throw new ApplicationException("Disable reflection calls to this constructor.");
|
||||
|
||||
// Value = value;
|
||||
// }
|
||||
|
||||
// internal ImmutableArray<byte[]> Value { get; init; }
|
||||
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// internal bool IsDefault() => this == default || Value == default || Value.IsEmpty;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为嵌入式插件,嵌入在程序安装目录中的
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
bool IsEmbeddedPlugin()
|
||||
{
|
||||
try
|
||||
{
|
||||
var rootPath = Path.GetFullPath(Path.Combine(AssemblyLocation,
|
||||
#if DEBUG
|
||||
"..", "..", "..", "..", ".."
|
||||
#else
|
||||
"..", ".."
|
||||
#endif
|
||||
));
|
||||
var value = Environment.ProcessPath!.StartsWith(rootPath);
|
||||
return value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsOfficial
|
||||
{
|
||||
get
|
||||
get // 实时计算,不缓存结果
|
||||
{
|
||||
//#if DEBUG
|
||||
//switch (Name)
|
||||
//{
|
||||
// case AssemblyInfo.Accelerator:
|
||||
// case AssemblyInfo.ArchiSteamFarmPlus:
|
||||
// case AssemblyInfo.Authenticator:
|
||||
// case AssemblyInfo.GameAccount:
|
||||
// case AssemblyInfo.GameList:
|
||||
// case AssemblyInfo.GameTools:
|
||||
// return true;
|
||||
//}
|
||||
//#endif
|
||||
if (mIsOfficial.HasValue)
|
||||
return mIsOfficial.Value;
|
||||
//try
|
||||
//{
|
||||
// byte[] hashData;
|
||||
// using (var fileStream = new FileStream(AssemblyLocation,
|
||||
// FileMode.Open,
|
||||
// FileAccess.Read,
|
||||
// FileShare.ReadWrite | FileShare.Delete))
|
||||
// {
|
||||
// hashData = SHA256.HashData(fileStream);
|
||||
// }
|
||||
// if (PluginBase<TPlugin>.OfficialPluginHashDatas.Contains(hashData))
|
||||
// {
|
||||
// mIsOfficial = true;
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
//catch
|
||||
//{
|
||||
// if (!OfficialPluginHashDatas.IsDefault())
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// byte[] hashData;
|
||||
// using (var fileStream = new FileStream(AssemblyLocation,
|
||||
// FileMode.Open,
|
||||
// FileAccess.Read,
|
||||
// FileShare.ReadWrite | FileShare.Delete))
|
||||
// {
|
||||
// hashData = SHA256.HashData(fileStream);
|
||||
// }
|
||||
// if (OfficialPluginHashDatas.Value.Contains(hashData))
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
|
||||
//}
|
||||
// }
|
||||
// }
|
||||
//#if DEBUG
|
||||
var thisType = GetType();
|
||||
mIsOfficial = thisType.Name == "BD.WTTS.Plugins.Plugin" &&
|
||||
var value = thisType.FullName == "BD.WTTS.Plugins.Plugin" &&
|
||||
thisType.Assembly.GetName().Name?.TrimStart("BD.WTTS.Client.Plugins.") switch
|
||||
{
|
||||
AssemblyInfo.Accelerator or
|
||||
AssemblyInfo.ArchiSteamFarmPlus or
|
||||
AssemblyInfo.Authenticator or
|
||||
AssemblyInfo.GameAccount or
|
||||
AssemblyInfo.GameList or
|
||||
AssemblyInfo.GameTools => true,
|
||||
AssemblyInfo.Accelerator => IsEmbeddedPlugin() &&
|
||||
UniqueEnglishName == AssemblyInfo.Accelerator &&
|
||||
Id.ToString() == AssemblyInfo.AcceleratorId,
|
||||
AssemblyInfo.ArchiSteamFarmPlus => IsEmbeddedPlugin() &&
|
||||
UniqueEnglishName == AssemblyInfo.ArchiSteamFarmPlus &&
|
||||
Id.ToString() == AssemblyInfo.ArchiSteamFarmPlusId,
|
||||
AssemblyInfo.Authenticator => IsEmbeddedPlugin() &&
|
||||
UniqueEnglishName == AssemblyInfo.Authenticator &&
|
||||
Id.ToString() == AssemblyInfo.AuthenticatorId,
|
||||
AssemblyInfo.GameAccount => IsEmbeddedPlugin() &&
|
||||
UniqueEnglishName == AssemblyInfo.GameAccount &&
|
||||
Id.ToString() == AssemblyInfo.GameAccountId,
|
||||
AssemblyInfo.GameList => IsEmbeddedPlugin() &&
|
||||
UniqueEnglishName == AssemblyInfo.GameList &&
|
||||
Id.ToString() == AssemblyInfo.GameListId,
|
||||
AssemblyInfo.GameTools => IsEmbeddedPlugin() &&
|
||||
UniqueEnglishName == AssemblyInfo.GameTools &&
|
||||
Id.ToString() == AssemblyInfo.GameToolsId,
|
||||
_ => false,
|
||||
};
|
||||
return mIsOfficial.Value;
|
||||
//mIsOfficial = false;
|
||||
//return false;
|
||||
return value;
|
||||
//#else
|
||||
// return false;
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +204,8 @@ partial class PluginBase
|
||||
}
|
||||
|
||||
public DateTimeOffset InstallTime => mInstallTime.Value;
|
||||
|
||||
public DateTimeOffset ReleaseTime { get; internal set; }
|
||||
}
|
||||
|
||||
partial class PluginBase<TPlugin>
|
||||
|
||||
@@ -21,42 +21,16 @@ public static class PluginsCore
|
||||
|
||||
static DisablePluginsAssemblyLoadContext disablePluginsAssemblyLoadContext = new();
|
||||
|
||||
sealed class DisablePlugin : IPlugin
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
sealed record class DisablePlugin : IPlugin
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
|
||||
public string Name { get; private set; } = null!;
|
||||
|
||||
public string UniqueEnglishName { get; private set; } = null!;
|
||||
|
||||
public string Version { get; private set; } = null!;
|
||||
|
||||
public string? Description { get; private set; }
|
||||
|
||||
public string StoreUrl { get; private set; } = null!;
|
||||
|
||||
public string HelpUrl { get; private set; } = null!;
|
||||
|
||||
public Type? SettingsPageViewType => null;
|
||||
|
||||
public string? Author { get; private set; }
|
||||
|
||||
public string AuthorStoreUrl { get; private set; } = null!;
|
||||
|
||||
public string AssemblyLocation { get; private set; } = null!;
|
||||
|
||||
public string AppDataDirectory { get; private set; } = null!;
|
||||
|
||||
public string CacheDirectory { get; private set; } = null!;
|
||||
|
||||
public bool IsOfficial { get; private set; }
|
||||
|
||||
public string? Icon { get; private set; }
|
||||
|
||||
public DateTimeOffset InstallTime { get; private set; }
|
||||
|
||||
public void SetValue(IPlugin plugin)
|
||||
public DisablePlugin(IPlugin plugin)
|
||||
{
|
||||
StackTrace stackTrace = new();
|
||||
var isInternalCall = ReflectionHelper.IsInternalCall<DisablePlugin>(stackTrace);
|
||||
if (!isInternalCall)
|
||||
throw new ApplicationException("Disable reflection calls to this constructor.");
|
||||
|
||||
Id = plugin.Id;
|
||||
Name = plugin.Name;
|
||||
UniqueEnglishName = plugin.UniqueEnglishName;
|
||||
@@ -72,8 +46,47 @@ public static class PluginsCore
|
||||
IsOfficial = plugin.IsOfficial;
|
||||
Icon = plugin.Icon;
|
||||
InstallTime = plugin.InstallTime;
|
||||
ReleaseTime = plugin.ReleaseTime;
|
||||
}
|
||||
|
||||
string DebuggerDisplay => $"{UniqueEnglishName} v{Version}";
|
||||
|
||||
public override string ToString() => DebuggerDisplay;
|
||||
|
||||
public Guid Id { get; init; }
|
||||
|
||||
public string Name { get; init; }
|
||||
|
||||
public string UniqueEnglishName { get; init; }
|
||||
|
||||
public string Version { get; init; }
|
||||
|
||||
public string? Description { get; init; }
|
||||
|
||||
public string StoreUrl { get; init; }
|
||||
|
||||
public string HelpUrl { get; init; }
|
||||
|
||||
public Type? SettingsPageViewType => null;
|
||||
|
||||
public string? Author { get; init; }
|
||||
|
||||
public string AuthorStoreUrl { get; init; }
|
||||
|
||||
public string AssemblyLocation { get; init; }
|
||||
|
||||
public string AppDataDirectory { get; init; }
|
||||
|
||||
public string CacheDirectory { get; init; }
|
||||
|
||||
public bool IsOfficial { get; init; }
|
||||
|
||||
public string? Icon { get; init; }
|
||||
|
||||
public DateTimeOffset InstallTime { get; init; }
|
||||
|
||||
public DateTimeOffset ReleaseTime { get; init; }
|
||||
|
||||
public void ConfigureDemandServices(IServiceCollection services, Startup startup)
|
||||
{
|
||||
}
|
||||
@@ -309,9 +322,12 @@ public static class PluginsCore
|
||||
using CompositionHost container = configuration.CreateContainer();
|
||||
foreach (var plugin in container.GetExports<IPlugin>())
|
||||
{
|
||||
if (plugin.HasValue() && plugin is PluginBase)
|
||||
if (plugin.HasValue() && plugin is PluginBase pluginBase)
|
||||
{
|
||||
plugins.Add(plugin);
|
||||
#if DEBUG
|
||||
var isOfficial = pluginBase.IsOfficial;
|
||||
#endif
|
||||
plugins.Add(pluginBase);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -408,9 +424,15 @@ public static class PluginsCore
|
||||
{
|
||||
foreach (var plugin in disablePlugins)
|
||||
{
|
||||
var plugin_ = new DisablePlugin();
|
||||
plugin_.SetValue(plugin);
|
||||
pluginResults.Add(new(true, plugin_));
|
||||
try
|
||||
{
|
||||
var plugin_ = new DisablePlugin(plugin);
|
||||
pluginResults.Add(new(true, plugin_));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(TAG, e, "Failed to initialize disabled plugin.");
|
||||
}
|
||||
}
|
||||
disablePluginsAssemblyLoadContext.Unload();
|
||||
disablePluginsAssemblyLoadContext = null!;
|
||||
|
||||
@@ -115,6 +115,7 @@ public abstract partial class Startup
|
||||
throw new ArgumentOutOfRangeException("CurrentThread != ApartmentState.STA");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user