diff --git a/libs/UIX.DecompXml/DecompileContext.cs b/libs/UIX.DecompXml/DecompileContext.cs index e4eeafb..8e8bdf1 100644 --- a/libs/UIX.DecompXml/DecompileContext.cs +++ b/libs/UIX.DecompXml/DecompileContext.cs @@ -1,4 +1,5 @@ using Humanizer; +using Microsoft.CodeAnalysis; using Microsoft.Iris.Asm; using Microsoft.Iris.Asm.Models; using Microsoft.Iris.Markup; @@ -14,6 +15,7 @@ internal class DecompileContext { private readonly MarkupLoadResult _loadResult; private readonly MarkupLoadResult _dataTableLoadResult; + private readonly Dictionary<(ulong, uint), SyntaxTree> _scriptMap; private readonly Dictionary _namespaces; private readonly HashSet _usedNamespacePrefixes; private readonly Dictionary _uriAliasMap; @@ -54,6 +56,8 @@ internal class DecompileContext GenerateNamespaces(); + _scriptMap = []; + _instructions = ObjectSection.Decode(_loadResult.ObjectSection) .OfType() .ToArray(); @@ -99,6 +103,12 @@ internal class DecompileContext } } + public void SetScriptContent(TypeSchema type, uint startOffset, SyntaxTree tree) => _scriptMap[(type.UniqueId, startOffset)] = tree; + + public SyntaxTree GetScriptContent(TypeSchema type, uint startOffset) => _scriptMap[(type.UniqueId, startOffset)]; + + public IEnumerable GetScriptContents(TypeSchema type) => _scriptMap.Where(k => k.Key.Item1 == type.UniqueId).Select(k => k.Value); + public IEnumerable> GetUsedNamespaces() { return _namespaces diff --git a/libs/UIX.DecompXml/Decompiler.Script.cs b/libs/UIX.DecompXml/Decompiler.Script.cs index e512f6b..11de4e0 100644 --- a/libs/UIX.DecompXml/Decompiler.Script.cs +++ b/libs/UIX.DecompXml/Decompiler.Script.cs @@ -253,6 +253,107 @@ partial class Decompiler .WithModifiers(modifiers); } + private void AnalyzeRefreshMethod(uint startOffset, MarkupTypeSchema initType, string methodName = "") + { + var methodBody = _context.GetMethodBody(startOffset).ToArray(); + + Stack stack = new(); + stack.Push(initType); + + for (int i = 0; i < methodBody.Length; i++) + { + var instruction = methodBody[i]; + + try + { + switch (instruction.OpCode) + { + case OpCode.LookupSymbol: + var symbolIndex = (ushort)instruction.Operands.ElementAt(0).Value; + stack.Push(initType.SymbolReferenceTable[symbolIndex]); + break; + + case OpCode.Listen: + case OpCode.DestructiveListen: + var listenerIndex = (ushort)instruction.Operands.ElementAt(0).Value; + var listenerType = (ListenerType)(byte)instruction.Operands.ElementAt(1).Value; + var watchIndex = (ushort)instruction.Operands.ElementAt(2).Value; + var scriptId = (uint)instruction.Operands.ElementAt(3).Value; + + var refreshOffset = uint.MaxValue; + if (instruction.OpCode is OpCode.DestructiveListen) + refreshOffset = (uint)instruction.Operands.ElementAt(4).Value; + + string watch = null; + switch (listenerType) + { + case ListenerType.Property: + watch = _context.ImportTables.PropertyImports[watchIndex].Name; + break; + + case ListenerType.Event: + watch = _context.ImportTables.EventImports[watchIndex].Name; + break; + + case ListenerType.Symbol: + watch = initType.SymbolReferenceTable[watchIndex].Symbol; + break; + } + + // What does this mean? + if (scriptId is uint.MaxValue) + break; + + object handlerObj = stack.Peek(); + + try + { + var markupTypeSchema = initType.ResolveScriptId(scriptId, out var scriptOffset); + var scriptContent = _context.GetScriptContent(initType, scriptOffset); + var scriptRoot = scriptContent.GetRoot(); + + var nodesDbg = scriptRoot.DescendantNodes() + .OfType() + .Select(m => $"{m.Expression}.{m.Name}") + .ToArray(); + + SyntaxNode node = scriptRoot + .DescendantNodes() + .OfType() + .FirstOrDefault(n => n.Expression.ToString() == $"{handlerObj}" && n.Name.ToString() == watch); + + if (node is not null) + { + var octothorpeTrivia = SkippedTokensTrivia() + .AddTokens(BadToken(TriviaList(), "#", TriviaList())); + + SyntaxNode newNode = node + .WithLeadingTrivia(TriviaList(Trivia(octothorpeTrivia))) + .WithTrailingTrivia(TriviaList(Trivia(octothorpeTrivia))); + + SyntaxNode? parent = node.Parent; + while (parent is not null) + { + newNode = node.Parent.ReplaceNode(node, newNode); + node = node.Parent; + parent = node.Parent; + } + + _context.SetScriptContent(initType, scriptOffset, newNode.SyntaxTree); + } + } + catch { } + + break; + } + } + catch (Exception ex) + { + throw new Exception($"Failed to analyze instruction `{instruction}` @ 0x{instruction.Offset:X}, {methodName}[{i}]", ex); + } + } + } + public static SyntaxTree CreateTree(IEnumerable statements) { return SyntaxTree( diff --git a/libs/UIX.DecompXml/Decompiler.cs b/libs/UIX.DecompXml/Decompiler.cs index 8fdfc75..adbb4ca 100644 --- a/libs/UIX.DecompXml/Decompiler.cs +++ b/libs/UIX.DecompXml/Decompiler.cs @@ -1,6 +1,6 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Iris.Asm; -using Microsoft.Iris.Debug.Data; using Microsoft.Iris.DecompXml.Mock; using Microsoft.Iris.Markup; using System; @@ -65,61 +65,40 @@ public partial class Decompiler if (export.InitializeLocalsInputOffset is not uint.MaxValue) AnalyzeMethodForInit(export.InitializeLocalsInputOffset, xExport, export, name + "_locl"); - if (export.InitialEvaluateOffsets is { Length: > 0 }) + foreach (var offset in export.InitialEvaluateOffsets ?? []) { - var xScripts = GetOrCreateElement(xExport, _nsUix + "Scripts"); - - foreach (var offset in export.InitialEvaluateOffsets) - { - var syntaxTree = DecompileScript(offset, export); - var scriptText = FormatScript(syntaxTree); - - XElement xScript = new(_nsUix + "Script", scriptText); - xScripts.Add(xScript); - } + var syntaxTree = DecompileScript(offset, export); + _context.SetScriptContent(export, offset, syntaxTree); } - if (export.FinalEvaluateOffsets is { Length: > 0 }) + foreach (var offset in export.FinalEvaluateOffsets ?? []) { - var xScripts = GetOrCreateElement(xExport, _nsUix + "Scripts"); - - foreach (var offset in export.FinalEvaluateOffsets) - { - var syntaxTree = DecompileScript(offset, export, "FinalEvaluate"); - var scriptText = FormatScript(syntaxTree); - - XElement xScript = new(_nsUix + "Script", scriptText); - xScripts.Add(xScript); - } + var syntaxTree = DecompileScript(offset, export, "FinalEvaluate"); + _context.SetScriptContent(export, offset, syntaxTree); } - if (export.Methods is { Length: > 0 }) + foreach (var method in export.Methods.OfType()) { - var xScripts = GetOrCreateElement(xExport, _nsUix + "Scripts"); - - foreach (var method in export.Methods.OfType()) - { - var methodSyntax = DecompileMethodDeclaration(method, export); - var scriptText = FormatScript(methodSyntax.SyntaxTree); - - XElement xScript = new(_nsUix + "Script", scriptText); - - xScripts.Add(xScript); - } + var methodSyntax = DecompileMethodDeclaration(method, export); + _context.SetScriptContent(export, method.CodeOffset, methodSyntax.SyntaxTree); } - if (export.RefreshGroupOffsets is { Length: > 0 }) + foreach (var offset in export.RefreshGroupOffsets ?? []) { - var xScripts = GetOrCreateElement(xExport, _nsUix + "Scripts"); - - foreach (var offset in export.RefreshGroupOffsets) - { - var scriptText = AnalyzeRefreshMethod(offset, export, $"{name}_rfsh_0x{offset:X}"); - - XElement xScript = new(_nsUix + "Script", scriptText); - xScripts.Add(xScript); - } + AnalyzeRefreshMethod(offset, export, $"{name}_rfsh_0x{offset:X}"); } + + XElement xScripts = GetOrCreateElement(xExport, _nsUix + "Scripts"); + + foreach (var syntaxTree in _context.GetScriptContents(export)) + { + var scriptText = FormatScript(syntaxTree); + XElement xScript = new(_nsUix + "Script", scriptText); + xScripts.Add(xScript); + } + + if (!xScripts.HasElements) + xScripts.Remove(); if (export.InitializeContentOffset is not uint.MaxValue) AnalyzeMethodForInit(export.InitializeContentOffset, xExport, export, name + "_cont"); @@ -312,68 +291,6 @@ public partial class Decompiler return stack; } - private string AnalyzeRefreshMethod(uint startOffset, MarkupTypeSchema initType, string methodName = "") - { - var methodBody = _context.GetMethodBody(startOffset).ToArray(); - - Stack stack = new(); - - for (int i = 0; i < methodBody.Length; i++) - { - var instruction = methodBody[i]; - - try - { - switch (instruction.OpCode) - { - case OpCode.Listen: - case OpCode.DestructiveListen: - var listenerIndex = (ushort)instruction.Operands.ElementAt(0).Value; - var listenerType = (ListenerType)(byte)instruction.Operands.ElementAt(1).Value; - var watchIndex = (ushort)instruction.Operands.ElementAt(2).Value; - var scriptId = (uint)instruction.Operands.ElementAt(3).Value; - - var refreshOffset = uint.MaxValue; - if (instruction.OpCode is OpCode.DestructiveListen) - refreshOffset = (uint)instruction.Operands.ElementAt(4).Value; - - // What does this mean? - if (scriptId is uint.MaxValue) - break; - - var markupTypeSchema = initType.ResolveScriptId(scriptId, out var scriptOffset); - - string watch = null; - InstructionObjectSource watchSource = InstructionObjectSource.Dynamic; - switch (listenerType) - { - case ListenerType.Property: - watch = _context.ImportTables.PropertyImports[watchIndex].Name; - watchSource = InstructionObjectSource.PropertyImports; - break; - case ListenerType.Event: - watch = _context.ImportTables.EventImports[watchIndex].Name; - watchSource = InstructionObjectSource.EventImports; - break; - case ListenerType.Symbol: - watch = initType.SymbolReferenceTable[watchIndex].Symbol; - watchSource = InstructionObjectSource.SymbolReference; - break; - } - - //object handlerObj = stack.Peek(); - break; - } - } - catch (Exception ex) - { - throw new Exception($"Failed to analyze instruction `{instruction}` @ 0x{instruction.Offset:X}, {methodName}[{i}]", ex); - } - } - - return ""; - } - private static XElement GetOrCreateElement(XElement parent, XName name) { var elem = parent.Element(name);