263 lines
11 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|