diff --git a/libs/UIX.Asm/Disassembler.cs b/libs/UIX.Asm/Disassembler.cs new file mode 100644 index 0000000..a3bfe66 --- /dev/null +++ b/libs/UIX.Asm/Disassembler.cs @@ -0,0 +1,142 @@ +using Humanizer; +using Microsoft.Iris.Markup; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.Iris.Asm; + +public class Disassembler +{ + private readonly MarkupLoadResult _loadResult; + + private Disassembler(MarkupLoadResult loadResult) + { + _loadResult = loadResult; + } + + public static Disassembler Load(MarkupLoadResult loadResult) => new(loadResult); + + public IEnumerable GetImports() + { + foreach (var typeImport in _loadResult.ImportTables.TypeImports) + { + var uri = typeImport.Owner.Uri; + + // Skip default UIX namespace + if (uri == "http://schemas.microsoft.com/2007/uix") + continue; + + string name = uri; + var schemeLength = uri.IndexOf("://"); + if (schemeLength > 0) + { + var scheme = uri[..schemeLength]; + if (scheme == "assembly") + { + // Assume the URI represents a C# namespace + name = uri.Split('.', '/', '\\')[^1]; + } + else + { + // Assume the URI represents a file, + // skip the extension + name = uri.Split('.', '/', '\\')[^2]; + } + } + + yield return new NamespaceImport(uri, name.Camelize()); + }; + } + + public IEnumerable GetBody() + { + var reader = _loadResult.ObjectSection; + + while (reader.CurrentOffset < reader.Size) + { + var opCode = (OpCode)reader.ReadByte(); + + switch (opCode) + { + case OpCode.ConstructObject: + // COBJ + yield return new Instruction("COBJ", [new(reader.ReadUInt16())]); + break; + + case OpCode.ConstructObjectIndirect: + // COBI + yield return new Instruction("COBI", [new(reader.ReadUInt16())]); + break; + + case OpCode.ConstructObjectParam: + // COBP + yield return new Instruction("COBP", [new(reader.ReadUInt16()), new(reader.ReadUInt16())]); + break; + + case OpCode.ConstructFromString: + // CSTR + yield return new Instruction("CSTR", [new(reader.ReadUInt16()), new(reader.ReadUInt16())]); + break; + + case OpCode.ConstructFromBinary: + // CBIN + var cbinTypeIndex = reader.ReadUInt16(); + TypeSchema cbinTypeSchema = _loadResult.ImportTables.TypeImports[cbinTypeIndex]; + object cbinObject = cbinTypeSchema.DecodeBinary(reader); + + yield return new Instruction("CBIN", [new(cbinTypeIndex), new(cbinObject)]); + break; + + // TODO + + case OpCode.PropertyInitialize: + // PINI + yield return new Instruction("PINI", [new(reader.ReadUInt16())]); + break; + + case OpCode.PropertyInitializeIndirect: + // PINII + yield return new Instruction("PINII", [new(reader.ReadUInt16())]); + break; + + // TODO + + case OpCode.PushConstant: + // PSHC + yield return new Instruction("PSHC", [new(reader.ReadUInt16())]); + break; + + // TODO + + case OpCode.ReturnValue: + // RET + yield return new Instruction("RET", []); + break; + + case OpCode.ReturnVoid: + // RETV + yield return new Instruction("RETV", []); + break; + } + } + + yield break; + } + + public string Write() + { + _loadResult.Load(LoadPass.DeclareTypes); + _loadResult.Load(LoadPass.PopulatePublicModel); + _loadResult.Load(LoadPass.Full); + _loadResult.Load(LoadPass.Done); + + List imports = new(GetImports()); + List body = new(GetBody()); + + Program asmProgram = new(imports, body); + return asmProgram.ToString(); + } +} diff --git a/libs/UIX.Asm/UIX.Asm.csproj b/libs/UIX.Asm/UIX.Asm.csproj index 0f8a1f7..6957de7 100644 --- a/libs/UIX.Asm/UIX.Asm.csproj +++ b/libs/UIX.Asm/UIX.Asm.csproj @@ -8,6 +8,7 @@ + diff --git a/libs/UIX.Test/Assembly.cs b/libs/UIX.Test/Assembly.cs index 3fdd78f..7d15d32 100644 --- a/libs/UIX.Test/Assembly.cs +++ b/libs/UIX.Test/Assembly.cs @@ -1,6 +1,7 @@ using Sprache; using Microsoft.Iris.Asm; using Xunit.Abstractions; +using Microsoft.Iris.Markup; namespace UIX.Test; @@ -11,8 +12,8 @@ public class Assembly(ITestOutputHelper output) { const string code = """ -.import Me as me -.import assembly://UIX/Microsoft.Iris as iris +.import-ns Me as me +.import-ns assembly://UIX/Microsoft.Iris as iris main: COBJ 2 @@ -32,4 +33,23 @@ main: Assert.Equal(2, ast.Imports.Count()); Assert.Equal(9, ast.Body.Count()); } + + [Fact] + public async Task Disassemble() + { + const string path = @"D:\Repos\yoshiask\ZuneUIXTools\test\testA.uix"; + string uri = @"file://" + path; + + //FileResource resource = new(uri, path, true); + //resource.Acquire(); + + MarkupSystem.Startup(true); + var sourceLoadResult = MarkupSystem.ResolveLoadResult(uri, MarkupSystem.RootIslandId); + var dis = Disassembler.Load(sourceLoadResult as MarkupLoadResult); + + var newPath = Path.ChangeExtension(path, ".uixa"); + File.WriteAllText(newPath, dis.Write()); + + MarkupSystem.Shutdown(); + } } \ No newline at end of file