diff --git a/libs/UIX.DecompXml/Mock/IrisExpression.cs b/libs/UIX.DecompXml/Mock/IrisExpression.cs index b28d7ed..93be8dd 100644 --- a/libs/UIX.DecompXml/Mock/IrisExpression.cs +++ b/libs/UIX.DecompXml/Mock/IrisExpression.cs @@ -1,9 +1,12 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Iris.Asm; +using Microsoft.Iris.Asm.Models; using Microsoft.Iris.Markup; using System; using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.Iris.DecompXml.Mock; @@ -40,11 +43,30 @@ internal class IrisExpression if (TryMapPredefinedType(type, out var kind)) return PredefinedType(Token(kind)); - return IdentifierName(context.GetQualifiedName(type).ToString()); + var typeName = context.GetQualifiedName(type); + + if (type.Name.StartsWith("SingletonModelItem")) + { + // Special handling for this weird wrapper type + var genericName = ParseGenericType(typeName); + var innerFullTypeName = genericName.TypeArgumentList.Arguments[0].ToString(); + + // Resolve namespace to prefix + var nsEndIndex = innerFullTypeName.LastIndexOf('.'); + var innerTypeName = innerFullTypeName[(nsEndIndex + 1)..]; + var ns = innerFullTypeName[..nsEndIndex]; + + var innerType = context.ImportTables.TypeImports + .Where(t => t.Name == innerTypeName || t.AlternateName == innerTypeName) + .FirstOrDefault(t => (t.Owner as AssemblyLoadResult)?.Namespace == ns); + + typeName = context.GetQualifiedName(innerType); + } + + return IdentifierName(typeName.ToString()); } - public static IdentifierNameSyntax ToSyntax(SymbolReference symbolRef) => - IdentifierName(symbolRef.Symbol); + public static IdentifierNameSyntax ToSyntax(SymbolReference symbolRef) => IdentifierName(symbolRef.Symbol); public static ExpressionSyntax ToSyntax(object obj, TypeSchema type, uint offset, DecompileContext context) { @@ -90,4 +112,22 @@ internal class IrisExpression return false; } + + private static GenericNameSyntax ParseGenericType(QualifiedTypeName typeName) + { + Regex rxGeneric = new(@"([A-z_:]\w*)`(\d+)\[(.+)\]"); + var genericMatch = rxGeneric.Match(typeName.TypeName); + if (!genericMatch.Success) + throw new ArgumentException("Invalid generic"); + + var unqualifiedTypeName = genericMatch.Groups[1].Value; + var typeArgCount = int.Parse(genericMatch.Groups[2].Value); + var typeArgNames = genericMatch.Groups[3].Value + .Split(',') + .Select(IdentifierName); + + // TODO: Handle nested generics + var trimmedTypeName = $"{typeName.NamespacePrefix}:{unqualifiedTypeName}"; + return GenericName(Identifier(trimmedTypeName), TypeArgumentList([.. typeArgNames])); + } } diff --git a/libs/UIX.DecompXml/Mock/IrisObject.cs b/libs/UIX.DecompXml/Mock/IrisObject.cs index 7a0ccb0..077abca 100644 --- a/libs/UIX.DecompXml/Mock/IrisObject.cs +++ b/libs/UIX.DecompXml/Mock/IrisObject.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Iris.Asm; using Microsoft.Iris.Asm.Models; using Microsoft.Iris.Markup; +using System.Collections.Generic; using System.Linq; namespace Microsoft.Iris.DecompXml.Mock; @@ -52,7 +53,7 @@ internal record IrisObject(object Object, TypeSchema Type) if (expr is MemberAccessExpressionSyntax memberAccessExpression) { - // TODO: Handle member access on instance methods + // TODO: Handle member access on instances try { var sourceExpr = (IdentifierNameSyntax)memberAccessExpression.Expression; @@ -90,6 +91,35 @@ internal record IrisObject(object Object, TypeSchema Type) _ => throw new System.NotImplementedException(), }; } + else if (expr is InvocationExpressionSyntax invocationExpression) + { + // TODO: Handle invocation of instance methods + try + { + var sourceExpr = invocationExpression.Expression; + + List names = []; + while (sourceExpr is MemberAccessExpressionSyntax innerSourceExpr) + { + names.Add(innerSourceExpr.Name); + sourceExpr = innerSourceExpr.Expression; + } + + var sourceTypeName = QualifiedTypeName.Parse(sourceExpr.ToString()); + var sourceType = ctx.GetImportedType(sourceTypeName); + + var memberName = names[^1].ToString(); + + // Try to disambiguate overloads using number of parameters + var parameterCount = invocationExpression.ArgumentList.Arguments.Count; + + var method = sourceType.Methods.FirstOrDefault( + m => m.Name == memberName && m.ParameterTypes.Length == parameterCount); + + return method?.ReturnType; + } + catch { } + } return null; } diff --git a/test/NOWPLAYINGLAND_decomp.uix b/test/NOWPLAYINGLAND_decomp.uix index d7520d1..e43867f 100644 --- a/test/NOWPLAYINGLAND_decomp.uix +++ b/test/NOWPLAYINGLAND_decomp.uix @@ -5,7 +5,7 @@ - + @@ -55,7 +55,7 @@ bool IsQuickMix() - + @@ -126,8 +126,8 @@ UI.CreateInterestOnFocus = false; ShowMPContent.Value = nowPlayingLand:NowPlayingPersistentState.ShowZunePassTracks; ]]> @@ -936,8 +936,8 @@ else