Add debug utility to byteswap N64 roms
This commit is contained in:
parent
5b859960e9
commit
2b4d9c0c7d
src
BizHawk.Client.EmuHawk
BizHawk.Common
BizHawk.Emulation.Common
BizHawk.Tests/Common/EndiannessUtils
|
@ -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>),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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> <=> <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue