Created
May 14, 2025 23:38
-
-
Save MrPowerGamerBR/b66c52456154a36a5482149e3140e643 to your computer and use it in GitHub Desktop.
A port of Discord's experiment hash calculation (ported by Gemini 2.5 Pro with some fixes thx Gemini xoxoxo)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.perfectdreams.loritta.morenitta | |
import kotlinx.io.bytestring.encodeToByteString | |
import java.nio.ByteBuffer | |
import java.nio.ByteOrder | |
// Constants used in the MurmurHash3 algorithm | |
// These are the 32-bit patterns. Using .toInt() correctly captures their signed representation if needed. | |
private const val C1_INT: Int = 0xCC9E2D51.toInt() // JavaScript: 3432918353 | |
private const val C2_INT: Int = 0x1B873593.toInt() // JavaScript: 461845907 | |
private const val M_ADD_INT: Int = 0xE6546B64.toInt() // JavaScript: 3864292196 (used in addition) | |
private const val FMIX_C1_INT: Int = 0x85EBCA6B.toInt() // JavaScript: 2246822507 | |
private const val FMIX_C2_INT: Int = 0xC2B2AE35.toInt() // JavaScript: 3266489909 | |
/** | |
* Calculates a hash value for the given string, similar to the provided JavaScript function. | |
* | |
* @param e The input string. | |
* @param seed An optional seed value, defaults to 0. | |
* @return A UInt representing the 32-bit hash value. | |
*/ | |
fun discordExperimentHash(e: String, seed: Int = 0): UInt { | |
var h1: Int = seed // This corresponds to '$' in the JavaScript code | |
// Encode the string to a UTF-8 byte array | |
val bytes = e.encodeToByteString(Charsets.UTF_8) | |
val len = bytes.size | |
val nblocks = len / 4 // Number of 4-byte chunks | |
// Use ByteBuffer to read 32-bit integers in little-endian order | |
val buffer = ByteBuffer.wrap(bytes.toByteArray()).order(ByteOrder.LITTLE_ENDIAN) | |
// --- Process 4-byte chunks --- | |
for (b in 0 until nblocks) { | |
val offset = b * 4 | |
var k1: Int = buffer.getInt(offset) // Corresponds to 'c' getting m.getUint32(n, !0) | |
// k1 = Math.imul(k1, 3432918353) | |
k1 *= C1_INT | |
// k1 = (k1 << 15) | (k1 >>> 17) | |
k1 = (k1 shl 15) or (k1 ushr 17) // 'ushr' is the logical right shift (>>>) | |
// k1 = Math.imul(k1, 461845907) | |
k1 *= C2_INT | |
// h1 ^= k1 | |
h1 = h1 xor k1 | |
// h1 = (h1 << 13) | (h1 >>> 19) | |
h1 = (h1 shl 13) or (h1 ushr 19) | |
// h1 = Math.imul(h1, 5) + 3864292196 | |
// Kotlin's Int arithmetic wraps, matching Math.imul and 32-bit addition | |
h1 = h1 * 5 + M_ADD_INT | |
// ($ >>>= 0) in JS is implicit in Kotlin's Int type for bitwise ops | |
} | |
// --- Process remaining tail bytes --- | |
var tailK1: Int = 0 // Corresponds to 'c' for the tail part | |
val tailIndex = nblocks * 4 | |
val remainder = len and 3 // Equivalent to len % 4 or (3 & u) in JS | |
// Mimic JavaScript's switch fallthrough for tail byte processing | |
if (remainder >= 3) { // case 3 | |
// c ^= t[f + 2] << 16 | |
tailK1 = tailK1 xor ((bytes[tailIndex + 2].toInt() and 0xFF) shl 16) | |
} | |
if (remainder >= 2) { // case 2 (or fallthrough from 3) | |
// c ^= t[f + 1] << 8 | |
tailK1 = tailK1 xor ((bytes[tailIndex + 1].toInt() and 0xFF) shl 8) | |
} | |
if (remainder >= 1) { // case 1 (or fallthrough from 2) | |
// c ^= t[f + 0] | |
tailK1 = tailK1 xor (bytes[tailIndex + 0].toInt() and 0xFF) | |
// Process tailK1 as in JS: | |
// c = Math.imul(c, 3432918353) | |
// c = (c << 15) | (c >>> 17) | |
// c = Math.imul(c, 461845907) | |
// $ ^= c | |
tailK1 *= C1_INT | |
tailK1 = (tailK1 shl 15) or (tailK1 ushr 17) | |
tailK1 *= C2_INT | |
h1 = h1 xor tailK1 | |
} | |
// --- Finalization (fmix32) --- | |
// h1 ^= len | |
h1 = h1 xor len | |
// h1 ^= h1 >>> 16 | |
h1 = h1 xor (h1 ushr 16) | |
// h1 = Math.imul(h1, 2246822507) | |
h1 *= FMIX_C1_INT | |
// h1 ^= h1 >>> 13 | |
h1 = h1 xor (h1 ushr 13) | |
// h1 = Math.imul(h1, 3266489909) | |
h1 *= FMIX_C2_INT | |
// h1 ^= h1 >>> 16 | |
h1 = h1 xor (h1 ushr 16) | |
// The final `>>> 0` in JavaScript effectively converts the 32-bit signed result | |
// (which is how JS bitwise operations treat numbers) into an unsigned 32-bit number. | |
// In Kotlin, we achieve this by converting the Int (which holds the correct 32-bit pattern) | |
// to a UInt. | |
return h1.toUInt() | |
} | |
fun main() { | |
println(discordExperimentHash("MrPowerGamerBR").toInt() % 10_000) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment