Files
ZuneUIXTools/libs/UIX.DecompXml/DecompileContext.cs
T

196 lines
7.1 KiB
C#
Raw Normal View History

2025-07-17 14:30:19 -05:00
using Humanizer;
using Microsoft.Iris.Asm;
using Microsoft.Iris.Asm.Models;
using Microsoft.Iris.Markup;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Xml.Linq;
namespace Microsoft.Iris.DecompXml;
internal class DecompileContext
{
private readonly MarkupLoadResult _loadResult;
private readonly MarkupLoadResult _dataTableLoadResult;
private readonly Dictionary<string, XNamespace> _namespaces;
private readonly HashSet<string> _usedNamespacePrefixes;
private readonly Dictionary<string, string> _uriAliasMap;
private Instruction[] _instructions;
private Disassembler.RawConstantInfo[] _constants;
public DecompileContext(MarkupLoadResult loadResult, MarkupLoadResult dataTableLoadResult = null)
{
_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 = [];
_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}'");
}
GenerateNamespaces();
_instructions = ObjectSection.Decode(_loadResult.ObjectSection)
.OfType<Instruction>()
.ToArray();
_constants = Disassembler.EnumerateConstantInfo(_loadResult).ToArray();
}
public Instruction[] Instructions => _instructions;
public MarkupLoadResult LoadResult => _loadResult;
public MarkupImportTables ImportTables => _loadResult.ImportTables;
[MemberNotNullWhen(true, nameof(_dataTableLoadResult))]
private bool UseSharedDataTable => _dataTableLoadResult is not null;
public Disassembler.RawConstantInfo GetConstant(Operand op) => _constants[((OperandReference)op).Index];
public TypeSchema GetImportedType(Operand op) => ImportTables.TypeImports[(ushort)op.Value];
public MethodSchema GetImportedMethod(Operand op) => ImportTables.MethodImports[(ushort)op.Value];
public PropertySchema GetImportedProperty(Operand op) => ImportTables.PropertyImports[(ushort)op.Value];
2025-07-20 02:03:16 -05:00
public ConstructorSchema GetImportedConstructor(Operand op) => ImportTables.ConstructorImports[(ushort)op.Value];
2025-07-18 14:04:06 -05:00
public IEnumerable<Instruction> GetMethodBody(uint startOffset)
2025-07-17 15:26:17 -05:00
{
2025-07-18 14:04:06 -05:00
foreach (var instruction in Instructions.SkipWhile(i => i.Offset < startOffset))
{
yield return instruction;
if (instruction.OpCode is OpCode.ReturnValue or OpCode.ReturnVoid)
yield break;
}
2025-07-17 15:26:17 -05:00
}
2025-07-17 14:30:19 -05:00
public IEnumerable<KeyValuePair<string, XNamespace>> GetUsedNamespaces()
{
return _namespaces
.Where(p => _usedNamespacePrefixes.Contains(p.Key) && !string.IsNullOrEmpty(p.Key));
}
public string MapNamespaceToPrefix(string uri)
{
_uriAliasMap.TryGetValue(uri, out string prefix);
_usedNamespacePrefixes.Add(prefix);
return prefix;
}
public QualifiedTypeName GetQualifiedName(TypeSchema schema)
{
var prefix = MapNamespaceToPrefix(schema.Owner.Uri);
return new(prefix, schema.Name);
}
public XName GetXName(TypeSchema schema)
{
var prefix = MapNamespaceToPrefix(schema.Owner.Uri);
var ns = _namespaces[prefix ?? ""];
2025-07-20 02:03:16 -05:00
// Strip potential generic type parameters
var name = schema.Name;
var genericIndex = name.IndexOf('`');
if (genericIndex > 0)
name = name[..genericIndex];
return ns + name;
2025-07-17 14:30:19 -05:00
}
private void GenerateNamespaces()
{
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);
}
}
}
}