Implement some decomp of listeners

This commit is contained in:
Yoshi Askharoun
2025-08-03 22:06:22 -05:00
parent cf2b553ba0
commit 32c2b97bf0
3 changed files with 136 additions and 108 deletions
+10
View File
@@ -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<string, XNamespace> _namespaces;
private readonly HashSet<string> _usedNamespacePrefixes;
private readonly Dictionary<string, string> _uriAliasMap;
@@ -54,6 +56,8 @@ internal class DecompileContext
GenerateNamespaces();
_scriptMap = [];
_instructions = ObjectSection.Decode(_loadResult.ObjectSection)
.OfType<Instruction>()
.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<SyntaxTree> GetScriptContents(TypeSchema type) => _scriptMap.Where(k => k.Key.Item1 == type.UniqueId).Select(k => k.Value);
public IEnumerable<KeyValuePair<string, XNamespace>> GetUsedNamespaces()
{
return _namespaces
+101
View File
@@ -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<object> 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<MemberAccessExpressionSyntax>()
.Select(m => $"{m.Expression}.{m.Name}")
.ToArray();
SyntaxNode node = scriptRoot
.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>()
.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<StatementSyntax> statements)
{
return SyntaxTree(
+25 -108
View File
@@ -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<MarkupMethodSchema>())
{
var xScripts = GetOrCreateElement(xExport, _nsUix + "Scripts");
foreach (var method in export.Methods.OfType<MarkupMethodSchema>())
{
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<object> 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);