From 62216f0fade027a640db7376477e6287c96af872 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Thu, 25 Sep 2025 15:52:13 -0500 Subject: [PATCH] Make if and foreach constructs compatible --- libs/UIX.DecompXml/CodeBlockInfo.cs | 7 +- libs/UIX.DecompXml/Decompiler.Script.cs | 114 +++++++++++++++++------- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/libs/UIX.DecompXml/CodeBlockInfo.cs b/libs/UIX.DecompXml/CodeBlockInfo.cs index 459f897..c422376 100644 --- a/libs/UIX.DecompXml/CodeBlockInfo.cs +++ b/libs/UIX.DecompXml/CodeBlockInfo.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; using System.Collections.Generic; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -35,7 +36,11 @@ internal record CodeBlockInfo case ElseBlockInfo _: var elseClause = ElseClause(blockBody); - var ifElseBlock = (IfStatementSyntax)parentBlock.Statements[^1]; + + var parentLastStatement = parentBlock.Statements[^1]; + if (parentLastStatement is not IfStatementSyntax ifElseBlock) + throw new InvalidOperationException($"Else block must be preceded by an if block, got '{parentLastStatement.Kind()}'"); + parentBlock.Statements[^1] = ifElseBlock.WithElse(elseClause); break; diff --git a/libs/UIX.DecompXml/Decompiler.Script.cs b/libs/UIX.DecompXml/Decompiler.Script.cs index 339514f..5714c2e 100644 --- a/libs/UIX.DecompXml/Decompiler.Script.cs +++ b/libs/UIX.DecompXml/Decompiler.Script.cs @@ -56,37 +56,58 @@ partial class Decompiler { var instruction = methodBody[i]; - if (jumpFalseToOffsets.Contains(instruction.Offset) && blockStack.Count >= 2) + while (blockStack.Count > 1) { + var currentBlock = blockStack.Pop(); - var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset }; + if (currentBlock.EndOffset != instruction.Offset) + { + blockStack.Push(currentBlock); + break; + } + + System.Diagnostics.Debug.WriteLine($"END: Finalizing {currentBlock}"); currentBlock.FinalizeBlock(blockStack.Peek()); - - // This address marks the end of the affirmative branch of an IF clause. - // If multiple blocks lead to this address, then we're outside of the IF clause entirely. - // Otherwise, it's probably the start of an ELSE clause. - - var currentControlBlock = cfa.GetByInstruction(instruction); - if (controlBlocks.Count(b => b.HasEdgeTo(currentControlBlock, controlBlocks)) <= 1) - { - blockStack.Push(new(instruction.Offset, uint.MaxValue, new ElseBlockInfo())); - } } - if (!foreachLoopHeadOffsets.Contains(instruction.Offset) && cfa.IsAlwaysExecuted(instruction.Offset)) - { - while (blockStack.Count > 1) - { - var currentBlock = blockStack.Pop(); - if (currentBlock.EndOffset is uint.MaxValue) - currentBlock = currentBlock with { EndOffset = instruction.Offset }; + //if (jumpToOffsets.Contains(instruction.Offset) && TryPeekBlock(out _)) + //{ + // // This address marks the end of the affirmative branch of an IF clause. + // // If multiple blocks lead to this address, then we're outside of the IF clause entirely. + // // Otherwise, it's probably the start of an ELSE clause. - if (currentBlock.EndOffset != instruction.Offset) - break; + // while (blockStack.Count > 1) + // { + // var currentBlock = blockStack.Pop(); - currentBlock.FinalizeBlock(blockStack.Peek()); - } - } + // if (currentBlock.AdditionalInfo is not (ElseBlockInfo)) + // break; + + // if (currentBlock.EndOffset is uint.MaxValue) + // currentBlock = currentBlock with { EndOffset = instruction.Offset }; + + // System.Diagnostics.Debug.WriteLine($"JMP: Finalizing ELSE {currentBlock}"); + // currentBlock.FinalizeBlock(blockStack.Peek()); + // } + //} + + //if (!foreachLoopHeadOffsets.Contains(instruction.Offset) && cfa.IsAlwaysExecuted(instruction.Offset)) + //{ + // while (blockStack.Count > 1) + // { + // var currentBlock = blockStack.Pop(); + // if (currentBlock.EndOffset is uint.MaxValue) + // { + + // } + + // if (currentBlock.EndOffset != instruction.Offset || currentBlock.AdditionalInfo is not (IfBlockInfo or ElseBlockInfo)) + // break; + + // System.Diagnostics.Debug.WriteLine($"Always exec'ed: Automatically finalizing {currentBlock}"); + // currentBlock.FinalizeBlock(blockStack.Peek()); + // } + //} var opCode = instruction.OpCode; @@ -299,7 +320,8 @@ partial class Decompiler case OpCode.JumpIfTruePeek: var jumpToOffset = (uint)instruction.Operands.First().Value; - if (opCode is OpCode.JumpIfFalse && TryPeekBlock(out _)) + if (opCode is OpCode.JumpIfFalse && TryPeekBlock(out var jmpfForEachBlockInfo) + && jmpfForEachBlockInfo.Type is null) break; var isPeek = opCode is OpCode.JumpIfFalsePeek or OpCode.JumpIfTruePeek; @@ -308,7 +330,13 @@ partial class Decompiler if (opCode is OpCode.JumpIfFalse) { // JMPF is used to evaluate the branch condition - var ifBlock = new CodeBlockInfo(instruction.Offset, jumpToOffset, new IfBlockInfo(jumpCondition)); + var ifBlockEndOffset = methodBody + .Reverse() + .SkipWhile(i => i.Offset >= jumpToOffset) + .First() + .Offset; + + var ifBlock = new CodeBlockInfo(instruction.Offset, ifBlockEndOffset, new IfBlockInfo(jumpCondition)); blockStack.Push(ifBlock); } else @@ -329,14 +357,33 @@ partial class Decompiler if (foreachLoopHeadOffsets.Contains(jumpOffset)) { - var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset }; - currentBlock.FinalizeBlock(blockStack.Peek()); + //var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset }; + //System.Diagnostics.Debug.WriteLine($"JMP: Finalizing loop {currentBlock}"); + //currentBlock.FinalizeBlock(blockStack.Peek()); } else { throw new NotImplementedException("For and while loops are not supported at this time."); } } + else + { + // End of if block, skipping else block + + // Figure out where the else block ends by searching for the last instruction we skip + var elseBlockEndOffset = methodBody + .Reverse() + .SkipWhile(i => i.Offset >= jumpOffset) + .First() + .Offset; + + //var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset }; + //System.Diagnostics.Debug.WriteLine($"JMP: Finalizing presumed IF {currentBlock}"); + //currentBlock.FinalizeBlock(blockStack.Peek()); + + var elseBlock = new CodeBlockInfo(instruction.Offset, elseBlockEndOffset, new ElseBlockInfo()); + blockStack.Push(elseBlock); + } break; @@ -381,11 +428,14 @@ partial class Decompiler bool TryPeekBlock([NotNullWhen(true)] out T additionalInfo) where T : ICodeBlockAdditionalInfo { - var currentBlock = blockStack.Peek(); - if (currentBlock.AdditionalInfo is T a) + if (blockStack.Count > 1) { - additionalInfo = a; - return true; + var currentBlock = blockStack.Peek(); + if (currentBlock.AdditionalInfo is T a) + { + additionalInfo = a; + return true; + } } additionalInfo = default;