Magic Numbers
A magic number is a 32-bit hash value produced by applying a hash function on a ~~string~~ series of bytes. Hash tables are maintained in the game executable storing the hash values for string literals like game file paths, object types and parameter names. This is probably done to avoid expensive string comparisons and allow a more compact way of packing the binary game files.
Hash Function
Thumper's hash function is split into three parts: 1. Prologue - Performed once per hash 2. Loop - Performed one or more times per hash 3. Epilogie - Performed once per hash
These functions NO NOT match the function signitures exactly as seen in the executable, but they are written in a way thats close. See the thumper binary section below for accurate function signitures.
Prologue
void prologue(std::uint32_t& value) {
value = 0x811c9dc5;
}
Epilogue
void epilogue(std::uint32_t& value) {
value *= 0x2001;
value = (value ^ (value >> 0x7)) * 0x9;
value = (value ^ (value >> 0x11)) * 0x21;
}
Loop / fnv1a
void fnv1a(std::uint32_t& value, std::span<std::byte const> bytes) {
for (auto const& byte : bytes) {
value ^= static_cast<std::uint32_t>(byte);
value *= 0x1000193;
}
}
How hashes are computed
Thumper never explicitly affixes an input string into the hash, rather this is implemented by invoking the internal loop for each section of the hash. Hash inputs may be non ascii. When implementing a hash function, make sure it can handle non-ascii.
Current there are 1, 2 and 7 known iteration counts for hashing.
An example of a 1 iteration hash is turn. The prologue is called, the fnv1a is called once and the epilogue is called once.
For hashing file paths the most common situation is a 2 iteration hash. For example take levels/demo.objlib. First the prologue is invoked. Then fnv1a is called with A. fnv1a is then invoked again with levels/demo.objlib. Important: When multiple iterations are invoked, the prologue or epoligue are not invoked between them.
This is effectivly the same as directly hashing Alevels/demo.objlib but is faster compute without doing string concatenation.
The other known iteration case is 7: This is primarily seen with .x files.
For example to get the correct hash for avatar/shell_right_eye_bulge.x. Thumper will perform hashing in the following order: A, avatar/shell_right_eye_bulge.x, \x9A\x99\x99\x3E, \x00\x00\x00\x00, \x00\x00\x00\x00, \x00, \x01\x00\x00\x00. Effectivly the same as: Aavatar/shell_right_eye_bulge.x\x9A\x99\x99\x3E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00
The specifics on why this happens are still unknown.
Single function implementaiton
void hash32(std::uint32_t& value, std::string_view bytes) {
// Prologue
std::uint32_t h = 0x811c9dc5;
// fnv1a
for (auto const& byte : bytes) {
value = (value ^ static_cast<std::uint32_t>(byte)) * 0x1000193;
}
// Epilogue
value *= 0x2001;
value = value ^ (value >> 0x7);
value *= 0x9;
value = value ^ (value >> 0x11);
value *= 0x21;
}
Thumper Binary Details
After patching the thumper binary, the function entrypoints can be located at the following .text segment offsets:
* thump_hash_prologue_811c9dc5 : 0x00007FF66AB1FA90
* thump_hash_loop : 0x00007FF66AB1FAB0
* thump_hash_epilogue : 0x00007FF66AB1FA70
* thump_hash_epilogue_stub : 00007FF66AB1FA50
Function signitures
List of Magic Numbers
Here is a list of different magic numbers used in Thumper game files. * Hashed File Names * Types of .objlib Files * Object Types * Object/Parameter Selectors