From 2fc06e8f3828d75d67a5efdf2b70238b499ecbe0 Mon Sep 17 00:00:00 2001 From: Sammi Husky Date: Thu, 2 Mar 2023 21:27:56 -0800 Subject: [PATCH] New disassembly options + allow expressions in rel tags --- reltools/ModuleDumper.cs | 169 ++++++++++++++++++++++++++++----------- reltools/Program.cs | 88 ++++++++++++++------ reltools/Relocations.cs | 4 +- reltools/Symbols.cs | 15 +++- 4 files changed, 202 insertions(+), 74 deletions(-) diff --git a/reltools/ModuleDumper.cs b/reltools/ModuleDumper.cs index 15163f5..224a6b9 100644 --- a/reltools/ModuleDumper.cs +++ b/reltools/ModuleDumper.cs @@ -13,11 +13,21 @@ namespace reltools { + [Flags] + public enum DumpOptions + { + DUMP_STRINGS = 0b00000010, + DUMP_FLOATS = 0b00000100, + USE_SECTION_NAMES = 0b00001000, + DEFAULT = 0, + } internal class ModuleDumper { - public ModuleDumper(RELNode node) + public ModuleDumper(RELNode node) : this(node, DumpOptions.DEFAULT) { } + public ModuleDumper(RELNode node, DumpOptions options) { _node = node; + Options = options; LogOutput = new StringBuilder(); _labelMap = SymbolManager.Map; _labelMap.AddSymbol(node.ModuleID, (int)node.PrologSection, new Symbol(node._prologOffset, "__entry"), true); @@ -49,7 +59,14 @@ private RELNode Node private StringBuilder LogOutput { get; set; } + private DumpOptions Options { get; set; } + #region static methods + public static string DumpRel(RELNode node, string outputFolder, DumpOptions options) + { + ModuleDumper dumper = new ModuleDumper(node, options); + return dumper.DumpRel(outputFolder); + } public static string DumpRel(RELNode node, string outputFolder) { ModuleDumper dumper = new ModuleDumper(node); @@ -138,16 +155,23 @@ public string DumpRel(string outputFolder) }; LogOutput.AppendLine($"Unpacking: {Node.FilePath}:"); + + if (Node.Sections.Length <= 0) + { + LogOutput.AppendLine("Module has no sections! Aborting"); + return LogOutput.ToString(); + } + for (int i = 0; i < Node.Sections.Length; i++) { var section = Node.Sections[i]; string sectionFN = $"Section[{i}].asm"; - // While it would be more correct to name the sections - // after what they really are, it would be confusing - // for brawl modders to sudenly switch after all this time - //if (i < SectionNames.Length) - // sectionFN = $"{SectionNames[i]}.asm"; + if ((Options & DumpOptions.USE_SECTION_NAMES) != 0) + { + if (i < SectionNames.Length) + sectionFN = $"{SectionNames[i]}.asm"; + } string sectionFP = Path.Combine(outputFolder, sectionFN); @@ -206,7 +230,7 @@ private unsafe void DumpSection(ModuleSectionNode node, string filepath) var command = node._manager.GetCommand(i); var linked = node._manager.GetLinked(i); Symbol sym = LabelMap.GetSymbol(node.ModuleID, node.Index, (uint)i * 4); - //bool needsAlign = false; + bool needsAlign = false; // if this location is referenced write the label for it if (sym != null) @@ -218,60 +242,115 @@ private unsafe void DumpSection(ModuleSectionNode node, string filepath) sb.AppendLine($"loc_{i * 4:X}:"); } - string dataStr = $" .4byte 0x{data:X8}"; - - //// is data at addr a string? - //byte tmp = *(byte*)(addr + i * 4); - //if (Util.IsAscii(tmp)) - //{ - // int x = 0; - // List chars = new List(); - // while (tmp != 0 && Util.IsAscii(tmp)) - // { - // chars.Add((char)tmp); - - // x++; - // tmp = *(byte*)(addr + x + (i * 4)); - // } - - // // threshold - // if (chars.Count >= 4) - // { - // dataStr = $" .asciz \"{new string(chars.ToArray())}\""; - // if (x % 4 > 0) - // { - // x = x.RoundUp(4); - // needsAlign = true; - // } - // i += (x / 4) - 1; // subtract one as for loop will advance the index itself - // } - //} + string dataStr = ""; + if ((Options & DumpOptions.DUMP_STRINGS) != 0) + { + if (string.IsNullOrEmpty(dataStr)) + dataStr = TryDumpString(addr, ref i, ref needsAlign); + } + if ((Options & DumpOptions.DUMP_FLOATS) != 0) + { + if (string.IsNullOrEmpty(dataStr)) + dataStr = TryDumpFloat(addr, ref i); + } + if (string.IsNullOrEmpty(dataStr)) + dataStr = $" .4byte 0x{data:X8}"; // write relocation tag to end of line if (command != null) { - Symbol relSymbol = LabelMap.GetSymbol(command._moduleID, (int)command.TargetSectionID, command.TargetOffset); - var tag = new RelTag(command, relSymbol?.Name ?? $"loc_{ command.TargetOffset:X}"); - sb.AppendLine($" {dataStr,-30}{tag}"); + uint offsetAdjusted = command.TargetOffset.RoundDown(4); + Symbol relSymbol = LabelMap.GetSymbol(command._moduleID, (int)command.TargetSectionID, offsetAdjusted); + + string name = relSymbol?.Name ?? $"loc_{ offsetAdjusted:X}"; + RelTag tag = new RelTag(command, name); + + if (command.TargetOffset != offsetAdjusted) + { + tag.Expression = $" + {command.TargetOffset % 4}"; + } + + dataStr = $"{dataStr,-30}{tag}"; } - else + + sb.AppendLine($" {dataStr}"); + + if (needsAlign) { - sb.AppendLine($" {dataStr}"); + sb.AppendLine(" .balign 4"); } - //if (needsAlign) - //{ - // sb.AppendLine(" .balign 4"); - //} - } writer.Write(sb.ToString()); } } + private unsafe string TryDumpFloat(VoidPtr addr, ref int i) + { + byte data = *(byte*)(addr + i * 4); + float f = *(bfloat*)(addr + i * 4); + + // no valid floats start with 0x0 + if (((data & 0xF0) >> 4) == 0) + return ""; + + // try to limit to sensible + // max and min values for detection. + // also don't disassemble 0 because + // it could just be an integer + if ((f > 0 || f < 0) && (f < 10000000f || f > -10000000f)) + { + return $" .float {f}"; + } + return ""; + } + private unsafe string TryDumpString(VoidPtr addr, ref int i, ref bool needsAlign) + { + string dataStr = ""; + // is data at addr a string? + byte tmp = *(byte*)(addr + i * 4); + if (Util.IsAscii(tmp)) + { + int x = 0; + List chars = new List(); + while (tmp != 0) + { + if (!Util.IsAscii(tmp)) + return ""; + + char c = (char)tmp; + + chars.Add(c.ToString()); + + x++; + tmp = *(byte*)(addr + x + (i * 4)); + } + x++; // count null terminator + + // threshold + if (chars.Count >= 4) + { + dataStr = $" .asciz \"{string.Join("", chars)}\""; + if (x % 4 > 0) + { + x = x.RoundUp(4); + needsAlign = true; + } + i += (x / 4) - 1; // subtract one as for loop will advance the index itself + } + } + return dataStr; + } private unsafe void DumpCode(ModuleSectionNode node, string filepath) { var lines = GetAsm(node); + + if(lines.Length == 0) + { + LogOutput.AppendLine("Failed to get ASM!"); + throw new Exception("Failed to disassembly target"); + } + using (var writer = File.CreateText(filepath)) { StringBuilder sb = new StringBuilder(); diff --git a/reltools/Program.cs b/reltools/Program.cs index d1e876a..e588a81 100644 --- a/reltools/Program.cs +++ b/reltools/Program.cs @@ -24,7 +24,7 @@ public class Program private static string output = null; private static APP_MODE mode = APP_MODE.DUMP; - + private static DumpOptions options = DumpOptions.DEFAULT; public static void Main(string[] args) { if (args.Length < 1 || ParseArguments(args)) @@ -62,18 +62,15 @@ private static void DumpTargets(string outputFolder) targets.Clear(); targets.AddRange(GatherFiles(folder, "*.rel", true)); } - Parallel.ForEach(targets, target => - { - Console.WriteLine(target); - RELNode node = (RELNode)NodeFactory.FromFile(null, target); - ModuleDumper d = new ModuleDumper(node); - string log = d.DumpRel(Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(target))); - //string log = ModuleDumper.DumpRel(node, Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(target)), map); - // race condition where linked branches will be - // null or 0 if we don't do this????? - node.Dispose(); - }); + foreach (var target in targets) + { + DumpTarget(target, outputFolder); + } + //Parallel.ForEach(targets, target => + //{ + //DumpTarget(target, outputFolder); + //}); } private static void BuildTargets(string outputFolder) { @@ -86,20 +83,40 @@ private static void BuildTargets(string outputFolder) targets.Clear(); targets.AddRange(GatherFiles(folder, "*.json", true)); } - + foreach(var target in targets) + { + BuildTarget(target, outputFolder); + } // run builds in parallel for speed - Parallel.ForEach(targets, target => + //Parallel.ForEach(targets, target => + //{ + //BuildTarget(target, outputFolder); + //}); + } + private static void BuildTarget(string target, string outputFolder) + { + try { - try - { - string log = ModuleBuilder.BuildRel(target, outputFolder, defsyms); - Console.Write(log); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {target}"); - } - }); + string log = ModuleBuilder.BuildRel(target, outputFolder, defsyms); + Console.Write(log); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {target}"); + } + } + private static void DumpTarget(string target, string outputFolder) + { + Console.WriteLine(target); + RELNode node = (RELNode)NodeFactory.FromFile(null, target); + ModuleDumper d = new ModuleDumper(node, options); + string log = d.DumpRel(Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(target))); + Console.Write(log); + //string log = ModuleDumper.DumpRel(node, Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(target)), map); + + // race condition where linked branches will be + // null or 0 if we don't do this????? + node.Dispose(); } private static void GenMapsForTargets(string outputFolder) { @@ -249,6 +266,15 @@ private static bool ParseArguments(string[] args) defsyms.Add(args[++i]); } break; + case "--strings": + options |= DumpOptions.DUMP_STRINGS; + break; + case "--floats": + options |= DumpOptions.DUMP_FLOATS; + break; + case "--use-section-names": + options |= DumpOptions.USE_SECTION_NAMES; + break; default: targets.Add(args[i]); break; @@ -264,7 +290,10 @@ private static bool ParseArguments(string[] args) private static void PrintHelp() { Console.WriteLine("reltools: Copyright (c) SammiHusky 2021-2022"); - Console.WriteLine("options:"); + + Console.WriteLine("\nUsage: reltools.exe [options] [targets]\n"); + + Console.WriteLine("General Options:"); Console.WriteLine(" -x, --extract [file(s)/folder]: (default)"); Console.WriteLine(" Extracts and dissassembles rel file(s) to text files."); Console.WriteLine(" -r, --rebuild:"); @@ -278,7 +307,14 @@ private static void PrintHelp() Console.WriteLine(" -D, --def [symbol]:"); Console.WriteLine(" Defines 'symbol' and passes it to the assembler when rebuilding. " + " Example: \"-D Release\" will define symbol \"Release\". Used in conditional statements(.ifdef/.endif)"); - Console.WriteLine("\nUsage: reltools.exe [options] [targets]"); + + Console.WriteLine("\nExtraction Options:"); + Console.WriteLine(" --strings:"); + Console.WriteLine(" Attempts to detect and disassemble strings for output"); + Console.WriteLine(" --floats:"); + Console.WriteLine(" Attempts to detect and disassemble floating point numbers for output"); + Console.WriteLine(" --use-section-names:"); + Console.WriteLine(" Uses standard section naming scheme instead of 'Section[x]'"); } private static string[] GatherFiles(string directory, string pattern, bool recursive) { diff --git a/reltools/Relocations.cs b/reltools/Relocations.cs index 2f8e5d8..b7d49ca 100644 --- a/reltools/Relocations.cs +++ b/reltools/Relocations.cs @@ -115,11 +115,11 @@ public override string ToString() string module = SymbolManager.GetModuleNameFromID(this.TargetModule); if (!string.IsNullOrWhiteSpace(module)) { - return $"[{this.Command}(\"{module}\", {this.TargetSection}, \"{this.Label}\")]"; + return $"[{this.Command}(\"{module}\", {this.TargetSection}, \"{this.Label}\"{this.Expression})]"; } else { - return $"[{this.Command}({this.TargetModule}, {this.TargetSection}, \"{this.Label}\")]"; + return $"[{this.Command}({this.TargetModule}, {this.TargetSection}, \"{this.Label}\"{this.Expression})]"; } } } diff --git a/reltools/Symbols.cs b/reltools/Symbols.cs index f327e24..687a141 100644 --- a/reltools/Symbols.cs +++ b/reltools/Symbols.cs @@ -251,7 +251,20 @@ public string GetModuleNameFromID(uint ID) } internal static class SymbolManager { - public static SymbolMap Map; + public static SymbolMap Map + { + get + { + if (_map == null) + { + _map = new SymbolMap(); + } + + return _map; + } + set { _map = value; } + } + private static SymbolMap _map; public static string MangleSymbol(int moduleID, int sectionID, string symbol) { string module = moduleID.ToString();