diff --git a/CMakeLists.txt b/CMakeLists.txt index ba89525..d4341d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ if(WHOA_STORM_FLAVOR STREQUAL "SC1") endif() add_definitions(-DWHOA_RECT_USES_SCREEN_COORDINATES) + add_definitions(-DWHOA_SSTRHASH64_SUBTRACTS) elseif(WHOA_STORM_FLAVOR STREQUAL "WOW") message(STATUS "Building Storm with World of Warcraft flavoring") else() diff --git a/storm/String.cpp b/storm/String.cpp index 33116cd..d25b7e0 100644 --- a/storm/String.cpp +++ b/storm/String.cpp @@ -57,6 +57,13 @@ const uint32_t s_hashtable[16] = { 0xE3061AE7, 0xA39B0FA1, 0x9797F25F, 0xE4444563, }; +const uint64_t s_hashtable64[16] = { + 0x486E26EEDCAA16B3ULL, 0xE1918EEF202DAFDBULL, 0x341C7DC71C365303ULL, 0x40EF2D3765FD5E49ULL, + 0xD6057177904ECE93ULL, 0x1C38024F98FD323BULL, 0xE3061AE7A39B0FA1ULL, 0x9797F25FE4444563ULL, + 0xCD2EC20C8DC1B898ULL, 0x31759633799A306DULL, 0x8C2063852E6E9627ULL, 0x79237D9973922C66ULL, + 0x8728628D28628824ULL, 0x8F1F7E9625887795ULL, 0x296E3281389C0D60ULL, 0x6F4893CA61636542ULL, +}; + void GetNextTextUpper(uint32_t* orig, const char** string, uint32_t* upper) { uint8_t byte = **string; int32_t v3 = bytesFromUTF8[byte]; @@ -423,6 +430,50 @@ uint32_t STORMAPI SStrHashHT(const char* string) { return bjhash((uint8_t*)&normalized, length, 0); } +int64_t STORMAPI SStrHash64(const char* string, uint32_t flags, int64_t seed) { + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(string); + STORM_VALIDATE_END; + + int64_t result = seed ? seed : 0x7FED7FED7FED7FEDLL; + int64_t adjust = 0xEEEEEEEEEEEEEEEELL; + uint32_t ch; + + if (flags & SSTR_HASH_CASESENSITIVE) { + for (; *string; string++) { + ch = static_cast(*string); + +#if defined(WHOA_SSTRHASH64_SUBTRACTS) + result = (s_hashtable64[ch / 16] - s_hashtable64[ch % 16]) ^ (adjust + result); +#else + result = (s_hashtable64[ch / 16] + s_hashtable64[ch % 16]) ^ (adjust + result); +#endif + adjust = 33 * adjust + result + ch + 3; + } + } + else { + for (; *string; string++) { + ch = static_cast(*string); + + if (ch >= 'a' && ch <= 'z') { + ch -= 32; + } + + if (ch == '/') { + ch = '\\'; + } + +#if defined(WHOA_SSTRHASH64_SUBTRACTS) + result = (s_hashtable64[ch / 16] - s_hashtable64[ch % 16]) ^ (adjust + result); +#else + result = (s_hashtable64[ch / 16] + s_hashtable64[ch % 16]) ^ (adjust + result); +#endif + adjust = 33 * adjust + result + ch + 3; + } + } + return result ? result : 1LL; +} + size_t STORMAPI SStrLen(const char* string) { STORM_VALIDATE_BEGIN; STORM_VALIDATE(string); diff --git a/storm/String.hpp b/storm/String.hpp index f48cb72..5a6f837 100644 --- a/storm/String.hpp +++ b/storm/String.hpp @@ -30,6 +30,8 @@ uint32_t STORMAPI SStrHash(const char* string, uint32_t flags = 0, uint32_t seed uint32_t STORMAPI SStrHashHT(const char* string); +int64_t STORMAPI SStrHash64(const char* string, uint32_t flags = 0, int64_t seed = 0); + size_t STORMAPI SStrLen(const char* string); void STORMAPI SStrLower(char* string); diff --git a/test/String.cpp b/test/String.cpp index 14a4cd2..f13b1e3 100644 --- a/test/String.cpp +++ b/test/String.cpp @@ -257,6 +257,11 @@ struct TestHash { uint32_t hash; }; +struct TestHash64 { + const char* str; + uint64_t hash; +}; + TEST_CASE("SStrHash", "[string]") { SECTION("hashes strings with case insensitivity") { // Results obtained by directly calling Starcraft 1.17's SStrHash @@ -373,6 +378,180 @@ TEST_CASE("SStrHashHT", "[string]") { } #endif +TEST_CASE("SStrHash64", "[string]") { +#if defined(WHOA_SSTRHASH64_SUBTRACTS) + SECTION("hashes strings with case insensitivity") { + // Results obtained by directly calling Starcraft 1.17's SStrHash64 + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0xE14D89BC96BA2B0DULL }, + TestHash64{ "BLOOP bloop", 0xE14D89BC96BA2B0DULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0x7759083272301974ULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0x7759083272301974ULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0x73D40422F72E5B88ULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x73D40422F72E5B88ULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0x71E9A046243BE465ULL }, + TestHash64{ "/", 0xFBD3F11E1E46C4CCULL }, + TestHash64{ "\\", 0xFBD3F11E1E46C4CCULL }, + TestHash64{ "\r\n\t ", 0xA739BEA33457DEECULL } + ); + + auto hash = SStrHash64(testcase.str); + CHECK(hash == testcase.hash); + } + + SECTION("hashes strings with case sensitivity") { + // Results obtained by directly calling Starcraft 1.17's SStrHash64 + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0x4F12AE31024D45F9ULL }, + TestHash64{ "BLOOP bloop", 0x5D851E9D86AE9B9DULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0xA90280432B37C9C0ULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0x76D3CB7BEEA8D106ULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0xA6A43E35042C251CULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x73D40422F72E5B88ULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0x71E9A046243BE465ULL }, + TestHash64{ "/", 0xAA0F8720D40E831AULL }, + TestHash64{ "\\", 0xFBD3F11E1E46C4CCULL }, + TestHash64{ "\r\n\t ", 0xA739BEA33457DEECULL } + ); + + auto hash = SStrHash64(testcase.str, SSTR_HASH_CASESENSITIVE); + CHECK(hash == testcase.hash); + } + + SECTION("hashes strings with fixed seeds case insensitive") { + // Results obtained by directly calling Starcraft 1.17's SStrHash64 + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0x03D3898FEC03C4D9ULL }, + TestHash64{ "BLOOP bloop", 0x03D3898FEC03C4D9ULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0xCEACEDE00D62B2B6ULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0xCEACEDE00D62B2B6ULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0xA39E182D711E5D58ULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0xA39E182D711E5D58ULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0x5BF56F80621C969BULL }, + TestHash64{ "/", 0x7BE1712C9E74457EULL }, + TestHash64{ "\\", 0x7BE1712C9E74457EULL }, + TestHash64{ "\r\n\t ", 0xEE8A467B9D01DE46ULL } + ); + + auto hash = SStrHash64(testcase.str, 0, 123LL); + CHECK(hash == testcase.hash); + } + + SECTION("hashes strings with fixed seeds case sensitive") { + // Results obtained by directly calling Starcraft 1.17's SStrHash64 + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0x6FFDAAD262D140BDULL }, + TestHash64{ "BLOOP bloop", 0xF300F24AB99DFB15ULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0xAE42461A0E6433CEULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0x19E9DB9ACCD45BDCULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0x1666B3E05FB2DCA8ULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0xA39E182D711E5D58ULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0x5BF56F80621C969BULL }, + TestHash64{ "/", 0x2A3D0712543C02A8ULL }, + TestHash64{ "\\", 0x7BE1712C9E74457EULL }, + TestHash64{ "\r\n\t ", 0xEE8A467B9D01DE46ULL } + ); + + auto hash = SStrHash64(testcase.str, SSTR_HASH_CASESENSITIVE, 123); + CHECK(hash == testcase.hash); + } +#else + SECTION("hashes strings with case insensitivity") { + // Results unverified + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0x84FD59305F3877FFULL }, + TestHash64{ "BLOOP bloop", 0x84FD59305F3877FFULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0x2B34AA2D06B98C8EULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0x2B34AA2D06B98C8EULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0x8E7D62B3AD83962AULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x8E7D62B3AD83962AULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0xC0B7B00ECE9E2F07ULL }, + TestHash64{ "/", 0xCDBC0A00AF83D484ULL }, + TestHash64{ "\\", 0xCDBC0A00AF83D484ULL }, + TestHash64{ "\r\n\t ", 0xD8904DDFC816244AULL } + ); + + auto hash = SStrHash64(testcase.str); + CHECK(hash == testcase.hash); + } + + SECTION("hashes strings with case sensitivity") { + // Results unverified + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0xBBB2156CEDE15737ULL }, + TestHash64{ "BLOOP bloop", 0x535CFCB6D2D7F37BULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0x5302DB8D4BA78ABAULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0xE8BF631EA4E66D58ULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0x7D8899096CB4CC02ULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x8E7D62B3AD83962AULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0xC0B7B00ECE9E2F07ULL }, + TestHash64{ "/", 0xCDB97F4D1345D69EULL }, + TestHash64{ "\\", 0xCDBC0A00AF83D484ULL }, + TestHash64{ "\r\n\t ", 0xD8904DDFC816244AULL } + ); + + auto hash = SStrHash64(testcase.str, SSTR_HASH_CASESENSITIVE); + CHECK(hash == testcase.hash); + } + + SECTION("hashes strings with fixed seeds case insensitive") { + // Results unverified + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0xC6F40635E6E3C117ULL }, + TestHash64{ "BLOOP bloop", 0xC6F40635E6E3C117ULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0x6451F965B5D0FD5CULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0x6451F965B5D0FD5CULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0x203432E33CF40F76ULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x203432E33CF40F76ULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0xC3891C1567328CB1ULL }, + TestHash64{ "/", 0x4D8E8A322FB15536ULL }, + TestHash64{ "\\", 0x4D8E8A322FB15536ULL }, + TestHash64{ "\r\n\t ", 0x2406686F9A68C604ULL } + ); + + auto hash = SStrHash64(testcase.str, 0, 123LL); + CHECK(hash == testcase.hash); + } + + SECTION("hashes strings with fixed seeds case sensitive") { + // Results unverified + auto testcase = GENERATE( + TestHash64{ "bloop bloop", 0xE57741252C85E807ULL }, + TestHash64{ "BLOOP bloop", 0x904B2F5E071239EFULL }, + TestHash64{ "Objects\\CameraHelper\\CameraHelper.mdx", 0xEA1C02D18CA081F4ULL }, + TestHash64{ "Objects/CameraHelper/CameraHelper.mdx", 0x391D997C0A76FB82ULL }, + TestHash64{ "abcdefghijklmnopqrstuvwxyz", 0x2416669E47057DB2ULL }, + TestHash64{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x203432E33CF40F76ULL }, + TestHash64{ "123456789~!@#$%^&*()_+`-=[]{}|;':\",.<>?", 0xC3891C1567328CB1ULL }, + TestHash64{ "/", 0x4D8BFF7F9377572CULL }, + TestHash64{ "\\", 0x4D8E8A322FB15536ULL }, + TestHash64{ "\r\n\t ", 0x2406686F9A68C604ULL } + ); + + auto hash = SStrHash64(testcase.str, SSTR_HASH_CASESENSITIVE, 123); + CHECK(hash == testcase.hash); + } +#endif + + SECTION("hashing empty string with seed of 0 gives default seed") { + auto hash = SStrHash64("", 0, 0); + CHECK(hash == 0x7FED7FED7FED7FEDLL); + + hash = SStrHash64("", SSTR_HASH_CASESENSITIVE, 0); + CHECK(hash == 0x7FED7FED7FED7FEDLL); + } + + SECTION("hashing empty string with custom seed returns that seed") { + auto seed = GENERATE(1LL, 123LL, 0x7FED7FEDLL, 0xFFFFFFFFLL, 0x7FED7FED7FED7FEDLL, 0xFFFFFFFFFFFFFFFFLL); + + auto hash = SStrHash64("", 0, seed); + CHECK(hash == seed); + + hash = SStrHash64("", SSTR_HASH_CASESENSITIVE, seed); + CHECK(hash == seed); + } +} + TEST_CASE("SStrLen", "[string]") { SECTION("calculates string length correctly") { REQUIRE(SStrLen("foo") == 3); diff --git a/test/stormdll/storm.def b/test/stormdll/storm.def index 93c801f..a539988 100644 --- a/test/stormdll/storm.def +++ b/test/stormdll/storm.def @@ -400,7 +400,7 @@ EXPORTS ;SStrToInt64 @577 NONAME SStrPrintf @578 NONAME SStrLower @579 NONAME - ;SStrHash64 @580 NONAME + SStrHash64 @580 NONAME SStrVPrintf @581 NONAME ; More drawing diff --git a/test/stormdll/stormstubs.cpp b/test/stormdll/stormstubs.cpp index dfc23ab..6e20edd 100644 --- a/test/stormdll/stormstubs.cpp +++ b/test/stormdll/stormstubs.cpp @@ -98,6 +98,7 @@ size_t STORMAPI SStrCopy(char*, const char*, size_t) { return 0; } char* STORMAPI SStrDupA(const char*, const char*, uint32_t) { return 0; } uint32_t STORMAPI SStrHash(const char*, uint32_t, uint32_t) { return 0; } uint32_t STORMAPI SStrHashHT(const char*) { return 0; } +int64_t STORMAPI SStrHash64(const char*, uint32_t, int64_t) { return 0; } size_t STORMAPI SStrLen(const char*) { return 0; } void STORMAPI SStrLower(char*) {} uint32_t STORMAPI SStrPack(char*, const char*, uint32_t) { return 0; }