diff --git a/src/BizHawk.Common/checksums/CRC32.cs b/src/BizHawk.Common/checksums/CRC32.cs
index 7d7516a2b1..df0f111674 100644
--- a/src/BizHawk.Common/checksums/CRC32.cs
+++ b/src/BizHawk.Common/checksums/CRC32.cs
@@ -1,46 +1,134 @@
-using System;
-
-namespace BizHawk.Common
-{
-	// we could get a little list of crcs from here and make it clear which crc this class was for, and expose others
-	// http://www.ross.net/crc/download/crc_v3.txt
-	// TODO - why am I here? put me alongside hash_md5 and such
-	public static class CRC32
-	{
-		// Lookup table for speed.
-		private static readonly uint[] Crc32Table;
-
-		static CRC32()
-		{
-			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;
-			}
-		}
-
-		public static uint Calculate(ReadOnlySpan<byte> data)
-		{
-			uint result = 0xFFFFFFFF;
-			foreach (var b in data)
-			{
-				result = (result >> 8) ^ Crc32Table[b ^ (result & 0xFF)];
-			}
-
-			return ~result;
-		}
-	}
-}
+using System;
+
+namespace BizHawk.Common
+{
+	/// <remarks>Implementation of CRC-32 (i.e. POSIX cksum), intended for comparing discs against the Redump.org database</remarks>
+	public sealed class CRC32
+	{
+		/// <remarks>coefficients of the polynomial, in the format Wikipedia calls "reversed"</remarks>
+		public const uint POLYNOMIAL_CONST = 0xEDB88320U;
+
+		private static readonly uint[] COMBINER_INIT_STATE;
+
+		private static readonly uint[] CRC32Table;
+
+		static CRC32()
+		{
+			// for Add (CRC32 computation):
+			CRC32Table = new uint[256];
+			for (var i = 0U; i < 256U; i++)
+			{
+				var crc = i;
+				for (var j = 0; j < 8; j++)
+				{
+					var xor = (crc & 1U) == 1U;
+					crc >>= 1;
+					if (xor) crc ^= POLYNOMIAL_CONST;
+				}
+				CRC32Table[i] = crc;
+			}
+
+			// for Incorporate:
+			var combinerState = (COMBINER_INIT_STATE = new uint[64]).AsSpan();
+			var even = combinerState.Slice(start: 0, length: 32); // even-power-of-two zeros operator
+			var odd = combinerState.Slice(start: 32, length: 32); // odd-power-of-two zeros operator
+			// put operator for one zero bit in odd
+			odd[0] = POLYNOMIAL_CONST;
+			var oddTail = odd.Slice(1);
+			for (var n = 0; n < oddTail.Length; n++) oddTail[n] = 1U << n;
+			// 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);
+		}
+
+		public static uint Calculate(ReadOnlySpan<byte> data)
+		{
+			CRC32 crc32 = new();
+			crc32.Add(data);
+			return crc32.Result;
+		}
+
+		private static void gf2_matrix_square(Span<uint> square, ReadOnlySpan<uint> mat)
+		{
+			if (mat.Length != square.Length) throw new ArgumentException();
+			for (var n = 0; n < square.Length; n++) square[n] = gf2_matrix_times(mat, mat[n]);
+		}
+
+		private static uint gf2_matrix_times(ReadOnlySpan<uint> mat, uint vec)
+		{
+			var matIdx = 0;
+			uint sum = 0U;
+			while (vec != 0U)
+			{
+				if ((vec & 1U) != 0U) sum ^= mat[matIdx];
+				vec >>= 1;
+				matIdx++;
+			}
+			return sum;
+		}
+
+		private uint _current = 0xFFFFFFFFU;
+
+		/// <summary>The raw non-negated output</summary>
+		public uint Current
+		{
+			get => _current;
+			set => _current = value;
+		}
+
+		/// <summary>The negated output (the typical result of the CRC calculation)</summary>
+		public uint Result => ~_current;
+
+		public void Add(byte datum)
+		{
+			_current = CRC32Table[(_current ^ datum) & 0xFF] ^ (_current >> 8);
+		}
+
+		public void Add(ReadOnlySpan<byte> data)
+		{
+			foreach (var b in data)
+			{
+//				Add(b); // I assume this would be slower
+				_current = CRC32Table[(_current ^ b) & 0xFF] ^ (_current >> 8);
+			}
+		}
+
+		/// <summary>
+		/// Incorporates a pre-calculated CRC with the given length by combining crcs<br/>
+		/// It's a bit flaky, so be careful, but it works
+		/// </summary>
+		/// <remarks>algorithm from zlib's crc32_combine. read http://www.leapsecond.com/tools/crcomb.c for more</remarks>
+		public void Incorporate(uint crc, int len)
+		{
+			if (len == 0) return; // degenerate case
+
+			Span<uint> combinerState = stackalloc uint[64];
+			COMBINER_INIT_STATE.CopyTo(combinerState);
+			var even = combinerState.Slice(start: 0, length: 32);
+			var odd = combinerState.Slice(start: 32, length: 32);
+
+			// apply len 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 len
+				gf2_matrix_square(even, odd);
+				if ((len & 1U) != 0U) _current = gf2_matrix_times(even, _current);
+				len >>= 1;
+
+				// if no more bits set, then done
+				if (len == 0U) break;
+
+				// another iteration of the loop with odd and even swapped
+				gf2_matrix_square(odd, even);
+				if ((len & 1U) != 0U) _current = gf2_matrix_times(odd, _current);
+				len >>= 1;
+
+				// if no more bits set, then done
+			} while (len != 0U);
+
+			// finally, combine and return
+			_current ^= crc;
+		}
+	}
+}
diff --git a/src/BizHawk.Common/checksums/SpecialCRC32.cs b/src/BizHawk.Common/checksums/SpecialCRC32.cs
deleted file mode 100644
index 57a82d3ee6..0000000000
--- a/src/BizHawk.Common/checksums/SpecialCRC32.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-#nullable disable
-
-using System;
-
-namespace BizHawk.Common
-{
-	/// <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;
-			}
-		}
-
-		private uint current = 0xFFFFFFFF;
-
-		public void Add(ReadOnlySpan<byte> data)
-		{
-			foreach (var b in data) current = CRC32Table[(current ^ b) & 0xFF] ^ (current >> 8);
-		}
-
-		/// <summary>
-		/// The negated output (the typical result of the CRC calculation)
-		/// </summary>
-		public uint Result => current ^ 0xFFFFFFFF;
-
-		/// <summary>
-		/// The raw non-negated output
-		/// </summary>
-		public uint Current
-		{
-			get => current;
-			set => current = value;
-		}
-
-		private 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;
-		}
-
-		private 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
-		private uint[] even, odd;
-
-		//algorithm from zlib's crc32_combine. read http://www.leapsecond.com/tools/crcomb.c for more
-		private uint crc32_combine(uint crc1, uint crc2, int len2)
-		{
-			even ??= new uint[32]; // even-power-of-two zeros operator
-			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;
-		}
-	}
-}
diff --git a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs
index 022ee45dc5..8a108d40ed 100644
--- a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs
+++ b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs
@@ -24,10 +24,8 @@ namespace BizHawk.Emulation.DiscSystem
 			//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();
+			CRC32 crc = new();
 			byte[] buffer2352 = new byte[2352];
 
 			var dsr = new DiscSectorReader(disc)
@@ -36,7 +34,7 @@ namespace BizHawk.Emulation.DiscSystem
 			};
 
 			//hash the TOC
-			static void AddAsBytesTo(SpecialCRC32 crc32, int i)
+			static void AddAsBytesTo(CRC32 crc32, int i)
 				=> crc32.Add(BitConverter.GetBytes(i));
 			AddAsBytesTo(crc, (int)disc.TOC.Session1Format);
 			AddAsBytesTo(crc, disc.TOC.FirstRecordedTrackNumber);
@@ -64,8 +62,7 @@ namespace BizHawk.Emulation.DiscSystem
 		/// </summary>
 		public uint Calculate_PSX_RedumpHash()
 		{
-			//a special CRC32 is used to help us match redump's DB
-			SpecialCRC32 crc = new SpecialCRC32();
+			CRC32 crc = new();
 			byte[] buffer2352 = new byte[2352];
 
 			var dsr = new DiscSectorReader(disc)
diff --git a/src/BizHawk.Tests/Common/checksums/CRC32Tests.cs b/src/BizHawk.Tests/Common/checksums/CRC32Tests.cs
index 7994719054..734c3d7912 100644
--- a/src/BizHawk.Tests/Common/checksums/CRC32Tests.cs
+++ b/src/BizHawk.Tests/Common/checksums/CRC32Tests.cs
@@ -33,12 +33,12 @@ namespace BizHawk.Tests.Common.checksums
 			Assert.AreEqual(EXPECTED, CRC32.Calculate(data));
 
 			data = InitialiseArray();
-			SpecialCRC32 crc32 = new();
+			CRC32 crc32 = new();
 			crc32.Add(data);
 			Assert.AreEqual(EXPECTED, crc32.Result);
 
 			var dataExtra = InitialiseArrayExtra();
-			SpecialCRC32 crc32Extra = new();
+			CRC32 crc32Extra = new();
 			crc32Extra.Add(dataExtra);
 			Assert.AreEqual(EXPECTED_EXTRA, crc32Extra.Result);
 			crc32.Incorporate(crc32Extra.Result, dataExtra.Length);