Client LuaCsForBarotrauma
LuaCsHook.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Linq;
6 using System.Linq.Expressions;
7 using System.Reflection;
8 using System.Reflection.Emit;
9 using System.Text;
10 using System.Text.RegularExpressions;
11 using HarmonyLib;
12 using Microsoft.Xna.Framework;
13 using MoonSharp.Interpreter;
14 using MoonSharp.Interpreter.Interop;
15 using Sigil;
16 using Sigil.NonGeneric;
17 
18 namespace Barotrauma
19 {
20  public delegate void LuaCsAction(params object[] args);
21  public delegate object LuaCsFunc(params object[] args);
22  public delegate DynValue LuaCsPatchFunc(object instance, LuaCsHook.ParameterTable ptable);
23 
24  internal static class SigilExtensions
25  {
32  public static void LoadType(this Emit il, Type type)
33  {
34  if (type == null) throw new ArgumentNullException(nameof(type));
35  il.LoadConstant(type); // ldtoken
36  // This converts the type token into a Type object
37  il.Call(typeof(Type).GetMethod(
38  name: nameof(Type.GetTypeFromHandle),
39  bindingAttr: BindingFlags.Public | BindingFlags.Static,
40  binder: null,
41  types: new Type[] { typeof(RuntimeTypeHandle) },
42  modifiers: null));
43  }
44 
50  public static void ToObject(this Emit il, Type type)
51  {
52  if (type == null) throw new ArgumentNullException(nameof(type));
53  il.DerefIfByRef(ref type);
54  if (type.IsValueType)
55  {
56  il.Box(type);
57  }
58  else if (type != typeof(object))
59  {
60  il.CastClass<object>();
61  }
62  }
63 
69  public static void DerefIfByRef(this Emit il, Type type) => il.DerefIfByRef(ref type);
70 
76  public static void DerefIfByRef(this Emit il, ref Type type)
77  {
78  if (type == null) throw new ArgumentNullException(nameof(type));
79  if (type.IsByRef)
80  {
81  type = type.GetElementType();
82  if (type.IsValueType)
83  {
84  il.LoadObject(type);
85  }
86  else
87  {
88  il.LoadIndirect(type);
89  }
90  }
91  }
92 
93  // Copied from https://github.com/evilfactory/moonsharp/blob/5264656c6442e783f3c75082cce69a93d66d4cc0/src/MoonSharp.Interpreter/Interop/Converters/ScriptToClrConversions.cs#L79-L99
94  private static MethodInfo GetImplicitOperatorMethod(Type baseType, Type targetType)
95  {
96  try
97  {
98  return Expression.Convert(Expression.Parameter(baseType, null), targetType).Method;
99  }
100  catch
101  {
102  if (baseType.BaseType != null)
103  {
104  return GetImplicitOperatorMethod(baseType.BaseType, targetType);
105  }
106 
107  if (targetType.BaseType != null)
108  {
109  return GetImplicitOperatorMethod(baseType, targetType.BaseType);
110  }
111 
112  return null;
113  }
114  }
115 
122  public static void LoadLocalAndCast(this Emit il, Local value, Type targetType)
123  {
124  if (value == null) throw new ArgumentNullException(nameof(value));
125  if (targetType == null) throw new ArgumentNullException(nameof(targetType));
126  if (value.LocalType != typeof(object))
127  {
128  throw new ArgumentException($"Expected local type {typeof(object)}; got {value.LocalType}.", nameof(value));
129  }
130 
131  var guid = Guid.NewGuid().ToString("N");
132 
133  if (targetType.IsByRef)
134  {
135  targetType = targetType.GetElementType();
136  }
137 
138  // IL: var baseType = value.GetType();
139  var baseType = il.DeclareLocal(typeof(Type), $"cast_baseType_{guid}");
140  il.LoadLocal(value);
141  il.Call(typeof(object).GetMethod("GetType"));
142  il.StoreLocal(baseType);
143 
144  // IL: var implicitOperatorMethod = SigilExtensions.GetImplicitOperatorMethod(baseType, <targetType>);
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);
150 
151  // IL: <TargetType> castValue;
152  var castValue = il.DeclareLocal(targetType, $"cast_castValue_{guid}");
153 
154  // IL: if (implicitConversionMethod != null)
155  il.LoadLocal(implicitOperatorMethod);
156  il.Branch((il) =>
157  {
158  // IL: var methodInvokeParams = new object[1];
159  var methodInvokeParams = il.DeclareLocal(typeof(object[]), $"cast_methodInvokeParams_{guid}");
160  il.LoadConstant(1);
161  il.NewArray(typeof(object));
162  il.StoreLocal(methodInvokeParams);
163 
164  // IL: methodInvokeParams[0] = value;
165  il.LoadLocal(methodInvokeParams);
166  il.LoadConstant(0);
167  il.LoadLocal(value);
168  il.StoreElement<object>();
169 
170  // IL: castValue = (<TargetType>)implicitConversionMethod.Invoke(null, methodInvokeParams);
171  il.LoadLocal(implicitOperatorMethod);
172  il.LoadNull(); // first parameter is null because implicit cast operators are static
173  il.LoadLocal(methodInvokeParams);
174  il.Call(typeof(MethodInfo).GetMethod("Invoke", new[] { typeof(object), typeof(object[]) }));
175  if (targetType.IsValueType)
176  {
177  il.UnboxAny(targetType);
178  }
179  else
180  {
181  il.CastClass(targetType);
182  }
183  il.StoreLocal(castValue);
184  },
185  (il) =>
186  {
187  // IL: castValue = (<TargetType>)value;
188  il.LoadLocal(value);
189  if (targetType.IsValueType)
190  {
191  il.UnboxAny(targetType);
192  }
193  else
194  {
195  il.CastClass(targetType);
196  }
197  il.StoreLocal(castValue);
198  });
199 
200  il.LoadLocal(castValue);
201  }
202 
209  public static void FormatString(this Emit il, string format, params Local[] args)
210  {
211  if (format == null) throw new ArgumentNullException(nameof(format));
212  if (args == null) throw new ArgumentNullException(nameof(args));
213 
214  var guid = Guid.NewGuid().ToString("N");
215 
216  var listType = typeof(List<>).MakeGenericType(typeof(object));
217  var list = il.DeclareLocal(listType, $"formatString_list_{guid}");
218  il.NewObject(listType);
219  il.StoreLocal(list);
220 
221  foreach (var arg in args)
222  {
223  il.LoadLocal(list);
224  il.LoadLocal(arg);
225  il.ToObject(arg.LocalType);
226  il.CallVirtual(listType.GetMethod("Add", new[] { typeof(object) }));
227  }
228 
229  var arr = il.DeclareLocal<object[]>($"formatString_arr_{guid}");
230  il.LoadLocal(list);
231  il.CallVirtual(listType.GetMethod("ToArray", new Type[0]));
232  il.StoreLocal(arr);
233 
234  il.LoadConstant(format);
235  il.LoadLocal(arr);
236  il.Call(typeof(string).GetMethod("Format", new[] { typeof(string), typeof(object[]) }));
237  }
238 
244  public static void NewMessage(this Emit il, string message)
245  {
246  var newMessage = typeof(DebugConsole).GetMethod(
247  name: nameof(DebugConsole.NewMessage),
248  bindingAttr: BindingFlags.Public | BindingFlags.Static,
249  binder: null,
250  types: new Type[] { typeof(string), typeof(Color?), typeof(bool) },
251  modifiers: null);
252  il.LoadConstant(message);
253  il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod());
254  il.LoadConstant(false);
255  il.Call(newMessage);
256  }
257 
263  public static void NewMessage(this Emit il)
264  {
265  var newMessage = typeof(DebugConsole).GetMethod(
266  name: nameof(DebugConsole.NewMessage),
267  bindingAttr: BindingFlags.Public | BindingFlags.Static,
268  binder: null,
269  types: new Type[] { typeof(string), typeof(Color?), typeof(bool) },
270  modifiers: null);
271  il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod());
272  il.LoadConstant(false);
273  il.Call(newMessage);
274  }
275 
283  public static void ForEachEnumerable<T>(this Emit il, Local enumerable, Action<Emit, Local, Sigil.Label> action)
284  {
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))
288  {
289  throw new ArgumentException($"Expected local type {typeof(IEnumerator<T>)}; got {enumerable.LocalType}.", nameof(enumerable));
290  }
291 
292  var guid = Guid.NewGuid().ToString("N");
293 
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);
299  }
300 
308  public static void ForEachEnumerator<T>(this Emit il, Local enumerator, Action<Emit, Local, Sigil.Label> action)
309  {
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))
313  {
314  throw new ArgumentException($"Expected local type {typeof(IEnumerator<T>)}; got {enumerator.LocalType}.", nameof(enumerator));
315  }
316 
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}");
321 
322  il.BeginExceptionBlock(out var exceptionBlock);
323  il.Branch(labelMoveNext); // MoveNext() needs to be called at least once before iterating
324  il.MarkLabel(labelLoopStart);
325 
326  // IL: var current = enumerator.Current;
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);
331 
332  action(il, current, labelLeave);
333 
334  il.MarkLabel(labelMoveNext);
335  il.LoadLocal(enumerator);
336  il.CallVirtual(typeof(IEnumerator).GetMethod("MoveNext"));
337  il.BranchIfTrue(labelLoopStart); // loop if MoveNext() returns true
338 
339  // IL: finally { enumerator.Dispose(); }
340  il.BeginFinallyBlock(exceptionBlock, out var finallyBlock);
341  il.LoadLocal(enumerator);
342  il.CallVirtual(typeof(IDisposable).GetMethod("Dispose"));
343  il.EndFinallyBlock(finallyBlock);
344 
345  il.EndExceptionBlock(exceptionBlock);
346 
347  il.MarkLabel(labelLeave);
348  }
349 
356  public static void If(this Emit il, Action<Emit> action)
357  {
358  if (action == null) throw new ArgumentNullException(nameof(action));
359  il.Branch(@if: action);
360  }
361 
368  public static void IfNot(this Emit il, Action<Emit> action)
369  {
370  if (action == null) throw new ArgumentNullException(nameof(action));
371  il.Branch(@else: action);
372  }
373 
384  public static void Branch(this Emit il, Action<Emit> @if = null, Action<Emit> @else = null)
385  {
386  if (@if == null && @else == null) throw new ArgumentException("At least one of the two branches must be defined.");
387 
388  var guid = Guid.NewGuid().ToString("N");
389  var labelEnd = il.DefineLabel($"branch_end_{guid}");
390  if (@if != null && @else != null)
391  {
392  var labelElse = il.DefineLabel($"branch_else_{guid}");
393  il.BranchIfFalse(labelElse);
394  @if(il);
395  il.Branch(labelEnd);
396  il.MarkLabel(labelElse);
397  @else(il);
398  }
399  else if (@if != null)
400  {
401  il.BranchIfFalse(labelEnd);
402  @if(il);
403  }
404  else
405  {
406  il.BranchIfTrue(labelEnd);
407  @else(il);
408  }
409  il.MarkLabel(labelEnd);
410  }
411  }
412 
413  public partial class LuaCsHook
414  {
415  public enum HookMethodType
416  {
417  Before, After
418  }
419 
420  private class LuaCsHookCallback
421  {
422  public string name;
423  public string hookName;
424  public LuaCsFunc func;
425 
426  public LuaCsHookCallback(string name, string hookName, LuaCsFunc func)
427  {
428  this.name = name;
429  this.hookName = hookName;
430  this.func = func;
431  }
432  }
433 
434  private class LuaCsPatch
435  {
436  public string Identifier { get; set; }
437 
438  public LuaCsPatchFunc PatchFunc { get; set; }
439  }
440 
441  private class PatchedMethod
442  {
443  public PatchedMethod(MethodInfo harmonyPrefix, MethodInfo harmonyPostfix)
444  {
445  HarmonyPrefixMethod = harmonyPrefix;
446  HarmonyPostfixMethod = harmonyPostfix;
447  Prefixes = new Dictionary<string, LuaCsPatch>();
448  Postfixes = new Dictionary<string, LuaCsPatch>();
449  }
450 
451  public MethodInfo HarmonyPrefixMethod { get; }
452 
453  public MethodInfo HarmonyPostfixMethod { get; }
454 
455  public IEnumerator<LuaCsPatch> GetPrefixEnumerator() => Prefixes.Values.GetEnumerator();
456 
457  public IEnumerator<LuaCsPatch> GetPostfixEnumerator() => Postfixes.Values.GetEnumerator();
458 
459  public Dictionary<string, LuaCsPatch> Prefixes { get; }
460 
461  public Dictionary<string, LuaCsPatch> Postfixes { get; }
462  }
463 
464  public class ParameterTable
465  {
466  private readonly Dictionary<string, object> parameters;
467  private bool returnValueModified;
468  private object returnValue;
469 
470  public ParameterTable(Dictionary<string, object> dict)
471  {
472  parameters = dict;
473  }
474 
475  public object this[string paramName]
476  {
477  get
478  {
479  if (ModifiedParameters.TryGetValue(paramName, out var value))
480  {
481  return value;
482  }
483  return OriginalParameters[paramName];
484  }
485  set
486  {
487  ModifiedParameters[paramName] = value;
488  }
489  }
490 
491  public object OriginalReturnValue { get; private set; }
492 
493  public object ReturnValue
494  {
495  get
496  {
497  if (returnValueModified) return returnValue;
498  return OriginalReturnValue;
499  }
500  set
501  {
502  returnValueModified = true;
503  returnValue = value;
504  }
505  }
506 
507  public bool PreventExecution { get; set; }
508 
509  public Dictionary<string, object> OriginalParameters => parameters;
510 
511  [MoonSharpHidden]
512  public Dictionary<string, object> ModifiedParameters { get; } = new Dictionary<string, object>();
513  }
514 
515  private static readonly string[] prohibitedHooks =
516  {
517  "Barotrauma.Lua",
518  "Barotrauma.Cs",
519  "Barotrauma.ContentPackageManager",
520  };
521 
522  private static void ValidatePatchTarget(MethodBase method)
523  {
524  if (prohibitedHooks.Any(h => method.DeclaringType.FullName.StartsWith(h)))
525  {
526  throw new ArgumentException("Hooks into the modding environment are prohibited.");
527  }
528  }
529 
530  private static string NormalizeIdentifier(string identifier)
531  {
532  return identifier?.Trim().ToLowerInvariant();
533  }
534 
535  private Harmony harmony;
536 
537  private Lazy<ModuleBuilder> patchModuleBuilder;
538 
539  private readonly Dictionary<string, Dictionary<string, (LuaCsHookCallback, ACsMod)>> hookFunctions = new Dictionary<string, Dictionary<string, (LuaCsHookCallback, ACsMod)>>();
540 
541  private readonly Dictionary<MethodKey, PatchedMethod> registeredPatches = new Dictionary<MethodKey, PatchedMethod>();
542 
543  private LuaCsSetup luaCs;
544 
545  private static LuaCsHook instance;
546 
547  private struct MethodKey : IEquatable<MethodKey>
548  {
549  public ModuleHandle ModuleHandle { get; set; }
550 
551  public int MetadataToken { get; set; }
552 
553  public override bool Equals(object obj)
554  {
555  return obj is MethodKey key && Equals(key);
556  }
557 
558  public bool Equals(MethodKey other)
559  {
560  return ModuleHandle.Equals(other.ModuleHandle) && MetadataToken == other.MetadataToken;
561  }
562 
563  public override int GetHashCode()
564  {
565  return HashCode.Combine(ModuleHandle, MetadataToken);
566  }
567 
568  public static bool operator ==(MethodKey left, MethodKey right)
569  {
570  return left.Equals(right);
571  }
572 
573  public static bool operator !=(MethodKey left, MethodKey right)
574  {
575  return !(left == right);
576  }
577 
578  public static MethodKey Create(MethodBase method) => new MethodKey
579  {
580  ModuleHandle = method.Module.ModuleHandle,
581  MetadataToken = method.MetadataToken,
582  };
583  }
584 
585  internal LuaCsHook(LuaCsSetup luaCs)
586  {
587  instance = this;
588  this.luaCs = luaCs;
589  }
590 
591  public void Initialize()
592  {
593  harmony = new Harmony("LuaCsForBarotrauma");
594  patchModuleBuilder = new Lazy<ModuleBuilder>(CreateModuleBuilder);
595 
596  UserData.RegisterType<ParameterTable>();
597  var hookType = UserData.RegisterType<LuaCsHook>();
598  var hookDesc = (StandardUserDataDescriptor)hookType;
599  typeof(LuaCsHook).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m => {
600  if (
601  m.Name.Contains("HookMethod") ||
602  m.Name.Contains("UnhookMethod") ||
603  m.Name.Contains("EnqueueFunction") ||
604  m.Name.Contains("EnqueueTimedFunction")
605  )
606  {
607  hookDesc.AddMember(m.Name, new MethodMemberDescriptor(m, InteropAccessMode.Default));
608  }
609  });
610  }
611 
612  private ModuleBuilder CreateModuleBuilder()
613  {
614  var assemblyName = $"LuaCsHookPatch-{Guid.NewGuid():N}";
615  var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndCollect);
616  var moduleBuilder = assemblyBuilder.DefineDynamicModule("LuaCsHookPatch");
617 
618  // This code emits the Roslyn attribute
619  // "IgnoresAccessChecksToAttribute" so we can freely access
620  // the Barotrauma assembly from our dynamic patches.
621  // This is important because the generated IL references
622  // non-public types/members.
623 
624  // class IgnoresAccessChecksToAttribute {
625  var typeBuilder = moduleBuilder.DefineType(
626  name: "System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute",
627  attr: TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class,
628  parent: typeof(Attribute));
629 
630  // [AttributeUsage(AllowMultiple = true)]
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);
637 
638  // private readonly string assemblyName;
639  var attributeTypeFieldBuilder = typeBuilder.DefineField(
640  fieldName: "assemblyName",
641  type: typeof(string),
642  attributes: FieldAttributes.Private | FieldAttributes.InitOnly);
643 
644  var ctor = Emit.BuildConstructor(
645  parameterTypes: new[] { typeof(string) },
646  type: typeBuilder,
647  attributes: MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
648  callingConvention: CallingConventions.Standard | CallingConventions.HasThis);
649  // IL: this.assemblyName = arg;
650  ctor.LoadArgument(0);
651  ctor.LoadArgument(1);
652  ctor.StoreField(attributeTypeFieldBuilder);
653  ctor.Return();
654  ctor.CreateConstructor();
655 
656  // public string AttributeName => this.assemblyName;
657  var attributeNameGetter = Emit.BuildMethod(
658  returnType: typeof(string),
659  parameterTypes: new Type[0],
660  type: typeBuilder,
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();
667 
668  var attributeName = typeBuilder.DefineProperty(
669  name: "AttributeName",
670  attributes: PropertyAttributes.None,
671  returnType: typeof(string),
672  parameterTypes: null);
673  attributeName.SetGetMethod(attributeNameGetter.CreateMethod());
674  // }
675 
676  var type = typeBuilder.CreateTypeInfo().AsType();
677 
678  // The assembly names are hardcoded, otherwise it would
679  // break unit tests.
680  var assembliesToExpose = new[] { "Barotrauma", "DedicatedServer" };
681  foreach (var name in assembliesToExpose)
682  {
683  var attr = new CustomAttributeBuilder(
684  con: type.GetConstructor(new[] { typeof(string)}),
685  constructorArgs: new[] { name });
686  assemblyBuilder.SetCustomAttribute(attr);
687  }
688 
689  return moduleBuilder;
690  }
691 
692  public void Add(string name, LuaCsFunc func, ACsMod owner = null) => Add(name, name, func, owner);
693 
694  public void Add(string name, string identifier, LuaCsFunc func, ACsMod owner = null)
695  {
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));
699 
700  name = NormalizeIdentifier(name);
701  identifier = NormalizeIdentifier(identifier);
702 
703  if (!hookFunctions.ContainsKey(name))
704  {
705  hookFunctions.Add(name, new Dictionary<string, (LuaCsHookCallback, ACsMod)>());
706  }
707 
708  hookFunctions[name][identifier] = (new LuaCsHookCallback(name, identifier, func), owner);
709  }
710 
711  public bool Exists(string name, string identifier)
712  {
713  if (name == null) throw new ArgumentNullException(nameof(name));
714  if (identifier == null) throw new ArgumentNullException(nameof(identifier));
715 
716  name = NormalizeIdentifier(name);
717  identifier = NormalizeIdentifier(identifier);
718 
719  if (!hookFunctions.ContainsKey(name))
720  {
721  return false;
722  }
723 
724  return hookFunctions[name].ContainsKey(identifier);
725  }
726 
727  public void Remove(string name, string identifier)
728  {
729  if (name == null) throw new ArgumentNullException(nameof(name));
730  if (identifier == null) throw new ArgumentNullException(nameof(identifier));
731 
732  name = NormalizeIdentifier(name);
733  identifier = NormalizeIdentifier(identifier);
734 
735  if (hookFunctions.ContainsKey(name) && hookFunctions[name].ContainsKey(identifier))
736  {
737  hookFunctions[name].Remove(identifier);
738  }
739  }
740 
741  public void Clear()
742  {
743  harmony?.UnpatchAll();
744 
745  foreach (var (_, patch) in registeredPatches)
746  {
747  // Remove references stored in our dynamic types so the generated
748  // assembly can be garbage-collected.
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);
755  }
756 
757  hookFunctions.Clear();
758  registeredPatches.Clear();
759  patchModuleBuilder = null;
760 
761  compatHookPrefixMethods.Clear();
762  compatHookPostfixMethods.Clear();
763  }
764 
765  private Stopwatch performanceMeasurement = new Stopwatch();
766 
767  [MoonSharpHidden]
768  public T Call<T>(string name, params object[] args)
769  {
770  if (name == null) throw new ArgumentNullException(name);
771  if (args == null) args = new object[0];
772 
773  name = NormalizeIdentifier(name);
774  if (!hookFunctions.ContainsKey(name)) return default;
775 
776  T lastResult = default;
777 
778  var hooks = hookFunctions[name].ToArray();
779  foreach ((string key, var tuple) in hooks)
780  {
781  if (tuple.Item2 != null && tuple.Item2.IsDisposed)
782  {
783  hookFunctions[name].Remove(key);
784  continue;
785  }
786 
787  try
788  {
789  if (luaCs.PerformanceCounter.EnablePerformanceCounter)
790  {
791  performanceMeasurement.Start();
792  }
793 
794  var result = tuple.Item1.func(args);
795 
796  if (result is DynValue luaResult)
797  {
798  if (luaResult.Type == DataType.Tuple)
799  {
800  bool replaceNil = luaResult.Tuple.Length > 1 && luaResult.Tuple[1].CastToBool();
801 
802  if (!luaResult.Tuple[0].IsNil() || replaceNil)
803  {
804  lastResult = luaResult.ToObject<T>();
805  }
806  }
807  else if (!luaResult.IsNil())
808  {
809  lastResult = luaResult.ToObject<T>();
810  }
811  }
812  else
813  {
814  lastResult = (T)result;
815  }
816 
817  if (luaCs.PerformanceCounter.EnablePerformanceCounter)
818  {
819  performanceMeasurement.Stop();
820  luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks);
821  performanceMeasurement.Reset();
822  }
823  }
824  catch (Exception e)
825  {
826  var argsSb = new StringBuilder();
827  foreach (var arg in args)
828  {
829  argsSb.Append(arg + " ");
830  }
831  LuaCsLogger.LogError($"Error in Hook '{name}'->'{key}', with args '{argsSb}':\n{e}", LuaCsMessageOrigin.Unknown);
832  LuaCsLogger.HandleException(e, LuaCsMessageOrigin.Unknown);
833  }
834  }
835 
836  return lastResult;
837  }
838 
839  public object Call(string name, params object[] args) => Call<object>(name, args);
840 
841  private static MethodBase ResolveMethod(string className, string methodName, string[] parameters)
842  {
843  var classType = LuaUserData.GetType(className);
844  if (classType == null) throw new ScriptRuntimeException($"invalid class name '{className}'");
845 
846  const BindingFlags BINDING_FLAGS = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
847 
848  MethodBase method = null;
849 
850  try
851  {
852  if (parameters != null)
853  {
854  Type[] parameterTypes = new Type[parameters.Length];
855 
856  for (int i = 0; i < parameters.Length; i++)
857  {
858  Type type = LuaUserData.GetType(parameters[i]);
859  if (type == null)
860  {
861  throw new ScriptRuntimeException($"invalid parameter type '{parameters[i]}'");
862  }
863  parameterTypes[i] = type;
864  }
865 
866  method = methodName switch
867  {
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))
872  .SingleOrDefault(),
873  _ => classType.GetMethod(methodName, BINDING_FLAGS, null, parameterTypes, null),
874  };
875  }
876  else
877  {
878  ConstructorInfo GetCtor()
879  {
880  var ctors = classType.GetConstructors(BINDING_FLAGS)
881  .Except(new[] { classType.TypeInitializer })
882  .GetEnumerator();
883 
884  if (!ctors.MoveNext()) return null;
885  var ctor = ctors.Current;
886 
887  if (ctors.MoveNext()) throw new AmbiguousMatchException();
888  return ctor;
889  }
890 
891  method = methodName switch
892  {
893  ".cctor" => throw new ScriptRuntimeException("type initializers can't have parameters"),
894  ".ctor" => GetCtor(),
895  _ => classType.GetMethod(methodName, BINDING_FLAGS),
896  };
897  }
898  }
899  catch (AmbiguousMatchException)
900  {
901  throw new ScriptRuntimeException("ambiguous method signature");
902  }
903 
904  if (method == null)
905  {
906  var parameterNamesStr = parameters == null ? "" : string.Join(", ", parameters);
907  throw new ScriptRuntimeException($"method '{methodName}({parameterNamesStr})' not found in class '{className}'");
908  }
909 
910  return method;
911  }
912 
913  private class DynamicParameterMapping
914  {
915  public DynamicParameterMapping(string name, Type originalMethodParamType, Type harmonyPatchParamType)
916  {
917  ParameterName = name;
918  OriginalMethodParamType = originalMethodParamType;
919  HarmonyPatchParamType = harmonyPatchParamType;
920  }
921 
922  public string ParameterName { get; set; }
923 
924  public Type OriginalMethodParamType { get; set; }
925 
926  public Type HarmonyPatchParamType { get; set; }
927  }
928 
929  private static readonly Regex InvalidIdentifierCharsRegex = new Regex(@"[^\w\d]", RegexOptions.Compiled);
930 
931  private const string FIELD_LUACS = "LuaCs";
932 
933  // If you need to debug this:
934  // - use https://sharplab.io ; it's a very useful for resource for writing IL by hand.
935  // - use il.NewMessage("") or il.WriteLine("") to see where the IL crashes at runtime.
936  private MethodInfo CreateDynamicHarmonyPatch(string identifier, MethodBase original, HookMethodType hookType)
937  {
938  var parameters = new List<DynamicParameterMapping>
939  {
940  new DynamicParameterMapping("__originalMethod", null, typeof(MethodBase)),
941  new DynamicParameterMapping("__instance", null, typeof(object)),
942  };
943 
944  var hasReturnType = original is MethodInfo mi && mi.ReturnType != typeof(void);
945  if (hasReturnType)
946  {
947  parameters.Add(new DynamicParameterMapping("__result", null, typeof(object).MakeByRefType()));
948  }
949 
950  foreach (var parameter in original.GetParameters())
951  {
952  var paramName = parameter.Name;
953  var originalMethodParamType = parameter.ParameterType;
954  var harmonyPatchParamType = originalMethodParamType.IsByRef
955  ? originalMethodParamType
956  // Make all parameters modifiable by the harmony patch
957  : originalMethodParamType.MakeByRefType();
958  parameters.Add(new DynamicParameterMapping(paramName, originalMethodParamType, harmonyPatchParamType));
959  }
960 
961  static string MangleName(object o) => InvalidIdentifierCharsRegex.Replace(o?.ToString(), "_");
962 
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);
968 
969  var luaCsField = typeBuilder.DefineField(FIELD_LUACS, typeof(LuaCsSetup), FieldAttributes.Public | FieldAttributes.Static);
970 
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(),
975  type: typeBuilder,
976  name: methodName,
977  attributes: MethodAttributes.Public | MethodAttributes.Static,
978  callingConvention: CallingConventions.Standard);
979 
980  var labelReturn = il.DefineLabel("endOfFunction");
981 
982  il.BeginExceptionBlock(out var exceptionBlock);
983 
984  // IL: var harmonyReturnValue = true;
985  var harmonyReturnValue = il.DeclareLocal<bool>("harmonyReturnValue");
986  il.LoadConstant(true);
987  il.StoreLocal(harmonyReturnValue);
988 
989  // IL: var patchKey = MethodKey.Create(__originalMethod);
990  var patchKey = il.DeclareLocal<MethodKey>("patchKey");
991  il.LoadArgument(0); // load __originalMethod
992  il.CastClass<MethodBase>();
993  il.Call(typeof(MethodKey).GetMethod(nameof(MethodKey.Create)));
994  il.StoreLocal(patchKey);
995 
996  // IL: var patchExists = instance.registeredPatches.TryGetValue(patchKey, out MethodPatches patches)
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); // out parameter
1003  il.Call(typeof(Dictionary<MethodKey, PatchedMethod>).GetMethod("TryGetValue"));
1004  il.StoreLocal(patchExists);
1005 
1006  // IL: if (!patchExists)
1007  il.LoadLocal(patchExists);
1008  il.IfNot((il) =>
1009  {
1010  // XXX: if we get here, it's probably because a patched
1011  // method was running when `reloadlua` was executed.
1012  // This can happen with a postfix on
1013  // `Barotrauma.Networking.GameServer#Update`.
1014  il.Leave(labelReturn);
1015  });
1016 
1017  // IL: var parameterDict = new Dictionary<string, object>(<paramCount>);
1018  var parameterDict = il.DeclareLocal<Dictionary<string, object>>("parameterDict");
1019  il.LoadConstant(parameters.Count(x => x.OriginalMethodParamType != null)); // preallocate the dictionary using the # of args
1020  il.NewObject(typeof(Dictionary<string, object>), typeof(int));
1021  il.StoreLocal(parameterDict);
1022 
1023  for (ushort i = 0; i < parameters.Count; i++)
1024  {
1025  // Skip parameters that don't exist in the original method
1026  if (parameters[i].OriginalMethodParamType == null) continue;
1027 
1028  // IL: parameterDict.Add(<paramName>, <paramValue>);
1029  il.LoadLocal(parameterDict);
1030  il.LoadConstant(parameters[i].ParameterName);
1031  il.LoadArgument(i);
1032  il.ToObject(parameters[i].HarmonyPatchParamType);
1033  il.Call(typeof(Dictionary<string, object>).GetMethod("Add"));
1034  }
1035 
1036  // IL: var ptable = new ParameterTable(parameterDict);
1037  var ptable = il.DeclareLocal<ParameterTable>("ptable");
1038  il.LoadLocal(parameterDict);
1039  il.NewObject(typeof(ParameterTable), typeof(Dictionary<string, object>));
1040  il.StoreLocal(ptable);
1041 
1042  if (hasReturnType && hookType == HookMethodType.After)
1043  {
1044  // IL: ptable.OriginalReturnValue = __result;
1045  il.LoadLocal(ptable);
1046  il.LoadArgument(2); // ref __result
1047  il.ToObject(parameters[2].HarmonyPatchParamType);
1048  il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.OriginalReturnValue)).GetSetMethod(nonPublic: true));
1049  }
1050 
1051  // IL: var enumerator = patches.GetPrefixEnumerator();
1052  var enumerator = il.DeclareLocal<IEnumerator<LuaCsPatch>>("enumerator");
1053  il.LoadLocal(patches);
1054  il.CallVirtual(typeof(PatchedMethod).GetMethod(
1055  name: hookType == HookMethodType.Before
1056  ? nameof(PatchedMethod.GetPrefixEnumerator)
1057  : nameof(PatchedMethod.GetPostfixEnumerator),
1058  bindingAttr: BindingFlags.Public | BindingFlags.Instance));
1059  il.StoreLocal(enumerator);
1060 
1061  var labelUpdateParameters = il.DefineLabel("updateParameters");
1062 
1063  // Iterate over prefixes/postfixes
1064  il.ForEachEnumerator<LuaCsPatch>(enumerator, (il, current, labelLeave) =>
1065  {
1066  // IL: var luaReturnValue = current.PatchFunc.Invoke(__instance, ptable);
1067  var luaReturnValue = il.DeclareLocal<DynValue>("luaReturnValue");
1068  il.LoadLocal(current);
1069  il.Call(typeof(LuaCsPatch).GetProperty(nameof(LuaCsPatch.PatchFunc)).GetGetMethod());
1070  il.LoadArgument(1); // __instance
1071  il.LoadLocal(ptable);
1072  il.CallVirtual(typeof(LuaCsPatchFunc).GetMethod("Invoke"));
1073  il.StoreLocal(luaReturnValue);
1074 
1075  if (hasReturnType)
1076  {
1077  // IL: var ptableReturnValue = ptable.ReturnValue;
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);
1082 
1083  // IL: if (ptableReturnValue != null)
1084  il.LoadLocal(ptableReturnValue);
1085  il.If((il) =>
1086  {
1087  // IL: __result = ptableReturnValue;
1088  il.LoadArgument(2); // ref __result
1089  il.LoadLocal(ptableReturnValue);
1090  il.StoreIndirect(typeof(object));
1091  il.Break();
1092  });
1093 
1094  // IL: if (luaReturnValue != null)
1095  il.LoadLocal(luaReturnValue);
1096  il.If((il) =>
1097  {
1098  // IL: if (!luaReturnValue.IsVoid())
1099  il.LoadLocal(luaReturnValue);
1100  il.Call(typeof(DynValue).GetMethod(nameof(DynValue.IsVoid)));
1101  il.IfNot((il) =>
1102  {
1103  // IL: var csReturnType = Type.GetTypeFromHandle(<original.ReturnType>);
1104  var csReturnType = il.DeclareLocal<Type>("csReturnType");
1105  il.LoadType(((MethodInfo)original).ReturnType);
1106  il.StoreLocal(csReturnType);
1107 
1108  // IL: var csReturnValue = luaReturnValue.ToObject(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,
1115  binder: null,
1116  types: new Type[] { typeof(Type) },
1117  modifiers: null));
1118  il.StoreLocal(csReturnValue);
1119 
1120  // IL: __result = csReturnValue;
1121  il.LoadArgument(2); // ref __result
1122  il.LoadLocal(csReturnValue);
1123  il.StoreIndirect(typeof(object));
1124  });
1125  });
1126  }
1127 
1128  // IL: if (ptable.PreventExecution)
1129  il.LoadLocal(ptable);
1130  il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.PreventExecution)).GetGetMethod());
1131  il.If((il) =>
1132  {
1133  // IL: harmonyReturnValue = false;
1134  il.LoadConstant(false);
1135  il.StoreLocal(harmonyReturnValue);
1136 
1137  // IL: break;
1138  il.Leave(labelLeave);
1139  });
1140  });
1141 
1142  // IL: var modifiedParameters = ptable.ModifiedParameters;
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);
1147  // IL: object modifiedValue;
1148  var modifiedValue = il.DeclareLocal<object>("modifiedValue");
1149 
1150  // Update the parameters
1151  for (ushort i = 0; i < parameters.Count; i++)
1152  {
1153  // Skip parameters that don't exist in the original method
1154  if (parameters[i].OriginalMethodParamType == null) continue;
1155 
1156  // IL: if (modifiedParameters.TryGetValue("parameterName", out modifiedValue))
1157  il.LoadLocal(modifiedParameters);
1158  il.LoadConstant(parameters[i].ParameterName);
1159  il.LoadLocalAddress(modifiedValue); // out parameter
1160  il.Call(typeof(Dictionary<string, object>).GetMethod(nameof(Dictionary<string, object>.TryGetValue)));
1161  il.If((il) =>
1162  {
1163  // XXX: GetElementType() gets the "real" type behind
1164  // the ByRef. This is safe because all the parameters
1165  // are made into ByRef to support modification.
1166  var paramType = parameters[i].HarmonyPatchParamType.GetElementType();
1167 
1168  // IL: ref argName = modifiedValue;
1169  il.LoadArgument(i);
1170  il.LoadLocalAndCast(modifiedValue, paramType);
1171  if (paramType.IsValueType)
1172  {
1173  il.StoreObject(paramType);
1174  }
1175  else
1176  {
1177  il.StoreIndirect(paramType);
1178  }
1179  });
1180  }
1181 
1182  il.MarkLabel(labelReturn);
1183 
1184  // IL: catch (Exception exception)
1185  il.BeginCatchAllBlock(exceptionBlock, out var catchBlock);
1186  var exception = il.DeclareLocal<Exception>("exception");
1187  il.StoreLocal(exception);
1188 
1189  // IL: if (LuaCs != null)
1190  il.LoadField(luaCsField);
1191  il.If((il) =>
1192  {
1193  // IL: LuaCs.HandleException(exception, LuaCsMessageOrigin.LuaMod);
1194  il.LoadLocal(exception);
1195  il.LoadConstant((int)LuaCsMessageOrigin.LuaMod); // underlying enum type is int
1196  il.Call(typeof(LuaCsLogger).GetMethod(nameof(LuaCsLogger.HandleException), BindingFlags.Public | BindingFlags.Static));
1197  });
1198 
1199  il.EndCatchBlock(catchBlock);
1200 
1201  il.EndExceptionBlock(exceptionBlock);
1202 
1203  // Only prefixes return a bool
1204  if (hookType == HookMethodType.Before)
1205  {
1206  il.LoadLocal(harmonyReturnValue);
1207  }
1208  il.Return();
1209 
1210  var method = il.CreateMethod();
1211  for (var i = 0; i < parameters.Count; i++)
1212  {
1213  method.DefineParameter(i + 1, ParameterAttributes.None, parameters[i].ParameterName);
1214  }
1215 
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);
1219  }
1220 
1221  private string Patch(string identifier, MethodBase method, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before)
1222  {
1223  if (method == null) throw new ArgumentNullException(nameof(method));
1224  if (patch == null) throw new ArgumentNullException(nameof(patch));
1225  ValidatePatchTarget(method);
1226 
1227  identifier ??= Guid.NewGuid().ToString("N");
1228  identifier = NormalizeIdentifier(identifier);
1229 
1230  var patchKey = MethodKey.Create(method);
1231  if (!registeredPatches.TryGetValue(patchKey, out var methodPatches))
1232  {
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);
1237  }
1238 
1239  if (hookType == HookMethodType.Before)
1240  {
1241  if (methodPatches.Prefixes.Remove(identifier))
1242  {
1243  LuaCsLogger.LogMessage($"Replacing existing prefix: {identifier}");
1244  }
1245 
1246  methodPatches.Prefixes.Add(identifier, new LuaCsPatch
1247  {
1248  Identifier = identifier,
1249  PatchFunc = patch,
1250  });
1251  }
1252  else if (hookType == HookMethodType.After)
1253  {
1254  if (methodPatches.Postfixes.Remove(identifier))
1255  {
1256  LuaCsLogger.LogMessage($"Replacing existing postfix: {identifier}");
1257  }
1258 
1259  methodPatches.Postfixes.Add(identifier, new LuaCsPatch
1260  {
1261  Identifier = identifier,
1262  PatchFunc = patch,
1263  });
1264  }
1265 
1266  return identifier;
1267  }
1268 
1269  public string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before)
1270  {
1271  var method = ResolveMethod(className, methodName, parameterTypes);
1272  return Patch(identifier, method, patch, hookType);
1273  }
1274 
1275  public string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before)
1276  {
1277  var method = ResolveMethod(className, methodName, null);
1278  return Patch(identifier, method, patch, hookType);
1279  }
1280 
1281  public string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before)
1282  {
1283  var method = ResolveMethod(className, methodName, parameterTypes);
1284  return Patch(null, method, patch, hookType);
1285  }
1286 
1287  public string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before)
1288  {
1289  var method = ResolveMethod(className, methodName, null);
1290  return Patch(null, method, patch, hookType);
1291  }
1292 
1293  private bool RemovePatch(string identifier, MethodBase method, HookMethodType hookType)
1294  {
1295  if (identifier == null) throw new ArgumentNullException(nameof(identifier));
1296  identifier = NormalizeIdentifier(identifier);
1297 
1298  var patchKey = MethodKey.Create(method);
1299  if (!registeredPatches.TryGetValue(patchKey, out var methodPatches))
1300  {
1301  return false;
1302  }
1303 
1304  return hookType switch
1305  {
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)),
1309  };
1310  }
1311 
1312  public bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType)
1313  {
1314  var method = ResolveMethod(className, methodName, parameterTypes);
1315  return RemovePatch(identifier, method, hookType);
1316  }
1317 
1318  public bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType)
1319  {
1320  var method = ResolveMethod(className, methodName, null);
1321  return RemovePatch(identifier, method, hookType);
1322  }
1323  }
1324 }
Dictionary< string, object > OriginalParameters
Definition: LuaCsHook.cs:509
Dictionary< string, object > ModifiedParameters
Definition: LuaCsHook.cs:512
ParameterTable(Dictionary< string, object > dict)
Definition: LuaCsHook.cs:470
bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType)
Definition: LuaCsHook.cs:1318
T Call< T >(string name, params object[] args)
Definition: LuaCsHook.cs:768
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)
Definition: LuaCsHook.cs:1287
void Remove(string name, string identifier)
Definition: LuaCsHook.cs:727
bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType)
Definition: LuaCsHook.cs:1312
string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType=HookMethodType.Before)
Definition: LuaCsHook.cs:1281
string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType=HookMethodType.Before)
Definition: LuaCsHook.cs:1275
bool Exists(string name, string identifier)
Definition: LuaCsHook.cs:711
string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType=HookMethodType.Before)
Definition: LuaCsHook.cs:1269
void Add(string name, string identifier, LuaCsFunc func, ACsMod owner=null)
Definition: LuaCsHook.cs:694
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)