Skip to content

Commit 13ac1ec

Browse files
committed
Merge branch 'diff'
2 parents 5f07770 + a0bce84 commit 13ac1ec

122 files changed

Lines changed: 6869 additions & 9158 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ Run the server with the following arguments:
2323
- `game` (optional): Game type - `OLDSCHOOL` or `RUNESCAPE3` (default: `OLDSCHOOL`)
2424
- `environment` (optional): Environment - `LIVE`, `BETA`, or `TEST` (default: `LIVE`)
2525
- `port` (optional): Network port (default: 8090)
26+
- `navDisplayOverrides` (optional): Comma/semicolon-separated `key=value` list for nav display-name overrides.
27+
28+
### Nav Display Name Overrides
29+
30+
You can override sidebar labels returned by `/cache/nav` and `/cache/gameval/groups`.
31+
32+
Supported input formats:
33+
34+
- CLI arg 5: `worldentity=World Entities;worldmaparea=World Map Area;spotanim=SpotAnim;inv=Inventories`
35+
- Env var `OPENRUNE_NAV_DISPLAY_OVERRIDES` with the same format.
36+
37+
Examples:
38+
39+
```bash
40+
# CLI argument
41+
./gradlew run --args="237 OLDSCHOOL LIVE 8090 worldentity=World Entities;worldmaparea=World Map Area;spotanim=SpotAnim;inv=Inventories"
42+
43+
# Environment variable
44+
OPENRUNE_NAV_DISPLAY_OVERRIDES="worldentity=World Entities;worldmaparea=World Map Area;spotanim=SpotAnim;inv=Inventories" ./gradlew run --args="237 OLDSCHOOL LIVE 8090"
45+
```
2646

2747
### Examples
2848

build.gradle.kts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ repositories {
1515
}
1616

1717
dependencies {
18+
implementation(kotlin("reflect"))
1819
implementation("io.ktor:ktor-server-core:2.3.5")
1920
implementation("io.ktor:ktor-server-netty:2.3.5")
2021
implementation("io.ktor:ktor-server-content-negotiation:2.3.5")
22+
implementation("io.ktor:ktor-server-compression:2.3.5")
2123
implementation("io.ktor:ktor-serialization-gson:2.3.5")
2224
implementation("io.ktor:ktor-server-status-pages:2.3.5")
23-
implementation("dev.or2:all:2.3.7")
25+
implementation("dev.or2:all:2.4.1")
2426
implementation("cc.ekblad:4koma:1.2.2-openrune")
2527

2628
// JSON serialization with Gson
@@ -36,6 +38,9 @@ dependencies {
3638
// Progress bar (cross-platform, works on Linux)
3739
implementation("me.tongfei:progressbar:0.9.5")
3840

41+
// Zstd compression for diff binary format
42+
implementation("com.github.luben:zstd-jni:1.5.5-11")
43+
3944
testImplementation(kotlin("test"))
4045
}
4146

@@ -69,20 +74,47 @@ tasks {
6974
group = null
7075
}
7176

72-
fun registerBootTask(name: String, rev: Int, gameType: String, environment: String) {
77+
fun registerBootTask(name: String, cacheID : Int,gameType: String, environment: String) {
7378
register<JavaExec>(name) {
7479
group = "application"
7580
description = "Boots the RuneScape cache with $gameType ($environment)"
7681
mainClass.set("dev.openrune.MainKt")
7782
classpath = sourceSets["main"].runtimeClasspath
78-
args = listOf(rev.toString(), gameType, environment)
79-
jvmArgs("-Xmx4G")
83+
args = listOf(cacheID.toString(),gameType, environment)
84+
jvmArgs("-Xmx8G")
8085
}
8186
}
8287

83-
registerBootTask("bootRunescape", -1, "RUNESCAPE3", "LIVE")
84-
registerBootTask("bootOldschool", 235, "OLDSCHOOL", "LIVE")
85-
registerBootTask("bootSailing", 232, "OLDSCHOOL", "BETA")
88+
fun registerBootTaskDev(name: String, cacheID : Int,gameType: String, environment: String) {
89+
register<JavaExec>(name) {
90+
group = "application"
91+
description = "Boots the RuneScape cache with $gameType ($environment)"
92+
mainClass.set("dev.openrune.MainKt")
93+
classpath = sourceSets["main"].runtimeClasspath
94+
args = listOf(cacheID.toString(),gameType, environment)
95+
jvmArgs("-Xmx8G","-Dopenrune.perf.logs=true","-Dopenrune.table.logs=true")
96+
}
97+
}
98+
99+
registerBootTask("bootRunescape", -1, "RUNESCAPE3", "LIVE")
100+
registerBootTask("bootOldschool", 2518, "OLDSCHOOL", "LIVE")
101+
registerBootTaskDev("bootOldschoolDev", 2518, "OLDSCHOOL", "DEV")
102+
registerBootTask("bootSailing", -1, "OLDSCHOOL", "BETA")
103+
104+
register<JavaExec>("runDownloadAllCaches") {
105+
group = "application"
106+
description = "Download and unzip all OSRS caches (rev 1 to latest) one by one for later dumping"
107+
mainClass.set("dev.openrune.DownloadAllCachesMainKt")
108+
classpath = sourceSets["main"].runtimeClasspath
109+
}
110+
111+
register<JavaExec>("runDumperMain") {
112+
group = "application"
113+
description = "Runs the DiffDumper entrypoint in DumperMainKt"
114+
mainClass.set("dev.openrune.DumperMainKt")
115+
classpath = sourceSets["main"].runtimeClasspath
116+
jvmArgs("-Xmx4G")
117+
}
86118
}
87119

88120

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package dev.openrune
2+
3+
import dev.openrune.cache.CacheDownloader
4+
import dev.openrune.cache.CachePathHelper
5+
import dev.openrune.cache.tools.CacheEnvironment
6+
import dev.openrune.cache.tools.DownloadListener
7+
import dev.openrune.cache.tools.GameType
8+
import dev.openrune.cache.tools.OpenRS2
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.delay
11+
import kotlinx.coroutines.runBlocking
12+
import kotlinx.coroutines.withContext
13+
import kotlinx.coroutines.suspendCancellableCoroutine
14+
import kotlin.coroutines.resume
15+
import kotlin.coroutines.resumeWithException
16+
import mu.KotlinLogging
17+
import java.io.File
18+
import java.io.IOException
19+
20+
private val logger = KotlinLogging.logger {}
21+
22+
private const val MAX_DOWNLOAD_RETRIES = 3
23+
private const val RETRY_DELAY_MS = 15_000L
24+
private const val DELAY_BETWEEN_DOWNLOADS_MS = 60_000L
25+
26+
/**
27+
* Downloads and unzips all OSRS caches from rev 1 to latest (e.g. 236), one by one.
28+
* Skips revs that already have cache/data/cache on disk.
29+
* Run overnight so caches are ready for dumping in the morning.
30+
*
31+
* Usage: run this class as main (e.g. gradle runDownloadAllCaches or IDE run config).
32+
*/
33+
fun main() = runBlocking {
34+
val gameType = GameType.OLDSCHOOL
35+
val environment = CacheEnvironment.LIVE
36+
val downloader = CacheDownloader()
37+
38+
OpenRS2.loadCaches()
39+
val game = gameType.name
40+
val env = environment.name
41+
val available = OpenRS2.allCaches
42+
.filter { (it.game ?: "").equals(game, true) && (it.environment ?: "").equals(env, true) }
43+
.flatMap { c -> (c.builds ?: emptyList()).map { b -> b.major } }
44+
.distinct()
45+
.filter { it >= 1 }
46+
.sorted()
47+
48+
if (available.isEmpty()) {
49+
logger.error { "No OSRS revisions found in OpenRS2 for $game / $env" }
50+
return@runBlocking
51+
}
52+
53+
val total = available.size
54+
val maxRev = available.maxOrNull() ?: return@runBlocking
55+
logger.info { "Cache download: $total caches to process (rev 1..$maxRev). Existing caches skipped." }
56+
57+
var skipped = 0
58+
var downloaded = 0
59+
var failed = 0
60+
available.forEachIndexed { index, rev ->
61+
withContext(Dispatchers.IO) {
62+
val current = index + 1
63+
val cacheDir = CachePathHelper.getCacheDirectory(gameType, environment, rev)
64+
val cachePath = File(cacheDir, "data/cache")
65+
if (cachePath.exists()) {
66+
skipped++
67+
logger.info { "[$current/$total] Rev $rev: already present, skip" }
68+
return@withContext
69+
}
70+
cacheDir.mkdirs()
71+
var success = false
72+
var lastError: Exception? = null
73+
repeat(MAX_DOWNLOAD_RETRIES) { attempt ->
74+
if (success) return@repeat
75+
try {
76+
if (attempt > 0) {
77+
logger.warn { "[$current/$total] Rev $rev: retry $attempt/$MAX_DOWNLOAD_RETRIES after ${RETRY_DELAY_MS / 1000}s (last: ${lastError?.message})" }
78+
delay(RETRY_DELAY_MS)
79+
}
80+
logger.info { "[$current/$total] Rev $rev: downloading..." }
81+
suspendCancellableCoroutine<Unit> { cont ->
82+
val listener = object : DownloadListener {
83+
override fun onProgress(progress: Int, max: Long, currentBytes: Long) {
84+
if (max <= 0) return
85+
val pct = (currentBytes * 100 / max).toInt().coerceIn(0, 100)
86+
if (pct % 10 == 0 || pct == 100) {
87+
val curMb = currentBytes / (1024 * 1024)
88+
val maxMb = max / (1024 * 1024)
89+
logger.info { "[$current/$total] Rev $rev: download $pct% (${curMb}MB / ${maxMb}MB)" }
90+
}
91+
}
92+
override fun onError(exception: Exception) {
93+
cont.resumeWithException(exception)
94+
}
95+
override fun onFinished() {
96+
cont.resume(Unit)
97+
}
98+
}
99+
try {
100+
OpenRS2.downloadCacheByRevision(rev, cacheDir, gameType, environment, -1, listener)
101+
} catch (e: Exception) {
102+
cont.resumeWithException(e)
103+
}
104+
}
105+
logger.info { "[$current/$total] Rev $rev: download done, extracting..." }
106+
downloader.unzipCache(cacheDir) { _, progress, _ ->
107+
progress?.let { p ->
108+
val pct = ((p - 50) / 49 * 100).toInt().coerceIn(0, 100)
109+
if (pct % 25 == 0 || pct == 100) {
110+
logger.info { "[$current/$total] Rev $rev: extract $pct%" }
111+
}
112+
}
113+
}
114+
logger.info { "[$current/$total] Rev $rev: done" }
115+
downloaded++
116+
success = true
117+
if (current < total) {
118+
logger.info { "Waiting ${DELAY_BETWEEN_DOWNLOADS_MS / 1000}s before next..." }
119+
delay(DELAY_BETWEEN_DOWNLOADS_MS)
120+
}
121+
} catch (e: Exception) {
122+
lastError = e
123+
val isRetryable = e is IOException || e.cause is IOException
124+
if (!isRetryable || attempt == MAX_DOWNLOAD_RETRIES - 1) {
125+
logger.error(e) { "[$current/$total] Rev $rev: failed (${e.message}). Skipping." }
126+
failed++
127+
if (current < total) {
128+
logger.info { "Waiting ${DELAY_BETWEEN_DOWNLOADS_MS / 1000}s before next..." }
129+
delay(DELAY_BETWEEN_DOWNLOADS_MS)
130+
}
131+
return@repeat
132+
}
133+
logger.warn { "[$current/$total] Rev $rev: attempt ${attempt + 1} failed (${e.message}), will retry" }
134+
}
135+
}
136+
}
137+
}
138+
139+
logger.info { "Cache download complete: $total total, $downloaded downloaded+extracted, $skipped already present, $failed failed." }
140+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dev.openrune
2+
3+
import dev.openrune.cache.diff.DiffDumper
4+
import dev.openrune.cache.diff.DiffBinaryCache
5+
import dev.openrune.cache.tools.CacheEnvironment
6+
import dev.openrune.cache.tools.GameType
7+
import dev.openrune.cache.tools.OpenRS2
8+
import kotlinx.coroutines.runBlocking
9+
10+
fun main(args: Array<String>) {
11+
val gameType = GameType.OLDSCHOOL
12+
val environment = CacheEnvironment.LIVE
13+
14+
OpenRS2.loadCaches()
15+
try {
16+
runBlocking {
17+
DiffDumper(gameType, environment).run(1, 241)
18+
DiffDumper(gameType, environment).run(100, 210)
19+
DiffDumper(gameType, environment).run(236, 2499)
20+
DiffDumper(gameType, environment).run(237, 2518)
21+
}
22+
} finally {
23+
DiffBinaryCache.shutdown()
24+
}
25+
}

src/main/kotlin/dev/openrune/Main.kt

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,31 @@ import mu.KotlinLogging
99

1010
private val logger = KotlinLogging.logger {}
1111

12+
private const val NAV_OVERRIDES_ENV = "OPENRUNE_NAV_DISPLAY_OVERRIDES"
13+
14+
private fun parseNavDisplayOverrides(raw: String?): Map<String, String> {
15+
if (raw.isNullOrBlank()) return emptyMap()
16+
return raw
17+
.split(';', ',')
18+
.mapNotNull { part ->
19+
val token = part.trim()
20+
if (token.isEmpty()) return@mapNotNull null
21+
val idx = token.indexOf('=')
22+
if (idx <= 0 || idx >= token.length - 1) return@mapNotNull null
23+
val key = token.substring(0, idx).trim().lowercase()
24+
val value = token.substring(idx + 1).trim()
25+
if (key.isEmpty() || value.isEmpty()) return@mapNotNull null
26+
key to value
27+
}
28+
.toMap()
29+
}
30+
1231
fun main(args: Array<String>) {
13-
val rev = args.getOrNull(0)?.toIntOrNull() ?: -1
32+
val cacheID = args.getOrNull(0)?.toIntOrNull() ?: -1
1433
val game = args.getOrNull(1) ?: GameType.OLDSCHOOL.toString()
1534
val environmentType = args.getOrNull(2) ?: CacheEnvironment.LIVE.toString()
1635
val networkPort = args.getOrNull(3)?.toIntOrNull() ?: 8090
36+
val navOverrideArg = args.getOrNull(4)
1737

1838
val gameType = try {
1939
GameType.valueOf(game.uppercase())
@@ -27,11 +47,20 @@ fun main(args: Array<String>) {
2747
CacheEnvironment.LIVE
2848
}
2949

50+
val navDisplayNameOverrides = parseNavDisplayOverrides(
51+
navOverrideArg ?: System.getenv(NAV_OVERRIDES_ENV)
52+
)
53+
54+
if (navDisplayNameOverrides.isNotEmpty()) {
55+
logger.info { "Loaded ${navDisplayNameOverrides.size} nav display-name override(s) from ${if (navOverrideArg != null) "CLI arg" else NAV_OVERRIDES_ENV}" }
56+
}
57+
3058
val config = ServerConfig(
31-
revision = rev,
3259
gameType = gameType,
60+
cacheID = cacheID,
3361
environment = cacheEnv,
34-
port = networkPort
62+
port = networkPort,
63+
navDisplayNameOverrides = navDisplayNameOverrides,
3564
)
3665

3766
runBlocking {

src/main/kotlin/dev/openrune/ServerConfig.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@ package dev.openrune
22

33
import dev.openrune.cache.tools.CacheEnvironment
44
import dev.openrune.cache.tools.GameType
5+
import java.io.File
56

67
data class ServerConfig(
7-
val revision: Int,
88
val gameType: GameType,
9+
val cacheID: Int,
910
val environment: CacheEnvironment,
10-
val port: Int
11-
)
11+
val port: Int,
12+
/** Optional nav display-name overrides keyed by section/group id (e.g. "spotanim" -> "SpotAnim"). */
13+
val navDisplayNameOverrides: Map<String, String> = emptyMap(),
14+
/** When set, extractors write to this dir (e.g. diff/) instead of extracted/. Used by DiffDumper. */
15+
val diffOutputDir: File? = null
16+
) {
17+
/** Resolved from cacheID via OpenRS2 during startup. -1 until loaded. */
18+
var revision: Int = -1
19+
}
1220

1321

1422

0 commit comments

Comments
 (0)