Sam Rivera Build
Shipping Per-Slot Jars from the Gradle Plugin
The HashGuard Gradle plugin wraps the engine as a plrObfuscate task. Apply it in the Minecraft client project and one command produces per-slot jars, each encrypted with its own keys.
One task, N jars
Apply fr.polargames.plr-obfuscator and configure the plrObfuscator extension. The task takes your compiled jar and an obfuscator config, then writes client_0.jar to client_N.jar and a keys-manifest.json into build/plr-obfuscated.
// settings.gradle.kts of the Minecraft client projectpluginManagement { includeBuild("/path/to/PLR-Obfuscator")}// build.gradle.ktsplugins { id("fr.polargames.plr-obfuscator")}plrObfuscator { inputJar = tasks.named<Jar>("jar").flatMap { it.archiveFile } configFile = file("obfuscator-config.json") slotCount = 100 // default 1, handy for local tests}./gradlew plrObfuscateThe extension options
inputJaris the compiled jar to obfuscate, and it is required.configFileis the path to the obfuscator config JSON, and it is required.slotCountis the number of jars to produce, from 1 to 100,1by default.outputDiris the output folder,build/plr-obfuscatedby default.
- 01 Parse config Read the ObfuscatorConfig JSON
- 02 Draw seeds One random 32-byte seed per slot, from SecureRandom
- 03 Obfuscate N slots In parallel on virtual threads, each with its own keys
- 04 Write output client_0.jar to client_N.jar and keys-manifest.json
Where the keys come from
For every slot the plugin draws a random 32-byte seed with SecureRandom and derives two keys from it with HKDF: a 16-byte AES key with info plr-classfile-aes and a 32-byte HMAC key with info plr-classfile-hmac. Each slot then runs the obfuscator with its own keys, and magicCrypter encrypts every class into the PLRG format.
Rotation without bookkeeping
Seeds are not persisted. Every ./gradlew plrObfuscate generates new keys, and that is the rotation: nothing to store, nothing to forget to rotate. The keys-manifest.json lists the derived keys in hex for every slot, and it is meant to be consumed in the same CI run that produced it.
{ "slot_count": 100, "slots": [ { "slot": 0, "aes_key_hex": "...(32 hex)...", "hmac_key_hex": "...(64 hex)..." }, { "slot": 1, "aes_key_hex": "...", "hmac_key_hex": "..." } ]}A requirement: magicCrypter
The PLRG format only appears when magicCrypter is true in the config of the jar you build. A PLRG class replaces CAFEBABE with PLRG, followed by a 16-byte IV, an HMAC-SHA256 and the AES-128-CTR ciphertext of the zlib-compressed class. It needs the PLR JVM to load.
At runtime the slot is derived from the player’s id with sha256(user_uuid)[0:4] % slot_count, and the launcher downloads the matching jar.