A RuneTek3 client (377) that is deobfuscated, converted to Kotlin, and includes QoL improvements.
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: swap to server.properties, drop snakeyaml

+148 -93
+35
.forgejo/workflows/ci.yml
··· 1 + --- 2 + name: ci 3 + on: 4 + push: 5 + branches: [main] 6 + pull_request: 7 + workflow_dispatch: 8 + 9 + jobs: 10 + build: 11 + runs-on: kotlin # sickday/kotlin:latest (JDK 21; Gradle via ./gradlew wrapper) 12 + steps: 13 + - uses: actions/checkout@v4 14 + 15 + - name: Cache Gradle 16 + uses: actions/cache@v4 17 + with: 18 + path: | 19 + ~/.gradle/caches 20 + ~/.gradle/wrapper 21 + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 22 + restore-keys: gradle-${{ runner.os }}- 23 + 24 + # The wrapper self-downloads Gradle 9.0; the image only provides JDK 21. 25 + - name: Build jar 26 + run: ./gradlew --no-daemon clean jar 27 + 28 + - name: Publish jar to the package registry 29 + if: github.ref == 'refs/heads/main' 30 + run: | 31 + JAR=$(ls build/libs/*.jar | head -1) 32 + curl -fsSL --user "${{ github.actor }}:${{ secrets.REGISTRY_TOKEN }}" \ 33 + --upload-file "$JAR" \ 34 + "https://git.dunk.works/api/packages/${{ github.repository_owner }}/generic/hla-client/${{ github.sha }}/$(basename "$JAR")" 35 + # REGISTRY_TOKEN = a Forgejo PAT with write:package (org/repo Actions secret).
+1 -1
.gitignore
··· 56 56 .gradle/ 57 57 58 58 # Config 59 - config/client-config.yaml 59 + config/server.properties 60 60 61 61 # Debug dumps (research artifacts written by client code that's been removed) 62 62 DumpedData/
+6 -7
README.md
··· 7 7 8 8 ### Client Config 9 9 10 - Copy `config/EXAMPLE-client-config.yaml` to `config/client-config.yaml` and edit as needed. The config file is not tracked by git. 10 + Copy `config/EXAMPLE-server.properties` to `config/server.properties` and edit as needed. The config file is not tracked by git. 11 11 12 12 ### Running 13 13 ··· 24 24 25 25 RSA encrypts the login block between client and server. Disabled by default — both sides must match. 26 26 27 - In `config/client-config.yaml`, set the modulus from the server's `mix rsa.keygen` output: 28 - ```yaml 29 - rsa: 30 - rsaEnabled: true 31 - rsaPub: 65537 32 - rsaModulus: <decimal modulus from server> 27 + In `config/server.properties`, set the modulus from the server's `mix rsa.keygen` output: 28 + ```properties 29 + rsa.rsaEnabled=true 30 + rsa.rsaPub=65537 31 + rsa.rsaModulus=<decimal modulus from server> 33 32 ```
-1
build.gradle
··· 11 11 } 12 12 13 13 dependencies { 14 - implementation 'org.yaml:snakeyaml:2.0' 15 14 testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' 16 15 testImplementation 'org.testng:testng:7.9.0' 17 16 }
-21
config/EXAMPLE-client-config.yaml
··· 1 - net: 2 - address: 127.0.0.1 3 - game_port: 43594 4 - ondemand_port: 43594 5 - jaggrab_port: 43595 6 - http_port: 80 7 - cache: 8 - cacheDir: .377cache 9 - jaggrabEnabled: true 10 - rsa: 11 - rsaEnabled: false 12 - rsaPub: 65537 13 - rsaModulus: 170266381807335046121774073514220583891686029487165562794998484549236036467227923571770256617931840775621072487838687650522710227973331693237285456731778528244126984080232314114323601116304887478969296070648644633713088027922830600712492972687351204275625149978223159432963210789506993409208545916714905193639 14 - login: 15 - useStaticCredentials: false 16 - username: "" 17 - password: "" 18 - game: 19 - roofsEnabled: true 20 - freeTeleports: false 21 - debugContext: false
+31
config/EXAMPLE-server.properties
··· 1 + # Client configuration. Copy this file to config/server.properties and edit. 2 + # NOTE: '#' starts a comment only at the START of a line. Do NOT put an inline 3 + # comment after a value (e.g. `net.address=host # note`) — it becomes part of 4 + # the value. Put comments on their own lines, like these. 5 + 6 + # Network 7 + net.address=127.0.0.1 8 + net.game_port=43594 9 + net.ondemand_port=43594 10 + net.jaggrab_port=43595 11 + net.http_port=80 12 + 13 + # Cache 14 + cache.cacheDir=.377cache 15 + cache.jaggrabEnabled=true 16 + 17 + # RSA login block encryption. rsaEnabled must match the server's HLA_RSA_ENABLED. 18 + # Paste rsaModulus from the server's `mix rsa.keygen` output. 19 + rsa.rsaEnabled=false 20 + rsa.rsaPub=65537 21 + rsa.rsaModulus=170266381807335046121774073514220583891686029487165562794998484549236036467227923571770256617931840775621072487838687650522710227973331693237285456731778528244126984080232314114323601116304887478969296070648644633713088027922830600712492972687351204275625149978223159432963210789506993409208545916714905193639 22 + 23 + # Login (useStaticCredentials=true auto-logs in with username/password below) 24 + login.useStaticCredentials=false 25 + login.username= 26 + login.password= 27 + 28 + # Game 29 + game.roofsEnabled=true 30 + game.freeTeleports=false 31 + game.debugContext=false
+51 -42
src/main/kotlin/com/jagex/runescape/config/Configuration.kt
··· 1 1 package com.jagex.runescape.config 2 2 3 - import org.yaml.snakeyaml.Yaml 4 3 import java.io.File 5 4 import java.io.FileInputStream 6 5 import java.math.BigInteger 6 + import java.util.Properties 7 7 8 8 /** 9 - * Client configuration loaded from YAML at startup. Filesystem config at 10 - * `./config/client-config.yaml` takes precedence over the bundled JAR default. 11 - * If neither is found, the hardcoded defaults below are used. 9 + * Client configuration loaded from a Java properties file at startup. 10 + * Filesystem config at `./config/server.properties` takes precedence over the 11 + * bundled JAR default at `/server.properties`. If neither is found, the 12 + * hardcoded defaults below are used. Any individual key that is missing or 13 + * unparseable falls back to its default, so a partial file is valid. 14 + * 15 + * Keys are dotted, mirroring the former YAML sections — e.g. `net.address`, 16 + * `rsa.rsaModulus`. Note: in a properties file `#` starts a comment only at the 17 + * beginning of a line; an inline `#` after a value is part of the value, so keep 18 + * values clean and put comments on their own lines. 12 19 */ 13 20 object Configuration { 14 21 var SERVER_ADDRESS = "127.0.0.1" ··· 33 40 var FREE_TELEPORTS = false 34 41 var DEBUG_CONTEXT = true 35 42 36 - 37 - @Suppress("UNCHECKED_CAST") 38 43 fun read() { 44 + val props = Properties() 39 45 try { 40 - val yaml = Yaml() 41 - val inputStream = run { 42 - val external = File("./config/client-config.yaml") 43 - if (external.exists()) { 44 - println("Loaded config from ./config/client-config.yaml") 45 - FileInputStream(external) 46 - } else { 47 - val bundled = Configuration::class.java.getResourceAsStream("/client-config.yaml") 48 - if (bundled != null) println("Loaded bundled config from jar") 49 - bundled 50 - } 46 + val external = File("./config/server.properties") 47 + val stream = if (external.exists()) { 48 + println("Loaded config from ./config/server.properties") 49 + FileInputStream(external) 50 + } else { 51 + Configuration::class.java.getResourceAsStream("/server.properties") 52 + ?.also { println("Loaded bundled config from jar") } 51 53 } 52 54 53 - if (inputStream == null) { 55 + if (stream == null) { 54 56 println("No config found, using defaults.") 55 57 return 56 58 } 57 59 58 - val obj = inputStream.use { yaml.load<Map<String, Any>>(it) } 59 - 60 - val net = obj["net"] as Map<String, Any> 61 - val cache = obj["cache"] as Map<String, Any> 62 - val rsa = obj["rsa"] as Map<String, Any> 63 - val login = obj["login"] as Map<String, Any> 64 - val game = obj["game"] as Map<String, Any> 65 - 66 - SERVER_ADDRESS = net["address"] as String 67 - GAME_PORT = net["game_port"] as Int 68 - JAGGRAB_PORT = net["jaggrab_port"] as Int 69 - ONDEMAND_PORT = net["ondemand_port"] as Int 70 - HTTP_PORT = net["http_port"] as Int 71 - CACHE_NAME = cache["cacheDir"] as String 72 - JAGGRAB_ENABLED = cache["jaggrabEnabled"] as Boolean 73 - RSA_ENABLED = rsa["rsaEnabled"] as Boolean 74 - RSA_PUBLIC_KEY = BigInteger((rsa["rsaPub"] as Int).toString()) 75 - RSA_MODULUS = rsa["rsaModulus"] as BigInteger 76 - USE_STATIC_DETAILS = login["useStaticCredentials"] as Boolean 77 - USERNAME = (login["username"] as? String) ?: "" 78 - PASSWORD = (login["password"] as? String) ?: "" 79 - ROOFS_ENABLED = game["roofsEnabled"] as Boolean 80 - FREE_TELEPORTS = game["freeTeleports"] as Boolean 81 - DEBUG_CONTEXT = game["debugContext"] as Boolean 60 + stream.use { props.load(it) } 82 61 } catch (e: Exception) { 83 62 println("Unable to load config: ${e.message}") 63 + return 84 64 } 65 + 66 + SERVER_ADDRESS = props.str("net.address", SERVER_ADDRESS) 67 + GAME_PORT = props.int("net.game_port", GAME_PORT) 68 + ONDEMAND_PORT = props.int("net.ondemand_port", ONDEMAND_PORT) 69 + JAGGRAB_PORT = props.int("net.jaggrab_port", JAGGRAB_PORT) 70 + HTTP_PORT = props.int("net.http_port", HTTP_PORT) 71 + CACHE_NAME = props.str("cache.cacheDir", CACHE_NAME) 72 + JAGGRAB_ENABLED = props.bool("cache.jaggrabEnabled", JAGGRAB_ENABLED) 73 + RSA_ENABLED = props.bool("rsa.rsaEnabled", RSA_ENABLED) 74 + RSA_PUBLIC_KEY = props.bigint("rsa.rsaPub", RSA_PUBLIC_KEY) 75 + RSA_MODULUS = props.bigint("rsa.rsaModulus", RSA_MODULUS) 76 + USE_STATIC_DETAILS = props.bool("login.useStaticCredentials", USE_STATIC_DETAILS) 77 + USERNAME = props.str("login.username", USERNAME) 78 + PASSWORD = props.str("login.password", PASSWORD) 79 + ROOFS_ENABLED = props.bool("game.roofsEnabled", ROOFS_ENABLED) 80 + FREE_TELEPORTS = props.bool("game.freeTeleports", FREE_TELEPORTS) 81 + DEBUG_CONTEXT = props.bool("game.debugContext", DEBUG_CONTEXT) 85 82 } 83 + 84 + private fun Properties.str(key: String, default: String): String = 85 + getProperty(key)?.trim() ?: default 86 + 87 + private fun Properties.int(key: String, default: Int): Int = 88 + getProperty(key)?.trim()?.toIntOrNull() ?: default 89 + 90 + private fun Properties.bool(key: String, default: Boolean): Boolean = 91 + getProperty(key)?.trim()?.toBooleanStrictOrNull() ?: default 92 + 93 + private fun Properties.bigint(key: String, default: BigInteger): BigInteger = 94 + getProperty(key)?.trim()?.let { runCatching { BigInteger(it) }.getOrNull() } ?: default 86 95 }
-21
src/main/resources/client-config.yaml
··· 1 - net: 2 - address: 127.0.0.1 3 - game_port: 43594 4 - ondemand_port: 43594 5 - jaggrab_port: 43595 6 - http_port: 8080 7 - cache: 8 - cacheDir: .377cache 9 - jaggrabEnabled: true 10 - rsa: 11 - rsaEnabled: false 12 - rsaPub: 65537 13 - rsaModulus: 170266381807335046121774073514220583891686029487165562794998484549236036467227923571770256617931840775621072487838687650522710227973331693237285456731778528244126984080232314114323601116304887478969296070648644633713088027922830600712492972687351204275625149978223159432963210789506993409208545916714905193639 14 - login: 15 - useStaticCredentials: false 16 - username: 17 - password: 18 - game: 19 - roofsEnabled: true 20 - freeTeleports: false 21 - debugContext: false
+24
src/main/resources/server.properties
··· 1 + # Bundled default client configuration. 2 + # Used only when ./config/server.properties is absent. Copy 3 + # config/EXAMPLE-server.properties to config/server.properties to override. 4 + 5 + net.address=127.0.0.1 6 + net.game_port=43594 7 + net.ondemand_port=43594 8 + net.jaggrab_port=43595 9 + net.http_port=8080 10 + 11 + cache.cacheDir=.377cache 12 + cache.jaggrabEnabled=true 13 + 14 + rsa.rsaEnabled=false 15 + rsa.rsaPub=65537 16 + rsa.rsaModulus=170266381807335046121774073514220583891686029487165562794998484549236036467227923571770256617931840775621072487838687650522710227973331693237285456731778528244126984080232314114323601116304887478969296070648644633713088027922830600712492972687351204275625149978223159432963210789506993409208545916714905193639 17 + 18 + login.useStaticCredentials=false 19 + login.username= 20 + login.password= 21 + 22 + game.roofsEnabled=true 23 + game.freeTeleports=false 24 + game.debugContext=false