2025-07-20 02:03:16 -05:00
using Microsoft.CodeAnalysis.CSharp.Syntax ;
using Microsoft.Iris.Asm ;
using Microsoft.Iris.Debug.Data ;
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 ;
2025-07-18 19:57:50 -05:00
2025-07-17 14:07:44 -05:00
XElement xExport = new ( _nsUix + export . MarkupType . ToString (),
2025-07-18 19:57:15 -05:00
new XAttribute ( "Name" , name ));
var baseType = export . MarkupTypeBase ;
if ( baseType is not null )
{
2025-07-18 19:57:50 -05:00
var baseTypeName = _context . GetQualifiedName ( baseType );
2025-07-18 19:57:15 -05:00
xExport . SetAttributeValue ( "Base" , baseTypeName );
}
2025-02-01 20:31:25 -06:00
2025-07-20 02:03:16 -05:00
if ( export is ClassTypeSchema classExport )
{
if ( classExport . IsShared )
xExport . SetAttributeValue ( "Shared" , true );
}
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 20:01:49 -05:00
if ( export . InitializeLocalsInputOffset is not uint . MaxValue )
AnalyzeMethodForInit ( export . InitializeLocalsInputOffset , xExport , export , name + "_locl" );
2025-07-18 00:23:23 -05:00
if ( export . InitialEvaluateOffsets is { Length : > 0 })
{
2025-07-20 02:03:16 -05:00
var xScripts = GetOrCreateElement ( xExport , _nsUix + "Scripts" );
2025-07-18 00:23:23 -05:00
foreach ( var offset in export . InitialEvaluateOffsets )
{
var syntaxTree = DecompileScript ( offset , export );
var scriptText = FormatScript ( syntaxTree );
XElement xScript = new ( _nsUix + "Script" , scriptText );
xScripts . Add ( xScript );
}
2025-07-20 02:03:16 -05:00
}
2025-07-18 00:23:23 -05:00
2025-07-21 01:46:38 -05:00
if ( export . FinalEvaluateOffsets is { Length : > 0 })
{
throw new NotImplementedException ();
}
if ( export . Methods is { Length : > 0 })
{
var xScripts = GetOrCreateElement ( xExport , _nsUix + "Scripts" );
foreach ( var method in export . Methods . OfType < MarkupMethodSchema >())
{
var methodSyntax = DecompileMethodDeclaration ( method , export );
var scriptText = FormatScript ( methodSyntax . SyntaxTree );
XElement xScript = new ( _nsUix + "Script" , scriptText );
xScripts . Add ( xScript );
}
}
2025-07-20 02:03:16 -05:00
if ( export . RefreshGroupOffsets is { Length : > 0 })
{
var xScripts = GetOrCreateElement ( xExport , _nsUix + "Scripts" );
foreach ( var offset in export . RefreshGroupOffsets )
{
var scriptText = AnalyzeRefreshMethod ( offset , export , $"{name}_rfsh_0x{offset:X}" );
XElement xScript = new ( _nsUix + "Script" , scriptText );
xScripts . Add ( xScript );
}
2025-07-18 00:23:23 -05:00
}
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 ,
2025-07-20 02:03:16 -05:00
Encoding = Encoding . UTF8 ,
2025-02-01 20:31:25 -06:00
};
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-08-02 21:35:07 -05:00
case OpCode . ConstructFromString :
var typeFromStringSchema = _context . GetImportedType ( instruction . Operands . ElementAt ( 0 ));
var fromString = _context . GetConstant ( instruction . Operands . ElementAt ( 1 ));
var fromStringObj = IrisObject . Create ( fromString , typeFromStringSchema , _context );
stack . Push ( fromStringObj );
break ;
2025-07-17 17:55:04 -05:00
case OpCode . LookupSymbol :
var symbolIndex = ( ushort ) instruction . Operands . ElementAt ( 0 ). Value ;
2025-07-20 02:03:16 -05:00
stack . Push ( initType . SymbolReferenceTable [ symbolIndex ]);
2025-07-17 17:55:04 -05:00
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 ();
2025-07-20 09:44:26 -05:00
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 ();
2025-07-18 19:57:50 -05:00
var dictValueType = initType . InheritableSymbolsTable ?
. FirstOrDefault ( s => s . Name == key )?
. Type ;
2025-07-17 17:55:04 -05:00
2025-07-20 09:44:26 -05:00
var targetInstance = ( XElement ) stack . Peek ();
2025-07-17 17:55:04 -05:00
2025-07-18 19:57:50 -05:00
PropertyDictionaryAddOnXElement ( targetInstance , targetDictProperty , IrisObject . Create ( dictValue , dictValueType , _context ), key );
2025-07-17 17:55:04 -05:00
break ;
case OpCode . PropertyListAdd :
var valueToAdd = stack . Pop ();
2025-07-20 02:03:16 -05:00
TypeSchema valueToAddType = null ;
if ( valueToAdd is SymbolReference symRef )
{
valueToAddType = initType . InheritableSymbolsTable ?
. FirstOrDefault ( s => s . Name == symRef . Symbol )?
. Type ;
}
var valueToAddObj = IrisObject . Create ( valueToAdd , valueToAddType , _context );
2025-07-17 17:55:04 -05:00
var targetInstance2 = ( XElement ) ToXmlFriendlyObject ( stack . Peek ());
2025-07-20 02:03:16 -05:00
var targetListPropertyIndex = ( ushort ) instruction . Operands . First (). Value ;
if ( targetListPropertyIndex != ushort . MaxValue )
{
var targetListProperty = _context . ImportTables . PropertyImports [ targetListPropertyIndex ];
if ( valueToAddObj . Type is null )
{
var valueRuntimeType = targetListProperty . PropertyType . RuntimeType . GetGenericArguments (). FirstOrDefault ();
valueToAddType = _context . ImportTables . TypeImports . FirstOrDefault ( t => t . RuntimeType == valueRuntimeType );
valueToAddObj = valueToAddObj with { Type = valueToAddType };
}
PropertyListAddOnXElement ( targetInstance2 , targetListProperty , valueToAddObj );
}
else
{
PropertyListAddOnXElement ( targetInstance2 , valueToAddObj );
}
2025-07-17 17:55:04 -05:00
break ;
2025-07-18 20:01:49 -05:00
2025-07-20 02:03:16 -05:00
case OpCode . InitializeInstance :
case OpCode . JumpIfDictionaryContains :
2025-07-18 20:01:49 -05:00
case OpCode . ConstructListenerStorage :
2025-07-20 02:03:16 -05:00
// These instructions are inconsequential for determining how objects are initialized
break ;
case OpCode . ConstructObjectParam :
2025-07-20 02:09:49 -05:00
case OpCode . PushThis :
2025-07-20 02:03:16 -05:00
case OpCode . MethodInvoke :
case OpCode . MethodInvokePeek :
case OpCode . MethodInvokeStatic :
case OpCode . MethodInvokePushLastParam :
case OpCode . MethodInvokeStaticPushLastParam :
case OpCode . PropertyGet :
case OpCode . PropertyGetPeek :
case OpCode . PropertyGetStatic :
case OpCode . Operation :
2025-08-02 21:34:17 -05:00
case OpCode . TypeOf :
case OpCode . ConvertType :
2025-07-20 02:03:16 -05:00
// These instructions only appear in initializers as inline expressions
if (! TryDecompileExpression ( instruction , stack ))
throw new NotImplementedException ();
break ;
case not OpCode . ReturnVoid :
Console . WriteLine ( $"Unsupported instruction: {instruction}" );
2025-07-18 20:01:49 -05:00
break ;
2025-07-17 17:55:04 -05:00
}
}
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-20 02:03:16 -05:00
private string AnalyzeRefreshMethod ( uint startOffset , MarkupTypeSchema initType , string methodName = "" )
{
var methodBody = _context . GetMethodBody ( startOffset ). ToArray ();
Stack < object > stack = new ();
for ( int i = 0 ; i < methodBody . Length ; i ++)
{
var instruction = methodBody [ i ];
try
{
switch ( instruction . OpCode )
{
case OpCode . Listen :
case OpCode . DestructiveListen :
var listenerIndex = ( ushort ) instruction . Operands . ElementAt ( 0 ). Value ;
var listenerType = ( ListenerType )( byte ) instruction . Operands . ElementAt ( 1 ). Value ;
var watchIndex = ( ushort ) instruction . Operands . ElementAt ( 2 ). Value ;
var scriptId = ( uint ) instruction . Operands . ElementAt ( 3 ). Value ;
var refreshOffset = uint . MaxValue ;
if ( instruction . OpCode is OpCode . DestructiveListen )
refreshOffset = ( uint ) instruction . Operands . ElementAt ( 4 ). Value ;
2025-07-21 01:46:38 -05:00
var markupTypeSchema = initType . ResolveScriptId ( scriptId , out var scriptOffset );
2025-07-20 02:03:16 -05:00
string watch = null ;
InstructionObjectSource watchSource = InstructionObjectSource . Dynamic ;
switch ( listenerType )
{
case ListenerType . Property :
watch = _context . ImportTables . PropertyImports [ watchIndex ]. Name ;
watchSource = InstructionObjectSource . PropertyImports ;
break ;
case ListenerType . Event :
watch = _context . ImportTables . EventImports [ watchIndex ]. Name ;
watchSource = InstructionObjectSource . EventImports ;
break ;
case ListenerType . Symbol :
watch = initType . SymbolReferenceTable [ watchIndex ]. Symbol ;
watchSource = InstructionObjectSource . SymbolReference ;
break ;
}
//object handlerObj = stack.Peek();
break ;
}
}
catch ( Exception ex )
{
throw new Exception ( $"Failed to analyze instruction `{instruction}` @ 0x{instruction.Offset:X}, {methodName}[{i}]" , ex );
}
}
return "" ;
}
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-18 19:57:50 -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 )
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-20 02:03:16 -05:00
ExpressionSyntax expr => FormatInlineExpression ( expr ),
SymbolReference symRef => '{' + symRef . Symbol + '}' ,
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-18 19:57:50 -05:00
object xfValue = ToXmlFriendlyObject ( value );
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-20 02:03:16 -05:00
// Flatten collections
if ( typeof ( System . Collections . IList ). IsAssignableFrom ( value . Type . RuntimeType )
|| typeof ( System . Collections . IDictionary ). IsAssignableFrom ( value . Type . RuntimeType ))
{
xProperty . Add ( xValue . Elements ());
}
else
{
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 )
{
2025-07-18 19:57:50 -05:00
var xList = GetOrCreateElement ( xTarget , _nsUix + property . Name );
2025-07-20 02:03:16 -05:00
return PropertyListAddOnXElement ( xList , value );
}
2025-07-17 17:55:04 -05:00
2025-07-20 02:03:16 -05:00
private XElement PropertyListAddOnXElement ( XElement xList , IrisObject value )
{
2025-07-18 19:57:50 -05:00
object xValue = ToXmlFriendlyObject ( value );
2025-07-17 17:55:04 -05:00
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
}
2025-07-20 02:03:16 -05:00
private XElement PropertyDictionaryAddOnXElement ( XElement xDictionary , IrisObject value , string key )
2025-07-17 14:07:44 -05:00
{
2025-07-20 02:03:16 -05:00
var xDictionaryEntry = PropertyListAddOnXElement ( xDictionary , value );
2025-07-17 14:07:44 -05:00
xDictionaryEntry . SetAttributeValue ( "Name" , key );
return xDictionaryEntry ;
}
2025-07-20 02:03:16 -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 );
}
2025-02-01 20:31:25 -06:00
}