forked from Cookies_Github_mirror/AquaDX
[RF] AquaMai configuration refactor (#82)
更新了配置文件格式,原有的配置文件将被自动无缝迁移,详情请见新的配置文件中的注释(例外:`SlideJudgeTweak` 不再默认启用) 旧配置文件将被重命名备份,如果更新到此版本遇到 Bug 请联系我们 Updated configuration file schema. The old config file will be migrated automatically and seamlessly. See the comments in the new configuration file for details. (Except for `SlideJudgeTweak` is no longer enabled by default) Your old configuration file will be renamed as a backup. If you encounter any bug with this version, please contact us.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>AquaMai.Config.HeadlessLoader</RootNamespace>
|
||||
<AssemblyName>AquaMai.Config.HeadlessLoader</AssemblyName>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<LangVersion>12</LangVersion>
|
||||
<NoWarn>414;NU1702</NoWarn>
|
||||
<LibsPath>$(ProjectDir)../Libs/</LibsPath>
|
||||
<OutputPath>$(ProjectDir)../Output/</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>None</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Mono.Cecil">
|
||||
<HintPath>$(LibsPath)Mono.Cecil.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace AquaMai.Config.HeadlessLoader;
|
||||
|
||||
class ConfigAssemblyLoader
|
||||
{
|
||||
public static Assembly LoadConfigAssembly(AssemblyDefinition assembly)
|
||||
{
|
||||
var references = assembly.MainModule.AssemblyReferences;
|
||||
foreach (var reference in references)
|
||||
{
|
||||
if (reference.Name == "mscorlib" || reference.Name == "System" || reference.Name.StartsWith("System."))
|
||||
{
|
||||
reference.Name = "netstandard";
|
||||
reference.Version = new Version(2, 0, 0, 0);
|
||||
reference.PublicKeyToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
var targetFrameworkAttribute = assembly.CustomAttributes.FirstOrDefault(attr => attr.AttributeType.Name == "TargetFrameworkAttribute");
|
||||
if (targetFrameworkAttribute != null)
|
||||
{
|
||||
targetFrameworkAttribute.ConstructorArguments.Clear();
|
||||
targetFrameworkAttribute.ConstructorArguments.Add(new CustomAttributeArgument(
|
||||
assembly.MainModule.TypeSystem.String, ".NETStandard,Version=v2.0"));
|
||||
targetFrameworkAttribute.Properties.Clear();
|
||||
targetFrameworkAttribute.Properties.Add(new Mono.Cecil.CustomAttributeNamedArgument(
|
||||
"FrameworkDisplayName", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, ".NET Standard 2.0")));
|
||||
}
|
||||
|
||||
var stream = new MemoryStream();
|
||||
assembly.Write(stream);
|
||||
FixLoadedAssemblyResolution();
|
||||
return AppDomain.CurrentDomain.Load(stream.ToArray());
|
||||
}
|
||||
|
||||
private static bool FixedLoadedAssemblyResolution = false;
|
||||
|
||||
// XXX: Why, without this, the already loaded assemblies are not resolved?
|
||||
public static void FixLoadedAssemblyResolution()
|
||||
{
|
||||
if (FixedLoadedAssemblyResolution)
|
||||
{
|
||||
return;
|
||||
}
|
||||
FixedLoadedAssemblyResolution = true;
|
||||
|
||||
var loadedAssemblies = new Dictionary<string, Assembly>();
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
loadedAssemblies[assembly.FullName] = assembly;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
|
||||
{
|
||||
if (loadedAssemblies.TryGetValue(args.Name, out var assembly))
|
||||
{
|
||||
return assembly;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace AquaMai.Config.HeadlessLoader;
|
||||
|
||||
public class CustomAssemblyResolver : DefaultAssemblyResolver
|
||||
{
|
||||
public new void RegisterAssembly(AssemblyDefinition assembly)
|
||||
{
|
||||
base.RegisterAssembly(assembly);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using AquaMai.Config.Interfaces;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace AquaMai.Config.HeadlessLoader;
|
||||
|
||||
public class HeadlessConfigInterface
|
||||
{
|
||||
private readonly Assembly loadedConfigAssembly;
|
||||
|
||||
public IReflectionProvider ReflectionProvider { get; init; }
|
||||
public IReflectionManager ReflectionManager { get; init; }
|
||||
|
||||
public HeadlessConfigInterface(Assembly loadedConfigAssembly, AssemblyDefinition modsAssembly)
|
||||
{
|
||||
this.loadedConfigAssembly = loadedConfigAssembly;
|
||||
|
||||
ReflectionProvider = Activator.CreateInstance(
|
||||
loadedConfigAssembly.GetType("AquaMai.Config.Reflection.MonoCecilReflectionProvider"), [modsAssembly]) as IReflectionProvider;
|
||||
ReflectionManager = Activator.CreateInstance(
|
||||
loadedConfigAssembly.GetType("AquaMai.Config.Reflection.ReflectionManager"), [ReflectionProvider]) as IReflectionManager;
|
||||
}
|
||||
|
||||
public IConfigView CreateConfigView(string tomlString = null)
|
||||
{
|
||||
return Activator.CreateInstance(
|
||||
loadedConfigAssembly.GetType("AquaMai.Config.ConfigView"),
|
||||
tomlString == null ? [] : [tomlString]) as IConfigView;
|
||||
}
|
||||
|
||||
public IConfig CreateConfig()
|
||||
{
|
||||
return Activator.CreateInstance(
|
||||
loadedConfigAssembly.GetType("AquaMai.Config.Config"), [ReflectionManager]) as IConfig;
|
||||
}
|
||||
|
||||
public IConfigParser GetConfigParser()
|
||||
{
|
||||
return loadedConfigAssembly
|
||||
.GetType("AquaMai.Config.ConfigParser")
|
||||
.GetField("Instance", BindingFlags.Public | BindingFlags.Static)
|
||||
.GetValue(null) as IConfigParser;
|
||||
}
|
||||
|
||||
public IConfigSerializer CreateConfigSerializer(IConfigSerializer.Options options)
|
||||
{
|
||||
return Activator.CreateInstance(
|
||||
loadedConfigAssembly.GetType("AquaMai.Config.ConfigSerializer"), [options]) as IConfigSerializer;
|
||||
}
|
||||
|
||||
public IConfigMigrationManager GetConfigMigrationManager()
|
||||
{
|
||||
return loadedConfigAssembly
|
||||
.GetType("AquaMai.Config.Migration.ConfigMigrationManager")
|
||||
.GetField("Instance", BindingFlags.Public | BindingFlags.Static)
|
||||
.GetValue(null) as IConfigMigrationManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace AquaMai.Config.HeadlessLoader;
|
||||
|
||||
public class HeadlessConfigLoader
|
||||
{
|
||||
public static HeadlessConfigInterface LoadFromPacked(string fileName)
|
||||
=> LoadFromPacked(new FileStream(fileName, FileMode.Open));
|
||||
|
||||
public static HeadlessConfigInterface LoadFromPacked(byte[] assemblyBinary)
|
||||
=> LoadFromPacked(new MemoryStream(assemblyBinary));
|
||||
|
||||
public static HeadlessConfigInterface LoadFromPacked(Stream assemblyStream)
|
||||
=> LoadFromPacked(AssemblyDefinition.ReadAssembly(assemblyStream));
|
||||
|
||||
public static HeadlessConfigInterface LoadFromPacked(AssemblyDefinition assembly)
|
||||
{
|
||||
return LoadFromUnpacked(
|
||||
ResourceLoader.LoadEmbeddedAssemblies(assembly).Values);
|
||||
}
|
||||
|
||||
public static HeadlessConfigInterface LoadFromUnpacked(IEnumerable<byte[]> assemblyBinariess) =>
|
||||
LoadFromUnpacked(assemblyBinariess.Select(binary => new MemoryStream(binary)));
|
||||
|
||||
public static HeadlessConfigInterface LoadFromUnpacked(IEnumerable<Stream> assemblyStreams)
|
||||
{
|
||||
var resolver = new CustomAssemblyResolver();
|
||||
var assemblies = assemblyStreams
|
||||
.Select(
|
||||
assemblyStream =>
|
||||
AssemblyDefinition.ReadAssembly(
|
||||
assemblyStream,
|
||||
new ReaderParameters() {
|
||||
AssemblyResolver = resolver
|
||||
}))
|
||||
.ToArray();
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
resolver.RegisterAssembly(assembly);
|
||||
}
|
||||
|
||||
var configAssembly = assemblies.First(assembly => assembly.Name.Name == "AquaMai.Config");
|
||||
if (configAssembly == null)
|
||||
{
|
||||
throw new InvalidOperationException("AquaMai.Config assembly not found");
|
||||
}
|
||||
var loadedConfigAssembly = ConfigAssemblyLoader.LoadConfigAssembly(configAssembly);
|
||||
var modsAssembly = assemblies.First(assembly => assembly.Name.Name == "AquaMai.Mods");
|
||||
if (modsAssembly == null)
|
||||
{
|
||||
throw new InvalidOperationException("AquaMai.Mods assembly not found");
|
||||
}
|
||||
return new(loadedConfigAssembly, modsAssembly);
|
||||
}
|
||||
}
|
||||
4
AquaMai/AquaMai.Config.HeadlessLoader/Polyfills.cs
Normal file
4
AquaMai/AquaMai.Config.HeadlessLoader/Polyfills.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
internal static class IsExternalInit {}
|
||||
}
|
||||
42
AquaMai/AquaMai.Config.HeadlessLoader/ResourceLoader.cs
Normal file
42
AquaMai/AquaMai.Config.HeadlessLoader/ResourceLoader.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace AquaMai.Config.HeadlessLoader;
|
||||
|
||||
public class ResourceLoader
|
||||
{
|
||||
private const string DLL_SUFFIX = ".dll";
|
||||
private const string COMPRESSED_SUFFIX = ".compressed";
|
||||
private const string DLL_COMPRESSED_SUFFIX = $"{DLL_SUFFIX}{COMPRESSED_SUFFIX}";
|
||||
|
||||
public static Dictionary<string, Stream> LoadEmbeddedAssemblies(AssemblyDefinition assembly)
|
||||
{
|
||||
return assembly.MainModule.Resources
|
||||
.Where(resource => resource.Name.ToLower().EndsWith(DLL_SUFFIX) || resource.Name.ToLower().EndsWith(DLL_COMPRESSED_SUFFIX))
|
||||
.Select(LoadResource)
|
||||
.Where(data => data.Name != null)
|
||||
.ToDictionary(data => data.Name, data => data.Stream);
|
||||
}
|
||||
|
||||
public static (string Name, Stream Stream) LoadResource(Resource resource)
|
||||
{
|
||||
if (resource is EmbeddedResource embeddedResource)
|
||||
{
|
||||
if (resource.Name.ToLower().EndsWith(COMPRESSED_SUFFIX))
|
||||
{
|
||||
var decompressedStream = new MemoryStream();
|
||||
using (var deflateStream = new DeflateStream(embeddedResource.GetResourceStream(), CompressionMode.Decompress))
|
||||
{
|
||||
deflateStream.CopyTo(decompressedStream);
|
||||
}
|
||||
decompressedStream.Position = 0;
|
||||
return (resource.Name.Substring(0, resource.Name.Length - COMPRESSED_SUFFIX.Length), decompressedStream);
|
||||
}
|
||||
return (resource.Name, embeddedResource.GetResourceStream());
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user