using System; using BizHawk.Common.BufferExtensions; namespace BizHawk.Emulation.DiscSystem { public class DiscHasher { public DiscHasher(Disc disc) { this.disc = disc; } Disc disc; /// /// calculates the hash for quick PSX Disc identification /// public uint Calculate_PSX_BizIDHash() { //notes about the hash: //"Arc the Lad II (J) 1.0 and 1.1 conflict up to 25 sectors (so use 26) //Tekken 3 (Europe) (Alt) and Tekken 3 (Europe) conflict in track 2 and 3 unfortunately, not sure what to do about this yet //the TOC isn't needed! //but it will help detect dumps with mangled TOCs which are all too common // //a possibly special CRC32 is used to help us match redump's DB elsewhere SpecialCRC32 crc = new SpecialCRC32(); byte[] buffer2352 = new byte[2352]; var dsr = new DiscSectorReader(disc); dsr.Policy.DeterministicClearBuffer = false; //live dangerously //hash the TOC crc.Add((int)disc.TOC.Session1Format); crc.Add(disc.TOC.FirstRecordedTrackNumber); crc.Add(disc.TOC.LastRecordedTrackNumber); for (int i = 1; i <= 100; i++) { //if (disc.TOC.TOCItems[i].Exists) Console.WriteLine("{0:X8} {1:X2} {2:X2} {3:X8}", crc.Current, (int)disc.TOC.TOCItems[i].Control, disc.TOC.TOCItems[i].Exists ? 1 : 0, disc.TOC.TOCItems[i].LBATimestamp.Sector); //a little debugging crc.Add((int)disc.TOC.TOCItems[i].Control); crc.Add(disc.TOC.TOCItems[i].Exists ? 1 : 0); crc.Add((int)disc.TOC.TOCItems[i].LBA); } //hash first 26 sectors for (int i = 0; i < 26; i++) { dsr.ReadLBA_2352(i, buffer2352, 0); crc.Add(buffer2352, 0, 2352); } return crc.Result; } /// /// calculates the complete disc hash for matching to a redump /// public uint Calculate_PSX_RedumpHash() { //a special CRC32 is used to help us match redump's DB SpecialCRC32 crc = new SpecialCRC32(); byte[] buffer2352 = new byte[2352]; var dsr = new DiscSectorReader(disc); dsr.Policy.DeterministicClearBuffer = false; //live dangerously //read all sectors for redump hash for (int i = 0; i < disc.Session1.LeadoutLBA; i++) { dsr.ReadLBA_2352(i, buffer2352, 0); crc.Add(buffer2352, 0, 2352); } return crc.Result; } // gets an identifying hash. hashes the first 512 sectors of // the first data track on the disc. //TODO - this is a very platform-specific thing. hashing the TOC may be faster and be just as effective. so, rename it appropriately public string OldHash() { byte[] buffer = new byte[512 * 2352]; DiscSectorReader dsr = new DiscSectorReader(disc); foreach (var track in disc.Session1.Tracks) { if (track.IsAudio) continue; int lba_len = Math.Min(track.NextTrack.LBA, 512); for (int s = 0; s < 512 && s < lba_len; s++) dsr.ReadLBA_2352(track.LBA + s, buffer, s * 2352); return buffer.HashMD5(0, lba_len * 2352); } return "no data track found"; } /// /// A stateful special CRC32 calculator /// This may be absolutely standard and not special at all. I don't know, there were some differences between it and other CRC code I found in bizhawk /// public class SpecialCRC32 { private static readonly uint[] CRC32Table; static SpecialCRC32() { CRC32Table = new uint[256]; for (uint i = 0; i < 256; ++i) { uint crc = i; for (int j = 8; j > 0; --j) { if ((crc & 1) == 1) crc = ((crc >> 1) ^ 0xEDB88320); else crc >>= 1; } CRC32Table[i] = crc; } } uint current = 0xFFFFFFFF; public unsafe void Add(byte[] data, int offset, int size) { if (offset + size > data.Length) throw new ArgumentOutOfRangeException(); if (offset < 0) throw new ArgumentOutOfRangeException(); fixed (byte* pData = data) for (int i = 0; i < size; i++) { byte b = pData[offset + i]; current = CRC32Table[(current ^ b) & 0xFF] ^ (current >> 8); } } byte[] smallbuf = new byte[8]; public void Add(int data) { smallbuf[0] = (byte)((data) & 0xFF); smallbuf[1] = (byte)((data >> 8) & 0xFF); smallbuf[2] = (byte)((data >> 16) & 0xFF); smallbuf[3] = (byte)((data >> 24) & 0xFF); Add(smallbuf, 0, 4); } /// /// The negated output (the typical result of the CRC calculation) /// public uint Result { get { return current ^ 0xFFFFFFFF; } } /// /// The raw non-negated output /// public uint Current { get { return current; } set { current = value; } } uint gf2_matrix_times(uint[] mat, uint vec) { int matIdx = 0; uint sum = 0; while (vec != 0) { if ((vec & 1) != 0) sum ^= mat[matIdx]; vec >>= 1; matIdx++; } return sum; } void gf2_matrix_square(uint[] square, uint[] mat) { int n; for (n = 0; n < 32; n++) square[n] = gf2_matrix_times(mat, mat[n]); } /// /// Incorporates a pre-calculated CRC with the given length by combining crcs /// It's a bit flaky, so be careful, but it works /// public void Incorporate(uint crc, int len) { current = crc32_combine(current, crc, len); } //tables used by crc32_combine uint[] even, odd; //algorithm from zlib's crc32_combine. read http://www.leapsecond.com/tools/crcomb.c for more uint crc32_combine(uint crc1, uint crc2, int len2) { if (even == null) even = new uint[32]; // even-power-of-two zeros operator if (odd == null) odd = new uint[32]; // odd-power-of-two zeros operator // degenerate case if (len2 == 0) return crc1; // put operator for one zero bit in odd odd[0] = 0xedb88320; //CRC-32 polynomial uint row = 1; for (int n = 1; n < 32; n++) { odd[n] = row; row <<= 1; } //put operator for two zero bits in even gf2_matrix_square(even, odd); //put operator for four zero bits in odd gf2_matrix_square(odd, even); //apply len2 zeros to crc1 (first square will put the operator for one zero byte, eight zero bits, in even) do { //apply zeros operator for this bit of len2 gf2_matrix_square(even, odd); if ((len2 & 1) != 0) crc1 = gf2_matrix_times(even, crc1); len2 >>= 1; //if no more bits set, then done if (len2 == 0) break; //another iteration of the loop with odd and even swapped gf2_matrix_square(odd, even); if ((len2 & 1) != 0) crc1 = gf2_matrix_times(odd, crc1); len2 >>= 1; //if no more bits set, then done } while (len2 != 0); //return combined crc crc1 ^= crc2; return crc1; } } } }