Add debug utility to byteswap N64 roms

This commit is contained in:
YoshiRulz 2022-10-11 09:59:04 +10:00
parent 5b859960e9
commit 2b4d9c0c7d
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
5 changed files with 132 additions and 0 deletions
src
BizHawk.Client.EmuHawk
BizHawk.Common
BizHawk.Emulation.Common
BizHawk.Tests/Common/EndiannessUtils

View File

@ -56,6 +56,7 @@ namespace BizHawk.Client.EmuHawk
{
DropDownItems =
{
new DebugVSystemChildItem(N64RomByteswapToolForm.TOOL_NAME, OpenTool<N64RomByteswapToolForm>) { RequiresLoadedRom = false },
new DebugVSystemChildItem(N64VideoSettingsFuzzToolForm.TOOL_NAME, OpenTool<N64VideoSettingsFuzzToolForm>),
},
},

View File

@ -0,0 +1,96 @@
#nullable enable
using System;
using System.IO;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.WinForms.Controls;
namespace BizHawk.Client.EmuHawk.ForDebugging
{
internal sealed class N64RomByteswapToolForm : ToolFormBase
{
public const string TOOL_NAME = "Manual N64 ROM Byteswapping Tool";
protected override string WindowTitleStatic
=> TOOL_NAME;
public N64RomByteswapToolForm()
{
SzTextBoxEx txtBaseFile = new() { Size = new(224, 23) };
void ChooseBaseFile()
{
using OpenFileDialog ofd = new() { InitialDirectory = Config!.PathEntries.RomAbsolutePath() };
this.ShowDialogAsChild(ofd);
txtBaseFile.Text = ofd.FileName;
}
ComboBox comboFormats = new()
{
Items = { "rare little-endian (.n64)", "byte-swapped (.v64)", "native (.z64)" },
SelectedIndex = 2,
Size = new(160, 23),
};
SzTextBoxEx txtTargetFile = new() { Size = new(224, 23) };
void ChooseTargetFile()
{
using SaveFileDialog sfd = new()
{
DefaultExt = comboFormats.SelectedIndex switch
{
0 => "n64",
1 => "v64",
2 => "z64",
_ => string.Empty
},
InitialDirectory = Config!.PathEntries.RomAbsolutePath(),
};
this.ShowDialogAsChild(sfd);
txtTargetFile.Text = sfd.FileName;
}
void DoConvert()
{
try
{
var rom = File.ReadAllBytes(txtBaseFile.Text);
switch (comboFormats.SelectedIndex) // can't have Action<Span<byte>> (System.Buffers.SpanAction isn't suitable) or I'd be able to have a tiny switch expr >:( --yoshi
{
case 0:
N64RomByteswapper.ToN64LittleEndian(rom);
break;
case 1:
N64RomByteswapper.ToV64ByteSwapped(rom);
break;
case 2:
default:
N64RomByteswapper.ToZ64Native(rom);
break;
}
File.WriteAllBytes(txtTargetFile.Text, rom);
this.ModalMessageBox($"wrote {txtTargetFile.Text}\n{SHA1Checksum.ComputePrefixedHex(rom)}");
}
catch (Exception e)
{
this.ModalMessageBox($"caught {e.GetType().Name}:\n{e}");
}
}
ClientSize = new(320, 200);
SuspendLayout();
Controls.Add(new SingleColumnFLP
{
Controls =
{
new LabelEx { Text = "convert" },
new SingleRowFLP { Controls = { txtBaseFile, GenControl.Button("(browse)", width: 75, ChooseBaseFile) } },
new LabelEx { Text = "from <autodetected> (assumes .z64 on fail)" },
new SingleRowFLP { Controls = { new LabelEx { Text = "to" }, comboFormats } },
new SingleRowFLP { Controls = { txtTargetFile, GenControl.Button("(browse)", width: 75, ChooseTargetFile) } },
GenControl.Button("--> convert", width: 75, DoConvert),
}
});
ResumeLayout();
}
}
}

View File

@ -45,5 +45,13 @@ namespace BizHawk.Common
for (var i = 0; i < ints.Length; i++) ints[i] = BinaryPrimitives.ReverseEndianness(ints[i]);
#endif
}
/// <summary>swaps pairs of 16-bit words in-place: <c>0xAABBIIJJPPQQYYZZ</c> &lt;=> <c>0xIIJJAABBYYZZPPQQ</c></summary>
public static void MutatingShortSwap32(Span<byte> a)
{
// no need to optimise this further, it's only used in the manual byteswap tool and only between the two less common formats
MutatingByteSwap32(a); // calls can be in either order, though this order ensures the length-mod-4 assert is hit immediately
MutatingByteSwap16(a);
}
}
}

View File

@ -11,6 +11,22 @@ namespace BizHawk.Emulation.Common
/// <remarks>not actually magic, just always the same in commercial carts? https://n64brew.dev/wiki/ROM_Header works all the same</remarks>
private static readonly byte[] MAGIC_BYTES = { 0x80, 0x37, 0x12, 0x40 };
/// <summary>ensures <paramref name="rom"/> is in the rare little-endian (<c>.n64</c>) format, mutating it in-place if necessary</summary>
public static void ToN64LittleEndian(Span<byte> rom)
{
if (rom[0] == MAGIC_BYTES[0]) EndiannessUtils.MutatingByteSwap32(rom); // native (.z64)
else if (rom[0] == MAGIC_BYTES[1]) EndiannessUtils.MutatingShortSwap32(rom); // byte-swapped (.v64)
// else already rare little-endian .n64
}
/// <summary>ensures <paramref name="rom"/> is in the byte-swapped (<c>.v64</c>) format, mutating it in-place if necessary</summary>
public static void ToV64ByteSwapped(Span<byte> rom)
{
if (rom[0] == MAGIC_BYTES[0]) EndiannessUtils.MutatingByteSwap16(rom); // native (.z64)
else if (rom[0] == MAGIC_BYTES[3]) EndiannessUtils.MutatingShortSwap32(rom); // rare little-endian .n64
// else already byte-swapped (.v64)
}
/// <summary>ensures <paramref name="rom"/> is in the native (<c>.z64</c>) format, mutating it in-place if necessary</summary>
public static void ToZ64Native(Span<byte> rom)
{

View File

@ -32,5 +32,16 @@ namespace BizHawk.Tests.Common
EndiannessUtils.MutatingByteSwap32(a);
Assert.IsTrue(a.SequenceEqual(b));
}
[TestMethod]
public void TestShortSwap32()
{
var b = new byte[] { 0x45, 0x67, 0x01, 0x23, 0xCD, 0xEF, 0x89, 0xAB }.AsSpan();
var a = b.ToArray().AsSpan();
EndiannessUtils.MutatingShortSwap32(a);
Assert.IsTrue(a.SequenceEqual(expected));
EndiannessUtils.MutatingShortSwap32(a);
Assert.IsTrue(a.SequenceEqual(b));
}
}
}