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;
}