From 4826002fc2c915b5e38621f1d895da304a065914 Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Sat, 26 Mar 2022 03:51:11 -0400 Subject: [PATCH 1/8] Add basic struct decompilation --- UndertaleModLib/Decompiler/Decompiler.cs | 140 ++++++++++++++++++----- 1 file changed, 114 insertions(+), 26 deletions(-) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index ffb374f8c..4de0f81ba 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -89,6 +89,11 @@ public DecompileContext(GlobalDecompileContext globalContext, UndertaleCode code LoopEnd: return; } + #region Struct management + public List ArgumentReplacements; + public bool DecompilingStruct; + #endregion + #region Indentation management public const string Indent = " "; private int _indentationLevel = 0; @@ -1159,7 +1164,17 @@ bool checkEqual(ExpressionVar a, ExpressionVar b) } } } - return String.Format("{0}{1} = {2}", varPrefix, varName, Value.ToString(context)); + // Quick hack + if (varName.StartsWith("___struct___")) + { + Expression val = Value; + while (val is ExpressionCast cast) + val = cast; + + if (val is FunctionDefinition def) + return "// Hiding initial definition of " + def.FunctionBodyCodeEntry.Name.Content + "\n"; + } + return String.Format("{0}{1}{2} {3}", varPrefix, varName, context.DecompilingStruct ? ":" : " =", Value.ToString(context)); } public override Statement CleanStatement(DecompileContext context, BlockHLStatement block) @@ -1242,19 +1257,44 @@ internal override AssetIDType DoTypePropagation(DecompileContext context, AssetI // Represents an inline function definition public class FunctionDefinition : Expression { + public enum FunctionType + { + Function, + Constructor, + Struct + } + public UndertaleFunction Function { get; private set; } public UndertaleCode FunctionBodyCodeEntry { get; private set; } public Block FunctionBodyEntryBlock { get; private set; } - public bool Constructor { get; private set; } + public FunctionType Type { get; private set; } + + internal List Arguments; - public FunctionDefinition(UndertaleFunction target, UndertaleCode functionBodyCodeEntry, Block functionBodyEntryBlock, bool constructor) + public FunctionDefinition(UndertaleFunction target, UndertaleCode functionBodyCodeEntry, Block functionBodyEntryBlock, FunctionType type) { + Type = type; Function = target; - Constructor = constructor; FunctionBodyCodeEntry = functionBodyCodeEntry; FunctionBodyEntryBlock = functionBodyEntryBlock; } + public void PopulateArguments(params Expression[] arguments) + { + PopulateArguments(arguments.ToList()); + } + + public void PopulateArguments(List arguments) + { + if (Type != FunctionType.Struct) + throw new InvalidOperationException("Cannot populate arguments of non-struct"); + + if (Arguments == null) + Arguments = new List(); + + Arguments.AddRange(arguments); + } + public override Statement CleanStatement(DecompileContext context, BlockHLStatement block) { return this; @@ -1265,39 +1305,53 @@ public override string ToString(DecompileContext context) StringBuilder sb = new StringBuilder(); if (context.Statements.ContainsKey(FunctionBodyEntryBlock.Address.Value)) { - sb.Append("function("); - for (int i = 0; i < FunctionBodyCodeEntry.ArgumentsCount; ++i) + var oldDecompilingStruct = context.DecompilingStruct; + var oldReplacements = context.ArgumentReplacements; + if (Type == FunctionType.Struct) + context.DecompilingStruct = true; + else { - if (i != 0) - sb.Append(", "); - sb.Append("argument"); - sb.Append(i); + sb.Append("function("); + for (int i = 0; i < FunctionBodyCodeEntry.ArgumentsCount; ++i) + { + if (i != 0) + sb.Append(", "); + sb.Append("argument"); + sb.Append(i); + } + sb.Append(") "); + if (Type == FunctionType.Constructor) + sb.Append("constructor "); + sb.Append("//"); + sb.Append(Function.Name.Content); } - sb.Append(") "); - if (Constructor) - sb.Append("constructor "); - sb.Append("//"); - sb.Append(Function.Name.Content); + sb.Append("\n"); sb.Append(context.Indentation); - if (context.IndentationLevel == 0) // See #614 + if (true)//if (context.IndentationLevel == 0) // See #614 { sb.Append("{\n"); context.IndentationLevel++; + context.ArgumentReplacements = Arguments; foreach (Statement stmt in context.Statements[FunctionBodyEntryBlock.Address.Value]) { + if (Type == FunctionType.Struct && stmt is ReturnStatement) + continue; + sb.Append(context.Indentation); sb.Append(stmt.ToString(context)); sb.Append("\n"); } + context.ArgumentReplacements = oldReplacements; context.IndentationLevel--; sb.Append(context.Indentation); sb.Append("}\n"); } else { - sb.Append("{} // Nested function decompilation is not currently supported.\n"); + //sb.Append("{} // Nested function decompilation is not currently supported.\n"); } + context.DecompilingStruct = oldDecompilingStruct; } else { @@ -1351,14 +1405,27 @@ public override string ToString(DecompileContext context) { StringBuilder argumentString = new StringBuilder(); - if (Function.Name.Content == "@@NewGMLObject@@") // Instantiating a "script" with the "new" keyword (a constructor) + if (Function.Name.Content == "@@NewGMLObject@@") // Creating a new "object" via a constructor OR this is a struct definition { context.currentFunction = this; - string constructor = Arguments[0].ToString(context); - if (constructor.StartsWith("gml_Script_")) - constructor = constructor.Substring(11); + string constructor; var actualArgs = Arguments.Skip(1).ToList(); + if (Arguments[0] is FunctionDefinition def) + { + if (def.Type == FunctionDefinition.FunctionType.Struct) // Struct moment + { + def.PopulateArguments(actualArgs); + return def.ToString(context); + } + else + constructor = def.FunctionBodyCodeEntry.Name.Content; + } + else + constructor = Arguments[0].ToString(context); + + if (constructor.StartsWith("gml_Script_")) + constructor = constructor.Substring(11); if (AssetTypeResolver.builtin_funcs.TryGetValue(constructor, out AssetIDType[] types)) { @@ -1600,8 +1667,24 @@ public override string ToString(DecompileContext context) { if (context.GlobalContext.Data?.GMS2_3 == true) { - foreach (Expression e in ArrayIndices) - name += "[" + e.ToString(context) + "]"; + if (name == "argument" && context.DecompilingStruct && context.ArgumentReplacements != null && ArrayIndices.Count == 1) + { + var replacements = context.ArgumentReplacements; + int index = -1; + if (int.TryParse(ArrayIndices[0].ToString(context), out index) && index >= 0 && index < replacements.Count) + return replacements[index].ToString(context); + else + { + foreach (Expression e in ArrayIndices) + name += "[" + e.ToString(context) + "]"; + } + } + else + { + foreach (Expression e in ArrayIndices) + name += "[" + e.ToString(context) + "]"; + } + } else { @@ -2173,8 +2256,13 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary argCodeFunc) { - bool constructor = !(arg1 is ExpressionConstant argThis && argThis.Type == UndertaleInstruction.DataType.Int16 && - (short)argThis.Value == (short)UndertaleInstruction.InstanceType.Self); + FunctionDefinition.FunctionType type = FunctionDefinition.FunctionType.Function; + + if (arg1 is DirectFunctionCall call && call.Function.Name.Content == "@@NullObject@@") + type = FunctionDefinition.FunctionType.Struct; + else if (!(arg1 is ExpressionConstant argThis && argThis.Type == UndertaleInstruction.DataType.Int16 && + (short)argThis.Value == (short)UndertaleInstruction.InstanceType.Self)) + type = FunctionDefinition.FunctionType.Constructor; UndertaleCode functionBody = context.GlobalContext.Data.Code.First(x => x.Name.Content == argCodeFunc.Target.Name.Content); if (context.TargetCode.ChildEntries.Contains(functionBody)) @@ -2182,7 +2270,7 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary>(functionBodyEntryBlock, new List())); break; } From 97bbbb1d95bcbeef1bb9330e9dca6eb75f3df11c Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Sat, 26 Mar 2022 04:12:59 -0400 Subject: [PATCH 2/8] Avoid #614 --- UndertaleModLib/Decompiler/Decompiler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index 4de0f81ba..22597322a 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -1328,7 +1328,7 @@ public override string ToString(DecompileContext context) sb.Append("\n"); sb.Append(context.Indentation); - if (true)//if (context.IndentationLevel == 0) // See #614 + if (context.IndentationLevel == 0 || Type == FunctionType.Struct) // See #614 { sb.Append("{\n"); context.IndentationLevel++; @@ -1349,7 +1349,7 @@ public override string ToString(DecompileContext context) } else { - //sb.Append("{} // Nested function decompilation is not currently supported.\n"); + sb.Append("{} // Nested function decompilation is not currently supported.\n"); } context.DecompilingStruct = oldDecompilingStruct; } From 624b8ec384e9303ec0dfe6ba0d2af64d199b2638 Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Sat, 26 Mar 2022 04:42:58 -0400 Subject: [PATCH 3/8] Fix constructor detection --- UndertaleModLib/Decompiler/Decompiler.cs | 32 ++++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index 22597322a..190d2870d 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -1172,7 +1172,10 @@ bool checkEqual(ExpressionVar a, ExpressionVar b) val = cast; if (val is FunctionDefinition def) + { + def.PromoteToStruct(); return "// Hiding initial definition of " + def.FunctionBodyCodeEntry.Name.Content + "\n"; + } } return String.Format("{0}{1}{2} {3}", varPrefix, varName, context.DecompilingStruct ? ":" : " =", Value.ToString(context)); } @@ -1267,18 +1270,26 @@ public enum FunctionType public UndertaleFunction Function { get; private set; } public UndertaleCode FunctionBodyCodeEntry { get; private set; } public Block FunctionBodyEntryBlock { get; private set; } - public FunctionType Type { get; private set; } + public FunctionType RealType { get; private set; } internal List Arguments; public FunctionDefinition(UndertaleFunction target, UndertaleCode functionBodyCodeEntry, Block functionBodyEntryBlock, FunctionType type) { - Type = type; + RealType = type; Function = target; FunctionBodyCodeEntry = functionBodyCodeEntry; FunctionBodyEntryBlock = functionBodyEntryBlock; } + public void PromoteToStruct() + { + if (RealType == FunctionType.Function) + throw new InvalidOperationException("Cannot promote function to struct"); + + RealType = FunctionType.Struct; + } + public void PopulateArguments(params Expression[] arguments) { PopulateArguments(arguments.ToList()); @@ -1286,7 +1297,7 @@ public void PopulateArguments(params Expression[] arguments) public void PopulateArguments(List arguments) { - if (Type != FunctionType.Struct) + if (RealType != FunctionType.Struct) throw new InvalidOperationException("Cannot populate arguments of non-struct"); if (Arguments == null) @@ -1307,7 +1318,7 @@ public override string ToString(DecompileContext context) { var oldDecompilingStruct = context.DecompilingStruct; var oldReplacements = context.ArgumentReplacements; - if (Type == FunctionType.Struct) + if (RealType == FunctionType.Struct) context.DecompilingStruct = true; else { @@ -1320,7 +1331,7 @@ public override string ToString(DecompileContext context) sb.Append(i); } sb.Append(") "); - if (Type == FunctionType.Constructor) + if (RealType == FunctionType.Constructor) sb.Append("constructor "); sb.Append("//"); sb.Append(Function.Name.Content); @@ -1328,14 +1339,14 @@ public override string ToString(DecompileContext context) sb.Append("\n"); sb.Append(context.Indentation); - if (context.IndentationLevel == 0 || Type == FunctionType.Struct) // See #614 + if (context.IndentationLevel == 0 || RealType == FunctionType.Struct) // See #614 { sb.Append("{\n"); context.IndentationLevel++; context.ArgumentReplacements = Arguments; foreach (Statement stmt in context.Statements[FunctionBodyEntryBlock.Address.Value]) { - if (Type == FunctionType.Struct && stmt is ReturnStatement) + if (RealType == FunctionType.Struct && stmt is ReturnStatement) continue; sb.Append(context.Indentation); @@ -1413,7 +1424,7 @@ public override string ToString(DecompileContext context) var actualArgs = Arguments.Skip(1).ToList(); if (Arguments[0] is FunctionDefinition def) { - if (def.Type == FunctionDefinition.FunctionType.Struct) // Struct moment + if (def.RealType == FunctionDefinition.FunctionType.Struct) // Struct moment { def.PopulateArguments(actualArgs); return def.ToString(context); @@ -1426,6 +1437,8 @@ public override string ToString(DecompileContext context) if (constructor.StartsWith("gml_Script_")) constructor = constructor.Substring(11); + if (constructor.EndsWith(context.TargetCode.Name.Content)) + constructor = constructor.Substring(0, constructor.Length - context.TargetCode.Name.Content.Length - 1); if (AssetTypeResolver.builtin_funcs.TryGetValue(constructor, out AssetIDType[] types)) { @@ -2259,9 +2272,6 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary x.Name.Content == argCodeFunc.Target.Name.Content); From 551471e5d95f8d602405aac7f62f63e18a0ed90d Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Sat, 26 Mar 2022 04:53:10 -0400 Subject: [PATCH 4/8] Also hide implicit returns in constructors --- UndertaleModLib/Decompiler/Decompiler.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index 190d2870d..48aa1c6b3 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -1270,13 +1270,13 @@ public enum FunctionType public UndertaleFunction Function { get; private set; } public UndertaleCode FunctionBodyCodeEntry { get; private set; } public Block FunctionBodyEntryBlock { get; private set; } - public FunctionType RealType { get; private set; } + public FunctionType Subtype { get; private set; } internal List Arguments; public FunctionDefinition(UndertaleFunction target, UndertaleCode functionBodyCodeEntry, Block functionBodyEntryBlock, FunctionType type) { - RealType = type; + Subtype = type; Function = target; FunctionBodyCodeEntry = functionBodyCodeEntry; FunctionBodyEntryBlock = functionBodyEntryBlock; @@ -1284,10 +1284,10 @@ public FunctionDefinition(UndertaleFunction target, UndertaleCode functionBodyCo public void PromoteToStruct() { - if (RealType == FunctionType.Function) + if (Subtype == FunctionType.Function) throw new InvalidOperationException("Cannot promote function to struct"); - RealType = FunctionType.Struct; + Subtype = FunctionType.Struct; } public void PopulateArguments(params Expression[] arguments) @@ -1297,7 +1297,7 @@ public void PopulateArguments(params Expression[] arguments) public void PopulateArguments(List arguments) { - if (RealType != FunctionType.Struct) + if (Subtype != FunctionType.Struct) throw new InvalidOperationException("Cannot populate arguments of non-struct"); if (Arguments == null) @@ -1318,7 +1318,7 @@ public override string ToString(DecompileContext context) { var oldDecompilingStruct = context.DecompilingStruct; var oldReplacements = context.ArgumentReplacements; - if (RealType == FunctionType.Struct) + if (Subtype == FunctionType.Struct) context.DecompilingStruct = true; else { @@ -1331,7 +1331,7 @@ public override string ToString(DecompileContext context) sb.Append(i); } sb.Append(") "); - if (RealType == FunctionType.Constructor) + if (Subtype == FunctionType.Constructor) sb.Append("constructor "); sb.Append("//"); sb.Append(Function.Name.Content); @@ -1339,14 +1339,14 @@ public override string ToString(DecompileContext context) sb.Append("\n"); sb.Append(context.Indentation); - if (context.IndentationLevel == 0 || RealType == FunctionType.Struct) // See #614 + if (context.IndentationLevel == 0 || Subtype == FunctionType.Struct) // See #614 { sb.Append("{\n"); context.IndentationLevel++; context.ArgumentReplacements = Arguments; foreach (Statement stmt in context.Statements[FunctionBodyEntryBlock.Address.Value]) { - if (RealType == FunctionType.Struct && stmt is ReturnStatement) + if (Subtype != FunctionType.Function && stmt is ReturnStatement) continue; sb.Append(context.Indentation); @@ -1424,7 +1424,7 @@ public override string ToString(DecompileContext context) var actualArgs = Arguments.Skip(1).ToList(); if (Arguments[0] is FunctionDefinition def) { - if (def.RealType == FunctionDefinition.FunctionType.Struct) // Struct moment + if (def.Subtype == FunctionDefinition.FunctionType.Struct) // Struct moment { def.PopulateArguments(actualArgs); return def.ToString(context); From 1c30a49c6bad43a410088ddf0a99dc9e842079d4 Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Mon, 28 Mar 2022 02:16:05 -0400 Subject: [PATCH 5/8] Better mitigation for #614 --- UndertaleModLib/Decompiler/Decompiler.cs | 90 +++++++++++++++--------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index 48aa1c6b3..240249ea4 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -141,6 +141,14 @@ public TempVar NewTempVar() public HashSet LocalVarDefines = new HashSet(); #endregion + #region GMS 2.3+ Function management + /// + /// Set containing already-decompiled child code entries. + /// Used to prevent decompiling the same child entry multiple times. + /// Only applies to function entries, struct and constructors are unaffected. + /// + public ISet AlreadyProcessed = new HashSet(); + #endregion #region Asset type resolution /// @@ -1316,12 +1324,14 @@ public override string ToString(DecompileContext context) StringBuilder sb = new StringBuilder(); if (context.Statements.ContainsKey(FunctionBodyEntryBlock.Address.Value)) { + FunctionDefinition def; var oldDecompilingStruct = context.DecompilingStruct; var oldReplacements = context.ArgumentReplacements; if (Subtype == FunctionType.Struct) context.DecompilingStruct = true; else { + context.DecompilingStruct = false; sb.Append("function("); for (int i = 0; i < FunctionBodyCodeEntry.ArgumentsCount; ++i) { @@ -1339,30 +1349,41 @@ public override string ToString(DecompileContext context) sb.Append("\n"); sb.Append(context.Indentation); - if (context.IndentationLevel == 0 || Subtype == FunctionType.Struct) // See #614 + sb.Append("{\n"); + context.IndentationLevel++; + context.ArgumentReplacements = Arguments; + foreach (Statement stmt in context.Statements[FunctionBodyEntryBlock.Address.Value]) { - sb.Append("{\n"); - context.IndentationLevel++; - context.ArgumentReplacements = Arguments; - foreach (Statement stmt in context.Statements[FunctionBodyEntryBlock.Address.Value]) - { - if (Subtype != FunctionType.Function && stmt is ReturnStatement) - continue; + if (Subtype != FunctionType.Function && stmt is ReturnStatement) + continue; - sb.Append(context.Indentation); - sb.Append(stmt.ToString(context)); + sb.Append(context.Indentation); + + // See #614 + // This is not the place to monkey patch this + // issue, but it's like 2am and quite frankly + // I don't care anymore. + def = null; + if (stmt is FunctionDefinition) + def = stmt as FunctionDefinition; + else if (stmt is TempVarAssignmentStatement reference && reference.Value is FunctionDefinition) + def = reference.Value as FunctionDefinition; + + if (def?.Function == Function) + { + //sb.Append("// Error decompiling function: function contains its own declaration???\n"); sb.Append("\n"); + break; } - context.ArgumentReplacements = oldReplacements; - context.IndentationLevel--; - sb.Append(context.Indentation); - sb.Append("}\n"); - } - else - { - sb.Append("{} // Nested function decompilation is not currently supported.\n"); + else + sb.Append(stmt.ToString(context)); + sb.Append("\n"); } context.DecompilingStruct = oldDecompilingStruct; + context.ArgumentReplacements = oldReplacements; + context.IndentationLevel--; + sb.Append(context.Indentation); + sb.Append("}\n"); } else { @@ -1583,6 +1604,9 @@ public override string ToString(DecompileContext context) argumentString.Append(exp.ToString(context)); } + if (Function is FunctionDefinition) + return String.Format("{0}({1})", Function.ToString(context), argumentString.ToString()); + return String.Format("{0}.{1}({2})", FunctionThis.ToString(context), Function.ToString(context), argumentString.ToString()); } @@ -1683,20 +1707,11 @@ public override string ToString(DecompileContext context) if (name == "argument" && context.DecompilingStruct && context.ArgumentReplacements != null && ArrayIndices.Count == 1) { var replacements = context.ArgumentReplacements; - int index = -1; - if (int.TryParse(ArrayIndices[0].ToString(context), out index) && index >= 0 && index < replacements.Count) + if (int.TryParse(ArrayIndices[0].ToString(context), out int index) && index >= 0 && index < replacements.Count) return replacements[index].ToString(context); - else - { - foreach (Expression e in ArrayIndices) - name += "[" + e.ToString(context) + "]"; - } - } - else - { - foreach (Expression e in ArrayIndices) - name += "[" + e.ToString(context) + "]"; } + foreach (Expression e in ArrayIndices) + name += "[" + e.ToString(context) + "]"; } else @@ -2269,13 +2284,20 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary argCodeFunc) { + UndertaleCode functionBody = context.GlobalContext.Data.Code.First(x => x.Name.Content == argCodeFunc.Target.Name.Content); + FunctionDefinition.FunctionType type = FunctionDefinition.FunctionType.Function; + bool processChildEntry; if (arg1 is DirectFunctionCall call && call.Function.Name.Content == "@@NullObject@@") + { type = FunctionDefinition.FunctionType.Constructor; - - UndertaleCode functionBody = context.GlobalContext.Data.Code.First(x => x.Name.Content == argCodeFunc.Target.Name.Content); - if (context.TargetCode.ChildEntries.Contains(functionBody)) + processChildEntry = true; + } + else + processChildEntry = context.AlreadyProcessed.Add(functionBody); + + if (context.TargetCode.ChildEntries.Contains(functionBody) && processChildEntry) { // This function is somewhere inside this UndertaleCode block // inline the definition @@ -2315,7 +2337,7 @@ static string FindActualNameForAnonymousCodeObject(DecompileContext context, Und if (funcDef.FunctionBodyEntryBlock.Address == anonymousCodeObject.Offset / 4) return assign.Destination.Var.Name.Content; else - throw new Exception("Non-matching offset: " + funcDef.FunctionBodyEntryBlock.Address.ToString() + " versus " + (anonymousCodeObject.Offset / 4).ToString()); + return string.Empty; //throw new Exception("Non-matching offset: " + funcDef.FunctionBodyEntryBlock.Address.ToString() + " versus " + (anonymousCodeObject.Offset / 4).ToString() + " (got name " + assign.Destination.Var.Name.Content + ")"); } } throw new Exception("Unable to find the var name for anonymous code object " + anonymousCodeObject.Name.Content); From ada361578ef723840ed839ec0e09802e16ac46bd Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Mon, 28 Mar 2022 13:58:24 -0400 Subject: [PATCH 6/8] Collapse empty structs --- UndertaleModLib/Decompiler/Decompiler.cs | 70 +++++++++++++----------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index 240249ea4..10796d108 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -1347,43 +1347,51 @@ public override string ToString(DecompileContext context) sb.Append(Function.Name.Content); } - sb.Append("\n"); - sb.Append(context.Indentation); - sb.Append("{\n"); - context.IndentationLevel++; - context.ArgumentReplacements = Arguments; - foreach (Statement stmt in context.Statements[FunctionBodyEntryBlock.Address.Value]) - { - if (Subtype != FunctionType.Function && stmt is ReturnStatement) - continue; + var statements = context.Statements[FunctionBodyEntryBlock.Address.Value]; + int numNotReturn = statements.FindAll(stmt => !(stmt is ReturnStatement)).Count; + if (numNotReturn > 0 || Subtype != FunctionType.Struct) + { + sb.Append("\n"); sb.Append(context.Indentation); - - // See #614 - // This is not the place to monkey patch this - // issue, but it's like 2am and quite frankly - // I don't care anymore. - def = null; - if (stmt is FunctionDefinition) - def = stmt as FunctionDefinition; - else if (stmt is TempVarAssignmentStatement reference && reference.Value is FunctionDefinition) - def = reference.Value as FunctionDefinition; - - if (def?.Function == Function) + sb.Append("{\n"); + context.IndentationLevel++; + context.ArgumentReplacements = Arguments; + foreach (Statement stmt in statements) { - //sb.Append("// Error decompiling function: function contains its own declaration???\n"); + if (Subtype != FunctionType.Function && stmt is ReturnStatement) + continue; + + sb.Append(context.Indentation); + + // See #614 + // This is not the place to monkey patch this + // issue, but it's like 2am and quite frankly + // I don't care anymore. + def = null; + if (stmt is FunctionDefinition) + def = stmt as FunctionDefinition; + else if (stmt is TempVarAssignmentStatement reference && reference.Value is FunctionDefinition) + def = reference.Value as FunctionDefinition; + + if (def?.Function == Function) + { + //sb.Append("// Error decompiling function: function contains its own declaration???\n"); + sb.Append("\n"); + break; + } + else + sb.Append(stmt.ToString(context)); sb.Append("\n"); - break; } - else - sb.Append(stmt.ToString(context)); - sb.Append("\n"); + context.DecompilingStruct = oldDecompilingStruct; + context.ArgumentReplacements = oldReplacements; + context.IndentationLevel--; + sb.Append(context.Indentation); + sb.Append("}\n"); } - context.DecompilingStruct = oldDecompilingStruct; - context.ArgumentReplacements = oldReplacements; - context.IndentationLevel--; - sb.Append(context.Indentation); - sb.Append("}\n"); + else + sb.Append("{}"); } else { From d1de9984c0e83239f6cea5d35626a3a5e674ef34 Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Mon, 28 Mar 2022 14:04:59 -0400 Subject: [PATCH 7/8] Add commas to struct variable declarations --- UndertaleModLib/Decompiler/Decompiler.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index 10796d108..dd531dad9 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -1357,6 +1357,8 @@ public override string ToString(DecompileContext context) sb.Append("{\n"); context.IndentationLevel++; context.ArgumentReplacements = Arguments; + + int count = 0; foreach (Statement stmt in statements) { if (Subtype != FunctionType.Function && stmt is ReturnStatement) @@ -1381,7 +1383,11 @@ public override string ToString(DecompileContext context) break; } else + { sb.Append(stmt.ToString(context)); + if (Subtype == FunctionType.Struct && ++count < numNotReturn) + sb.Append(","); + } sb.Append("\n"); } context.DecompilingStruct = oldDecompilingStruct; From 22abd14afa2afe669e2fcd8525a55d519f97096d Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Thu, 31 Mar 2022 00:33:26 -0400 Subject: [PATCH 8/8] Cleanup struct decompilation output --- UndertaleModLib/Decompiler/Decompiler.cs | 76 ++++++++++++++++++------ 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index dd531dad9..cde753535 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -1085,7 +1085,11 @@ public override string ToString(DecompileContext context) Value.DoTypePropagation(context, AssetTypeResolver.return_types[script.Name.Content]); } - return "return " + Value.ToString(context) + ";"; + string cleanVal = Value.ToString(context); + if (cleanVal.EndsWith("\n")) + cleanVal = cleanVal.Substring(0, cleanVal.Length - 1); + + return "return " + cleanVal + ";"; } else return (context.GlobalContext.Data?.IsGameMaker2() ?? false ? "return;" : "exit"); @@ -1111,6 +1115,34 @@ public class AssignmentStatement : Statement public bool HasVarKeyword; + private bool _isStructDefinition, _checkedForDefinition; + public bool IsStructDefinition + { get { + // Quick hack + if (!_checkedForDefinition) + { + try + { + if (Destination.Var.Name.Content.StartsWith("___struct___")) + { + Expression val = Value; + while (val is ExpressionCast cast) + val = cast; + + if (val is FunctionDefinition def) + { + def.PromoteToStruct(); + _isStructDefinition = true; + } + } + } + catch (Exception) { } + _checkedForDefinition = true; + } + return _isStructDefinition; + } + } + public AssignmentStatement(ExpressionVar destination, Expression value) { Destination = destination; @@ -1119,9 +1151,14 @@ public AssignmentStatement(ExpressionVar destination, Expression value) public override string ToString(DecompileContext context) { + bool gms2 = context.GlobalContext.Data?.IsGameMaker2() ?? false; + + if (gms2 && IsStructDefinition) + return ""; + string varName = Destination.ToString(context); - if (context.GlobalContext.Data?.IsGameMaker2() ?? false && !HasVarKeyword) + if (gms2 && !HasVarKeyword) { var data = context.GlobalContext.Data; if (data != null) @@ -1172,19 +1209,6 @@ bool checkEqual(ExpressionVar a, ExpressionVar b) } } } - // Quick hack - if (varName.StartsWith("___struct___")) - { - Expression val = Value; - while (val is ExpressionCast cast) - val = cast; - - if (val is FunctionDefinition def) - { - def.PromoteToStruct(); - return "// Hiding initial definition of " + def.FunctionBodyCodeEntry.Name.Content + "\n"; - } - } return String.Format("{0}{1}{2} {3}", varPrefix, varName, context.DecompilingStruct ? ":" : " =", Value.ToString(context)); } @@ -1361,7 +1385,8 @@ public override string ToString(DecompileContext context) int count = 0; foreach (Statement stmt in statements) { - if (Subtype != FunctionType.Function && stmt is ReturnStatement) + count++; + if ((Subtype != FunctionType.Function && stmt is ReturnStatement) || (stmt is AssignmentStatement assign && assign.IsStructDefinition)) continue; sb.Append(context.Indentation); @@ -1385,7 +1410,7 @@ public override string ToString(DecompileContext context) else { sb.Append(stmt.ToString(context)); - if (Subtype == FunctionType.Struct && ++count < numNotReturn) + if (Subtype == FunctionType.Struct && count < numNotReturn) sb.Append(","); } sb.Append("\n"); @@ -1394,7 +1419,9 @@ public override string ToString(DecompileContext context) context.ArgumentReplacements = oldReplacements; context.IndentationLevel--; sb.Append(context.Indentation); - sb.Append("}\n"); + sb.Append("}"); + if(!oldDecompilingStruct) + sb.Append("\n"); } else sb.Append("{}"); @@ -1716,7 +1743,7 @@ public override string ToString(DecompileContext context) string name = Var.Name.Content; if (ArrayIndices != null) { - if (context.GlobalContext.Data?.GMS2_3 == true) + if (context?.GlobalContext.Data?.GMS2_3 == true) { if (name == "argument" && context.DecompilingStruct && context.ArgumentReplacements != null && ArrayIndices.Count == 1) { @@ -2723,6 +2750,9 @@ public string ToString(DecompileContext context, bool canSkipBrackets = true, bo sb.Append("{\n"); foreach (var stmt in Statements) { + if (stmt is AssignmentStatement assign && assign.IsStructDefinition) + continue; + sb.Append(context.Indentation); string resultStr = stmt.ToString(context); sb.Append(resultStr).Append('\n'); @@ -3719,7 +3749,15 @@ public static string Decompile(UndertaleCode code, GlobalDecompileContext global foreach (var warn in globalContext.DecompilerWarnings) sb.Append(warn + "\n"); foreach (var stmt in context.Statements[0]) + { + // Ignore initial struct definitions, they clutter + // decompiled output and generally make code more + // confusing to read. + if (stmt is AssignmentStatement assign && assign.IsStructDefinition) + continue; + sb.Append(stmt.ToString(context) + "\n"); + } globalContext.DecompilerWarnings.Clear(); context.Statements = null;