[WIP] Static analysis of XML constructors

This commit is contained in:
Yoshi Askharoun
2025-07-16 23:00:49 -05:00
parent 8e922468c5
commit 2f09fbf67c
4 changed files with 91 additions and 28 deletions
+6 -1
View File
@@ -2,7 +2,12 @@
"profiles": {
"UIXC": {
"commandName": "Project",
"commandLineArgs": "decompile C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources\\AboutDialog.uix -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources_xml -t C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources\\_DATATABLE.uib",
"commandLineArgs": "decompile E:\\Repos\\ZuneDev\\ZuneUIXTools\\test\\ABOUTDIALOG.31_48.UIB -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test",
//"commandLineArgs": "compile E:\\Repos\\ZuneDev\\ZuneUIXTools\\test\\ADDTOCOLLECTION.31_48.UIX -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test",
//"commandLineArgs": "decompile E:\\Documents\\REProj\\Zune\\Resources\\ZuneShellResources48\\ABOUTDIALOG.UIX -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Documents\\REProj\\Zune\\Resources\\ZuneShellResources48_xml -t E:\\Documents\\REProj\\Zune\\Resources\\ZuneShellResources48\\_DATATABLE.uib",
//"commandLineArgs": "decompile C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources\\AboutDialog.uix -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources_xml -t C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources\\_DATATABLE.uib",
//"commandLineArgs": "decompile C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources_xml -t C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources\\_DATATABLE.uib",
//"commandLineArgs": "decompile C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources\\_DATATABLE.UIB -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o C:\\Users\\jjask\\Documents\\Dump\\Zune\\ZuneShellResources_uixa",
+1 -1
View File
@@ -14,7 +14,7 @@ public static class SourceLanguageEx
SourceLanguage.Xml => ".uix",
SourceLanguage.Asm => ".uixa",
_ => throw new ArgumentException("Invalid source langauge", nameof(sourceLanguage)),
_ => throw new ArgumentException("Invalid source language", nameof(sourceLanguage)),
};
}
}
+2 -2
View File
@@ -314,7 +314,7 @@ public class Disassembler
}
}
private static IEnumerable<RawConstantInfo> EnumerateConstantInfo(MarkupLoadResult loadResult)
public static IEnumerable<RawConstantInfo> EnumerateConstantInfo(MarkupLoadResult loadResult)
{
var constantsTable = loadResult.ConstantsTable;
bool hasPersistList = constantsTable.PersistList is not null;
@@ -419,7 +419,7 @@ public class Disassembler
throw new NotSupportedException($"Unable to encode constant value '{constantValue}' of type '{qualifiedTypeName}'");
}
private record RawConstantInfo(int Index, TypeSchema Type, object Value)
public record RawConstantInfo(int Index, TypeSchema Type, object Value)
{
public string GenerateDefaultName() => $"const{Index:D}";
}
+82 -24
View File
@@ -1,7 +1,6 @@
using Humanizer;
using Microsoft.Iris.Asm;
using Microsoft.Iris.Asm.Models;
using Microsoft.Iris.DecompXml.Mock;
using Microsoft.Iris.Markup;
using System;
using System.Collections.Generic;
@@ -20,6 +19,7 @@ public class Decompiler
private readonly Dictionary<string, XNamespace> _namespaces;
private readonly Dictionary<string, string> _uriAliasMap;
private Instruction[] _instructions;
private Disassembler.RawConstantInfo[] _constants;
private Decompiler(MarkupLoadResult loadResult, MarkupLoadResult dataTableLoadResult = null)
{
@@ -74,6 +74,8 @@ public class Decompiler
.OfType<Instruction>()
.ToArray();
_constants = Disassembler.EnumerateConstantInfo(_loadResult).ToArray();
XNamespace nsUix = XNamespace.Get("http://schemas.microsoft.com/2007/uix");
XElement xRoot = new(nsUix + "UIX", new XAttribute("xmlns", nsUix));
@@ -88,37 +90,66 @@ public class Decompiler
new XAttribute("Name", name),
new XAttribute("Base", baseTypeName));
var initPropOffset = export.InitializePropertiesOffset;
var defaultType = export.ConstructDefault();
var initializedType = export.ConstructDefault();
try
{
export.InitializeInstance(ref initializedType);
}
catch { }
var initPropsOffset = export.InitializePropertiesOffset;
var initPropsBody = _instructions
.SkipWhile(i => i.Offset < initPropsOffset)
.OrderBy(i => i.Offset)
.TakeWhile(i => i.OpCode is not (OpCode.ReturnValue or OpCode.ReturnVoid))
.OrderBy(i => i.Offset)
.ToArray();
var propertyElements = new XElement[export.Properties.Length];
for (int i = 0; i < export.Properties.Length; i++)
Stack<XNode> xStack = new([xExport]);
Stack<Disassembler.RawConstantInfo> constantsStack = new();
for (int i = 0; i < initPropsBody.Length; i++)
{
PropertySchema property = export.Properties[i];
var typeName = GetXName(property.PropertyType);
var instruction = initPropsBody[i];
// TODO: Decode object section to get default property assignments
XElement xProperty = new(typeName,
new XAttribute("Name", property.Name));
if (instruction.OpCode is OpCode.JumpIfDictionaryContains)
{
}
else if (instruction.OpCode is OpCode.PushConstant)
{
var constantOperand = (OperandReference)instruction.Operands.First();
var constant = _constants[constantOperand.Index];
constantsStack.Push(constant);
//xStack.Push(IntoXNode(constant.Value));
}
else if (instruction.OpCode is OpCode.PushNull)
{
constantsStack.Push(null);
//xStack.Push(IntoXNode(null));
}
else if (instruction.OpCode is OpCode.PropertyDictionaryAdd)
{
var targetProperty = _loadResult.ImportTables.PropertyImports[(ushort)instruction.Operands.ElementAt(0).Value];
var defaultValue = property.GetValue(defaultType);
var initializedValue = property.GetValue(initializedType);
if (defaultValue != initializedValue)
xProperty.Add(new XAttribute("TODO_DEFAULT", initializedValue));
var keyReference = (OperandReference)instruction.Operands.ElementAt(1);
var key = _constants[keyReference.Index].Value.ToString();
propertyElements[i] = xProperty;
//var xValue = xStack.Pop();
var value = constantsStack.Pop();
var targetInstance = xStack.Peek() as XElement;
var xDictionary = GetOrCreateElement(targetInstance, nsUix + targetProperty.Name);
XElement xDictionaryEntry = new(GetXName(value.Type));
xDictionaryEntry.SetAttributeValue("Name", key);
xDictionary.Add(xDictionaryEntry);
if (value.Value is IStringEncodable valStrEnc)
{
xDictionaryEntry.SetAttributeValue(value.Type.Name, valStrEnc);
}
else
{
xDictionaryEntry.SetAttributeValue(value.Type.Name, value.Value.ToString());
}
}
}
XElement xProperties = new(nsUix + "Properties", propertyElements);
xExport.Add(xProperties);
xRoot.Add(xExport);
}
@@ -239,6 +270,33 @@ public class Decompiler
return ns + schema.Name;
}
private XElement GetOrCreateElement(XElement parent, XName name)
{
var elem = parent.Element(name);
if (elem is null)
{
elem = new XElement(name);
parent.Add(elem);
}
return elem;
}
private XNode IntoXNode(object obj)
{
if (obj is null)
return new XText("{null}");
return obj switch
{
string str => new XText(str),
IStringEncodable strEnc => new XText(strEnc.EncodeString()),
_ => throw new InvalidOperationException($"Cannot convert type '{obj.GetType().Name}' to an XNode")
};
}
[MemberNotNullWhen(true, nameof(_dataTableLoadResult))]
private bool UseSharedDataTable => _dataTableLoadResult is not null;
}