diff --git a/UndertaleModLib/Decompiler/Decompiler.cs b/UndertaleModLib/Decompiler/Decompiler.cs index 9cbb6b343..8c1290046 100644 --- a/UndertaleModLib/Decompiler/Decompiler.cs +++ b/UndertaleModLib/Decompiler/Decompiler.cs @@ -1379,6 +1379,32 @@ internal override AssetIDType DoTypePropagation(DecompileContext context, AssetI } } + public class TempExceptionValue : Expression + { + private Block tryBlock, catchBlock; + + public TempExceptionValue(Block tryBlock, Block catchBlock) + { + this.catchBlock = catchBlock; + this.tryBlock = tryBlock; + } + + public override Statement CleanStatement(DecompileContext context, BlockHLStatement block) + { + return this; + } + + public override string ToString(DecompileContext context) + { + return "// TempExceptionValue " + tryBlock + " " + catchBlock; + } + + internal override AssetIDType DoTypePropagation(DecompileContext context, AssetIDType suggestedType) + { + return suggestedType; + } + } + // Represents a high-level operation-equals statement, such as a += 1. public class OperationEqualsStatement : Statement { @@ -1713,6 +1739,11 @@ cast.Argument is ExpressionConstant constant && return String.Format("new {0}({1})", constructor, argumentString); } + else if (Function.Name.Content == "@@throw@@" && Arguments.Count == 1) + { + context.currentFunction = this; + return "throw " + Arguments[0].ToString(context); + } else { foreach (Expression exp in Arguments) @@ -1728,7 +1759,12 @@ cast.Argument is ExpressionConstant constant && if (Function.Name.Content == "@@NewGMLArray@@") // Inline array definitions return "[" + argumentString.ToString() + "]"; - return String.Format("{0}({1})", OverridenName != string.Empty ? OverridenName : Function.Name.Content, argumentString.ToString()); + string ret = String.Format("{0}({1})", OverridenName != string.Empty ? OverridenName : Function.Name.Content, argumentString.ToString()); + + if (ret.StartsWith("@@try_") || ret.StartsWith("@@finish_")) + return "// " + ret; + + return ret; } } @@ -2371,7 +2407,7 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary 0) val = stack.Pop(); if (val != null) { @@ -2505,20 +2541,25 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary args = new List(); + List cleanArgs = new List(); + Expression arg; for (int j = 0; j < instr.ArgumentsCount; j++) - args.Add(stack.Pop()); + { + arg = stack.Pop(); + args.Add(arg); + + while (arg is ExpressionCast cast) + arg = cast.Argument; + + cleanArgs.Add(arg); + } if (instr.Function.Target.Name.Content == "method" && args.Count == 2) { // Special case - method creation // See if the body should be inlined - Expression arg1 = args[0]; - while (arg1 is ExpressionCast cast) - arg1 = cast.Argument; - Expression arg2 = args[1]; - while (arg2 is ExpressionCast cast) - arg2 = cast.Argument; + Expression arg1 = cleanArgs[0], arg2 = cleanArgs[1]; if (arg2 is ExpressionConstant argCode && argCode.Type == UndertaleInstruction.DataType.Int32 && argCode.Value is UndertaleInstruction.Reference argCodeFunc) @@ -2547,6 +2588,51 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary(); + TempVar tempVar = context.NewTempVar(); + TempVarReference varRef = new TempVarReference(tempVar); + context.TempVarMap[tempVar.Name] = new TempVarAssignmentStatement(varRef, new TempExceptionValue(tryBlock, catchBlock)); + vars.Add(varRef); + + //catchBlock.TempVarsOnEntry ??= new(); + //tryBlock.TempVarsOnEntry ??= new(); + + //catchBlock.TempVarsOnEntry.Add(varRef); + //tryBlock.TempVarsOnEntry.Add(varRef); + + // Decompile the try and catch blocks + + workQueue.Push(new Tuple>(catchBlock, vars)); + workQueue.Push(new Tuple>(tryBlock, new List())); + + statements.Add(new TryCatchHLStatement(tryBlock, catchBlock)); + stack.Push(new TempExceptionValue(tryBlock, catchBlock)); + //stack.Push(new TempExceptionValue(tryBlock)); + block.nextBlockFalse = null; + block.nextBlockTrue = tmp; + break; + + } UndertaleCode callTargetBody = context.GlobalContext.Data?.Code.FirstOrDefault(x => x.Name.Content == instr.Function.Target.Name.Content); if (callTargetBody != null && callTargetBody.ParentEntry != null && !context.DisableAnonymousFunctionNameResolution) @@ -2735,14 +2821,21 @@ assign.Value is FunctionDefinition funcDef && List leftovers = new List(); for (int i = stack.Count - 1; i >= 0; i--) { - if (i < tempvars.Count) + Expression val = stack.Pop(); + + // Skip throw statements; they don't have a corresponding pop instruction, + // within their block (as far as I can tell), so they will always leak onto the stack. + if (val is DirectFunctionCall call && call.Function.Name.Content == "@@throw@@" && call.Arguments.Count == 1) + statements.Add(val); + else if (i < tempvars.Count) { - Expression val = stack.Pop(); - if (!(val is ExpressionTempVar) || (val as ExpressionTempVar).Var != tempvars[i]) { + if (!(val is ExpressionTempVar) || (val as ExpressionTempVar).Var != tempvars[i]) + { var assignment = new TempVarAssignmentStatement(tempvars[i], val); statements.Add(assignment); - if (val is ExpressionConstant) { + if (val is ExpressionConstant) + { context.TempVarMap[tempvars[i].Var.Name] = assignment; } } @@ -2751,7 +2844,6 @@ assign.Value is FunctionDefinition funcDef && } else { - Expression val = stack.Pop(); TempVar var = context.NewTempVar(); var.Type = val.Type; TempVarReference varref = new TempVarReference(var); @@ -2759,7 +2851,8 @@ assign.Value is FunctionDefinition funcDef && statements.Add(assignment); leftovers.Add(varref); - if (val is ExpressionConstant) { + if (val is ExpressionConstant) + { context.TempVarMap[var.Name] = assignment; } } @@ -2803,8 +2896,17 @@ public static Dictionary DecompileFlowGraph(UndertaleCode code, Lis blockByAddress[code.Length / 4] = finalBlock; Block currentBlock = entryBlock; + int index = -1; + bool skip = false; foreach (var instr in code.Instructions) { + index++; + if (skip) + { + skip = false; + continue; + } + if (blockByAddress.ContainsKey(instr.Address)) { if (currentBlock != null) @@ -2909,6 +3011,20 @@ public static Dictionary DecompileFlowGraph(UndertaleCode code, Lis currentBlock.nextBlockFalse = nextBlock; currentBlock = null; } + else if (instr.Kind == UndertaleInstruction.Opcode.Call && instr.Function?.Target.Name.Content == "@@try_hook@@") + { + UndertaleInstruction next; + if(code.Instructions.Count > index + 1 && (next = code.Instructions[index + 1]).Kind == UndertaleInstruction.Opcode.Popz) + { + skip = true; + Block nextBlock = GetBlock(next.Address + 1); + currentBlock.Instructions.Add(next); + currentBlock.conditionalExit = false; + currentBlock.nextBlockTrue = nextBlock; + currentBlock.nextBlockFalse = nextBlock; + currentBlock = null; + } + } } if (currentBlock != null) { @@ -2999,6 +3115,52 @@ private static bool CanSkipBrackets(BlockHLStatement blockStatement) } }; + public class TryCatchHLStatement : BlockHLStatement + { + public Block tryBlock, catchBlock; + + public TryCatchHLStatement(Block tryBlock, Block catchBlock = null) + { + this.catchBlock = catchBlock; + this.tryBlock = tryBlock; + } + + public new BlockHLStatement CleanBlockStatement(DecompileContext context) + { + if (tryBlock.Statements != null) + Statements = tryBlock.Statements; + return base.CleanBlockStatement(context); + } + + internal override AssetIDType DoTypePropagation(DecompileContext context, AssetIDType suggestedType) + { + return suggestedType; + } + + public override string ToString(DecompileContext context) + { + StringBuilder sb = new StringBuilder(); + Statements = tryBlock.Statements; + sb.AppendLine("try"); + sb.Append(context.Indentation); + sb.AppendLine(ToString(context, false)); + + if (catchBlock != null) + { + Statements = catchBlock.Statements.Skip(1).ToList(); + sb.Append(context.Indentation); + sb.Append("catch("); + sb.Append(catchBlock.Instructions[0].Destination.Target.Name.Content); + sb.AppendLine(")"); + sb.Append(context.Indentation); + sb.AppendLine(ToString(context, false)); + Statements = tryBlock.Statements; + } + + return sb.ToString(); + } + } + public class IfHLStatement : HLStatement { public Expression condition; @@ -3595,7 +3757,7 @@ private static BlockHLStatement HLDecompileBlocks(DecompileContext context, ref if (depth > 200) throw new Exception("Excessive recursion while processing blocks."); - BlockHLStatement output = new BlockHLStatement(); + BlockHLStatement output = new BlockHLStatement(); Block lastBlock = null; bool popenvDrop = false; diff --git a/UndertaleModLib/Decompiler/Disassembler.cs b/UndertaleModLib/Decompiler/Disassembler.cs index 480f3caff..188d94a16 100644 --- a/UndertaleModLib/Decompiler/Disassembler.cs +++ b/UndertaleModLib/Decompiler/Disassembler.cs @@ -82,8 +82,10 @@ public static List FindBlockAddresses(UndertaleCode code) if (code.Instructions.Count != 0) addresses.Add(0); + int index = -1; foreach (var inst in code.Instructions) { + index++; switch (inst.Kind) { case UndertaleInstruction.Opcode.B: @@ -101,6 +103,14 @@ public static List FindBlockAddresses(UndertaleCode code) case UndertaleInstruction.Opcode.Ret: addresses.Add(inst.Address + 1); break; + case UndertaleInstruction.Opcode.Call: + if (inst.Function?.Target.Name.Content == "@@try_hook@@" && code.Instructions.Count > index + 1) + { + UndertaleInstruction next = code.Instructions[index + 1]; + if (next.Kind == UndertaleInstruction.Opcode.Popz) + addresses.Add(next.Address + 1); + } + break; } } diff --git a/UndertaleModTool/Resources/GML.xshd b/UndertaleModTool/Resources/GML.xshd index 348619e4c..5520b3127 100644 --- a/UndertaleModTool/Resources/GML.xshd +++ b/UndertaleModTool/Resources/GML.xshd @@ -45,6 +45,10 @@ continue with new + throw + try + catch + finally constructor function return