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 ;
2025-07-17 18:12:17 -05:00
using System.Globalization ;
2025-02-01 20:31:25 -06:00
using System.Linq ;
using System.Text ;
using System.Xml ;
using System.Xml.Linq ;
namespace Microsoft.Iris.DecompXml ;
2025-07-18 00:23:23 -05:00
public partial class Decompiler
2025-02-01 20:31:25 -06:00
{
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 17:55:04 -05:00
if ( export . InitializePropertiesOffset is not uint . MaxValue )
AnalyzeMethodForInit ( export . InitializePropertiesOffset , xExport , export , name + "_prop" );
2025-07-18 00:23:23 -05:00
if ( export . InitialEvaluateOffsets is { Length : > 0 })
{
XElement xScripts = new ( _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 );
}
xExport . Add ( xScripts );
}
2025-07-17 17:55:04 -05:00
if ( export . InitializeContentOffset is not uint . MaxValue )
AnalyzeMethodForInit ( export . InitializeContentOffset , xExport , export , name + "_cont" );
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 17:55:04 -05:00
private Stack < object > AnalyzeMethodForInit ( uint startOffset , XElement elemToInit , MarkupTypeSchema initType , string methodName = "" )
2025-07-17 15:26:17 -05:00
{
2025-07-18 14:04:06 -05:00
var methodBody = _context . GetMethodBody ( startOffset ). ToArray ();
2025-07-17 15:26:17 -05:00
Stack < object > stack = new ([ elemToInit ]);
for ( int i = 0 ; i < methodBody . Length ; i ++)
{
var instruction = methodBody [ i ];
2025-07-17 17:55:04 -05:00
try
2025-07-17 15:26:17 -05:00
{
2025-07-17 17:55:04 -05:00
switch ( instruction . OpCode )
{
case OpCode . PushConstant :
var constant = _context . GetConstant ( instruction . Operands . First ());
stack . Push ( constant );
break ;
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . PushNull :
stack . Push ( null );
break ;
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . ConstructObject :
var typeToCtor = _context . GetImportedType ( instruction . Operands . ElementAt ( 0 ));
var xObj = new XElement ( _context . GetXName ( typeToCtor ));
stack . Push ( new IrisObject ( xObj , typeToCtor ));
break ;
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . LookupSymbol :
var symbolIndex = ( ushort ) instruction . Operands . ElementAt ( 0 ). Value ;
var symbol = initType . SymbolReferenceTable [ symbolIndex ];
stack . Push ( symbol );
break ;
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . MethodInvokeStatic :
var method = _context . GetImportedMethod ( instruction . Operands . First ());
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
int parameterCount = method . ParameterTypes . Length ;
object [] parameters = new object [ parameterCount ];
for ( parameterCount --; parameterCount >= 0 ; parameterCount --)
parameters [ parameterCount ] = stack . Pop ();
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
var callExpression = new IrisMethodCallExpression ( method , null , parameters . Select ( IrisExpression . Wrap ));
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
stack . Push ( callExpression );
break ;
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . PropertyGet :
case OpCode . PropertyGetPeek :
case OpCode . PropertyGetStatic :
var propToGet = _context . GetImportedProperty ( instruction . Operands . ElementAt ( 0 ));
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
var propTarget = instruction . OpCode switch
{
OpCode . PropertyGet => IrisExpression . Wrap ( stack . Pop ()),
OpCode . PropertyGetPeek => IrisExpression . Wrap ( stack . Peek ()),
_ => null ,
};
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
var propertyGetExpression = new IrisPropertyExpression ( propToGet , propTarget );
stack . Push ( propertyGetExpression );
break ;
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . PropertyInitialize :
var propertyToInit = _context . GetImportedProperty ( instruction . Operands . ElementAt ( 0 ));
var newPropValue = stack . Pop ();
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
var target = stack . Pop ();
var xTarget = ( XElement ) ToXmlFriendlyObject ( target );
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
PropertyAssignOnXElement ( xTarget , propertyToInit , IrisObject . Create ( newPropValue , propertyToInit . PropertyType , _context ));
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
stack . Push ( new IrisObject ( xTarget , propertyToInit . Owner ));
break ;
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . PropertyDictionaryAdd :
var targetDictProperty = _context . GetImportedProperty ( instruction . Operands . ElementAt ( 0 ));
var keyReference = instruction . Operands . ElementAt ( 1 );
var key = _context . GetConstant ( keyReference ). Value . ToString ();
var dictValue = stack . Pop ();
var targetInstance = ( XElement ) stack . Peek ();
PropertyDictionaryAddOnXElement ( targetInstance , targetDictProperty , IrisObject . Create ( dictValue , null , _context ), key );
break ;
case OpCode . PropertyListAdd :
var targetListProperty = _context . GetImportedProperty ( instruction . Operands . ElementAt ( 0 ));
var valueToAdd = stack . Pop ();
var targetInstance2 = ( XElement ) ToXmlFriendlyObject ( stack . Peek ());
PropertyListAddOnXElement ( targetInstance2 , targetListProperty , IrisObject . Create ( valueToAdd , null , _context ));
break ;
}
}
catch ( Exception ex )
{
throw new Exception ( $"Failed to analyze instruction `{instruction}` @ 0x{instruction.Offset:X}, {methodName}[{i}]" , ex );
2025-07-17 15:26:17 -05:00
}
}
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 17:55:04 -05:00
private object ToXmlFriendlyObject ( object obj , TypeSchema type = null )
2025-07-16 23:00:49 -05:00
{
2025-07-17 14:07:44 -05:00
if ( obj is Disassembler . RawConstantInfo rci )
2025-07-17 17:55:04 -05:00
{
2025-07-17 14:07:44 -05:00
obj = rci . Value ;
2025-07-17 17:55:04 -05:00
}
2025-07-17 14:07:44 -05:00
else if ( obj is IrisObject irisObj )
2025-07-17 17:55:04 -05:00
{
2025-07-17 14:07:44 -05:00
obj = irisObj . Object ;
2025-07-17 17:55:04 -05:00
}
2025-07-16 23:00:49 -05:00
return obj switch
{
2025-07-17 14:07:44 -05:00
string str => str ,
null => "{null}" ,
2025-07-17 17:55:04 -05:00
bool b => b ? "true" : "false" ,
IStringEncodable strEnc => strEnc . EncodeString (),
2025-07-17 14:30:19 -05:00
IrisExpression expr => '{' + expr . Decompile ( _context ) + '}' ,
2025-07-17 18:12:17 -05:00
IFormattable formattable => formattable . ToString ( null , CultureInfo . InvariantCulture ),
2025-07-17 17:55:04 -05:00
Layout . ILayout layoutObj
when Layout . PredefinedLayouts . TryConvertToString ( layoutObj , out var layoutName )
=> layoutName ,
2025-07-16 23:00:49 -05:00
2025-07-17 14:07:44 -05:00
XElement xElem => xElem ,
2025-07-17 17:55:04 -05:00
_ => SerializeToXml ( obj )
2025-07-16 23:00:49 -05:00
};
}
2025-07-17 17:55:04 -05:00
private XElement SerializeToXml ( object obj )
{
var type = Disassembler . GuessTypeSchema ( obj . GetType (), _context . LoadResult );
XElement xObj = new ( _context . GetXName ( type ));
var defaultObj = type . ConstructDefault ();
foreach ( var prop in type . Properties )
{
var defaultPropValue = prop . GetValue ( defaultObj );
var propValue = prop . GetValue ( obj );
if ( propValue == defaultPropValue || propValue . Equals ( defaultPropValue ))
continue ;
PropertyAssignOnXElement ( xObj , prop , new ( propValue , prop . PropertyType ));
}
return xObj ;
}
2025-07-17 14:07:44 -05:00
private XObject PropertyAssignOnXElement ( XElement xTarget , PropertySchema property , IrisObject value )
{
2025-07-17 17:55:04 -05:00
object xfValue = ToXmlFriendlyObject ( value . Object , value . Type );
2025-07-17 14:07:44 -05:00
switch ( xfValue )
{
case XElement xValue :
2025-07-17 18:12:17 -05:00
var xProperty = GetOrCreateElement ( xTarget , _nsUix + property . Name );
2025-07-17 14:07:44 -05:00
xProperty . Add ( xValue );
2025-07-17 17:55:04 -05:00
return xProperty ;
2025-07-17 14:07:44 -05:00
case string strValue :
2025-07-17 17:55:04 -05:00
var xAttr = new XAttribute ( property . Name , strValue );
xTarget . Add ( xAttr );
return xAttr ;
2025-07-17 14:07:44 -05:00
default :
throw new InvalidOperationException ();
}
2025-07-17 17:55:04 -05:00
}
2025-07-17 14:07:44 -05:00
2025-07-17 17:55:04 -05:00
private XElement PropertyListAddOnXElement ( XElement xTarget , PropertySchema property , IrisObject value )
{
var xDictionary = GetOrCreateElement ( xTarget , _nsUix + property . Name );
return PropertyListAddOnXElement ( xDictionary , value );
}
private XElement PropertyListAddOnXElement ( XElement xList , IrisObject value )
{
object xValue = ToXmlFriendlyObject ( value . Object , value . Type );
XElement xListEntry ;
switch ( xValue )
{
case string strValue :
xListEntry = new ( _context . GetXName ( value . Type ));
xListEntry . SetAttributeValue ( value . Type . Name , strValue );
break ;
case XElement xValueELem :
xListEntry = xValueELem ;
break ;
default :
throw new InvalidOperationException ();
}
xList . Add ( xListEntry );
return xListEntry ;
2025-07-17 14:07:44 -05:00
}
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 )
{
2025-07-17 17:55:04 -05:00
var xDictionaryEntry = PropertyListAddOnXElement ( xDictionary , value );
2025-07-17 14:07:44 -05:00
xDictionaryEntry . SetAttributeValue ( "Name" , key );
return xDictionaryEntry ;
}
2025-02-01 20:31:25 -06:00
}