2015-06-23 18:57:11 +00:00
|
|
|
using System;
|
|
|
|
|
|
|
|
using BizHawk.Common.BufferExtensions;
|
|
|
|
|
|
|
|
namespace BizHawk.Emulation.DiscSystem
|
|
|
|
{
|
2015-07-03 09:11:07 +00:00
|
|
|
public class DiscHasher
|
2015-06-23 18:57:11 +00:00
|
|
|
{
|
2015-07-03 09:11:07 +00:00
|
|
|
public DiscHasher(Disc disc)
|
2015-06-23 18:57:11 +00:00
|
|
|
{
|
|
|
|
this.disc = disc;
|
|
|
|
}
|
|
|
|
|
|
|
|
Disc disc;
|
|
|
|
|
2015-07-08 23:47:48 +00:00
|
|
|
/// <summary>
|
|
|
|
/// calculates the hash for quick PSX Disc identification
|
|
|
|
/// </summary>
|
|
|
|
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
|
|
|
|
//
|
2015-07-11 14:43:38 +00:00
|
|
|
//a possibly special CRC32 is used to help us match redump's DB elsewhere
|
2015-07-08 23:47:48 +00:00
|
|
|
|
|
|
|
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++)
|
|
|
|
{
|
2015-07-11 17:26:29 +00:00
|
|
|
//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
|
2015-07-08 23:47:48 +00:00
|
|
|
crc.Add((int)disc.TOC.TOCItems[i].Control);
|
|
|
|
crc.Add(disc.TOC.TOCItems[i].Exists ? 1 : 0);
|
2015-07-19 04:23:15 +00:00
|
|
|
crc.Add((int)disc.TOC.TOCItems[i].LBA);
|
2015-07-08 23:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// calculates the complete disc hash for matching to a redump
|
|
|
|
/// </summary>
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-23 18:57:11 +00:00
|
|
|
// 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
|
2015-07-03 09:11:07 +00:00
|
|
|
public string OldHash()
|
2015-06-23 18:57:11 +00:00
|
|
|
{
|
|
|
|
byte[] buffer = new byte[512 * 2352];
|
|
|
|
DiscSectorReader dsr = new DiscSectorReader(disc);
|
2015-07-08 03:29:11 +00:00
|
|
|
foreach (var track in disc.Session1.Tracks)
|
2015-06-23 18:57:11 +00:00
|
|
|
{
|
2015-07-03 09:11:07 +00:00
|
|
|
if (track.IsAudio)
|
2015-06-23 18:57:11 +00:00
|
|
|
continue;
|
|
|
|
|
2015-07-08 03:29:11 +00:00
|
|
|
int lba_len = Math.Min(track.NextTrack.LBA, 512);
|
|
|
|
for (int s = 0; s < 512 && s < lba_len; s++)
|
2015-07-03 09:11:07 +00:00
|
|
|
dsr.ReadLBA_2352(track.LBA + s, buffer, s * 2352);
|
2015-06-23 18:57:11 +00:00
|
|
|
|
|
|
|
return buffer.HashMD5(0, lba_len * 2352);
|
|
|
|
}
|
|
|
|
return "no data track found";
|
|
|
|
}
|
2015-07-11 14:43:38 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 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
|
|
|
|
/// </summary>
|
|
|
|
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;
|
2019-12-29 10:52:32 +00:00
|
|
|
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
|
|
/// <paramref name="offset"/> is negative, or
|
|
|
|
/// end index (<paramref name="offset"/> + <paramref name="size"/>) is beyond the end of <paramref name="data"/>
|
|
|
|
/// </exception>
|
2015-07-11 14:43:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The negated output (the typical result of the CRC calculation)
|
|
|
|
/// </summary>
|
|
|
|
public uint Result { get { return current ^ 0xFFFFFFFF; } }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The raw non-negated output
|
|
|
|
/// </summary>
|
2020-01-25 04:53:45 +00:00
|
|
|
public uint Current
|
|
|
|
{
|
|
|
|
get => current;
|
|
|
|
set => current = value;
|
|
|
|
}
|
2015-07-11 14:43:38 +00:00
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Incorporates a pre-calculated CRC with the given length by combining crcs
|
|
|
|
/// It's a bit flaky, so be careful, but it works
|
|
|
|
/// </summary>
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2015-06-23 18:57:11 +00:00
|
|
|
}
|
|
|
|
}
|