From ea9480736327299abcfdd6356131c9b08920b3b1 Mon Sep 17 00:00:00 2001 From: Jacky720 <32578221+Jacky720@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:37:38 -0500 Subject: [PATCH] Misc. fixes for functions and compiling them --- UndertaleModLib/Compiler/AssemblyWriter.cs | 43 ++++++++++++------- .../Decompiler.AssignmentStatement.cs | 5 ++- .../Decompiler.FunctionDefinition.cs | 7 ++- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/UndertaleModLib/Compiler/AssemblyWriter.cs b/UndertaleModLib/Compiler/AssemblyWriter.cs index e1e0f9c65..94ad6e232 100644 --- a/UndertaleModLib/Compiler/AssemblyWriter.cs +++ b/UndertaleModLib/Compiler/AssemblyWriter.cs @@ -249,9 +249,12 @@ bool hasLocal(string name) { if (patch.isNewFunc) { - UndertaleString childName = new("gml_Script_" + patch.Name); - int childNameIndex = compileContext.Data.Strings.Count; - compileContext.Data.Strings.Add(childName); + // If there's already a function at this position, it's almost certainly the same one renamed. + // Disconnect it anyway. + if (compileContext.OriginalCode.ChildEntries.FirstOrDefault(e => e.Offset == patch.Offset) is UndertaleCode oldChild) + compileContext.OriginalCode.ChildEntries.Remove(oldChild); + + UndertaleString childName = compileContext.Data.Strings.MakeString("gml_Script_" + patch.Name, out int childNameIndex); UndertaleCode childEntry = new() { @@ -1282,27 +1285,32 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser Patch startPatch = Patch.StartHere(cw); Patch endPatch = Patch.Start(); endPatch.Add(cw.Emit(Opcode.B)); - // we're accessing a subfunction here, so build the cache if needed + // We're accessing a subfunction here, so build the cache if needed Decompiler.Decompiler.BuildSubFunctionCache(cw.compileContext.Data); - //Attempt to find the function before rushing to create a new one - var func = cw.compileContext.Data.Functions.FirstOrDefault(f => f.Name.Content == "gml_Script_" + funcDefName.Text); + // Objects get a suffix for clarification. + string suffixedFuncName = funcDefName.Text; + if (!cw.compileContext.OriginalCode.Name.Content.StartsWith("gml_GlobalScript_")) + suffixedFuncName += $"@{cw.compileContext.OriginalCode.Name.Content}"; + + // Attempt to find the function before rushing to create a new one + var func = cw.compileContext.Data.Functions.FirstOrDefault(f => f.Name.Content == "gml_Script_" + suffixedFuncName); if (func != null) - cw.compileContext.Data.KnownSubFunctions.TryAdd(funcDefName.Text, func); + cw.compileContext.Data.KnownSubFunctions.TryAdd(suffixedFuncName, func); - if (cw.compileContext.Data.KnownSubFunctions.ContainsKey(funcDefName.Text)) + if (cw.compileContext.Data.KnownSubFunctions.ContainsKey(suffixedFuncName)) { - string subFunctionName = cw.compileContext.Data.KnownSubFunctions[funcDefName.Text].Name.Content; + string subFunctionName = cw.compileContext.Data.KnownSubFunctions[suffixedFuncName].Name.Content; UndertaleCode childEntry = cw.compileContext.OriginalCode.ChildEntries.ByName(subFunctionName); childEntry.Offset = cw.offset * 4; childEntry.ArgumentsCount = (ushort)e.Children[0].Children.Count; - childEntry.LocalsCount = cw.compileContext.OriginalCode.LocalsCount; // todo: use just the locals for the individual script + childEntry.LocalsCount = cw.compileContext.OriginalCode.LocalsCount; // Todo: use just the locals for the individual script } - else // we're making a new function baby + else // We're making a new function baby { cw.funcPatches.Add(new FunctionPatch() { - Name = funcDefName.Text, + Name = suffixedFuncName, Offset = cw.offset * 4, ArgCount = (ushort)e.Children[0].Children.Count, isNewFunc = true @@ -1310,7 +1318,7 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser } cw.loopContexts.Push(new LoopContext(endPatch, startPatch)); - AssembleStatement(cw, e.Children[1]); // body + AssembleStatement(cw, e.Children[1]); // Body AssembleExit(cw); cw.loopContexts.Pop(); endPatch.Finish(cw); @@ -1318,7 +1326,7 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser cw.funcPatches.Add(new FunctionPatch() { Target = cw.EmitRef(Opcode.Push, DataType.Int32), - Name = funcDefName.Text, + Name = suffixedFuncName, ArgCount = -1 }); cw.Emit(Opcode.Conv, DataType.Int32, DataType.Variable); @@ -1331,8 +1339,11 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser ArgCount = 2 }); cw.typeStack.Push(DataType.Variable); - cw.Emit(Opcode.Dup, DataType.Variable).Extra = 0; - cw.Emit(Opcode.PushI, DataType.Int16).Value = (short)-1; // todo: -6 sometimes? + if (funcDefName.ID == 0) // Anonymous functions being assigned to a value get a variable ID, normal functions don't. + { + cw.Emit(Opcode.Dup, DataType.Variable).Extra = 0; + cw.Emit(Opcode.PushI, DataType.Int16).Value = (short)-1; // Todo: -6 sometimes? + } } break; case Parser.Statement.StatementKind.ExprBinaryOp: diff --git a/UndertaleModLib/Decompiler/Instructions/Decompiler.AssignmentStatement.cs b/UndertaleModLib/Decompiler/Instructions/Decompiler.AssignmentStatement.cs index d16995bb4..b9b9e5f3e 100644 --- a/UndertaleModLib/Decompiler/Instructions/Decompiler.AssignmentStatement.cs +++ b/UndertaleModLib/Decompiler/Instructions/Decompiler.AssignmentStatement.cs @@ -72,9 +72,12 @@ public override string ToString(DecompileContext context) // Someone enlighten me on structs, I'm steering clear for now. // And find the "right" way to do this. - if (Value is FunctionDefinition functionVal && functionVal.Subtype != FunctionDefinition.FunctionType.Struct) + if (Value is FunctionDefinition functionVal && + functionVal.Subtype != FunctionDefinition.FunctionType.Struct && + Destination.VarType == UndertaleInstruction.VariableType.StackTop) { functionVal.IsStatement = true; + functionVal.StatementName = Destination.Var.Name.Content; return functionVal.ToString(context); } diff --git a/UndertaleModLib/Decompiler/Instructions/Decompiler.FunctionDefinition.cs b/UndertaleModLib/Decompiler/Instructions/Decompiler.FunctionDefinition.cs index 6fdc1d768..8d8596d28 100644 --- a/UndertaleModLib/Decompiler/Instructions/Decompiler.FunctionDefinition.cs +++ b/UndertaleModLib/Decompiler/Instructions/Decompiler.FunctionDefinition.cs @@ -23,6 +23,7 @@ public enum FunctionType public Block FunctionBodyEntryBlock { get; private set; } public FunctionType Subtype { get; private set; } public bool IsStatement = false; // I know it's an expression, yes. But I'm not duplicating the rest. + public string StatementName = null; internal List Arguments; @@ -81,7 +82,7 @@ public override string ToString(DecompileContext context) if (IsStatement) { sb.Append(" "); - + /* // For further optimization, we could *probably* create a dictionary that's just flipped KVPs (assuming there are no dup. values). // Doing so would save the need for LINQ and what-not. Not that big of an issue, but still an option. Dictionary subFuncs = context.GlobalContext.Data.KnownSubFunctions; @@ -106,6 +107,8 @@ public override string ToString(DecompileContext context) if(!gotFuncName) sb.Append((context.Statements[0].Last() as AssignmentStatement).Destination.Var.Name.Content); } + */ + sb.Append(StatementName); } sb.Append("("); for (int i = 0; i < FunctionBodyCodeEntry.ArgumentsCount; ++i) @@ -171,7 +174,7 @@ public override string ToString(DecompileContext context) context.IndentationLevel--; sb.Append(context.Indentation); sb.Append("}"); - if(!oldDecompilingStruct) + if (!oldDecompilingStruct) sb.Append("\n"); } else