Skip to content

Commit d553d02

Browse files
committed
fix: improve context adaptation and static field patch stability
- map nested closure/enumerator methods correctly during context-bound cloning - avoid duplicate generated method attributes - fix static generic field injection for static methods and world generation options - prevent unsafe ctor-call reordering when local variables are involved - return empty string for stripped string-return methods
1 parent 455f76e commit d553d02

11 files changed

Lines changed: 153 additions & 37 deletions

File tree

src/OTAPI.UnifiedServerProcess/Commons/Structure.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,6 @@ public static MethodDefinition DeepMapMethodDef(MethodDefinition method, MapOpti
262262
result.IsPreserveSig = method.IsPreserveSig;
263263
result.IsPInvokeImpl = method.IsPInvokeImpl;
264264

265-
foreach (var attrib in method.CustomAttributes) {
266-
result.CustomAttributes.Add(attrib.Clone());
267-
}
268-
269265
foreach (var @override in method.Overrides) {
270266
// TODO: implement
271267
result.Overrides.Add(@override);

src/OTAPI.UnifiedServerProcess/Core/Analysis/StaticFieldReferenceAnalysis/StaticFieldReferenceAnalyzer.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,6 @@ private void ProcessMethod(
159159
localVariableTraces[methodKey] = localTrace;
160160
}
161161

162-
if (method.Name == "AddVariant") {
163-
164-
}
165-
166162
// build jump source mapping
167163
var jumpSites = this.GetMethodJumpSites(method);
168164
bool hasInnerChange;
@@ -898,10 +894,6 @@ void HandleMethodCall(Instruction instruction) {
898894
// *caller arguments* into the call result stack slot.
899895
if (calleeParameterFlowData?.ReturnValueTrace is not null) {
900896

901-
if (methodRef.Name.StartsWith("get_") && method.GetIdentifier() is "Terraria.ObjectData.TileObjectData.GetTileData(System.Int32,System.Int32,System.Int32)") {
902-
903-
}
904-
905897
foreach (var originGroup in calleeParameterFlowData.ReturnValueTrace.ReferencedParameters) {
906898
if (!callerArgsByCalleeParamName.TryGetValue(originGroup.Key, out var origin)
907899
|| origin.Trace is null) {

src/OTAPI.UnifiedServerProcess/Core/FunctionalFeatures/IContextInjectFeature.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ public static void AdjustConstructorLoadRoot(this IContextInjectFeature point, T
326326
|| ctor.Parameters[0].ParameterType.FullName != rootContextDef.FullName) {
327327
return;
328328
}
329+
if (ctor.DeclaringType.Name is "UIWorldLoad") {
330+
331+
}
329332

330333
Instruction? baseCtorCall = null;
331334
Instruction? firstLoadRoot_shouldMoveCtorCallWhenNotNull = null;
@@ -379,6 +382,13 @@ public static void AdjustConstructorLoadRoot(this IContextInjectFeature point, T
379382

380383
var loadPaths = MonoModCommon.Stack.AnalyzeParametersSources(ctor, baseCtorCall, jumpSite);
381384

385+
if (loadPaths
386+
.SelectMany(p => p.ParametersSources.Skip(1)) // ignore 'this' parameter
387+
.SelectMany(s => s.Instructions)
388+
.Any(x => MonoModCommon.IL.TryGetReferencedVariable(ctor, x, out _))) {
389+
return;
390+
}
391+
382392
List<Instruction> movedInstructions = [];
383393
if (loadPaths.Length != 0 && loadPaths[0].ParametersSources.Length != 0) {
384394
var blockCheck = loadPaths[0].ParametersSources[0].Instructions[0];
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using ModFramework;
2+
using Mono.Cecil;
3+
using OTAPI.UnifiedServerProcess.Commons;
4+
using OTAPI.UnifiedServerProcess.Core.FunctionalFeatures;
5+
using OTAPI.UnifiedServerProcess.Loggers;
6+
7+
namespace OTAPI.UnifiedServerProcess.Core.Patching.FieldFilterPatching
8+
{
9+
public class DelegateWithCtxParamProcessor2(TypeDefinition rootContextDef) : IFieldFilterArgProcessor, IJumpSitesCacheFeature
10+
{
11+
public string Name => nameof(DelegateWithCtxParamProcessor);
12+
13+
public FieldReference[] FieldTasks() {
14+
var module = rootContextDef.Module;
15+
return [
16+
module.GetType("Terraria.DataStructures.PlacementHook").Field("hook"),
17+
module.GetType("Terraria.WorldBuilding.AWorldGenerationOption").Field("OnOptionStateChanged"),
18+
];
19+
}
20+
21+
public void Apply(LoggedComponent logger, ref FilterArgumentSource source) {
22+
foreach (var field in FieldTasks()) {
23+
}
24+
}
25+
26+
public void RunTask(ModuleDefinition module, FieldDefinition field) {
27+
28+
}
29+
}
30+
}

src/OTAPI.UnifiedServerProcess/Core/Patching/FieldFilterPatching/FilterArgumentSource.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ public class FilterArgumentSource(ModuleDefinition module, MethodDefinition[] in
2121
public class Dict : Dictionary<string, FieldDefinition>
2222
{
2323
public new bool TryAdd(string key, FieldDefinition field) {
24-
if (field.Name is "projHook") {
25-
26-
}
2724
return base.TryAdd(key, field);
2825
}
2926
public new bool Remove(string key) {

src/OTAPI.UnifiedServerProcess/Core/Patching/FieldFilterPatching/ForceStaticProcessor.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ public class ForceStaticProcessor() : IFieldFilterArgProcessor
3333
"Terraria.UI.UIElement",
3434
];
3535
public static readonly List<string> forceStaticFieldFullNames = [
36+
// global singleton
3637
"Terraria.Localization.LocalizedText.Empty",
37-
"Terraria.Main.projHook",
38+
// lazy loading cache, should be global
39+
"Terraria.Localization.LocalizedText._propertyLookupCache",
3840
];
3941
public void Apply(LoggedComponent logger, ref FilterArgumentSource source) {
4042
foreach (var modified in source.ModifiedStaticFields.ToArray()) {

src/OTAPI.UnifiedServerProcess/Core/Patching/FieldFilterPatching/StaticGenericProcessor.cs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class StaticGenericProcessor() : IFieldFilterArgProcessor, IJumpSitesCach
3030
public void Apply(LoggedComponent logger, ref FilterArgumentSource raw) {
3131
ProcessTerraria_Net_NetManager(raw);
3232
ProcessTerraria_GameContent_Creative_CreativePowerManager(raw);
33+
ProcessTerraria_WorldBuilding_WorldGenerationOptions(raw);
3334

3435
foreach (var fieldKV in raw.ModifiedStaticFields.ToArray()) {
3536
if (fieldKV.Value.DeclaringType.HasGenericParameters) {
@@ -91,10 +92,31 @@ void ProcessTerraria_GameContent_Creative_CreativePowerManager(FilterArgumentSou
9192
argument.ModifiedStaticFields.Remove(powerTypeStorage_Id.GetIdentifier());
9293
argument.ModifiedStaticFields.Remove(powerTypeStorage_Name.GetIdentifier());
9394
argument.ModifiedStaticFields.Remove(powerTypeStorage_Power.GetIdentifier());
94-
var powersCountField = creativePowerManager.GetField("PowerTypeStorageInstance");
95-
argument.ModifiedStaticFields.TryAdd(powersCountField.GetIdentifier(), powersCountField);
95+
96+
var powerStorageInstanceField = creativePowerManager.GetField("PowerTypeStorageInstance");
97+
argument.ModifiedStaticFields.TryAdd(powerStorageInstanceField.GetIdentifier(), powerStorageInstanceField);
9698
}
9799

100+
void ProcessTerraria_WorldBuilding_WorldGenerationOptions(FilterArgumentSource argument) {
101+
var worldGenerationOptions = argument.MainModule.GetType("Terraria.WorldBuilding.WorldGenerationOptions");
102+
var optionStorage = argument.MainModule.GetType("Terraria.WorldBuilding.WorldGenerationOptions/OptionStorage`1");
103+
104+
var optionStorage_Instance = optionStorage.GetField(nameof(Terraria.WorldBuilding.WorldGenerationOptions.OptionStorage<Terraria.WorldBuilding.AWorldGenerationOption>.Instance))
105+
?? throw new Exception("Terraria.WorldBuilding.WorldGenerationOptions.OptionStorage<Terraria.WorldBuilding.AWorldGenerationOption>.Instance not found");
106+
107+
RefactorFieldOperate_DictionaryStorage(worldGenerationOptions, optionStorage);
108+
109+
argument.ModifiedStaticFields.Remove(optionStorage_Instance.GetIdentifier());
110+
111+
var optionsField = worldGenerationOptions.GetField("_options");
112+
argument.ModifiedStaticFields.TryAdd(optionsField.GetIdentifier(), optionsField);
113+
114+
var optionStorageInstanceField = worldGenerationOptions.GetField("OptionStorageInstance");
115+
argument.ModifiedStaticFields.TryAdd(optionStorageInstanceField.GetIdentifier(), optionStorageInstanceField);
116+
}
117+
118+
#region
119+
98120
public readonly struct TypeInitializationParams
99121
{
100122
public readonly string Prefix;
@@ -398,11 +420,21 @@ void RefactorFieldOperate_DictionaryStorage(TypeDefinition containingType, TypeD
398420
genericInstancedDeclaringType.GenericArguments.Add(genericInstance.GenericArguments[0]);
399421
var genericInstancedFieldRef = new FieldReference(field.Name, field.FieldType, genericInstancedDeclaringType);
400422

401-
Instruction[] insertBefores = [
402-
Instruction.Create(OpCodes.Ldarg_0),
403-
Instruction.Create(OpCodes.Ldfld, containerField),
404-
Instruction.Create(OpCodes.Callvirt, genericInstancedGetMethod),
423+
Instruction[] insertBefores;
424+
if (method.IsStatic) {
425+
insertBefores = [
426+
Instruction.Create(OpCodes.Ldsfld, containerField),
427+
Instruction.Create(OpCodes.Callvirt, genericInstancedGetMethod),
428+
];
429+
}
430+
else {
431+
insertBefores = [
432+
Instruction.Create(OpCodes.Ldarg_0),
433+
Instruction.Create(OpCodes.Ldfld, containerField),
434+
Instruction.Create(OpCodes.Callvirt, genericInstancedGetMethod),
405435
];
436+
}
437+
406438
var insertTarget = instruction;
407439
ilProcessor.InsertBeforeSeamlessly(ref insertTarget, insertBefores);
408440
insertTarget.Operand = genericInstancedFieldRef;
@@ -429,11 +461,21 @@ void RefactorFieldOperate_DictionaryStorage(TypeDefinition containingType, TypeD
429461
genericInstancedDeclaringType.GenericArguments.Add(genericInstance.GenericArguments[0]);
430462
var genericInstancedFieldRef = new FieldReference(field.Name, field.FieldType, genericInstancedDeclaringType);
431463

432-
Instruction[] insertBefores = [
433-
Instruction.Create(OpCodes.Ldarg_0),
434-
Instruction.Create(OpCodes.Ldfld, containerField),
435-
Instruction.Create(OpCodes.Callvirt, genericInstancedGetMethod),
464+
Instruction[] insertBefores;
465+
if (method.IsStatic) {
466+
insertBefores = [
467+
Instruction.Create(OpCodes.Ldsfld, containerField),
468+
Instruction.Create(OpCodes.Callvirt, genericInstancedGetMethod),
436469
];
470+
}
471+
else {
472+
insertBefores = [
473+
Instruction.Create(OpCodes.Ldarg_0),
474+
Instruction.Create(OpCodes.Ldfld, containerField),
475+
Instruction.Create(OpCodes.Callvirt, genericInstancedGetMethod),
476+
];
477+
}
478+
437479
ilProcessor.InsertBeforeSeamlessly(ref loadValue, insertBefores);
438480
instruction.Operand = genericInstancedFieldRef;
439481
instruction.OpCode = OpCodes.Stfld;
@@ -447,5 +489,6 @@ void RefactorFieldOperate_DictionaryStorage(TypeDefinition containingType, TypeD
447489
}
448490
}
449491
}
492+
#endregion
450493
}
451494
}

src/OTAPI.UnifiedServerProcess/Core/Patching/GeneralPatching/EnumeratorCtxAdaptPatcher.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ void ProcessMethod(PatcherArguments arguments,
9595
var oldEnumeratorDef = enumeratorDef;
9696
enumeratorDef = PatchingCommon.MemberClonedType(enumeratorDef, enumeratorDef.Name, option.TypeReplaceMap);
9797

98+
static IEnumerable<(TypeDefinition otype, TypeDefinition ntype)> GetTypeReplacePairs(TypeDefinition oldTypeDef, TypeDefinition newTypeDef) {
99+
yield return (oldTypeDef, newTypeDef);
100+
foreach (var newNestedType in newTypeDef.NestedTypes) {
101+
var oldNestedType = oldTypeDef.NestedTypes.Single(ont => ont.Name == newNestedType.Name);
102+
foreach (var pair in GetTypeReplacePairs(oldNestedType, newNestedType)) {
103+
yield return pair;
104+
}
105+
}
106+
}
107+
foreach (var (otype, ntype) in GetTypeReplacePairs(oldEnumeratorDef, enumeratorDef)) {
108+
foreach (var method in ntype.Methods) {
109+
if (method.IsConstructor) {
110+
continue;
111+
}
112+
var omethod = otype.Methods.Single(m => m.GetIdentifier(withTypeName: false) == method.GetIdentifier(withTypeName: false));
113+
mappedMethod.contextBoundMethods.Add(method.GetIdentifier(), method);
114+
mappedMethod.originalToContextBound.Add(omethod.GetIdentifier(), method);
115+
}
116+
}
117+
98118
foreach (var redirectInst in caller.Body.Instructions) {
99119
if (redirectInst.Operand is TypeReference typeRef) {
100120
redirectInst.Operand = MonoModCommon.Structure.DeepMapTypeReference(typeRef, option);

src/OTAPI.UnifiedServerProcess/Core/Patching/GeneralPatching/InvocationCtxAdaptPatcher.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ public override void Patch(PatcherArguments arguments) {
6464
}
6565

6666
public void ProcessMethod(PatcherArguments arguments, ContextBoundMethodMap mappedMethods, Dictionary<string, ClosureData> cachedClosureObjs, MethodDefinition method) {
67+
if (method.Name is ".ctor" && method.DeclaringType.Name is "FreeCakeDialogue") {
6768

69+
}
6870
Dictionary<Instruction, int> instructionIndexes = [];
6971
Instruction[] copiedInstructions = [.. method.Body.Instructions];
7072
for (var i = 0; i < method.Body.Instructions.Count; i++) {
@@ -222,10 +224,28 @@ public void ProcessMethod(PatcherArguments arguments, ContextBoundMethodMap mapp
222224
var oldClosureType = closureType;
223225
closureType = PatchingCommon.MemberClonedType(closureType, closureType.Name, typeMap);
224226

227+
static IEnumerable<(TypeDefinition otype, TypeDefinition ntype)> GetTypeReplacePairs(TypeDefinition oldTypeDef, TypeDefinition newTypeDef) {
228+
yield return (oldTypeDef, newTypeDef);
229+
foreach (var newNestedType in newTypeDef.NestedTypes) {
230+
var oldNestedType = oldTypeDef.NestedTypes.Single(ont => ont.Name == newNestedType.Name);
231+
foreach (var pair in GetTypeReplacePairs(oldNestedType, newNestedType)) {
232+
yield return pair;
233+
}
234+
}
235+
}
236+
foreach (var (otype, ntype) in GetTypeReplacePairs(oldClosureType, closureType)) {
237+
foreach (var method in ntype.Methods) {
238+
if (method.IsConstructor) {
239+
continue;
240+
}
241+
var omethod = otype.Methods.Single(m => m.GetIdentifier(withTypeName: false) == method.GetIdentifier(withTypeName: false));
242+
mappedMethods.contextBoundMethods.Add(method.GetIdentifier(), method);
243+
mappedMethods.originalToContextBound.Add(omethod.GetIdentifier(), method);
244+
}
245+
}
246+
225247
foreach (var oldMethod in oldClosureType.Methods) {
226248
var newMethod = closureType.Methods.Single(m => m.Name == oldMethod.Name);
227-
mappedMethods.originalToContextBound.Add(oldMethod.GetIdentifier(), newMethod);
228-
mappedMethods.contextBoundMethods.Add(newMethod.GetIdentifier(), newMethod);
229249
ProcessMethod(arguments, mappedMethods, cachedClosureObjs, newMethod);
230250
}
231251

@@ -775,12 +795,14 @@ public void ProcessMethod(PatcherArguments arguments, ContextBoundMethodMap mapp
775795
generatedBaseCall.Body.Instructions.Add(Instruction.Create(OpCodes.Call, generatedMethodImpl));
776796
generatedBaseCall.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
777797

778-
var compilerGeneratedAttribute = new TypeReference("System.Runtime.CompilerServices", "CompilerGeneratedAttribute", module, module.TypeSystem.CoreLibrary);
779-
generatedBaseCall.CustomAttributes.Add(new CustomAttribute(new MethodReference(".ctor", module.TypeSystem.Void, compilerGeneratedAttribute) { HasThis = true }));
780-
781-
var debuggerHiddenAttribute = new TypeReference("System.Diagnostics", "DebuggerHiddenAttribute", module, module.TypeSystem.CoreLibrary);
782-
generatedBaseCall.CustomAttributes.Add(new CustomAttribute(new MethodReference(".ctor", module.TypeSystem.Void, debuggerHiddenAttribute) { HasThis = true }));
783-
798+
if (!generatedBaseCall.CustomAttributes.Any(x => x.AttributeType.Name is "CompilerGeneratedAttribute")) {
799+
var compilerGeneratedAttribute = new TypeReference("System.Runtime.CompilerServices", "CompilerGeneratedAttribute", module, module.TypeSystem.CoreLibrary);
800+
generatedBaseCall.CustomAttributes.Add(new CustomAttribute(new MethodReference(".ctor", module.TypeSystem.Void, compilerGeneratedAttribute) { HasThis = true }));
801+
}
802+
if (!generatedBaseCall.CustomAttributes.Any(x => x.AttributeType.Name is "DebuggerHiddenAttribute")) {
803+
var debuggerHiddenAttribute = new TypeReference("System.Diagnostics", "DebuggerHiddenAttribute", module, module.TypeSystem.CoreLibrary);
804+
generatedBaseCall.CustomAttributes.Add(new CustomAttribute(new MethodReference(".ctor", module.TypeSystem.Void, debuggerHiddenAttribute) { HasThis = true }));
805+
}
784806

785807
generatedMethodImpl = PatchingCommon.CreateMethodReference(generatedMethodImpl, generatedBaseCall);
786808
}

src/OTAPI.UnifiedServerProcess/Core/Patching/PatchingCommon.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public static MethodDefinition CreateInstanceConvdMethod(MethodDefinition static
6464
InsertParamAt0AndRemapIndices(newMethod.Body, InsertParamMode.MakeInstance);
6565
}
6666
else {
67+
if (staticMethod.DeclaringType.Name is "WorldGenerationOptions") {
68+
69+
}
6770
newMethod = new MethodDefinition(staticMethod.Name, staticMethod.Attributes, staticMethod.ReturnType);
6871
newMethod.CustomAttributes.AddRange(staticMethod.CustomAttributes.Select(c => c.Clone()));
6972

@@ -107,9 +110,6 @@ public enum InsertParamMode
107110
Insert,
108111
}
109112
public static void InsertParamAt0AndRemapIndices(MethodBody body, InsertParamMode mode, ParameterDefinition? definition = null) {
110-
if (body.Method.Name is "DyeInitializer_LoadLegacyHairdyes") {
111-
112-
}
113113
if (mode is InsertParamMode.Insert) {
114114
if (definition is null) {
115115
throw new ArgumentNullException($"The {nameof(definition)} is required when {nameof(mode)} is {InsertParamMode.Insert}");

0 commit comments

Comments
 (0)