Skip to content

Fix CRC32_Compute - #257

Open
ResurrectedTrader wants to merge 1 commit into
ThePhrozenKeep:masterfrom
ResurrectedTrader:fix/crc32-compute
Open

ResurrectedTrader wants to merge 1 commit into
ThePhrozenKeep:masterfrom
ResurrectedTrader:fix/crc32-compute

Conversation

@ResurrectedTrader

Copy link
Copy Markdown
Contributor

Three bugs made CRC32_Compute differ from the game's:

  • sgCrc32LookupTable[1] was written in decimal (77073096, i.e. 0x04980AC8) instead of 0x77073096.
  • The table index was not masked to a byte, so nCRC32 ^ nByte indexed far past the 256-entry table.
  • The final complement was missing.

The game's function is plain CRC-32: all 256 entries of its table are the standard (0xEDB88320) table, which MOO's table matches apart from entry 1, and its output equals zlib's crc32.

1.10f

D2Game.0x6FD269D3, Hex-Rays:

int __fastcall sub_6FD269D3(unsigned __int8 *a1, int a2)
{
  unsigned int v2; // eax

  if ( a2 == 0 )
  {
    Fog_10023(aDwsize, aCProjectsD2Hea_47, 64);
    exit(-1);
  }
  v2 = 0xFFFFFFFF;
  do
  {
    v2 = dword_6FD2A248[(unsigned __int8)v2 ^ *a1++] ^ (v2 >> 8);   // <== byte index
    --a2;
  }
  while ( a2 != 0 );
  return ~v2;                                                       // <== complement
}
6FD269F2  push    esi
6FD269F3  or      eax, 0FFFFFFFFh
6FD269F6  push    edi
6FD269F7  movzx   esi, byte ptr [ecx]
6FD269FA  movzx   edi, al                           ; <== low byte only
6FD269FD  xor     esi, edi
6FD269FF  shr     eax, 8
6FD26A02  mov     esi, [esi*4+6FD2A248h]
6FD26A09  xor     eax, esi
6FD26A0B  inc     ecx
6FD26A0C  dec     edx
6FD26A0D  jnz     6FD269F7
6FD26A0F  pop     edi
6FD26A10  pop     esi
6FD26A11  not     eax                               ; <== final complement
6FD26A13  retn

The table at 0x6FD2A248 starts 00000000 77073096 EE0E612C 990951BA.

The only caller, D2GAME_SAVE_CalculateChecksum_6FC8A140, returns the value unchanged:

result = sub_6FD269D3((unsigned __int8 *)a1, a2);
...
return result;

1.14d

D2HELL_CRC32_Compute (Game.exe 0x006C99F6, table 0x006FD3E8) is identical:

v2 = 0xFFFFFFFF;
do
{
  v2 = dword_6FD3E8[(unsigned __int8)v2 ^ *a1] ^ (v2 >> 8);
  --a2;
  ++a1;
}
while ( a2 != 0 );
return ~v2;

Change

Fix table entry 1, mask the index with & 0xFF, return ~nCRC32, and use size_t for the loop index. Not version-gated, since 1.10f and 1.14d agree. Also adds the 1.14d address.

🤖 Generated with Claude Code

Three bugs made CRC32_Compute differ from the game's:

- sgCrc32LookupTable[1] was written in decimal (77073096) instead of
  0x77073096.
- The table index was not masked to a byte, so it read past the table.
- The final complement was missing.

1.10f (D2Game.0x6FD269D3), Hex-Rays:

    v2 = 0xFFFFFFFF;
    do
    {
        v2 = dword_6FD2A248[(unsigned __int8)v2 ^ *a1++] ^ (v2 >> 8);
        --a2;
    }
    while ( a2 != 0 );
    return ~v2;

Disassembly:

    6FD269F3  or    eax, 0FFFFFFFFh
    6FD269F7  movzx esi, byte ptr [ecx]
    6FD269FA  movzx edi, al            ; low byte only
    6FD269FD  xor   esi, edi
    6FD269FF  shr   eax, 8
    6FD26A02  mov   esi, [esi*4+6FD2A248h]
    6FD26A09  xor   eax, esi
    6FD26A0B  inc   ecx
    6FD26A0C  dec   edx
    6FD26A0D  jnz   6FD269F7
    6FD26A11  not   eax                ; final complement

All 256 entries of the table at 0x6FD2A248 are the standard CRC-32
(0xEDB88320) table, and MOO's table matches it apart from entry 1, so
the game computes plain CRC-32 (the same result as zlib's crc32). 1.14d
(Game.exe 0x006C99F6, table 0x006FD3E8) is identical.

The only caller, D2GAME_SAVE_CalculateChecksum_6FC8A140, returns the
result unchanged (result = sub_6FD269D3(a1, a2); ... return result;).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant