mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
Move common decompiler state to new class
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
using Humanizer;
|
||||
using Microsoft.Iris.Asm;
|
||||
using Microsoft.Iris.Asm.Models;
|
||||
using Microsoft.Iris.Asm;
|
||||
using Microsoft.Iris.DecompXml.Mock;
|
||||
using Microsoft.Iris.Markup;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
@@ -15,35 +12,12 @@ namespace Microsoft.Iris.DecompXml;
|
||||
|
||||
public class Decompiler
|
||||
{
|
||||
internal readonly MarkupLoadResult _loadResult;
|
||||
private readonly MarkupLoadResult _dataTableLoadResult;
|
||||
private readonly XNamespace _nsUix = XNamespace.Get("http://schemas.microsoft.com/2007/uix");
|
||||
private readonly Dictionary<string, XNamespace> _namespaces;
|
||||
private readonly HashSet<string> _usedNamespacePrefixes;
|
||||
private readonly Dictionary<string, string> _uriAliasMap;
|
||||
private Instruction[] _instructions;
|
||||
private Disassembler.RawConstantInfo[] _constants;
|
||||
private static readonly XNamespace _nsUix = XNamespace.Get("http://schemas.microsoft.com/2007/uix");
|
||||
private readonly DecompileContext _context;
|
||||
|
||||
private Decompiler(MarkupLoadResult loadResult, MarkupLoadResult dataTableLoadResult = null)
|
||||
private Decompiler(DecompileContext context)
|
||||
{
|
||||
_loadResult = loadResult;
|
||||
|
||||
_dataTableLoadResult = dataTableLoadResult
|
||||
?? loadResult.BinaryDataTable?.SharedDependenciesTableWithBinaryDataTable?.FirstOrDefault() as MarkupLoadResult;
|
||||
|
||||
_uriAliasMap = new()
|
||||
{
|
||||
[_loadResult.Uri] = "me",
|
||||
["http://schemas.microsoft.com/2007/uix"] = null
|
||||
};
|
||||
|
||||
_namespaces = new()
|
||||
{
|
||||
["me"] = "Me",
|
||||
[""] = "http://schemas.microsoft.com/2007/uix"
|
||||
};
|
||||
|
||||
_usedNamespacePrefixes = [];
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public static Decompiler Load(LoadResult loadResult, LoadResult dataTableLoadResult = null)
|
||||
@@ -51,51 +25,30 @@ public class Decompiler
|
||||
if (loadResult is not MarkupLoadResult markupLoadResult)
|
||||
throw new ArgumentException($"Disassembly can only be performed on markup. Expected '{nameof(MarkupLoadResult)}', got '{loadResult?.GetType().Name}'.", nameof(loadResult));
|
||||
|
||||
if (dataTableLoadResult is MarkupLoadResult dataTableMarkupLoadResult)
|
||||
return new(markupLoadResult, dataTableMarkupLoadResult);
|
||||
|
||||
if (dataTableLoadResult is not null)
|
||||
if (dataTableLoadResult is not MarkupLoadResult and not null)
|
||||
throw new ArgumentException($"Data table must be markup. Expected '{nameof(MarkupLoadResult)}', got '{dataTableLoadResult?.GetType().Name}'.", nameof(dataTableLoadResult));
|
||||
|
||||
return new(markupLoadResult);
|
||||
|
||||
DecompileContext context = new(markupLoadResult, (MarkupLoadResult)dataTableLoadResult);
|
||||
return new(context);
|
||||
}
|
||||
|
||||
public XDocument Decompile()
|
||||
{
|
||||
_loadResult.FullLoad();
|
||||
if (_loadResult.Status == LoadResultStatus.Error)
|
||||
throw new Exception($"Failed to load '{_loadResult.ErrorContextUri}'");
|
||||
|
||||
if (UseSharedDataTable)
|
||||
{
|
||||
_dataTableLoadResult.FullLoad();
|
||||
if (_dataTableLoadResult.Status == LoadResultStatus.Error)
|
||||
throw new Exception($"Failed to load '{_dataTableLoadResult.ErrorContextUri}'");
|
||||
}
|
||||
|
||||
GetNamespaces();
|
||||
|
||||
_instructions = ObjectSection.Decode(_loadResult.ObjectSection)
|
||||
.OfType<Instruction>()
|
||||
.ToArray();
|
||||
|
||||
_constants = Disassembler.EnumerateConstantInfo(_loadResult).ToArray();
|
||||
|
||||
XElement xRoot = new(_nsUix + "UIX", new XAttribute("xmlns", _nsUix));
|
||||
|
||||
foreach (var export in _loadResult.ExportTable.Cast<MarkupTypeSchema>())
|
||||
foreach (var export in _context.LoadResult.ExportTable.Cast<MarkupTypeSchema>())
|
||||
{
|
||||
var name = export.Name;
|
||||
|
||||
var baseType = export.MarkupTypeBase;
|
||||
var baseTypeName = GetQualifiedName(baseType);
|
||||
var baseTypeName = _context.GetQualifiedName(baseType);
|
||||
|
||||
XElement xExport = new(_nsUix + export.MarkupType.ToString(),
|
||||
new XAttribute("Name", name),
|
||||
new XAttribute("Base", baseTypeName));
|
||||
|
||||
var initPropsOffset = export.InitializePropertiesOffset;
|
||||
var initPropsBody = _instructions
|
||||
var initPropsBody = _context.Instructions
|
||||
.SkipWhile(i => i.Offset < initPropsOffset)
|
||||
.OrderBy(i => i.Offset)
|
||||
.TakeWhile(i => i.OpCode is not (OpCode.ReturnValue or OpCode.ReturnVoid))
|
||||
@@ -115,8 +68,7 @@ public class Decompiler
|
||||
}
|
||||
else if (instruction.OpCode is OpCode.PushConstant)
|
||||
{
|
||||
var constantOperand = (OperandReference)instruction.Operands.First();
|
||||
var constant = _constants[constantOperand.Index];
|
||||
var constant = _context.GetConstant(instruction.Operands.First());
|
||||
stack.Push(constant);
|
||||
}
|
||||
else if (instruction.OpCode is OpCode.PushNull)
|
||||
@@ -125,13 +77,13 @@ public class Decompiler
|
||||
}
|
||||
else if (instruction.OpCode is OpCode.ConstructObject)
|
||||
{
|
||||
var type = _loadResult.ImportTables.TypeImports[(ushort)instruction.Operands.ElementAt(0).Value];
|
||||
var xObj = new XElement(GetXName(type));
|
||||
var type = _context.GetImportedType(instruction.Operands.ElementAt(0));
|
||||
var xObj = new XElement(_context.GetXName(type));
|
||||
stack.Push(new IrisObject(xObj, type));
|
||||
}
|
||||
else if (instruction.OpCode is OpCode.MethodInvokeStatic)
|
||||
{
|
||||
var method = _loadResult.ImportTables.MethodImports[(ushort)instruction.Operands.First().Value];
|
||||
var method = _context.GetImportedMethod(instruction.Operands.First());
|
||||
|
||||
int parameterCount = method.ParameterTypes.Length;
|
||||
object[] parameters = new object[parameterCount];
|
||||
@@ -144,28 +96,28 @@ public class Decompiler
|
||||
}
|
||||
else if (instruction.OpCode is OpCode.PropertyInitialize)
|
||||
{
|
||||
var property = _loadResult.ImportTables.PropertyImports[(ushort)instruction.Operands.ElementAt(0).Value];
|
||||
var property = _context.GetImportedProperty(instruction.Operands.ElementAt(0));
|
||||
var value = stack.Pop();
|
||||
|
||||
var target = stack.Pop();
|
||||
var xTarget = (XElement)ToXmlFriendlyObject(target);
|
||||
|
||||
PropertyAssignOnXElement(xTarget, property, IrisObject.Create(value, property.PropertyType, this));
|
||||
PropertyAssignOnXElement(xTarget, property, IrisObject.Create(value, property.PropertyType, _context));
|
||||
|
||||
stack.Push(new IrisObject(xTarget, property.Owner));
|
||||
}
|
||||
else if (instruction.OpCode is OpCode.PropertyDictionaryAdd)
|
||||
{
|
||||
var targetProperty = _loadResult.ImportTables.PropertyImports[(ushort)instruction.Operands.ElementAt(0).Value];
|
||||
var targetProperty = _context.GetImportedProperty(instruction.Operands.ElementAt(0));
|
||||
|
||||
var keyReference = (OperandReference)instruction.Operands.ElementAt(1);
|
||||
var key = _constants[keyReference.Index].Value.ToString();
|
||||
var keyReference = instruction.Operands.ElementAt(1);
|
||||
var key = _context.GetConstant(keyReference).Value.ToString();
|
||||
|
||||
var value = stack.Pop();
|
||||
|
||||
var targetInstance = stack.Peek() as XElement;
|
||||
|
||||
PropertyDictionaryAddOnXElement(targetInstance, targetProperty, IrisObject.Create(value, null, this), key);
|
||||
PropertyDictionaryAddOnXElement(targetInstance, targetProperty, IrisObject.Create(value, null, _context), key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,11 +127,11 @@ public class Decompiler
|
||||
XDocument xDoc = new(xRoot);
|
||||
|
||||
// Add all namespaces to root element
|
||||
var xNamespaceDeclarations = _namespaces
|
||||
.Where(p => _usedNamespacePrefixes.Contains(p.Key) && !string.IsNullOrEmpty(p.Key))
|
||||
.Select(p => new XAttribute(XNamespace.Xmlns + p.Key, p.Value));
|
||||
var xNamespaceDeclarations = _context.GetUsedNamespaces()
|
||||
.Select(p => new XAttribute(XNamespace.Xmlns + p.Key, p.Value))
|
||||
.ToArray();
|
||||
|
||||
xDoc.Root.Add(xNamespaceDeclarations.ToArray());
|
||||
xDoc.Root.Add(xNamespaceDeclarations);
|
||||
|
||||
return xDoc;
|
||||
}
|
||||
@@ -202,96 +154,6 @@ public class Decompiler
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void GetNamespaces()
|
||||
{
|
||||
foreach (var typeImport in _loadResult.ImportTables.TypeImports)
|
||||
{
|
||||
var typeName = typeImport.Name;
|
||||
var uri = typeImport.Owner.Uri;
|
||||
if (!_uriAliasMap.TryGetValue(uri, out var namespacePrefix))
|
||||
{
|
||||
var baseNamespacePrefix = uri;
|
||||
|
||||
var ownerUri = uri;
|
||||
var schemeLength = uri.IndexOf("://");
|
||||
if (schemeLength > 0)
|
||||
{
|
||||
var scheme = uri[..schemeLength];
|
||||
if (scheme == "assembly")
|
||||
{
|
||||
// Assume 'host' is an assembly name and path represents a C# namespace
|
||||
var path = uri[(schemeLength + 3)..];
|
||||
var nsIndex = path.IndexOf('/');
|
||||
if (nsIndex >= 0)
|
||||
{
|
||||
var importedNamespace = path[(nsIndex + 1)..];
|
||||
baseNamespacePrefix = importedNamespace.Split('.', '/', '\\', '!')[^1];
|
||||
|
||||
System.Reflection.AssemblyName assemblyName = new(path[..nsIndex]);
|
||||
uri = $"assembly://{assemblyName.Name}/{importedNamespace}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// No namespace was specified, assume we're importing the whole assembly
|
||||
System.Reflection.AssemblyName assemblyName = new(path);
|
||||
baseNamespacePrefix = assemblyName.Name;
|
||||
|
||||
uri = $"assembly://{assemblyName.Name}/";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume the URI represents a file,
|
||||
// skip the extension
|
||||
baseNamespacePrefix = uri.Split('.', '/', '\\', '!')[^2];
|
||||
}
|
||||
}
|
||||
|
||||
baseNamespacePrefix = baseNamespacePrefix.Camelize();
|
||||
namespacePrefix = baseNamespacePrefix;
|
||||
|
||||
// Some imports, such as assembly imports, require additional parsing
|
||||
// and might change the URI that actually gets imported.
|
||||
if (!_namespaces.ContainsValue(uri))
|
||||
{
|
||||
// Prevent similar imports from generating the same prefix
|
||||
int duplicateCount = 0;
|
||||
while (_namespaces.ContainsKey(namespacePrefix))
|
||||
namespacePrefix = $"{baseNamespacePrefix}{++duplicateCount}";
|
||||
|
||||
_namespaces.Add(namespacePrefix, uri);
|
||||
}
|
||||
|
||||
if (!_uriAliasMap.ContainsKey(uri))
|
||||
_uriAliasMap.Add(uri, namespacePrefix);
|
||||
|
||||
// Ensure that the original, un-normalized URI is saved too
|
||||
if (!_uriAliasMap.ContainsKey(ownerUri))
|
||||
_uriAliasMap.Add(ownerUri, namespacePrefix);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal string MapNamespaceToPrefix(string uri)
|
||||
{
|
||||
_uriAliasMap.TryGetValue(uri, out string prefix);
|
||||
_usedNamespacePrefixes.Add(prefix);
|
||||
return prefix;
|
||||
}
|
||||
|
||||
internal QualifiedTypeName GetQualifiedName(TypeSchema schema)
|
||||
{
|
||||
var prefix = MapNamespaceToPrefix(schema.Owner.Uri);
|
||||
return new(prefix, schema.Name);
|
||||
}
|
||||
|
||||
private XName GetXName(TypeSchema schema)
|
||||
{
|
||||
var prefix = MapNamespaceToPrefix(schema.Owner.Uri);
|
||||
var ns = _namespaces[prefix ?? ""];
|
||||
return ns + schema.Name;
|
||||
}
|
||||
|
||||
private static XElement GetOrCreateElement(XElement parent, XName name)
|
||||
{
|
||||
var elem = parent.Element(name);
|
||||
@@ -317,7 +179,7 @@ public class Decompiler
|
||||
string str => str,
|
||||
IStringEncodable strEnc => strEnc.EncodeString(),
|
||||
null => "{null}",
|
||||
IrisExpression expr => '{' + expr.Decompile(this) + '}',
|
||||
IrisExpression expr => '{' + expr.Decompile(_context) + '}',
|
||||
|
||||
XElement xElem => xElem,
|
||||
|
||||
@@ -366,7 +228,7 @@ public class Decompiler
|
||||
switch (xValue)
|
||||
{
|
||||
case string strValue:
|
||||
xDictionaryEntry = new(GetXName(value.Type));
|
||||
xDictionaryEntry = new(_context.GetXName(value.Type));
|
||||
xDictionaryEntry.SetAttributeValue(value.Type.Name, strValue);
|
||||
break;
|
||||
case XElement xValueELem:
|
||||
@@ -381,7 +243,4 @@ public class Decompiler
|
||||
|
||||
return xDictionaryEntry;
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(true, nameof(_dataTableLoadResult))]
|
||||
private bool UseSharedDataTable => _dataTableLoadResult is not null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user