2025-07-17 14:30:19 -05:00
using Microsoft.Iris.Asm ;
2025-07-17 00:46:05 -05:00
using Microsoft.Iris.DecompXml.Mock ;
2025-02-01 20:31:25 -06:00
using Microsoft.Iris.Markup ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Xml ;
using System.Xml.Linq ;
namespace Microsoft.Iris.DecompXml ;
public class Decompiler
{
2025-07-17 14:30:19 -05:00
private static readonly XNamespace _nsUix = XNamespace . Get ( "http://schemas.microsoft.com/2007/uix" );
private readonly DecompileContext _context ;
2025-02-01 20:31:25 -06:00
2025-07-17 14:30:19 -05:00
private Decompiler ( DecompileContext context )
2025-02-01 20:31:25 -06:00
{
2025-07-17 14:30:19 -05:00
_context = context ;
2025-02-01 20:31:25 -06:00
}
public static Decompiler Load ( LoadResult loadResult , LoadResult dataTableLoadResult = null )
{
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 ));
2025-07-17 14:30:19 -05:00
if ( dataTableLoadResult is not MarkupLoadResult and not null )
2025-02-01 20:31:25 -06:00
throw new ArgumentException ( $"Data table must be markup. Expected '{nameof(MarkupLoadResult)}', got '{dataTableLoadResult?.GetType().Name}'." , nameof ( dataTableLoadResult ));
2025-07-17 14:30:19 -05:00
DecompileContext context = new ( markupLoadResult , ( MarkupLoadResult ) dataTableLoadResult );
return new ( context );
2025-02-01 20:31:25 -06:00
}
public XDocument Decompile ()
{
2025-07-17 14:07:44 -05:00
XElement xRoot = new ( _nsUix + "UIX" , new XAttribute ( "xmlns" , _nsUix ));
2025-02-01 20:31:25 -06:00
2025-07-17 14:30:19 -05:00
foreach ( var export in _context . LoadResult . ExportTable . Cast < MarkupTypeSchema >())
2025-02-01 20:31:25 -06:00
{
var name = export . Name ;
var baseType = export . MarkupTypeBase ;
2025-07-17 14:30:19 -05:00
var baseTypeName = _context . GetQualifiedName ( baseType );
2025-02-01 20:31:25 -06:00
2025-07-17 14:07:44 -05:00
XElement xExport = new ( _nsUix + export . MarkupType . ToString (),
2025-02-01 20:31:25 -06:00
new XAttribute ( "Name" , name ),
new XAttribute ( "Base" , baseTypeName ));
2025-07-17 15:26:17 -05:00
AnalyzeMethodForInit ( export . InitializePropertiesOffset , xExport );
2025-02-01 20:31:25 -06:00
xRoot . Add ( xExport );
}
XDocument xDoc = new ( xRoot );
// Add all namespaces to root element
2025-07-17 14:30:19 -05:00
var xNamespaceDeclarations = _context . GetUsedNamespaces ()
. Select ( p => new XAttribute ( XNamespace . Xmlns + p . Key , p . Value ))
. ToArray ();
2025-02-01 20:31:25 -06:00
2025-07-17 14:30:19 -05:00
xDoc . Root . Add ( xNamespaceDeclarations );
2025-02-01 20:31:25 -06:00
return xDoc ;
}
public string DecompileToSource ()
{
var xmlDoc = Decompile ();
XmlWriterSettings writerSettings = new ()
{
Indent = true ,
NamespaceHandling = NamespaceHandling . OmitDuplicates ,
};
StringBuilder sb = new ();
using ( XmlWriter writer = XmlWriter . Create ( sb , writerSettings ))
{
xmlDoc . WriteTo ( writer );
}
return sb . ToString ();
}
2025-07-17 15:26:17 -05:00
private Stack < object > AnalyzeMethodForInit ( uint startOffset , XElement elemToInit )
{
var methodBody = _context . GetMethodBody ( startOffset );
Stack < object > stack = new ([ elemToInit ]);
for ( int i = 0 ; i < methodBody . Length ; i ++)
{
var instruction = methodBody [ i ];
if ( instruction . OpCode is OpCode . PushConstant )
{
var constant = _context . GetConstant ( instruction . Operands . First ());
stack . Push ( constant );
}
else if ( instruction . OpCode is OpCode . PushNull )
{
stack . Push ( null );
}
else if ( instruction . OpCode is OpCode . ConstructObject )
{
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 = _context . GetImportedMethod ( instruction . Operands . First ());
int parameterCount = method . ParameterTypes . Length ;
object [] parameters = new object [ parameterCount ];
for ( parameterCount --; parameterCount >= 0 ; parameterCount --)
parameters [ parameterCount ] = stack . Pop ();
var callExpression = new IrisMethodCallExpression ( method , null , parameters . Select ( IrisExpression . ToExpression ));
stack . Push ( callExpression );
}
else if ( instruction . OpCode is OpCode . PropertyInitialize )
{
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 , _context ));
stack . Push ( new IrisObject ( xTarget , property . Owner ));
}
else if ( instruction . OpCode is OpCode . PropertyDictionaryAdd )
{
var targetProperty = _context . GetImportedProperty ( instruction . Operands . ElementAt ( 0 ));
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 , _context ), key );
}
}
return stack ;
}
2025-07-17 14:07:44 -05:00
private static XElement GetOrCreateElement ( XElement parent , XName name )
2025-07-16 23:00:49 -05:00
{
var elem = parent . Element ( name );
if ( elem is null )
{
elem = new XElement ( name );
parent . Add ( elem );
}
return elem ;
}
2025-07-17 14:07:44 -05:00
private object ToXmlFriendlyObject ( object obj )
2025-07-16 23:00:49 -05:00
{
2025-07-17 14:07:44 -05:00
if ( obj is Disassembler . RawConstantInfo rci )
obj = rci . Value ;
else if ( obj is IrisObject irisObj )
obj = irisObj . Object ;
2025-07-16 23:00:49 -05:00
return obj switch
{
2025-07-17 14:07:44 -05:00
string str => str ,
IStringEncodable strEnc => strEnc . EncodeString (),
null => "{null}" ,
2025-07-17 14:30:19 -05:00
IrisExpression expr => '{' + expr . Decompile ( _context ) + '}' ,
2025-07-16 23:00:49 -05:00
2025-07-17 14:07:44 -05:00
XElement xElem => xElem ,
_ => throw new InvalidOperationException ( $"Cannot convert type '{obj.GetType().Name}' to an XML object" )
2025-07-16 23:00:49 -05:00
};
}
2025-07-17 14:07:44 -05:00
private XObject PropertyAssignOnXElement ( XElement xTarget , PropertySchema property , IrisObject value )
{
object xfValue = ToXmlFriendlyObject ( value . Object );
XObject xObject ;
switch ( xfValue )
{
case XElement xValue :
var xProperty = GetOrCreateElement ( xTarget , property . Name );
xProperty . Add ( xValue );
xObject = xProperty ;
break ;
case string strValue :
xObject = new XAttribute ( property . Name , strValue );
break ;
default :
throw new InvalidOperationException ();
}
xTarget . Add ( xObject );
return xObject ;
}
private XElement PropertyDictionaryAddOnXElement ( XElement xTarget , PropertySchema property , IrisObject value , string key )
{
var xDictionary = GetOrCreateElement ( xTarget , _nsUix + property . Name );
return PropertyDictionaryAddOnXElement ( xDictionary , value , key );
}
private XElement PropertyDictionaryAddOnXElement ( XElement xDictionary , IrisObject value , string key )
{
object xValue = ToXmlFriendlyObject ( value . Object );
XElement xDictionaryEntry ;
switch ( xValue )
{
case string strValue :
2025-07-17 14:30:19 -05:00
xDictionaryEntry = new ( _context . GetXName ( value . Type ));
2025-07-17 14:07:44 -05:00
xDictionaryEntry . SetAttributeValue ( value . Type . Name , strValue );
break ;
case XElement xValueELem :
xDictionaryEntry = xValueELem ;
break ;
default :
throw new InvalidOperationException ();
}
xDictionaryEntry . SetAttributeValue ( "Name" , key );
xDictionary . Add ( xDictionaryEntry );
return xDictionaryEntry ;
}
2025-02-01 20:31:25 -06:00
}