Skip to content

Commit

Permalink
[RF] AquaMai configuration refactor (#82)
Browse files Browse the repository at this point in the history
更新了配置文件格式,原有的配置文件将被自动无缝迁移,详情请见新的配置文件中的注释(例外:`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.
  • Loading branch information
Menci authored Nov 24, 2024
1 parent e9ee31b commit 37044da
Show file tree
Hide file tree
Showing 217 changed files with 6,053 additions and 3,042 deletions.
3 changes: 0 additions & 3 deletions AquaMai/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,3 @@ MigrationBackup/

Output
.idea
Libs/Assembly-CSharp.dll
Libs/AMDaemon.NET.dll
packages
50 changes: 50 additions & 0 deletions AquaMai/AquaMai.Build/AquaMai.Build.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>AquaMai.Build</RootNamespace>
<AssemblyName>AquaMai.Build</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.HeadlessLoader/AquaMai.Config.HeadlessLoader.csproj" />
</ItemGroup>

<ItemGroup>
<Reference Include="Mono.Cecil">
<HintPath>$(LibsPath)Mono.Cecil.dll</HintPath>
</Reference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="17.0.0" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.0.0" />
</ItemGroup>

</Project>
42 changes: 42 additions & 0 deletions AquaMai/AquaMai.Build/GenerateExampleConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.IO;
using AquaMai.Config.Interfaces;
using AquaMai.Config.HeadlessLoader;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class GenerateExampleConfig : Task
{
[Required]
public string DllPath { get; set; }

[Required]
public string OutputPath { get; set; }

public override bool Execute()
{
try
{
var configInterface = HeadlessConfigLoader.LoadFromPacked(DllPath);
var config = configInterface.CreateConfig();
foreach (var lang in (string[]) ["en", "zh"])
{
var configSerializer = configInterface.CreateConfigSerializer(new IConfigSerializer.Options()
{
Lang = lang,
IncludeBanner = true,
OverrideLocaleValue = true
});
var example = configSerializer.Serialize(config);
File.WriteAllText(Path.Combine(OutputPath, $"AquaMai.{lang}.toml"), example);
}

return true;
}
catch (Exception e)
{
Log.LogErrorFromException(e, true);
return false;
}
}
}
52 changes: 52 additions & 0 deletions AquaMai/AquaMai.Build/PostBuildPatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;

public class PostBuildPatch : Task
{
[Required]
public string DllPath { get; set; }

public override bool Execute()
{
try
{
var assembly = AssemblyDefinition.ReadAssembly(new MemoryStream(File.ReadAllBytes(DllPath)));
CompressEmbeddedAssemblies(assembly);
var outputStream = new MemoryStream();
assembly.Write(outputStream);
File.WriteAllBytes(DllPath, outputStream.ToArray());
return true;
}
catch (Exception e)
{
Log.LogErrorFromException(e, true);
return false;
}
}

private void CompressEmbeddedAssemblies(AssemblyDefinition assembly)
{
foreach (var resource in assembly.MainModule.Resources.ToList())
{
if (resource.Name.EndsWith(".dll") && resource is EmbeddedResource embeddedResource)
{
using var compressedStream = new MemoryStream();
using (var deflateStream = new DeflateStream(compressedStream, CompressionLevel.Optimal))
{
embeddedResource.GetResourceStream().CopyTo(deflateStream);
}
var compressedBytes = compressedStream.ToArray();

Log.LogMessage($"Compressed {resource.Name} from {embeddedResource.GetResourceStream().Length} to {compressedBytes.Length} bytes");

assembly.MainModule.Resources.Remove(resource);
assembly.MainModule.Resources.Add(new EmbeddedResource(resource.Name + ".compressed", resource.Attributes, compressedBytes));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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>
68 changes: 68 additions & 0 deletions AquaMai/AquaMai.Config.HeadlessLoader/ConfigAssemblyLoader.cs
Original file line number Diff line number Diff line change
@@ -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;
};
}
}
11 changes: 11 additions & 0 deletions AquaMai/AquaMai.Config.HeadlessLoader/CustomAssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Mono.Cecil;

namespace AquaMai.Config.HeadlessLoader;

public class CustomAssemblyResolver : DefaultAssemblyResolver
{
public new void RegisterAssembly(AssemblyDefinition assembly)
{
base.RegisterAssembly(assembly);
}
}
59 changes: 59 additions & 0 deletions AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigInterface.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 37044da

Please sign in to comment.