diff --git a/libs/UIX.DecompXml/CodeBlockInfo.cs b/libs/UIX.DecompXml/CodeBlockInfo.cs new file mode 100644 index 0000000..72b6490 --- /dev/null +++ b/libs/UIX.DecompXml/CodeBlockInfo.cs @@ -0,0 +1,50 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; + +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Microsoft.Iris.DecompXml; + +internal class CodeBlockInfo +{ + public CodeBlockInfo(uint startOffset, uint endOffset, SyntaxKind kind, ExpressionSyntax expression = null) + { + StartOffset = startOffset; + EndOffset = endOffset; + Kind = kind; + Expression = expression; + } + + public uint StartOffset { get; init; } + + public uint EndOffset { get; init; } + + public SyntaxKind Kind { get; init; } + + public ExpressionSyntax Expression { get; init; } + + public List Statements { get; } = []; + + public void FinalizeBlock(CodeBlockInfo parentBlock) + { + var blockBody = Block(Statements); + + switch (Kind) + { + case SyntaxKind.IfStatement: + var ifStatement = IfStatement(Expression, blockBody); + parentBlock.Statements.Add(ifStatement); + break; + + case SyntaxKind.ElseClause: + var elseClause = ElseClause(blockBody); + var ifElseBlock = (IfStatementSyntax)parentBlock.Statements[^1]; + parentBlock.Statements[^1] = ifElseBlock.WithElse(elseClause); + break; + + default: + throw new System.NotImplementedException($"Unrecognized code block kind '{Kind}'"); + } + } +} diff --git a/libs/UIX.DecompXml/DecompileContext.cs b/libs/UIX.DecompXml/DecompileContext.cs index bb639a1..7f8be4c 100644 --- a/libs/UIX.DecompXml/DecompileContext.cs +++ b/libs/UIX.DecompXml/DecompileContext.cs @@ -78,14 +78,14 @@ internal class DecompileContext public PropertySchema GetImportedProperty(Operand op) => ImportTables.PropertyImports[(ushort)op.Value]; - public Instruction[] GetMethodBody(uint startOffset) + public IEnumerable GetMethodBody(uint startOffset) { - return Instructions - .SkipWhile(i => i.Offset < startOffset) - .OrderBy(i => i.Offset) - .TakeWhile(i => i.OpCode is not (OpCode.ReturnValue or OpCode.ReturnVoid)) - .OrderBy(i => i.Offset) - .ToArray(); + foreach (var instruction in Instructions.SkipWhile(i => i.Offset < startOffset)) + { + yield return instruction; + if (instruction.OpCode is OpCode.ReturnValue or OpCode.ReturnVoid) + yield break; + } } public IEnumerable> GetUsedNamespaces() diff --git a/libs/UIX.DecompXml/Decompiler.Script.cs b/libs/UIX.DecompXml/Decompiler.Script.cs index c1eb64f..65067d3 100644 --- a/libs/UIX.DecompXml/Decompiler.Script.cs +++ b/libs/UIX.DecompXml/Decompiler.Script.cs @@ -18,16 +18,36 @@ partial class Decompiler { private SyntaxTree DecompileScript(uint startOffset, MarkupTypeSchema export) { - var methodBody = _context.GetMethodBody(startOffset); + var methodBody = _context.GetMethodBody(startOffset).ToArray(); - Stack blockStack = []; - blockStack.Push(Block()); + Stack blockStack = []; + blockStack.Push(new(0, methodBody[^1].Offset, SyntaxKind.Block, null)); Stack stack = new(); for (int i = 0; i < methodBody.Length; i++) { var instruction = methodBody[i]; + + // TODO: Handle for loops + if (instruction.Offset == blockStack.Peek().EndOffset) + { + // Make sure there is only one top-level block + if (blockStack.Count > 1) + { + var currentBlock = blockStack.Pop(); + currentBlock.FinalizeBlock(blockStack.Peek()); + } + else + { + // End of function + if (i + 1 != methodBody.Length) + throw new InvalidOperationException("Expected end of function!"); + + break; + } + } + var opCode = instruction.OpCode; try @@ -73,7 +93,7 @@ partial class Decompiler IrisExpression.ToSyntax(newSymbolValue, _context) ); - AddStatementToBlock(blockStack, ExpressionStatement(symbolAssignmentExpr)); + blockStack.Peek().Statements.Add(ExpressionStatement(symbolAssignmentExpr)); break; case OpCode.MethodInvoke: @@ -116,7 +136,7 @@ partial class Decompiler } else { - AddStatementToBlock(blockStack, ExpressionStatement(methodResult)); + blockStack.Peek().Statements.Add(ExpressionStatement(methodResult)); } if (pushLastParam) @@ -130,7 +150,7 @@ partial class Decompiler case OpCode.PropertyGetStatic: var propToGet = _context.GetImportedProperty(instruction.Operands.First()); - var propTarget = instruction.OpCode switch + var propGetTarget = instruction.OpCode switch { OpCode.PropertyGet => stack.Pop(), OpCode.PropertyGetPeek => stack.Peek(), @@ -138,26 +158,36 @@ partial class Decompiler }; var propertyGetExpression = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - IrisExpression.ToSyntax(propTarget, _context), + IrisExpression.ToSyntax(propGetTarget, _context), IdentifierName(propToGet.Name) ); stack.Push(propertyGetExpression); break; - case OpCode.PropertyInitialize: - var propertyToInit = _context.GetImportedProperty(instruction.Operands.First()); - var newPropValue = stack.Pop(); + case OpCode.PropertyAssign: + case OpCode.PropertyAssignStatic: + var propToSet = _context.GetImportedProperty(instruction.Operands.First()); - var target = stack.Pop(); - var xTarget = (XElement)ToXmlFriendlyObject(target); + var propSetTarget = opCode is OpCode.PropertyAssignStatic + ? propToSet.Owner + : stack.Pop(); - PropertyAssignOnXElement(xTarget, propertyToInit, IrisObject.Create(newPropValue, propertyToInit.PropertyType, _context)); + var newPropValue = IrisExpression.ToSyntax(stack.Peek(), _context); - stack.Push(new IrisObject(xTarget, propertyToInit.Owner)); + var propertySetExpression = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IrisExpression.ToSyntax(propSetTarget, _context), + IdentifierName(propToSet.Name) + ), + newPropValue + ); + + blockStack.Peek().Statements.Add(ExpressionStatement(propertySetExpression)); break; case OpCode.PropertyDictionaryAdd: + // TODO var targetDictProperty = _context.GetImportedProperty(instruction.Operands.ElementAt(0)); var keyReference = instruction.Operands.ElementAt(1); @@ -171,12 +201,74 @@ partial class Decompiler break; case OpCode.PropertyListAdd: + // TODO var targetListProperty = _context.GetImportedProperty(instruction.Operands.First()); var valueToAdd = stack.Pop(); var targetInstance2 = (XElement)ToXmlFriendlyObject(stack.Peek()); PropertyListAddOnXElement(targetInstance2, targetListProperty, IrisObject.Create(valueToAdd, null, _context)); break; + + case OpCode.JumpIfFalse: + case OpCode.JumpIfFalsePeek: + case OpCode.JumpIfTruePeek: + var jumpToOffset = (uint)instruction.Operands.First().Value; + + // TODO: What about for loops? + if (instruction.Offset > jumpToOffset) + { + throw new NotImplementedException(); + } + + var isPeek = opCode is OpCode.JumpIfFalsePeek or OpCode.JumpIfTruePeek or OpCode.JumpIfNullPeek; + var rawJumpCondition = IrisExpression.ToSyntax(isPeek ? stack.Peek() : stack.Pop(), _context); + + var jumpCondition = opCode switch + { + OpCode.JumpIfFalse or + OpCode.JumpIfFalsePeek => PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, rawJumpCondition), + + OpCode.JumpIfTruePeek => rawJumpCondition, + + OpCode.JumpIfNullPeek => BinaryExpression(SyntaxKind.EqualsExpression, + rawJumpCondition, + LiteralExpression(SyntaxKind.NullLiteralExpression)), + + _ => throw new NotImplementedException() + }; + + var ifBlock = new CodeBlockInfo(instruction.Offset, jumpToOffset, SyntaxKind.IfStatement, jumpCondition); + blockStack.Push(ifBlock); + break; + + case OpCode.Operation: + var opHost = _context.GetImportedType(instruction.Operands.ElementAt(0)); + + var op = (OperationType)(int)(byte)instruction.Operands.ElementAt(1).Value; + var isUnary = TypeSchema.IsUnaryOperation(op); + var opSyntax = OperationToSyntaxKind(op); + + ExpressionSyntax operationExpr; + + if (isUnary) + { + var left = IrisExpression.ToSyntax(stack.Pop(), _context); + var isPostfix = op is OperationType.PostIncrement or OperationType.PostDecrement; + + operationExpr = isPostfix + ? PostfixUnaryExpression(opSyntax, left) + : PrefixUnaryExpression(opSyntax, left); + } + else + { + var right = IrisExpression.ToSyntax(stack.Pop(), _context); + var left = IrisExpression.ToSyntax(stack.Pop(), _context); + + operationExpr = BinaryExpression(opSyntax, left, right); + } + + stack.Push(operationExpr); + break; } } catch (Exception ex) @@ -216,4 +308,34 @@ partial class Decompiler block = block.AddStatements(statement); blockStack.Push(block); } + + private static SyntaxKind OperationToSyntaxKind(OperationType operation) + { + return operation switch + { + OperationType.MathAdd => SyntaxKind.AddExpression, + OperationType.MathSubtract => SyntaxKind.SubtractExpression, + OperationType.MathMultiply => SyntaxKind.MultiplyExpression, + OperationType.MathDivide => SyntaxKind.DivideExpression, + OperationType.MathModulus => SyntaxKind.ModuloExpression, + OperationType.MathNegate => SyntaxKind.UnaryMinusExpression, + + OperationType.LogicalAnd => SyntaxKind.LogicalAndExpression, + OperationType.LogicalOr => SyntaxKind.LogicalOrExpression, + OperationType.LogicalNot => SyntaxKind.LogicalNotExpression, + + OperationType.RelationalEquals => SyntaxKind.EqualsExpression, + OperationType.RelationalNotEquals => SyntaxKind.NotEqualsExpression, + OperationType.RelationalLessThan => SyntaxKind.LessThanExpression, + OperationType.RelationalGreaterThan => SyntaxKind.GreaterThanExpression, + OperationType.RelationalLessThanEquals => SyntaxKind.LessThanOrEqualExpression, + OperationType.RelationalGreaterThanEquals => SyntaxKind.GreaterThanOrEqualExpression, + OperationType.RelationalIs => SyntaxKind.IsExpression, + + OperationType.PostIncrement => SyntaxKind.PostIncrementExpression, + OperationType.PostDecrement => SyntaxKind.PostDecrementExpression, + + _ => throw new ArgumentException($"Invalid operation type '{operation}'", nameof(operation)) + }; + } } diff --git a/libs/UIX.DecompXml/Decompiler.cs b/libs/UIX.DecompXml/Decompiler.cs index a228f1c..52afa19 100644 --- a/libs/UIX.DecompXml/Decompiler.cs +++ b/libs/UIX.DecompXml/Decompiler.cs @@ -105,7 +105,7 @@ public partial class Decompiler private Stack AnalyzeMethodForInit(uint startOffset, XElement elemToInit, MarkupTypeSchema initType, string methodName = "") { - var methodBody = _context.GetMethodBody(startOffset); + var methodBody = _context.GetMethodBody(startOffset).ToArray(); Stack stack = new([elemToInit]); diff --git a/libs/UIX.DecompXml/Mock/IrisExpression.cs b/libs/UIX.DecompXml/Mock/IrisExpression.cs index 5758df3..a935671 100644 --- a/libs/UIX.DecompXml/Mock/IrisExpression.cs +++ b/libs/UIX.DecompXml/Mock/IrisExpression.cs @@ -2,7 +2,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Iris.Asm; using Microsoft.Iris.Markup; -using Newtonsoft.Json.Linq; using System; using System.Linq.Expressions; diff --git a/test/ABOUTDIALOG.31_48_decomp.uix b/test/ABOUTDIALOG.31_48_decomp.uix index a71d8c5..1f69c63 100644 --- a/test/ABOUTDIALOG.31_48_decomp.uix +++ b/test/ABOUTDIALOG.31_48_decomp.uix @@ -15,7 +15,11 @@ ButtonCommands.Add(Dialog.Cancel); DefaultButtonModel = Dialog.Cancel; ButtonModelToFocus = Dialog.Cancel; - +