Yesterday I continued my work on looking into actor paramaters. I decided to figure out the hashing function SE was using to convert a string like
charaWork.parameterSave.hp[0]
to the hex number
0x4232BCAA
I already knew the location of the hash routine from yesterday's post, but in all the excitement I didn't actually take a look at what it was doing. Finding out the algorithm was actually very easy. I found a constant being multiplied repeatedly throughout the routine; 0x5bd1e995. A quick Google gave me my answer: MurmurHash2. I used this code https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp?r=130#37 to compare what was being run in assembly code, to the C code in the link. The only difference was SE used a seed value of 0 (strange), and the program worked end to beginning unlike this code that works beginning to end. After modifying the C code I was able to convert a bunch of hashes to their proper IDs. Woo, no more hex numbers! I can use the proper labels!
This is the C# version of the code, though I have a bug in the switch statement I still have to figure out. I'm still learning C# and I found my first weird caveat with the language: switch statements can't fall through. People suggested using GOTO as a workaround. Never thought I'd every use GOTO this day and age, but there you go!
public static uint MurmurHash2(string key, uint seed)
{
// 'm' and 'r' are mixing constants generated offline.
// They're not really 'magic', they just happen to work well.
byte[] data = Encoding.ASCII.GetBytes(key);
const uint m = 0x5bd1e995;
const int r = 24;
int len = key.Length;
int dataIndex = len - 4;
// Initialize the hash to a 'random' value
uint h = seed ^ (uint)len;
// Mix 4 bytes at a time into the hash
while (len >= 4)
{
h *= m;
uint k = (uint)BitConverter.ToInt32(data, dataIndex);
k = ((k >> 24) & 0xff) | // move byte 3 to byte 0
((k << 8) & 0xff0000) | // move byte 1 to byte 2
((k >> 8) & 0xff00) | // move byte 2 to byte 1
((k << 24) & 0xff000000); // byte 0 to byte 3
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
dataIndex -= 4;
len -= 4;
}
// Handle the last few bytes of the input array
switch (len)
{
case 3:
h ^= (uint)data[2] << 16; goto case 2;
case 2:
h ^= (uint)data[0] << 8; goto case 1;
case 1:
h ^= data[1];
h *= m;
break;
};
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}