Files
0A-KFCommonUtilityLib/Scripts/Utilities/Modular/ModuleManagers.cs
2025-06-04 16:13:32 +09:30

263 lines
11 KiB
C#

using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Permissions;
using UniLinq;
using UnityEngine;
namespace KFCommonUtilityLib
{
public static class ModuleManagers
{
private static class ModuleExtensions<T>
{
public readonly static List<Type> extensions = new List<Type>();
}
public static AssemblyDefinition WorkingAssembly { get; private set; } = null;
public static event Action OnAssemblyCreated;
public static event Action OnAssemblyLoaded;
public static bool Inited { get; private set; }
private static bool extensionScanned;
private static readonly HashSet<string> list_registered_path = new HashSet<string>();
private static readonly List<Assembly> list_created = new List<Assembly>();
private static DefaultAssemblyResolver resolver;
private static ModuleAttributes moduleAttributes;
private static ModuleCharacteristics moduleCharacteristics;
private static MethodInfo mtdinf = AccessTools.Method(typeof(ModuleManagers), nameof(ModuleManagers.AddModuleExtension));
public static void InitModuleExtensions()
{
if (extensionScanned)
{
return;
}
var assemblies = ModManager.GetLoadedAssemblies();
foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes())
{
var attr = type.GetCustomAttribute<TypeTargetExtensionAttribute>();
if (attr != null)
{
if ((bool)mtdinf.MakeGenericMethod(attr.ModuleType).Invoke(null, new object[] { type }))
{
Log.Out($"Found Module Extension {type.FullName}");
}
}
}
}
extensionScanned = true;
}
public static bool AddModuleExtension<T>(Type extType)
{
if (typeof(T).GetCustomAttribute<TypeTargetAttribute>() == null)
{
return false;
}
if (!ModuleExtensions<T>.extensions.Contains(extType))
ModuleExtensions<T>.extensions.Add(extType);
return true;
}
public static Type[] GetModuleExtensions<T>()
{
if (typeof(T).GetCustomAttribute<TypeTargetAttribute>() == null)
{
return Array.Empty<Type>();
}
return ModuleExtensions<T>.extensions.ToArray();
}
public static void AddAssemblySearchPath(string path)
{
if (Directory.Exists(path))
{
list_registered_path.Add(path);
}
}
internal static void ClearOutputFolder()
{
Mod self = ModManager.GetMod("CommonUtilityLib");
string path = Path.Combine(self.Path, "AssemblyOutput");
if (Directory.Exists(path))
Array.ForEach(Directory.GetFiles(path), File.Delete);
else
Directory.CreateDirectory(path);
}
internal static void InitNew()
{
if (Inited)
{
return;
}
InitModuleExtensions();
WorkingAssembly?.Dispose();
if (resolver == null)
{
resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(Path.Combine(Application.dataPath, "Managed"));
foreach (var mod in ModManager.GetLoadedMods())
{
resolver.AddSearchDirectory(mod.Path);
}
foreach (var path in list_registered_path)
{
resolver.AddSearchDirectory(path);
}
AssemblyDefinition assdef_main = AssemblyDefinition.ReadAssembly($"{Application.dataPath}/Managed/Assembly-CSharp.dll", new ReaderParameters() { AssemblyResolver = resolver });
moduleAttributes = assdef_main.MainModule.Attributes;
moduleCharacteristics = assdef_main.MainModule.Characteristics;
Log.Out("Reading Attributes from assembly: " + assdef_main.FullName);
}
string assname = "RuntimeAssembled" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
WorkingAssembly = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition(assname,
new Version(0, 0, 0, 0)),
assname + ".dll",
new ModuleParameters()
{
Kind = ModuleKind.Dll,
AssemblyResolver = resolver,
Architecture = TargetArchitecture.I386,
Runtime = TargetRuntime.Net_4_0,
});
WorkingAssembly.MainModule.Attributes = moduleAttributes;
WorkingAssembly.MainModule.Characteristics = moduleCharacteristics;
//write security attributes so that calling non-public patch methods from this assembly is allowed
Mono.Cecil.SecurityAttribute sattr_permission = new Mono.Cecil.SecurityAttribute(WorkingAssembly.MainModule.ImportReference(typeof(SecurityPermissionAttribute)));
Mono.Cecil.CustomAttributeNamedArgument caarg_SkipVerification = new Mono.Cecil.CustomAttributeNamedArgument(nameof(SecurityPermissionAttribute.SkipVerification), new CustomAttributeArgument(WorkingAssembly.MainModule.TypeSystem.Boolean, true));
sattr_permission.Properties.Add(caarg_SkipVerification);
SecurityDeclaration sdec = new SecurityDeclaration(Mono.Cecil.SecurityAction.RequestMinimum);
sdec.SecurityAttributes.Add(sattr_permission);
WorkingAssembly.SecurityDeclarations.Add(sdec);
OnAssemblyCreated?.Invoke();
Inited = true;
Log.Out("======Init New======");
}
public static bool PatchType<T>(Type targetType, Type baseType, string moduleNames, out string typename) where T : IModuleProcessor, new()
{
Type[] moduleTypes = moduleNames.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => new T().GetModuleTypeByName(s.Trim()))
.Where(t => t.GetCustomAttribute<TypeTargetAttribute>().BaseType.IsAssignableFrom(targetType)).ToArray();
return PatchType(targetType, baseType, moduleTypes, new T(), out typename);
}
public static bool PatchType<T>(Type targetType, Type baseType, Type[] moduleTypes, out string typename) where T : IModuleProcessor, new()
{
return PatchType(targetType, baseType, moduleTypes, new T(), out typename);
}
public static bool PatchType<T>(Type targetType, Type baseType, Type[] moduleTypes, T processor, out string typename) where T : IModuleProcessor
{
if (moduleTypes.Length == 0)
{
typename = string.Empty;
return false;
}
typename = ModuleUtils.CreateTypeName(targetType, moduleTypes);
//Log.Out(typename);
if (!ModuleManagers.TryFindType(typename, out _) && !ModuleManagers.TryFindInCur(typename, out _))
_ = new ModuleManipulator(ModuleManagers.WorkingAssembly, processor, targetType, baseType, moduleTypes);
return true;
}
internal static void FinishAndLoad()
{
if (!Inited)
{
return;
}
//output assembly
Mod self = ModManager.GetMod("CommonUtilityLib");
if (self == null)
{
Log.Warning("Failed to get mod!");
self = ModManager.GetModForAssembly(typeof(ItemActionModuleManager).Assembly);
}
if (self != null && WorkingAssembly != null)
{
if (WorkingAssembly.MainModule.Types.Count > 1)
{
Log.Out("Assembly is valid!");
using (MemoryStream ms = new MemoryStream())
{
try
{
WorkingAssembly.Write(ms);
}
catch (Exception)
{
new ConsoleCmdShutdown().Execute(new List<string>(), new CommandSenderInfo());
}
DirectoryInfo dirInfo = Directory.CreateDirectory(Path.Combine(self.Path, "AssemblyOutput"));
string filename = Path.Combine(dirInfo.FullName, WorkingAssembly.Name.Name + ".dll");
Log.Out("Output Assembly: " + filename);
using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write))
{
ms.WriteTo(fs);
}
Assembly newAssembly = Assembly.LoadFile(filename);
list_created.Add(newAssembly);
}
}
Log.Out("======Finish and Load======");
Inited = false;
OnAssemblyLoaded?.Invoke();
Cleanup();
}
}
//cleanup
internal static void Cleanup()
{
Inited = false;
WorkingAssembly?.Dispose();
WorkingAssembly = null;
}
/// <summary>
/// Check if type is already generated in previous assemblies.
/// </summary>
/// <param name="name">Full type name.</param>
/// <param name="type">The retrieved type, null if not found.</param>
/// <returns>true if found.</returns>
public static bool TryFindType(string name, out Type type)
{
type = null;
foreach (var assembly in list_created)
{
type = assembly.GetType(name, false);
if (type != null)
return true;
}
return false;
}
/// <summary>
/// Check if type is already generated in current working assembly definition.
/// </summary>
/// <param name="name">Full type name.</param>
/// <param name="typedef">The retrieved type definition, null if not found.</param>
/// <returns>true if found.</returns>
public static bool TryFindInCur(string name, out TypeDefinition typedef)
{
typedef = WorkingAssembly?.MainModule.GetType(name);
return typedef != null;
}
}
}