mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
Implement some decomp of listeners
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user