2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Diagnostics;
6 using System.Linq.Expressions;
7 using System.Reflection;
8 using System.Reflection.Emit;
10 using System.Text.RegularExpressions;
12 using Microsoft.Xna.Framework;
13 using MoonSharp.Interpreter;
14 using MoonSharp.Interpreter.Interop;
16 using Sigil.NonGeneric;
21 public delegate
object LuaCsFunc(params
object[] args);
24 internal static class SigilExtensions
32 public static void LoadType(
this Emit il, Type type)
34 if (type ==
null)
throw new ArgumentNullException(nameof(type));
35 il.LoadConstant(type);
37 il.Call(typeof(Type).GetMethod(
38 name: nameof(Type.GetTypeFromHandle),
39 bindingAttr: BindingFlags.Public | BindingFlags.Static,
41 types:
new Type[] { typeof(RuntimeTypeHandle) },
50 public static void ToObject(
this Emit il, Type type)
52 if (type ==
null)
throw new ArgumentNullException(nameof(type));
53 il.DerefIfByRef(ref type);
58 else if (type != typeof(
object))
60 il.CastClass<
object>();
69 public static void DerefIfByRef(
this Emit il, Type type) => il.DerefIfByRef(ref type);
76 public static void DerefIfByRef(
this Emit il, ref Type type)
78 if (type ==
null)
throw new ArgumentNullException(nameof(type));
81 type = type.GetElementType();
88 il.LoadIndirect(type);
94 private static MethodInfo GetImplicitOperatorMethod(Type baseType, Type targetType)
98 return Expression.Convert(Expression.Parameter(baseType,
null), targetType).Method;
102 if (baseType.BaseType !=
null)
104 return GetImplicitOperatorMethod(baseType.BaseType, targetType);
107 if (targetType.BaseType !=
null)
109 return GetImplicitOperatorMethod(baseType, targetType.BaseType);
122 public static void LoadLocalAndCast(
this Emit il, Local value, Type targetType)
124 if (value ==
null)
throw new ArgumentNullException(nameof(value));
125 if (targetType ==
null)
throw new ArgumentNullException(nameof(targetType));
126 if (value.LocalType != typeof(
object))
128 throw new ArgumentException($
"Expected local type {typeof(object)}; got {value.LocalType}.", nameof(value));
131 var guid = Guid.NewGuid().ToString(
"N");
133 if (targetType.IsByRef)
135 targetType = targetType.GetElementType();
139 var baseType = il.DeclareLocal(typeof(Type), $
"cast_baseType_{guid}");
141 il.Call(typeof(
object).GetMethod(
"GetType"));
142 il.StoreLocal(baseType);
145 var implicitOperatorMethod = il.DeclareLocal(typeof(MethodInfo), $
"cast_implicitOperatorMethod_{guid}");
146 il.LoadLocal(baseType);
147 il.LoadType(targetType);
148 il.Call(typeof(SigilExtensions).GetMethod(nameof(GetImplicitOperatorMethod), BindingFlags.NonPublic | BindingFlags.Static));
149 il.StoreLocal(implicitOperatorMethod);
152 var castValue = il.DeclareLocal(targetType, $
"cast_castValue_{guid}");
155 il.LoadLocal(implicitOperatorMethod);
159 var methodInvokeParams = il.DeclareLocal(typeof(
object[]), $
"cast_methodInvokeParams_{guid}");
161 il.NewArray(typeof(
object));
162 il.StoreLocal(methodInvokeParams);
165 il.LoadLocal(methodInvokeParams);
168 il.StoreElement<
object>();
171 il.LoadLocal(implicitOperatorMethod);
173 il.LoadLocal(methodInvokeParams);
174 il.Call(typeof(MethodInfo).GetMethod(
"Invoke",
new[] { typeof(
object), typeof(
object[]) }));
175 if (targetType.IsValueType)
177 il.UnboxAny(targetType);
181 il.CastClass(targetType);
183 il.StoreLocal(castValue);
189 if (targetType.IsValueType)
191 il.UnboxAny(targetType);
195 il.CastClass(targetType);
197 il.StoreLocal(castValue);
200 il.LoadLocal(castValue);
209 public static void FormatString(
this Emit il,
string format, params Local[] args)
211 if (format ==
null)
throw new ArgumentNullException(nameof(format));
212 if (args ==
null)
throw new ArgumentNullException(nameof(args));
214 var guid = Guid.NewGuid().ToString(
"N");
216 var listType = typeof(List<>).MakeGenericType(typeof(
object));
217 var list = il.DeclareLocal(listType, $
"formatString_list_{guid}");
218 il.NewObject(listType);
221 foreach (var arg
in args)
225 il.ToObject(arg.LocalType);
226 il.CallVirtual(listType.GetMethod(
"Add",
new[] { typeof(object) }));
229 var arr = il.DeclareLocal<
object[]>($
"formatString_arr_{guid}");
231 il.CallVirtual(listType.GetMethod(
"ToArray",
new Type[0]));
234 il.LoadConstant(format);
236 il.Call(typeof(
string).GetMethod(
"Format",
new[] { typeof(
string), typeof(
object[]) }));
244 public static void NewMessage(
this Emit il,
string message)
246 var newMessage = typeof(DebugConsole).GetMethod(
247 name: nameof(DebugConsole.NewMessage),
248 bindingAttr: BindingFlags.Public | BindingFlags.Static,
250 types:
new Type[] { typeof(string), typeof(Color?), typeof(bool) },
252 il.LoadConstant(message);
253 il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod());
254 il.LoadConstant(
false);
263 public static void NewMessage(
this Emit il)
265 var newMessage = typeof(DebugConsole).GetMethod(
266 name: nameof(DebugConsole.NewMessage),
267 bindingAttr: BindingFlags.Public | BindingFlags.Static,
269 types:
new Type[] { typeof(string), typeof(Color?), typeof(bool) },
271 il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod());
272 il.LoadConstant(
false);
283 public static void ForEachEnumerable<T>(
this Emit il, Local enumerable, Action<Emit, Local, Sigil.Label> action)
285 if (enumerable ==
null)
throw new ArgumentNullException(nameof(enumerable));
286 if (action ==
null)
throw new ArgumentNullException(nameof(action));
287 if (!typeof(IEnumerable<T>).IsAssignableFrom(enumerable.LocalType))
289 throw new ArgumentException($
"Expected local type {typeof(IEnumerator<T>)}; got {enumerable.LocalType}.", nameof(enumerable));
292 var guid = Guid.NewGuid().ToString(
"N");
294 var enumerator = il.DeclareLocal<IEnumerator<T>>($
"forEachEnumerable_enumerator_{guid}");
295 il.LoadLocal(enumerable);
296 il.CallVirtual(typeof(IEnumerable<T>).GetMethod(
"GetEnumerator"));
297 il.StoreLocal(enumerator);
298 ForEachEnumerator<T>(il, enumerator, action);
308 public static void ForEachEnumerator<T>(
this Emit il, Local enumerator, Action<Emit, Local, Sigil.Label> action)
310 if (enumerator ==
null)
throw new ArgumentNullException(nameof(enumerator));
311 if (action ==
null)
throw new ArgumentNullException(nameof(action));
312 if (!typeof(IEnumerator<T>).IsAssignableFrom(enumerator.LocalType))
314 throw new ArgumentException($
"Expected local type {typeof(IEnumerator<T>)}; got {enumerator.LocalType}.", nameof(enumerator));
317 var guid = Guid.NewGuid().ToString(
"N");
318 var labelLoopStart = il.DefineLabel($
"forEach_loopStart_{guid}");
319 var labelMoveNext = il.DefineLabel($
"forEach_moveNext_{guid}");
320 var labelLeave = il.DefineLabel($
"forEach_leave_{guid}");
322 il.BeginExceptionBlock(out var exceptionBlock);
323 il.Branch(labelMoveNext);
324 il.MarkLabel(labelLoopStart);
327 var current = il.DeclareLocal<T>($
"forEachEnumerator_current_{guid}");
328 il.LoadLocal(enumerator);
329 il.CallVirtual(enumerator.LocalType.GetProperty(
"Current").GetGetMethod());
330 il.StoreLocal(current);
332 action(il, current, labelLeave);
334 il.MarkLabel(labelMoveNext);
335 il.LoadLocal(enumerator);
336 il.CallVirtual(typeof(IEnumerator).GetMethod(
"MoveNext"));
337 il.BranchIfTrue(labelLoopStart);
340 il.BeginFinallyBlock(exceptionBlock, out var finallyBlock);
341 il.LoadLocal(enumerator);
342 il.CallVirtual(typeof(IDisposable).GetMethod(
"Dispose"));
343 il.EndFinallyBlock(finallyBlock);
345 il.EndExceptionBlock(exceptionBlock);
347 il.MarkLabel(labelLeave);
356 public static void If(
this Emit il, Action<Emit> action)
358 if (action ==
null)
throw new ArgumentNullException(nameof(action));
359 il.Branch(@
if: action);
368 public static void IfNot(
this Emit il, Action<Emit> action)
370 if (action ==
null)
throw new ArgumentNullException(nameof(action));
371 il.Branch(@
else: action);
384 public static void Branch(
this Emit il, Action<Emit> @
if =
null, Action<Emit> @
else =
null)
386 if (@
if ==
null && @
else ==
null)
throw new ArgumentException(
"At least one of the two branches must be defined.");
388 var guid = Guid.NewGuid().ToString(
"N");
389 var labelEnd = il.DefineLabel($
"branch_end_{guid}");
390 if (@
if !=
null && @
else !=
null)
392 var labelElse = il.DefineLabel($
"branch_else_{guid}");
393 il.BranchIfFalse(labelElse);
396 il.MarkLabel(labelElse);
399 else if (@
if !=
null)
401 il.BranchIfFalse(labelEnd);
406 il.BranchIfTrue(labelEnd);
409 il.MarkLabel(labelEnd);
420 private class LuaCsHookCallback
423 public string hookName;
426 public LuaCsHookCallback(
string name,
string hookName,
LuaCsFunc func)
429 this.hookName = hookName;
436 public string Identifier {
get;
set; }
441 private class PatchedMethod
443 public PatchedMethod(MethodInfo harmonyPrefix, MethodInfo harmonyPostfix)
445 HarmonyPrefixMethod = harmonyPrefix;
446 HarmonyPostfixMethod = harmonyPostfix;
447 Prefixes =
new Dictionary<string, LuaCsPatch>();
448 Postfixes =
new Dictionary<string, LuaCsPatch>();
451 public MethodInfo HarmonyPrefixMethod {
get; }
453 public MethodInfo HarmonyPostfixMethod {
get; }
455 public IEnumerator<LuaCsPatch> GetPrefixEnumerator() => Prefixes.Values.GetEnumerator();
457 public IEnumerator<LuaCsPatch> GetPostfixEnumerator() => Postfixes.Values.GetEnumerator();
459 public Dictionary<string, LuaCsPatch> Prefixes {
get; }
461 public Dictionary<string, LuaCsPatch> Postfixes {
get; }
466 private readonly Dictionary<string, object> parameters;
467 private bool returnValueModified;
468 private object returnValue;
475 public object this[
string paramName]
497 if (returnValueModified)
return returnValue;
502 returnValueModified =
true;
515 private static readonly
string[] prohibitedHooks =
519 "Barotrauma.ContentPackageManager",
522 private static void ValidatePatchTarget(MethodBase method)
524 if (prohibitedHooks.Any(h => method.DeclaringType.FullName.StartsWith(h)))
526 throw new ArgumentException(
"Hooks into the modding environment are prohibited.");
530 private static string NormalizeIdentifier(
string identifier)
532 return identifier?.Trim().ToLowerInvariant();
535 private Harmony harmony;
537 private Lazy<ModuleBuilder> patchModuleBuilder;
539 private readonly Dictionary<string, Dictionary<string, (LuaCsHookCallback, ACsMod)>> hookFunctions =
new Dictionary<
string, Dictionary<
string, (LuaCsHookCallback, ACsMod)>>();
541 private readonly Dictionary<MethodKey, PatchedMethod> registeredPatches =
new Dictionary<MethodKey, PatchedMethod>();
543 private LuaCsSetup luaCs;
545 private static LuaCsHook instance;
547 private struct MethodKey : IEquatable<MethodKey>
549 public ModuleHandle ModuleHandle {
get;
set; }
551 public int MetadataToken {
get;
set; }
553 public override bool Equals(
object obj)
555 return obj is MethodKey key && Equals(key);
558 public bool Equals(MethodKey other)
560 return ModuleHandle.Equals(other.ModuleHandle) && MetadataToken == other.MetadataToken;
563 public override int GetHashCode()
565 return HashCode.Combine(ModuleHandle, MetadataToken);
568 public static bool operator ==(MethodKey left, MethodKey right)
570 return left.Equals(right);
573 public static bool operator !=(MethodKey left, MethodKey right)
575 return !(left == right);
578 public static MethodKey Create(MethodBase method) =>
new MethodKey
580 ModuleHandle = method.Module.ModuleHandle,
581 MetadataToken = method.MetadataToken,
585 internal LuaCsHook(LuaCsSetup luaCs)
593 harmony =
new Harmony(
"LuaCsForBarotrauma");
594 patchModuleBuilder =
new Lazy<ModuleBuilder>(CreateModuleBuilder);
597 var hookType = UserData.RegisterType<
LuaCsHook>();
598 var hookDesc = (StandardUserDataDescriptor)hookType;
599 typeof(
LuaCsHook).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m => {
601 m.Name.Contains(
"HookMethod") ||
602 m.Name.Contains(
"UnhookMethod") ||
603 m.Name.Contains(
"EnqueueFunction") ||
604 m.Name.Contains(
"EnqueueTimedFunction")
607 hookDesc.AddMember(m.Name, new MethodMemberDescriptor(m, InteropAccessMode.Default));
612 private ModuleBuilder CreateModuleBuilder()
614 var assemblyName = $
"LuaCsHookPatch-{Guid.NewGuid():N}";
615 var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndCollect);
616 var moduleBuilder = assemblyBuilder.DefineDynamicModule(
"LuaCsHookPatch");
625 var typeBuilder = moduleBuilder.DefineType(
626 name:
"System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute",
627 attr: TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class,
628 parent: typeof(Attribute));
631 var attributeUsageAttribute =
new CustomAttributeBuilder(
632 con: typeof(AttributeUsageAttribute).GetConstructor(
new[] { typeof(AttributeTargets) }),
633 constructorArgs:
new object[] { AttributeTargets.Assembly },
634 namedProperties:
new[] { typeof(AttributeUsageAttribute).GetProperty(
"AllowMultiple") },
635 propertyValues:
new object[] {
true });
636 typeBuilder.SetCustomAttribute(attributeUsageAttribute);
639 var attributeTypeFieldBuilder = typeBuilder.DefineField(
640 fieldName:
"assemblyName",
641 type: typeof(
string),
642 attributes: FieldAttributes.Private | FieldAttributes.InitOnly);
644 var ctor = Emit.BuildConstructor(
645 parameterTypes:
new[] { typeof(
string) },
647 attributes: MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
648 callingConvention: CallingConventions.Standard | CallingConventions.HasThis);
650 ctor.LoadArgument(0);
651 ctor.LoadArgument(1);
652 ctor.StoreField(attributeTypeFieldBuilder);
654 ctor.CreateConstructor();
657 var attributeNameGetter = Emit.BuildMethod(
658 returnType: typeof(
string),
659 parameterTypes:
new Type[0],
661 name:
"get_AttributeName",
662 attributes: MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
663 callingConvention: CallingConventions.Standard | CallingConventions.HasThis);
664 attributeNameGetter.LoadArgument(0);
665 attributeNameGetter.LoadField(attributeTypeFieldBuilder);
666 attributeNameGetter.Return();
668 var attributeName = typeBuilder.DefineProperty(
669 name:
"AttributeName",
670 attributes: PropertyAttributes.None,
671 returnType: typeof(
string),
672 parameterTypes:
null);
673 attributeName.SetGetMethod(attributeNameGetter.CreateMethod());
676 var type = typeBuilder.CreateTypeInfo().AsType();
680 var assembliesToExpose =
new[] {
"Barotrauma",
"DedicatedServer" };
681 foreach (var name
in assembliesToExpose)
683 var attr =
new CustomAttributeBuilder(
684 con: type.GetConstructor(
new[] { typeof(string)}),
685 constructorArgs:
new[] { name });
686 assemblyBuilder.SetCustomAttribute(attr);
689 return moduleBuilder;
696 if (name ==
null)
throw new ArgumentNullException(nameof(name));
697 if (identifier ==
null)
throw new ArgumentNullException(nameof(identifier));
698 if (func ==
null)
throw new ArgumentNullException(nameof(func));
700 name = NormalizeIdentifier(name);
701 identifier = NormalizeIdentifier(identifier);
703 if (!hookFunctions.ContainsKey(name))
705 hookFunctions.Add(name,
new Dictionary<
string, (LuaCsHookCallback,
ACsMod)>());
708 hookFunctions[name][identifier] = (
new LuaCsHookCallback(name, identifier, func), owner);
711 public bool Exists(
string name,
string identifier)
713 if (name ==
null)
throw new ArgumentNullException(nameof(name));
714 if (identifier ==
null)
throw new ArgumentNullException(nameof(identifier));
716 name = NormalizeIdentifier(name);
717 identifier = NormalizeIdentifier(identifier);
719 if (!hookFunctions.ContainsKey(name))
724 return hookFunctions[name].ContainsKey(identifier);
727 public void Remove(
string name,
string identifier)
729 if (name ==
null)
throw new ArgumentNullException(nameof(name));
730 if (identifier ==
null)
throw new ArgumentNullException(nameof(identifier));
732 name = NormalizeIdentifier(name);
733 identifier = NormalizeIdentifier(identifier);
735 if (hookFunctions.ContainsKey(name) && hookFunctions[name].ContainsKey(identifier))
737 hookFunctions[name].Remove(identifier);
743 harmony?.UnpatchAll();
745 foreach (var (_, patch) in registeredPatches)
749 patch.HarmonyPrefixMethod.DeclaringType
750 .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static)
751 .SetValue(
null,
null);
752 patch.HarmonyPostfixMethod.DeclaringType
753 .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static)
754 .SetValue(
null,
null);
757 hookFunctions.Clear();
758 registeredPatches.Clear();
759 patchModuleBuilder =
null;
761 compatHookPrefixMethods.Clear();
762 compatHookPostfixMethods.Clear();
765 private Stopwatch performanceMeasurement =
new Stopwatch();
768 public T
Call<T>(
string name, params
object[] args)
770 if (name ==
null)
throw new ArgumentNullException(name);
771 if (args ==
null) args =
new object[0];
773 name = NormalizeIdentifier(name);
774 if (!hookFunctions.ContainsKey(name))
return default;
776 T lastResult =
default;
778 var hooks = hookFunctions[name].ToArray();
779 foreach ((
string key, var tuple) in hooks)
781 if (tuple.Item2 !=
null && tuple.Item2.IsDisposed)
783 hookFunctions[name].Remove(key);
789 if (luaCs.PerformanceCounter.EnablePerformanceCounter)
791 performanceMeasurement.Start();
794 var result = tuple.Item1.func(args);
796 if (result is DynValue luaResult)
798 if (luaResult.Type == DataType.Tuple)
800 bool replaceNil = luaResult.Tuple.Length > 1 && luaResult.Tuple[1].CastToBool();
802 if (!luaResult.Tuple[0].IsNil() || replaceNil)
804 lastResult = luaResult.ToObject<T>();
807 else if (!luaResult.IsNil())
809 lastResult = luaResult.ToObject<T>();
814 lastResult = (T)result;
817 if (luaCs.PerformanceCounter.EnablePerformanceCounter)
819 performanceMeasurement.Stop();
820 luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks);
821 performanceMeasurement.Reset();
826 var argsSb =
new StringBuilder();
827 foreach (var arg
in args)
829 argsSb.Append(arg +
" ");
831 LuaCsLogger.
LogError($
"Error in Hook '{name}'->'{key}', with args '{argsSb}':\n{e}", LuaCsMessageOrigin.Unknown);
839 public object Call(
string name, params
object[] args) => Call<object>(name, args);
841 private static MethodBase ResolveMethod(
string className,
string methodName,
string[] parameters)
844 if (classType ==
null)
throw new ScriptRuntimeException($
"invalid class name '{className}'");
846 const BindingFlags BINDING_FLAGS = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
848 MethodBase method =
null;
852 if (parameters !=
null)
854 Type[] parameterTypes =
new Type[parameters.Length];
856 for (
int i = 0; i < parameters.Length; i++)
861 throw new ScriptRuntimeException($
"invalid parameter type '{parameters[i]}'");
863 parameterTypes[i] = type;
866 method = methodName
switch
868 ".cctor" => classType.TypeInitializer,
869 ".ctor" => classType.GetConstructors(BINDING_FLAGS)
870 .Except(
new[] { classType.TypeInitializer })
871 .Where(x => x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes))
873 _ => classType.GetMethod(methodName, BINDING_FLAGS,
null, parameterTypes,
null),
878 ConstructorInfo GetCtor()
880 var ctors = classType.GetConstructors(BINDING_FLAGS)
881 .Except(
new[] { classType.TypeInitializer })
884 if (!ctors.MoveNext())
return null;
885 var ctor = ctors.Current;
887 if (ctors.MoveNext())
throw new AmbiguousMatchException();
891 method = methodName
switch
893 ".cctor" =>
throw new ScriptRuntimeException(
"type initializers can't have parameters"),
894 ".ctor" => GetCtor(),
895 _ => classType.GetMethod(methodName, BINDING_FLAGS),
899 catch (AmbiguousMatchException)
901 throw new ScriptRuntimeException(
"ambiguous method signature");
906 var parameterNamesStr = parameters ==
null ?
"" :
string.Join(
", ", parameters);
907 throw new ScriptRuntimeException($
"method '{methodName}({parameterNamesStr})' not found in class '{className}'");
913 private class DynamicParameterMapping
915 public DynamicParameterMapping(
string name, Type originalMethodParamType, Type harmonyPatchParamType)
917 ParameterName = name;
918 OriginalMethodParamType = originalMethodParamType;
919 HarmonyPatchParamType = harmonyPatchParamType;
922 public string ParameterName {
get;
set; }
924 public Type OriginalMethodParamType {
get;
set; }
926 public Type HarmonyPatchParamType {
get;
set; }
929 private static readonly Regex InvalidIdentifierCharsRegex =
new Regex(
@"[^\w\d]", RegexOptions.Compiled);
931 private const string FIELD_LUACS =
"LuaCs";
936 private MethodInfo CreateDynamicHarmonyPatch(
string identifier, MethodBase original,
HookMethodType hookType)
938 var parameters =
new List<DynamicParameterMapping>
940 new DynamicParameterMapping(
"__originalMethod",
null, typeof(MethodBase)),
941 new DynamicParameterMapping(
"__instance",
null, typeof(
object)),
944 var hasReturnType = original is MethodInfo mi && mi.ReturnType != typeof(
void);
947 parameters.Add(
new DynamicParameterMapping(
"__result",
null, typeof(
object).MakeByRefType()));
950 foreach (var parameter
in original.GetParameters())
952 var paramName = parameter.Name;
953 var originalMethodParamType = parameter.ParameterType;
954 var harmonyPatchParamType = originalMethodParamType.IsByRef
955 ? originalMethodParamType
957 : originalMethodParamType.MakeByRefType();
958 parameters.Add(
new DynamicParameterMapping(paramName, originalMethodParamType, harmonyPatchParamType));
961 static string MangleName(
object o) => InvalidIdentifierCharsRegex.Replace(o?.ToString(),
"_");
963 var moduleBuilder = patchModuleBuilder.Value;
964 var mangledName = original.DeclaringType !=
null
965 ? $
"{MangleName(original.DeclaringType)}-{MangleName(original)}"
966 : MangleName(original);
967 var typeBuilder = moduleBuilder.DefineType($
"Patch_{identifier}_{Guid.NewGuid():N}_{mangledName}", TypeAttributes.Public);
969 var luaCsField = typeBuilder.DefineField(FIELD_LUACS, typeof(LuaCsSetup), FieldAttributes.Public | FieldAttributes.Static);
971 var methodName = hookType ==
HookMethodType.Before ?
"HarmonyPrefix" :
"HarmonyPostfix";
972 var il = Emit.BuildMethod(
973 returnType: hookType ==
HookMethodType.Before ? typeof(
bool) : typeof(
void),
974 parameterTypes: parameters.Select(x => x.HarmonyPatchParamType).ToArray(),
977 attributes: MethodAttributes.Public | MethodAttributes.Static,
978 callingConvention: CallingConventions.Standard);
980 var labelReturn = il.DefineLabel(
"endOfFunction");
982 il.BeginExceptionBlock(out var exceptionBlock);
985 var harmonyReturnValue = il.DeclareLocal<
bool>(
"harmonyReturnValue");
986 il.LoadConstant(
true);
987 il.StoreLocal(harmonyReturnValue);
990 var patchKey = il.DeclareLocal<MethodKey>(
"patchKey");
992 il.CastClass<MethodBase>();
993 il.Call(typeof(MethodKey).GetMethod(nameof(MethodKey.Create)));
994 il.StoreLocal(patchKey);
997 var patchExists = il.DeclareLocal<
bool>(
"patchExists");
998 var patches = il.DeclareLocal<PatchedMethod>(
"patches");
999 il.LoadField(typeof(LuaCsHook).GetField(nameof(instance), BindingFlags.NonPublic | BindingFlags.Static));
1000 il.LoadField(typeof(LuaCsHook).GetField(nameof(registeredPatches), BindingFlags.NonPublic | BindingFlags.Instance));
1001 il.LoadLocal(patchKey);
1002 il.LoadLocalAddress(patches);
1003 il.Call(typeof(Dictionary<MethodKey, PatchedMethod>).GetMethod(
"TryGetValue"));
1004 il.StoreLocal(patchExists);
1007 il.LoadLocal(patchExists);
1014 il.Leave(labelReturn);
1018 var parameterDict = il.DeclareLocal<Dictionary<string, object>>(
"parameterDict");
1019 il.LoadConstant(parameters.Count(x => x.OriginalMethodParamType !=
null));
1020 il.NewObject(typeof(Dictionary<string, object>), typeof(
int));
1021 il.StoreLocal(parameterDict);
1023 for (ushort i = 0; i < parameters.Count; i++)
1026 if (parameters[i].OriginalMethodParamType ==
null)
continue;
1029 il.LoadLocal(parameterDict);
1030 il.LoadConstant(parameters[i].ParameterName);
1032 il.ToObject(parameters[i].HarmonyPatchParamType);
1033 il.Call(typeof(Dictionary<string, object>).GetMethod(
"Add"));
1037 var ptable = il.DeclareLocal<ParameterTable>(
"ptable");
1038 il.LoadLocal(parameterDict);
1039 il.NewObject(typeof(ParameterTable), typeof(Dictionary<string, object>));
1040 il.StoreLocal(ptable);
1045 il.LoadLocal(ptable);
1047 il.ToObject(parameters[2].HarmonyPatchParamType);
1048 il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.OriginalReturnValue)).GetSetMethod(nonPublic:
true));
1052 var enumerator = il.DeclareLocal<IEnumerator<LuaCsPatch>>(
"enumerator");
1053 il.LoadLocal(patches);
1054 il.CallVirtual(typeof(PatchedMethod).GetMethod(
1056 ? nameof(PatchedMethod.GetPrefixEnumerator)
1057 : nameof(PatchedMethod.GetPostfixEnumerator),
1058 bindingAttr: BindingFlags.Public | BindingFlags.Instance));
1059 il.StoreLocal(enumerator);
1061 var labelUpdateParameters = il.DefineLabel(
"updateParameters");
1064 il.ForEachEnumerator<
LuaCsPatch>(enumerator, (il, current, labelLeave) =>
1067 var luaReturnValue = il.DeclareLocal<DynValue>(
"luaReturnValue");
1068 il.LoadLocal(current);
1071 il.LoadLocal(ptable);
1073 il.StoreLocal(luaReturnValue);
1078 var ptableReturnValue = il.DeclareLocal<
object>(
"ptableReturnValue");
1079 il.LoadLocal(ptable);
1080 il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.ReturnValue)).GetGetMethod());
1081 il.StoreLocal(ptableReturnValue);
1084 il.LoadLocal(ptableReturnValue);
1089 il.LoadLocal(ptableReturnValue);
1090 il.StoreIndirect(typeof(
object));
1095 il.LoadLocal(luaReturnValue);
1099 il.LoadLocal(luaReturnValue);
1100 il.Call(typeof(DynValue).GetMethod(nameof(DynValue.IsVoid)));
1104 var csReturnType = il.DeclareLocal<Type>(
"csReturnType");
1105 il.LoadType(((MethodInfo)original).ReturnType);
1106 il.StoreLocal(csReturnType);
1109 var csReturnValue = il.DeclareLocal<
object>(
"csReturnValue");
1110 il.LoadLocal(luaReturnValue);
1111 il.LoadLocal(csReturnType);
1112 il.Call(typeof(DynValue).GetMethod(
1113 name: nameof(DynValue.ToObject),
1114 bindingAttr: BindingFlags.Public | BindingFlags.Instance,
1116 types:
new Type[] { typeof(Type) },
1118 il.StoreLocal(csReturnValue);
1122 il.LoadLocal(csReturnValue);
1123 il.StoreIndirect(typeof(
object));
1129 il.LoadLocal(ptable);
1130 il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.PreventExecution)).GetGetMethod());
1134 il.LoadConstant(
false);
1135 il.StoreLocal(harmonyReturnValue);
1138 il.Leave(labelLeave);
1143 var modifiedParameters = il.DeclareLocal<Dictionary<string, object>>(
"modifiedParameters");
1144 il.LoadLocal(ptable);
1145 il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.ModifiedParameters)).GetGetMethod());
1146 il.StoreLocal(modifiedParameters);
1148 var modifiedValue = il.DeclareLocal<
object>(
"modifiedValue");
1151 for (ushort i = 0; i < parameters.Count; i++)
1154 if (parameters[i].OriginalMethodParamType ==
null)
continue;
1157 il.LoadLocal(modifiedParameters);
1158 il.LoadConstant(parameters[i].ParameterName);
1159 il.LoadLocalAddress(modifiedValue);
1160 il.Call(typeof(Dictionary<string, object>).GetMethod(nameof(Dictionary<string, object>.TryGetValue)));
1166 var paramType = parameters[i].HarmonyPatchParamType.GetElementType();
1170 il.LoadLocalAndCast(modifiedValue, paramType);
1171 if (paramType.IsValueType)
1173 il.StoreObject(paramType);
1177 il.StoreIndirect(paramType);
1182 il.MarkLabel(labelReturn);
1185 il.BeginCatchAllBlock(exceptionBlock, out var catchBlock);
1186 var exception = il.DeclareLocal<Exception>(
"exception");
1187 il.StoreLocal(exception);
1190 il.LoadField(luaCsField);
1194 il.LoadLocal(exception);
1195 il.LoadConstant((
int)LuaCsMessageOrigin.LuaMod);
1196 il.Call(typeof(LuaCsLogger).GetMethod(nameof(LuaCsLogger.HandleException), BindingFlags.Public | BindingFlags.Static));
1199 il.EndCatchBlock(catchBlock);
1201 il.EndExceptionBlock(exceptionBlock);
1206 il.LoadLocal(harmonyReturnValue);
1210 var method = il.CreateMethod();
1211 for (var i = 0; i < parameters.Count; i++)
1213 method.DefineParameter(i + 1, ParameterAttributes.None, parameters[i].ParameterName);
1216 var type = typeBuilder.CreateType();
1217 type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(
null, luaCs);
1218 return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
1223 if (method ==
null)
throw new ArgumentNullException(nameof(method));
1224 if (patch ==
null)
throw new ArgumentNullException(nameof(patch));
1225 ValidatePatchTarget(method);
1227 identifier ??= Guid.NewGuid().ToString(
"N");
1228 identifier = NormalizeIdentifier(identifier);
1230 var patchKey = MethodKey.Create(method);
1231 if (!registeredPatches.TryGetValue(patchKey, out var methodPatches))
1233 var harmonyPrefix = CreateDynamicHarmonyPatch(identifier, method,
HookMethodType.Before);
1234 var harmonyPostfix = CreateDynamicHarmonyPatch(identifier, method,
HookMethodType.After);
1235 harmony.Patch(method, prefix:
new HarmonyMethod(harmonyPrefix), postfix:
new HarmonyMethod(harmonyPostfix));
1236 methodPatches = registeredPatches[patchKey] =
new PatchedMethod(harmonyPrefix, harmonyPostfix);
1241 if (methodPatches.Prefixes.Remove(identifier))
1243 LuaCsLogger.LogMessage($
"Replacing existing prefix: {identifier}");
1246 methodPatches.Prefixes.Add(identifier,
new LuaCsPatch
1248 Identifier = identifier,
1254 if (methodPatches.Postfixes.Remove(identifier))
1256 LuaCsLogger.LogMessage($
"Replacing existing postfix: {identifier}");
1259 methodPatches.Postfixes.Add(identifier,
new LuaCsPatch
1261 Identifier = identifier,
1271 var method = ResolveMethod(className, methodName, parameterTypes);
1272 return Patch(identifier, method, patch, hookType);
1277 var method = ResolveMethod(className, methodName,
null);
1278 return Patch(identifier, method, patch, hookType);
1283 var method = ResolveMethod(className, methodName, parameterTypes);
1284 return Patch(
null, method, patch, hookType);
1289 var method = ResolveMethod(className, methodName,
null);
1290 return Patch(
null, method, patch, hookType);
1293 private bool RemovePatch(
string identifier, MethodBase method,
HookMethodType hookType)
1295 if (identifier ==
null)
throw new ArgumentNullException(nameof(identifier));
1296 identifier = NormalizeIdentifier(identifier);
1298 var patchKey = MethodKey.Create(method);
1299 if (!registeredPatches.TryGetValue(patchKey, out var methodPatches))
1304 return hookType
switch
1306 HookMethodType.Before => methodPatches.Prefixes.Remove(identifier),
1307 HookMethodType.After => methodPatches.Postfixes.Remove(identifier),
1308 _ =>
throw new ArgumentException($
"Invalid {nameof(HookMethodType)} enum value.", nameof(hookType)),
1314 var method = ResolveMethod(className, methodName, parameterTypes);
1315 return RemovePatch(identifier, method, hookType);
1320 var method = ResolveMethod(className, methodName,
null);
1321 return RemovePatch(identifier, method, hookType);
Dictionary< string, object > OriginalParameters
Dictionary< string, object > ModifiedParameters
object OriginalReturnValue
ParameterTable(Dictionary< string, object > dict)
bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType)
T Call< T >(string name, params object[] args)
object Call(string name, params object[] args)
void Add(string name, LuaCsFunc func, ACsMod owner=null)
string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType=HookMethodType.Before)
void Remove(string name, string identifier)
bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType)
string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType=HookMethodType.Before)
string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType=HookMethodType.Before)
bool Exists(string name, string identifier)
string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType=HookMethodType.Before)
void Add(string name, string identifier, LuaCsFunc func, ACsMod owner=null)
static void LogError(string message, LuaCsMessageOrigin origin)
static void HandleException(Exception ex, LuaCsMessageOrigin origin)
static Type GetType(string typeName)
delegate object LuaCsPatch(object self, Dictionary< string, object > args)
delegate void LuaCsAction(params object[] args)
delegate DynValue LuaCsPatchFunc(object instance, LuaCsHook.ParameterTable ptable)
delegate object LuaCsFunc(params object[] args)