Skip to content

Commit

Permalink
New disassembly options + allow expressions in rel tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Sammi-Husky committed Mar 3, 2023
1 parent 788dfd0 commit 2fc06e8
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 74 deletions.
169 changes: 124 additions & 45 deletions reltools/ModuleDumper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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)
Expand All @@ -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<char> chars = new List<char>();
// 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<string> chars = new List<string>();
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();
Expand Down
88 changes: 62 additions & 26 deletions reltools/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
{
Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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:");
Expand All @@ -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)
{
Expand Down
4 changes: 2 additions & 2 deletions reltools/Relocations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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})]";
}
}
}
Expand Down
15 changes: 14 additions & 1 deletion reltools/Symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 2fc06e8

Please sign in to comment.