Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass at try-catch decompilation #930

Closed
Closed
194 changes: 178 additions & 16 deletions UndertaleModLib/Decompiler/Decompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -2371,7 +2407,7 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary<uin
target.InstType = stack.Pop();
}

if (instr.Type1 == UndertaleInstruction.DataType.Variable)
if (instr.Type1 == UndertaleInstruction.DataType.Variable && stack.Count > 0)
val = stack.Pop();
if (val != null)
{
Expand Down Expand Up @@ -2505,20 +2541,25 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary<uin
case UndertaleInstruction.Opcode.Call:
{
List<Expression> args = new List<Expression>();
List<Expression> cleanArgs = new List<Expression>();
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<UndertaleFunction> argCodeFunc)
Expand Down Expand Up @@ -2547,6 +2588,51 @@ internal static void DecompileFromBlock(DecompileContext context, Dictionary<uin
}
}
}
else if (instr.Function.Target.Name.Content == "@@try_hook@@" && args.Count == 2)
{
Block tryBlock = block.nextBlockFalse, catchBlock = null, tmp = block.nextBlockFalse;

string catchStr = cleanArgs[0].ToString(context), endStr = cleanArgs[1].ToString(context);
if (!uint.TryParse(catchStr, out uint catchAddr) || !uint.TryParse(endStr, out uint endAddr))
throw new InvalidDataException("Bad arguments to @@try_hook@@: " + catchStr + " " + endStr);

catchAddr /= 4;
endAddr /= 4;

catchBlock = blocks[catchAddr];

// Find first block that's not in the try block
tmp = blocks[endAddr];

// Set up a temporary reference as a placeholder
// value for the localvar that holds the exception
// in the catch block.

var vars = new List<TempVarReference>();
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<Block, List<TempVarReference>>(catchBlock, vars));
workQueue.Push(new Tuple<Block, List<TempVarReference>>(tryBlock, new List<TempVarReference>()));

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)
Expand Down Expand Up @@ -2735,14 +2821,21 @@ assign.Value is FunctionDefinition funcDef &&
List<TempVarReference> leftovers = new List<TempVarReference>();
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;
}
}
Expand All @@ -2751,15 +2844,15 @@ assign.Value is FunctionDefinition funcDef &&
}
else
{
Expression val = stack.Pop();
TempVar var = context.NewTempVar();
var.Type = val.Type;
TempVarReference varref = new TempVarReference(var);
var assignment = new TempVarAssignmentStatement(varref, val);
statements.Add(assignment);
leftovers.Add(varref);

if (val is ExpressionConstant) {
if (val is ExpressionConstant)
{
context.TempVarMap[var.Name] = assignment;
}
}
Expand Down Expand Up @@ -2803,8 +2896,17 @@ public static Dictionary<uint, Block> 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)
Expand Down Expand Up @@ -2909,6 +3011,20 @@ public static Dictionary<uint, Block> 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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions UndertaleModLib/Decompiler/Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ public static List<uint> 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:
Expand All @@ -101,6 +103,14 @@ public static List<uint> 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;
}
}

Expand Down
4 changes: 4 additions & 0 deletions UndertaleModTool/Resources/GML.xshd
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
<Word>continue</Word>
<Word>with</Word>
<Word>new</Word>
<Word>throw</Word>
<Word>try</Word>
<Word>catch</Word>
<Word>finally</Word>
<Word>constructor</Word>
<Word>function</Word>
<Word>return</Word>
Expand Down
Loading