diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/Ares64.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/Ares64.cs index 92d40b5b58..6ff37dec87 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/Ares64.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/Ares64.cs @@ -66,13 +66,13 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64 return ninLogoSha1 == SHA1Checksum.ComputeDigestHex(new ReadOnlySpan(rom).Slice(0x104, 48)); } - var gbRoms = lp.Roms.FindAll(r => IsGBRom(r.FileData)).Select(r => r.FileData).ToList(); + var gbRoms = lp.Roms.FindAll(r => IsGBRom(r.FileData)).Select(r => r.FileData).ToArray(); var rom = lp.Roms.Find(r => !gbRoms.Contains(r.FileData) && (char)r.RomData[0x3B] is 'N' or 'C')?.RomData; - var disk = lp.Roms.Find(r => !gbRoms.Contains(r.FileData) && (char)r.RomData[0x3B] is 'D' or 'E')?.RomData; + var (disk, error) = TransformDisk(lp.Roms.Find(r => !gbRoms.Contains(r.FileData) && r.RomData != rom)?.FileData); if (rom is null && disk is null) { - if (gbRoms.Count == 0 && lp.Roms.Count == 1) // let's just assume it's an N64 ROM then + if (gbRoms.Length == 0 && lp.Roms.Count == 1) // let's just assume it's an N64 ROM then { rom = lp.Roms[0].RomData; } @@ -113,7 +113,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64 } byte[] GetGBRomOrNull(int n) - => n < gbRoms.Count ? gbRoms[n] : null; + => n < gbRoms.Length ? gbRoms[n] : null; unsafe { @@ -122,6 +122,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64 iplPtr = ipl, romPtr = rom, diskPtr = disk, + diskErrorPtr = error, gb1RomPtr = GetGBRomOrNull(0), gb2RomPtr = GetGBRomOrNull(1), gb3RomPtr = GetGBRomOrNull(2), @@ -137,6 +138,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64 RomLen = rom?.Length ?? 0, DiskData = (IntPtr)diskPtr, DiskLen = disk?.Length ?? 0, + DiskErrorData = (IntPtr)diskErrorPtr, + DiskErrorLen = error?.Length ?? 0, Gb1RomData = (IntPtr)gb1RomPtr, Gb1RomLen = GetGBRomOrNull(0)?.Length ?? 0, Gb2RomData = (IntPtr)gb2RomPtr, @@ -284,5 +287,200 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64 Power = controller.IsPressed("Power"), }; } + + // creates an "error table" for the disk + // see https://github.com/ares-emulator/ares/blob/09aa6346c71a770fc68e9540d86156dd4769d677/mia/medium/nintendo-64dd.cpp#L102-L189 + private static byte[] CreateErrorTable(byte[] disk) + { + static bool RepeatCheck(ReadOnlySpan slice, int size, int repeat) + { + for (int i = 0; i < size; i++) + { + for (int j = 0; j < repeat; j++) + { + if (slice[i] != slice[(j * size) + i]) + { + return false; + } + } + } + + return true; + } + + var ret = new byte[1175 * 2 * 2]; + ret[12] = 0; + var systemBlocks = new[] { 0, 1, 8, 9 }; + + for (int i = 0; i < 4; i++) + { + var systemOffset = systemBlocks[i] * 0x4D08; + if (disk[systemOffset + 0x00] is not 0xE8 and not 0x22) continue; + if (disk[systemOffset + 0x01] is not 0x48 and not 0x63) continue; + if (disk[systemOffset + 0x02] is not 0xD3 and not 0xEE) continue; + if (disk[systemOffset + 0x03] is not 0x16 and not 0x56) continue; + if (disk[systemOffset + 0x04] != 0x10) continue; + if (disk[systemOffset + 0x05] <= 0x0F) continue; + if (disk[systemOffset + 0x05] >= 0x17) continue; + if (disk[systemOffset + 0x18] != 0xFF) continue; + if (disk[systemOffset + 0x19] != 0xFF) continue; + if (disk[systemOffset + 0x1A] != 0xFF) continue; + if (disk[systemOffset + 0x1B] != 0xFF) continue; + if (disk[systemOffset + 0x1C] != 0x80) continue; + if (!RepeatCheck(new(disk, systemOffset, 0xE8 * 0x55), 0xE8, 0x55)) continue; + ret[systemBlocks[i]] = 0; + ret[12] = 1; + } + + if (ret[12] == 0) + { + for (int i = 0; i < 4; i++) + { + var systemBlock = disk.Length == 0x3DEC800 ? (systemBlocks[i] + 2) ^ 1 : (systemBlocks[i] + 2); + var systemOffset = systemBlock * 0x4D08; + ret[systemBlocks[i] + 2] = 1; + if (disk[systemOffset + 0x00] != 0x00) continue; + if (disk[systemOffset + 0x01] != 0x00) continue; + if (disk[systemOffset + 0x02] != 0x00) continue; + if (disk[systemOffset + 0x03] != 0x00) continue; + if (disk[systemOffset + 0x04] != 0x10) continue; + if (disk[systemOffset + 0x05] <= 0x0F) continue; + if (disk[systemOffset + 0x05] >= 0x17) continue; + if (disk[systemOffset + 0x18] != 0xFF) continue; + if (disk[systemOffset + 0x19] != 0xFF) continue; + if (disk[systemOffset + 0x1A] != 0xFF) continue; + if (disk[systemOffset + 0x1B] != 0xFF) continue; + if (disk[systemOffset + 0x1C] != 0x80) continue; + if (!RepeatCheck(new(disk, systemOffset, 0xC0 * 0x55), 0xC0, 0x55)) continue; + ret[systemBlocks[i] + 2] = 0; + } + } + + for (int i = 0; i < 2; i++) + { + var diskIdOffset = (14 + i) * 0x4D08; + ret[14 + i] = 1; + if (!RepeatCheck(new(disk, diskIdOffset, 0xE8 * 0x55), 0xE8, 0x55)) continue; + ret[14 + i] = 0; + } + + return ret; + } + + private static readonly int[] _blockSizeTable = new[] { 0x4D08, 0x47B8, 0x4510, 0x3FC0, 0x3A70, 0x3520, 0x2FD0, 0x2A80, 0x2530 }; + private static readonly int[,] _vzoneLbaTable = new[,] + { + { 0x0124, 0x0248, 0x035A, 0x047E, 0x05A2, 0x06B4, 0x07C6, 0x08D8, 0x09EA, 0x0AB6, 0x0B82, 0x0C94, 0x0DA6, 0x0EB8, 0x0FCA, 0x10DC }, + { 0x0124, 0x0248, 0x035A, 0x046C, 0x057E, 0x06A2, 0x07C6, 0x08D8, 0x09EA, 0x0AFC, 0x0BC8, 0x0C94, 0x0DA6, 0x0EB8, 0x0FCA, 0x10DC }, + { 0x0124, 0x0248, 0x035A, 0x046C, 0x057E, 0x0690, 0x07A2, 0x08C6, 0x09EA, 0x0AFC, 0x0C0E, 0x0CDA, 0x0DA6, 0x0EB8, 0x0FCA, 0x10DC }, + { 0x0124, 0x0248, 0x035A, 0x046C, 0x057E, 0x0690, 0x07A2, 0x08B4, 0x09C6, 0x0AEA, 0x0C0E, 0x0D20, 0x0DEC, 0x0EB8, 0x0FCA, 0x10DC }, + { 0x0124, 0x0248, 0x035A, 0x046C, 0x057E, 0x0690, 0x07A2, 0x08B4, 0x09C6, 0x0AD8, 0x0BEA, 0x0D0E, 0x0E32, 0x0EFE, 0x0FCA, 0x10DC }, + { 0x0124, 0x0248, 0x035A, 0x046C, 0x057E, 0x0690, 0x07A2, 0x086E, 0x0980, 0x0A92, 0x0BA4, 0x0CB6, 0x0DC8, 0x0EEC, 0x1010, 0x10DC }, + { 0x0124, 0x0248, 0x035A, 0x046C, 0x057E, 0x0690, 0x07A2, 0x086E, 0x093A, 0x0A4C, 0x0B5E, 0x0C70, 0x0D82, 0x0E94, 0x0FB8, 0x10DC }, + }; + private static readonly int[,] _vzone2pzoneTable = new[,] + { + { 0x0, 0x1, 0x2, 0x9, 0x8, 0x3, 0x4, 0x5, 0x6, 0x7, 0xF, 0xE, 0xD, 0xC, 0xB, 0xA }, + { 0x0, 0x1, 0x2, 0x3, 0xA, 0x9, 0x8, 0x4, 0x5, 0x6, 0x7, 0xF, 0xE, 0xD, 0xC, 0xB }, + { 0x0, 0x1, 0x2, 0x3, 0x4, 0xB, 0xA, 0x9, 0x8, 0x5, 0x6, 0x7, 0xF, 0xE, 0xD, 0xC }, + { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0xC, 0xB, 0xA, 0x9, 0x8, 0x6, 0x7, 0xF, 0xE, 0xD }, + { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0xF, 0xE }, + { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0xF }, + { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8 }, + }; + private static readonly int[] _trackPhysicalTable = new[] { 0x000, 0x09E, 0x13C, 0x1D1, 0x266, 0x2FB, 0x390, 0x425, 0x091, 0x12F, 0x1C4, 0x259, 0x2EE, 0x383, 0x418, 0x48A }; + private static readonly int[] _startOffsetTable = new[] { 0x0, 0x5F15E0, 0xB79D00, 0x10801A0, 0x1523720, 0x1963D80, 0x1D414C0, 0x20BBCE0, 0x23196E0, 0x28A1E00, 0x2DF5DC0, 0x3299340, 0x36D99A0, 0x3AB70E0, 0x3E31900, 0x4149200 }; + + // converts ndd to "mame format" (which ares uses) + // see https://github.com/ares-emulator/ares/blob/09aa6346c71a770fc68e9540d86156dd4769d677/mia/medium/nintendo-64dd.cpp#L191-L302 + private static (byte[] Disk, byte[] Error) TransformDisk(byte[] disk) + { + if (disk is null) return default; + + // already in mame format + if (disk.Length == 0x435B0C0) return (disk, CreateErrorTable(disk)); + + // ndd is always 0x3DEC800 bytes apparently? + if (disk.Length != 0x3DEC800) return default; + + // need the error table for this + var errorTable = CreateErrorTable(disk); + + // "system area check" + var systemCheck = false; + var systemBlocks = new[] { 9, 8, 1, 0 }; + var systemOffset = 0; + for (int i = 0; i < 4; i++) + { + if (errorTable[12] == 0) + { + var systemBlock = systemBlocks[i] + 2; + if (errorTable[systemBlock] == 0) + { + systemCheck = true; + systemOffset = (systemBlock ^ 1) * 0x4D08; + } + } + else + { + var systemBlock = systemBlocks[i]; + if (errorTable[systemBlock] == 0) + { + systemCheck = true; + systemOffset = systemBlock * 0x4D08; + } + } + } + + if (!systemCheck) return default; + + var dataFormat = new ReadOnlySpan(disk, systemOffset, 0xE8); + + var diskIndex = 0; + var ret = new byte[0x435B0C0]; + + var type = dataFormat[5] & 0xF; + var vzone = 0; + + for (int lba = 0; lba < 0x10DC; lba++) + { + if (lba >= _vzoneLbaTable[type, vzone]) vzone++; + var pzoneCalc = _vzone2pzoneTable[type, vzone]; + var headCalc = pzoneCalc > 7; + + var lba_vzone = lba; + if (vzone > 0) lba_vzone -= _vzoneLbaTable[type, vzone - 1]; + + var trackStart = _trackPhysicalTable[headCalc ? pzoneCalc - 8 : pzoneCalc]; + var trackCalc = _trackPhysicalTable[pzoneCalc]; + if (headCalc) trackCalc -= (lba_vzone >> 1); + else trackCalc += (lba_vzone >> 1); + + var defectOffset = 0; + if (pzoneCalc > 0) defectOffset = dataFormat[8 + pzoneCalc - 1]; + var defectAmount = dataFormat[8 + pzoneCalc] - defectOffset; + + while ((defectAmount != 0) && ((dataFormat[0x20 + defectOffset] + trackStart) <= trackCalc)) + { + trackCalc++; + defectOffset++; + defectAmount--; + } + + var blockCalc = ((lba & 3) == 0 || (lba & 3) == 3) ? 0 : 1; + + var offsetCalc = _startOffsetTable[pzoneCalc]; + offsetCalc += (trackCalc - trackStart) * _blockSizeTable[headCalc ? pzoneCalc - 7 : pzoneCalc] * 2; + offsetCalc += _blockSizeTable[headCalc ? pzoneCalc - 7 : pzoneCalc] * blockCalc; + + var blockSize = _blockSizeTable[headCalc ? pzoneCalc - 7 : pzoneCalc]; + for (int i = 0; i < blockSize; i++) + { + ret[offsetCalc + i] = disk[diskIndex++]; + } + } + + return (ret, errorTable); + } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/LibAres64.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/LibAres64.cs index f4d2fe0c56..80cbb291a0 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/LibAres64.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Ares64/LibAres64.cs @@ -87,6 +87,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64 public long RomLen; public IntPtr DiskData; public long DiskLen; + public IntPtr DiskErrorData; + public long DiskErrorLen; public IntPtr Gb1RomData; public long Gb1RomLen; public IntPtr Gb2RomData;