Files
ZuneUIXTools/UIXC/Commands/DecompileCommand.cs
T

177 lines
5.9 KiB
C#
Raw Normal View History

2024-02-22 11:13:31 -06:00
using Microsoft.Iris.Asm;
using Microsoft.Iris.Debug;
using Microsoft.Iris.Markup;
2024-06-13 17:30:27 -05:00
using Microsoft.Iris.Session;
2024-02-22 11:13:31 -06:00
using Spectre.Console;
using Spectre.Console.Cli;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
2024-06-13 17:30:27 -05:00
using System.Runtime.Loader;
2024-02-22 11:13:31 -06:00
namespace UIXC.Commands;
public class DecompileCommand : Command<DecompileCommand.Settings>
{
public override int Execute(CommandContext context, Settings settings)
{
if (settings.Inputs is null || settings.Inputs.Length <= 0)
{
AnsiConsole.MarkupLine("[red]Missing arguments: must specify a UIB file to decompile.[/]");
return -1;
}
if (settings.Verbose)
{
// Configure error and warning messages
TraceSettings.Current.SetCategoryLevel(TraceCategory.Markup, byte.MaxValue);
TraceSettings.Current.SetCategoryLevel(TraceCategory.MarkupCompiler, byte.MaxValue);
TraceSettings.Current.SetCategoryLevel(TraceCategory.MarkupDebug, byte.MaxValue);
TraceSettings.Current.OnWriteLine += (line) =>
{
AnsiConsole.MarkupLineInterpolated($"[grey]{line}[/]");
};
}
2024-06-13 17:30:27 -05:00
ErrorManager.OnErrors += (errors) =>
{
foreach (ErrorRecord error in errors)
{
var (color, type) = error.Warning
? ("yellow", "WARN") : ("red", "ERROR");
AnsiConsole.MarkupLineInterpolated($"[{color}]{type}: {error.Message}[/]");
}
};
var searchPaths = settings.IncludeDirectories.Prepend(Environment.CurrentDirectory).ToArray();
2024-06-13 17:30:27 -05:00
foreach (var givenPath in settings.Assemblies ?? [])
{
try
{
var assemblyPath = ResolvePath(givenPath, searchPaths);
2024-06-13 17:30:27 -05:00
_ = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
}
catch (Exception ex)
{
AnsiConsole.WriteException(ex);
if (!AnsiConsole.Confirm("Do you want to continue?", false))
2024-06-13 17:30:27 -05:00
return -1;
}
}
foreach (var redirectOption in settings.ImportRedirects ?? [])
{
var parts = redirectOption.Split('>');
MarkupSystem.AddImportRedirect(parts[0], parts[1]);
}
2024-02-22 11:13:31 -06:00
MarkupSystem.Startup(true);
bool success = false;
if (settings.Language == SourceLanguage.Asm)
{
foreach (var input in settings.Inputs)
{
try
{
2024-07-07 15:37:20 -05:00
if (!ResolvePath(input, searchPaths, out var inputPath))
inputPath = input;
2024-07-07 15:37:20 -05:00
if (!inputPath.Contains("://"))
inputPath = $"file://{inputPath}";
var uibLoadResult = MarkupSystem.Load(inputPath, (uint)Random.Shared.Next());
2024-02-22 11:13:31 -06:00
uibLoadResult.FullLoad();
if (uibLoadResult.Status != LoadResultStatus.Success)
throw new Exception($"Failed to load UIB source ({uibLoadResult.ErrorContextUri})");
var disassembler = Disassembler.Load(uibLoadResult as MarkupLoadResult);
var asm = disassembler.Write();
File.WriteAllText(GetOutputPath(settings, input), asm);
success = true;
}
catch (Exception ex)
{
AnsiConsole.WriteException(ex);
success = false;
}
}
}
else if (settings.Language == SourceLanguage.Xml)
{
AnsiConsole.MarkupLine("[red]UIX XML is not currently a supported decompilation target.[/]");
}
else
{
AnsiConsole.MarkupLine($"[red]{settings.Language} is not currently a supported decompilation target.[/]");
}
if (!success)
{
AnsiConsole.MarkupLine("[red]Decompilation failed.[/]");
return -1;
}
else
{
AnsiConsole.MarkupLine("[green]Decompilation finished.[/]");
AnsiConsole.MarkupLine($"[green]Output saved to '{settings.OutputDir}'.[/]");
2024-02-22 11:13:31 -06:00
return 0;
}
}
2024-06-13 17:30:27 -05:00
2024-02-22 11:13:31 -06:00
private static string GetOutputPath(Settings settings, string inputFilePath)
{
2024-07-07 15:37:20 -05:00
var fileName = Path.GetFileName(inputFilePath);//.Replace('!', '/');
var outputFile = Path.Combine(settings.OutputDir, Path.ChangeExtension(fileName, settings.Language.GetExtension()));
var outputDir = Path.GetDirectoryName(outputFile);
Directory.CreateDirectory(outputDir);
return outputFile;
}
private static string ResolvePath(string givenPath, ICollection<string> searchPaths)
{
if (!ResolvePath(givenPath, searchPaths, out var assemblyPath))
throw new FileNotFoundException(null, givenPath);
return assemblyPath;
}
private static bool ResolvePath(string givenPath, ICollection<string> searchPaths, [NotNullWhen(true)] out string? absolutePath)
{
if (Path.IsPathFullyQualified(givenPath))
{
absolutePath = givenPath;
return Path.Exists(absolutePath);
}
absolutePath = null;
if (searchPaths.Count == 0)
return false;
foreach (var searchPath in searchPaths)
{
absolutePath = Path.GetFullPath(givenPath, searchPath);
if (Path.Exists(absolutePath))
return true;
}
return false;
2024-02-22 11:13:31 -06:00
}
2024-06-13 17:30:27 -05:00
public sealed class Settings : CompilerSettings
2024-02-22 11:13:31 -06:00
{
[Description("The UIB files to decompile.")]
[CommandArgument(0, "[inputs]")]
public required string[] Inputs { get; init; }
// TODO: Enum options
[Description("The language to decompile to.")]
[CommandOption("-l|--lang <lang>")]
public SourceLanguage Language { get; init; } = SourceLanguage.Asm;
}
}