#include "stdafx.h" #include "GameUUID.h" #include #include #include #ifdef _WIN32 #include #include #pragma comment(lib, "bcrypt.lib") #endif static uint8_t hexVal(char c) { if (c >= '0' && c <= '9') return (uint8_t)(c - '0'); if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 10); if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 10); return 0; } std::string GameUUID::toDashed() const { // Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx char buf[37]; sprintf_s(buf, sizeof(buf), "%08x-%04x-%04x-%04x-%012llx", (unsigned int)(hi >> 32), (unsigned int)((hi >> 16) & 0xFFFF), (unsigned int)(hi & 0xFFFF), (unsigned int)(lo >> 48), (unsigned long long)(lo & 0x0000FFFFFFFFFFFFULL)); return std::string(buf); } std::string GameUUID::toUndashed() const { char buf[33]; sprintf_s(buf, sizeof(buf), "%016llx%016llx", (unsigned long long)hi, (unsigned long long)lo); return std::string(buf); } std::wstring GameUUID::toWDashed() const { std::string s = toDashed(); return std::wstring(s.begin(), s.end()); } GameUUID GameUUID::fromDashed(const std::string& s) { std::string undashed; undashed.reserve(32); for (size_t i = 0; i < s.size(); i++) { if (s[i] != '-') undashed.push_back(s[i]); } return fromUndashed(undashed); } GameUUID GameUUID::fromUndashed(const std::string& s) { GameUUID uuid; if (s.size() < 32) return uuid; uuid.hi = 0; for (int i = 0; i < 16; i++) { uuid.hi = (uuid.hi << 4) | hexVal(s[i]); } uuid.lo = 0; for (int i = 16; i < 32; i++) { uuid.lo = (uuid.lo << 4) | hexVal(s[i]); } return uuid; } // ---- UUID v4 (random) ---- GameUUID GameUUID::generateV4() { GameUUID uuid; uint8_t bytes[16] = {}; #ifdef _WIN32 NTSTATUS status = BCryptGenRandom(NULL, bytes, sizeof(bytes), BCRYPT_USE_SYSTEM_PREFERRED_RNG); if (!BCRYPT_SUCCESS(status)) { // Fallback: weak but avoids uninitialized memory for (int i = 0; i < 16; i++) bytes[i] = (uint8_t)(rand() & 0xFF); } #else // Fallback: mix runtime entropy static uint64_t counter = 0; uint64_t seed = (uint64_t)time(NULL) ^ (++counter * 6364136223846793005ULL); for (int i = 0; i < 16; i++) { seed = seed * 6364136223846793005ULL + 1442695040888963407ULL; bytes[i] = (uint8_t)(seed >> 56); } #endif // Set version 4 (bits 48-51 of hi = 0100) bytes[6] = (bytes[6] & 0x0F) | 0x40; // Set variant 1 (bits 0-1 of byte 8 = 10) bytes[8] = (bytes[8] & 0x3F) | 0x80; uuid.hi = 0; for (int i = 0; i < 8; i++) uuid.hi = (uuid.hi << 8) | bytes[i]; uuid.lo = 0; for (int i = 8; i < 16; i++) uuid.lo = (uuid.lo << 8) | bytes[i]; return uuid; } // ---- UUID v3 (MD5 name-based) for offline players ---- // Matches Java's UUID.nameUUIDFromBytes() used by Mojang for offline UUIDs // Minimal MD5 implementation (RFC 1321) for offline UUID generation namespace { struct MD5State { uint32_t state[4]; uint64_t count; uint8_t buffer[64]; }; static const uint32_t md5_T[64] = { 0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501, 0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821, 0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8, 0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a, 0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70, 0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665, 0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1, 0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391 }; static const int md5_S[64] = { 7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22, 5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20, 4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23, 6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21 }; static inline uint32_t rotl32(uint32_t x, int n) { return (x << n) | (x >> (32 - n)); } static void md5_transform(uint32_t state[4], const uint8_t block[64]) { uint32_t M[16]; for (int i = 0; i < 16; i++) M[i] = (uint32_t)block[i * 4] | ((uint32_t)block[i * 4 + 1] << 8) | ((uint32_t)block[i * 4 + 2] << 16) | ((uint32_t)block[i * 4 + 3] << 24); uint32_t a = state[0], b = state[1], c = state[2], d = state[3]; for (int i = 0; i < 64; i++) { uint32_t f; int g; if (i < 16) { f = (b & c) | (~b & d); g = i; } else if (i < 32) { f = (d & b) | (~d & c); g = (5 * i + 1) % 16; } else if (i < 48) { f = b ^ c ^ d; g = (3 * i + 5) % 16; } else { f = c ^ (b | ~d); g = (7 * i) % 16; } uint32_t temp = d; d = c; c = b; b = b + rotl32(a + f + md5_T[i] + M[g], md5_S[i]); a = temp; } state[0] += a; state[1] += b; state[2] += c; state[3] += d; } static void md5(const uint8_t* data, size_t len, uint8_t digest[16]) { uint32_t state[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 }; size_t i = 0; for (; i + 64 <= len; i += 64) md5_transform(state, data + i); uint8_t block[64] = {}; size_t remain = len - i; if (remain > 0) memcpy(block, data + i, remain); block[remain] = 0x80; if (remain >= 56) { md5_transform(state, block); memset(block, 0, 64); } uint64_t bitLen = (uint64_t)len * 8; for (int b = 0; b < 8; b++) block[56 + b] = (uint8_t)(bitLen >> (b * 8)); md5_transform(state, block); for (int b = 0; b < 4; b++) { digest[b * 4 + 0] = (uint8_t)(state[b]); digest[b * 4 + 1] = (uint8_t)(state[b] >> 8); digest[b * 4 + 2] = (uint8_t)(state[b] >> 16); digest[b * 4 + 3] = (uint8_t)(state[b] >> 24); } } } GameUUID GameUUID::generateOffline(const std::string& playerName) { // Matches Mojang's offline UUID: UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes("UTF-8")) std::string input = "OfflinePlayer:" + playerName; uint8_t digest[16]; md5((const uint8_t*)input.c_str(), input.size(), digest); // Set version 3 digest[6] = (digest[6] & 0x0F) | 0x30; // Set variant 1 digest[8] = (digest[8] & 0x3F) | 0x80; GameUUID uuid; uuid.hi = 0; for (int i = 0; i < 8; i++) uuid.hi = (uuid.hi << 8) | digest[i]; uuid.lo = 0; for (int i = 8; i < 16; i++) uuid.lo = (uuid.lo << 8) | digest[i]; return uuid; }