From d6b77b37f65d1d38980a3d7b5de36768dccd4b81 Mon Sep 17 00:00:00 2001 From: kalimag Date: Sat, 7 Sep 2024 09:03:01 +0200 Subject: [PATCH 1/3] Fix non-PSX disc hashing somewhat - Don't hash generated (empty) lead-in/lead-out tracks - Limit to track length, not absolute LBA of next track --- src/BizHawk.Emulation.DiscSystem/DiscHasher.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs index c606696633f..fd874223fc2 100644 --- a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs +++ b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs @@ -87,12 +87,15 @@ public string OldHash() { var buffer = new byte[512 * 2352]; var dsr = new DiscSectorReader(disc); - foreach (var track in disc.Session1.Tracks) + // don't hash generated lead-in and lead-out tracks + for (int i = 1; i < disc.Session1.Tracks.Count - 1; i++) { + var track = disc.Session1.Tracks[i]; + if (track.IsAudio) continue; - var lba_len = Math.Min(track.NextTrack.LBA, 512); + var lba_len = Math.Min(track.NextTrack.LBA - track.LBA, 512); for (var s = 0; s < 512 && s < lba_len; s++) dsr.ReadLBA_2352(track.LBA + s, buffer, s * 2352); From 795d8075781bd2292a05168466bc2cdf3b7734be Mon Sep 17 00:00:00 2001 From: kalimag Date: Mon, 9 Sep 2024 01:04:12 +0200 Subject: [PATCH 2/3] Use `InformationTrackCount` instead of `Tracks.Count` --- src/BizHawk.Emulation.DiscSystem/DiscHasher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs index fd874223fc2..4db040c6e28 100644 --- a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs +++ b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs @@ -88,7 +88,7 @@ public string OldHash() var buffer = new byte[512 * 2352]; var dsr = new DiscSectorReader(disc); // don't hash generated lead-in and lead-out tracks - for (int i = 1; i < disc.Session1.Tracks.Count - 1; i++) + for (int i = 1; i <= disc.Session1.InformationTrackCount; i++) { var track = disc.Session1.Tracks[i]; From cb2c62966446b75ae50cdb698721946132a5ec65 Mon Sep 17 00:00:00 2001 From: kalimag Date: Tue, 10 Sep 2024 21:03:58 +0200 Subject: [PATCH 3/3] Add Jaguar CD hashing Reuse RetroAchievements implementation, move into `DiscHasher` --- src/BizHawk.Client.Common/RomLoader.cs | 4 +- .../RetroAchievements.GameVerification.cs | 113 +--------------- .../DiscHasher.cs | 124 ++++++++++++++++++ 3 files changed, 130 insertions(+), 111 deletions(-) diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index b686365d61f..d7d67634544 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -234,9 +234,7 @@ private GameInfo MakeGameFromDisc(Disc disc, string ext, string name) // TODO - use more sophisticated IDer var discType = new DiscIdentifier(disc).DetectDiscType(); var discHasher = new DiscHasher(disc); - var discHash = discType == DiscType.SonyPSX - ? discHasher.Calculate_PSX_BizIDHash() - : discHasher.OldHash(); + var discHash = discHasher.CalculateBizHash(discType); var game = Database.CheckDatabase(discHash); if (game is not null) return game; diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs index 7155fb10b4f..7441efc94d9 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs @@ -166,115 +166,12 @@ int GetFileSector(string filename, out int filesize) buffer.AddRange(new ArraySegment(buf2048, 0, 512)); break; case ConsoleID.JaguarCD: - // we want to hash the second session of the disc - if (disc.Sessions.Count > 2) + var discHasher = new DiscHasher(disc); + return discHasher.CalculateRAJaguarHash() switch { - static string HashJaguar(DiscTrack bootTrack, DiscSectorReader dsr, bool commonHomebrewHash) - { - const string _jaguarHeader = "ATARI APPROVED DATA HEADER ATRI"; - const string _jaguarBSHeader = "TARA IPARPVODED TA AEHDAREA RT"; - var buffer = new List(); - var buf2352 = new byte[2352]; - - // find the boot track header - // see https://github.com/TASEmulators/BizHawk/blob/f29113287e88c6a644dbff30f92a9833307aad20/waterbox/virtualjaguar/src/cdhle.cpp#L109-L145 - var startLba = bootTrack.LBA; - var numLbas = bootTrack.NextTrack.LBA - bootTrack.LBA; - int bootLen = 0, bootLba = 0, bootOff = 0; - bool byteswapped = false, foundHeader = false; - var bootLenOffset = (commonHomebrewHash ? 0x40 : 0) + 32 + 4; - for (var i = 0; i < numLbas; i++) - { - dsr.ReadLBA_2352(startLba + i, buf2352, 0); - - for (var j = 0; j < 2352 - bootLenOffset - 4; j++) - { - if (buf2352[j] == _jaguarHeader[0]) - { - if (_jaguarHeader == Encoding.ASCII.GetString(buf2352, j, 32 - 1)) - { - bootLen = (buf2352[j + bootLenOffset + 0] << 24) | (buf2352[j + bootLenOffset + 1] << 16) | - (buf2352[j + bootLenOffset + 2] << 8) | buf2352[j + bootLenOffset + 3]; - bootLba = startLba + i; - bootOff = j + bootLenOffset + 4; - // byteswapped = false; - foundHeader = true; - break; - } - } - else if (buf2352[j] == _jaguarBSHeader[0]) - { - if (_jaguarBSHeader == Encoding.ASCII.GetString(buf2352, j, 32 - 2)) - { - bootLen = (buf2352[j + bootLenOffset + 1] << 24) | (buf2352[j + bootLenOffset + 0] << 16) | - (buf2352[j + bootLenOffset + 3] << 8) | buf2352[j + bootLenOffset + 2]; - bootLba = startLba + i; - bootOff = j + bootLenOffset + 4; - byteswapped = true; - foundHeader = true; - break; - } - } - } - - if (foundHeader) - { - break; - } - } - - if (!foundHeader) - { - return null; - } - - dsr.ReadLBA_2352(bootLba++, buf2352, 0); - - if (byteswapped) - { - EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan()); - } - - buffer.AddRange(new ArraySegment(buf2352, bootOff, Math.Min(2352 - bootOff, bootLen))); - bootLen -= 2352 - bootOff; - - while (bootLen > 0) - { - dsr.ReadLBA_2352(bootLba++, buf2352, 0); - - if (byteswapped) - { - EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan()); - } - - buffer.AddRange(new ArraySegment(buf2352, 0, Math.Min(2352, bootLen))); - bootLen -= 2352; - } - - return MD5Checksum.ComputeDigestHex(buffer.ToArray()); - } - - var jaguarHash = HashJaguar(disc.Sessions[2].Tracks[1], dsr, false); - switch (jaguarHash) - { - case null: - return 0; - case "254487B59AB21BC005338E85CBF9FD2F": // see https://github.com/RetroAchievements/rcheevos/pull/234 - { - jaguarHash = HashJaguar(disc.Sessions[1].Tracks[2], dsr, true); - if (jaguarHash is null) - { - return 0; - } - - break; - } - } - - return IdentifyHash(jaguarHash); - } - - return 0; + string jaguarHash => IdentifyHash(jaguarHash), + null => 0, + }; } var hash = MD5Checksum.ComputeDigestHex(buffer.ToArray()); diff --git a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs index 4db040c6e28..6aa3ff96013 100644 --- a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs +++ b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs @@ -1,3 +1,7 @@ +#nullable enable + +using System.Collections.Generic; +using System.Text; using BizHawk.Common; using BizHawk.Common.BufferExtensions; @@ -12,6 +16,16 @@ public DiscHasher(Disc disc) private readonly Disc disc; + public string CalculateBizHash(DiscType discType) + { + return discType switch + { + DiscType.SonyPSX => Calculate_PSX_BizIDHash(), + DiscType.JaguarCD => CalculateRAJaguarHash() ?? "", + _ => OldHash(), + }; + } + /// /// calculates the hash for quick PSX Disc identification /// @@ -103,5 +117,115 @@ public string OldHash() } return "no data track found"; } + + /// + /// Calculate Jaguar CD hash according to RetroAchievements logic + /// + public string? CalculateRAJaguarHash() + { + if (disc.Sessions.Count <= 2) + { + return null; + } + + var dsr = new DiscSectorReader(disc) + { + Policy = { DeterministicClearBuffer = false } // let's make this a little faster + }; + + static string? HashJaguar(DiscTrack bootTrack, DiscSectorReader dsr, bool commonHomebrewHash) + { + const string _jaguarHeader = "ATARI APPROVED DATA HEADER ATRI"; + const string _jaguarBSHeader = "TARA IPARPVODED TA AEHDAREA RT"; + var buffer = new List(); + var buf2352 = new byte[2352]; + + // find the boot track header + // see https://github.com/TASEmulators/BizHawk/blob/f29113287e88c6a644dbff30f92a9833307aad20/waterbox/virtualjaguar/src/cdhle.cpp#L109-L145 + var startLba = bootTrack.LBA; + var numLbas = bootTrack.NextTrack.LBA - bootTrack.LBA; + int bootLen = 0, bootLba = 0, bootOff = 0; + bool byteswapped = false, foundHeader = false; + var bootLenOffset = (commonHomebrewHash ? 0x40 : 0) + 32 + 4; + for (var i = 0; i < numLbas; i++) + { + dsr.ReadLBA_2352(startLba + i, buf2352, 0); + + for (var j = 0; j < 2352 - bootLenOffset - 4; j++) + { + if (buf2352[j] == _jaguarHeader[0]) + { + if (_jaguarHeader == Encoding.ASCII.GetString(buf2352, j, 32 - 1)) + { + bootLen = (buf2352[j + bootLenOffset + 0] << 24) | (buf2352[j + bootLenOffset + 1] << 16) | + (buf2352[j + bootLenOffset + 2] << 8) | buf2352[j + bootLenOffset + 3]; + bootLba = startLba + i; + bootOff = j + bootLenOffset + 4; + // byteswapped = false; + foundHeader = true; + break; + } + } + else if (buf2352[j] == _jaguarBSHeader[0]) + { + if (_jaguarBSHeader == Encoding.ASCII.GetString(buf2352, j, 32 - 2)) + { + bootLen = (buf2352[j + bootLenOffset + 1] << 24) | (buf2352[j + bootLenOffset + 0] << 16) | + (buf2352[j + bootLenOffset + 3] << 8) | buf2352[j + bootLenOffset + 2]; + bootLba = startLba + i; + bootOff = j + bootLenOffset + 4; + byteswapped = true; + foundHeader = true; + break; + } + } + } + + if (foundHeader) + { + break; + } + } + + if (!foundHeader) + { + return null; + } + + dsr.ReadLBA_2352(bootLba++, buf2352, 0); + + if (byteswapped) + { + EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan()); + } + + buffer.AddRange(new ArraySegment(buf2352, bootOff, Math.Min(2352 - bootOff, bootLen))); + bootLen -= 2352 - bootOff; + + while (bootLen > 0) + { + dsr.ReadLBA_2352(bootLba++, buf2352, 0); + + if (byteswapped) + { + EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan()); + } + + buffer.AddRange(new ArraySegment(buf2352, 0, Math.Min(2352, bootLen))); + bootLen -= 2352; + } + + return MD5Checksum.ComputeDigestHex(buffer.ToArray()); + } + + var jaguarHash = HashJaguar(disc.Sessions[2].Tracks[1], dsr, false); + + if (jaguarHash is "254487B59AB21BC005338E85CBF9FD2F") // see https://github.com/RetroAchievements/rcheevos/pull/234 + { + jaguarHash = HashJaguar(disc.Sessions[1].Tracks[2], dsr, true); + } + + return jaguarHash; + } } } \ No newline at end of file