From 412d6f4c1fec3744ae1e445aedfdebde408be18c Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 6 Feb 2021 12:58:12 +0000 Subject: [PATCH] Add XTEA key export endpoint Signed-off-by: Graham --- archive/build.gradle.kts | 2 + .../org/openrs2/archive/ArchiveModule.kt | 2 + .../openrs2/archive/cache/CacheExporter.kt | 59 +++++++++++++++++++ .../openrs2/archive/web/CachesController.kt | 10 ++++ .../org/openrs2/archive/web/WebServer.kt | 13 +++- .../archive/templates/caches/index.html | 3 +- 6 files changed, 87 insertions(+), 2 deletions(-) diff --git a/archive/build.gradle.kts b/archive/build.gradle.kts index 3461ca86b9..3229fb6874 100644 --- a/archive/build.gradle.kts +++ b/archive/build.gradle.kts @@ -16,10 +16,12 @@ dependencies { implementation(project(":cli")) implementation(project(":db")) implementation(project(":json")) + implementation(project(":json")) implementation(project(":net")) implementation(project(":protocol")) implementation(project(":util")) implementation("com.google.guava:guava:${Versions.guava}") + implementation("io.ktor:ktor-jackson:${Versions.ktor}") implementation("io.ktor:ktor-server-netty:${Versions.ktor}") implementation("io.ktor:ktor-thymeleaf:${Versions.ktor}") implementation("org.flywaydb:flyway-core:${Versions.flyway}") diff --git a/archive/src/main/kotlin/org/openrs2/archive/ArchiveModule.kt b/archive/src/main/kotlin/org/openrs2/archive/ArchiveModule.kt index 15999d192b..1d9bc1658d 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/ArchiveModule.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/ArchiveModule.kt @@ -3,6 +3,7 @@ package org.openrs2.archive import com.google.inject.AbstractModule import com.google.inject.Scopes import org.openrs2.buffer.BufferModule +import org.openrs2.cache.CacheModule import org.openrs2.db.Database import org.openrs2.json.JsonModule import org.openrs2.net.NetworkModule @@ -10,6 +11,7 @@ import org.openrs2.net.NetworkModule public object ArchiveModule : AbstractModule() { override fun configure() { install(BufferModule) + install(CacheModule) install(JsonModule) install(NetworkModule) diff --git a/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt b/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt index b607efcdde..b7d340704c 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt @@ -5,6 +5,7 @@ import io.netty.buffer.Unpooled import org.openrs2.buffer.use import org.openrs2.cache.Js5Archive import org.openrs2.cache.Store +import org.openrs2.crypto.XteaKey import org.openrs2.db.Database import java.time.Instant import javax.inject.Inject @@ -24,6 +25,14 @@ public class CacheExporter @Inject constructor( val name: String? ) + public data class GroupKey( + val archive: Int, + val group: Int, + val nameHash: Int?, + val name: String?, + val key: XteaKey + ) + public suspend fun list(): List { return database.execute { connection -> connection.prepareStatement( @@ -116,6 +125,56 @@ public class CacheExporter @Inject constructor( } } + public suspend fun exportKeys(id: Long): List { + return database.execute { connection -> + connection.prepareStatement( + """ + WITH t AS ( + SELECT a.archive_id, c.data, g.container_id + FROM master_indexes m + JOIN master_index_archives a ON a.container_id = m.container_id + JOIN groups g ON g.archive_id = 255 AND g.group_id = a.archive_id::INTEGER AND g.truncated_version = a.version & 65535 + JOIN containers c ON c.id = g.container_id AND c.crc32 = a.crc32 + JOIN indexes i ON i.container_id = g.container_id AND i.version = a.version + WHERE m.container_id = ? + ) + SELECT t.archive_id, ig.group_id, ig.name_hash, n.name, (k.key).k0, (k.key).k1, (k.key).k2, (k.key).k3 + FROM t + JOIN index_groups ig ON ig.container_id = t.container_id + JOIN groups g ON g.archive_id = t.archive_id::INTEGER AND g.group_id = ig.group_id AND g.truncated_version = ig.version & 65535 + JOIN containers c ON c.id = g.container_id AND c.crc32 = ig.crc32 + JOIN keys k ON k.id = c.key_id + LEFT JOIN names n ON n.hash = ig.name_hash AND n.name ~ '^l(?:[0-9]|[1-9][0-9])_(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' + """.trimIndent() + ).use { stmt -> + stmt.setLong(1, id) + + stmt.executeQuery().use { rows -> + val keys = mutableListOf() + + while (rows.next()) { + val archive = rows.getInt(1) + val group = rows.getInt(2) + var nameHash: Int? = rows.getInt(3) + if (rows.wasNull()) { + nameHash = null + } + val name = rows.getString(4) + + val k0 = rows.getInt(5) + val k1 = rows.getInt(6) + val k2 = rows.getInt(7) + val k3 = rows.getInt(8) + + keys += GroupKey(archive, group, nameHash, name, XteaKey(k0, k1, k2, k3)) + } + + keys + } + } + } + } + private companion object { private const val BATCH_SIZE = 1024 } diff --git a/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt b/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt index fe7a2fdeb8..b15969ab31 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt @@ -38,4 +38,14 @@ public class CachesController @Inject constructor( } } } + + public suspend fun exportKeys(call: ApplicationCall) { + val id = call.parameters["id"]?.toLongOrNull() + if (id == null) { + call.respond(HttpStatusCode.NotFound) + return + } + + call.respond(exporter.exportKeys(id)) + } } diff --git a/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt b/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt index 90a021e9c5..b837fb1991 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt @@ -1,12 +1,17 @@ package org.openrs2.archive.web +import com.fasterxml.jackson.databind.ObjectMapper import io.ktor.application.call import io.ktor.application.install +import io.ktor.features.ContentNegotiation +import io.ktor.http.ContentType +import io.ktor.jackson.JacksonConverter import io.ktor.routing.get import io.ktor.routing.routing import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import io.ktor.thymeleaf.Thymeleaf +import org.openrs2.json.Json import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect import org.thymeleaf.templatemode.TemplateMode import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver @@ -16,10 +21,15 @@ import javax.inject.Singleton @Singleton public class WebServer @Inject constructor( private val cachesController: CachesController, - private val keysController: KeysController + private val keysController: KeysController, + @Json private val mapper: ObjectMapper ) { public fun start() { embeddedServer(Netty, port = 8000) { + install(ContentNegotiation) { + register(ContentType.Application.Json, JacksonConverter(mapper)) + } + install(Thymeleaf) { addDialect(Java8TimeDialect()) @@ -32,6 +42,7 @@ public class WebServer @Inject constructor( routing { get("/caches") { cachesController.index(call) } get("/caches/{id}.zip") { cachesController.export(call) } + get("/caches/{id}.json") { cachesController.exportKeys(call) } // ideally we'd use POST /keys here, but I want to be compatible with the RuneLite/OpenOSRS API get("/keys/submit") { keysController.create(call) } diff --git a/archive/src/main/resources/org/openrs2/archive/templates/caches/index.html b/archive/src/main/resources/org/openrs2/archive/templates/caches/index.html index bcd70a9e67..acf2af6e9a 100644 --- a/archive/src/main/resources/org/openrs2/archive/templates/caches/index.html +++ b/archive/src/main/resources/org/openrs2/archive/templates/caches/index.html @@ -21,7 +21,8 @@ - Download + Cache + Keys