Add XTEA key export endpoint

Signed-off-by: Graham <gpe@openrs2.org>
bzip2
Graham 4 years ago
parent 936968363b
commit 412d6f4c1f
  1. 2
      archive/build.gradle.kts
  2. 2
      archive/src/main/kotlin/org/openrs2/archive/ArchiveModule.kt
  3. 59
      archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt
  4. 10
      archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt
  5. 13
      archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt
  6. 3
      archive/src/main/resources/org/openrs2/archive/templates/caches/index.html

@ -16,10 +16,12 @@ dependencies {
implementation(project(":cli")) implementation(project(":cli"))
implementation(project(":db")) implementation(project(":db"))
implementation(project(":json")) implementation(project(":json"))
implementation(project(":json"))
implementation(project(":net")) implementation(project(":net"))
implementation(project(":protocol")) implementation(project(":protocol"))
implementation(project(":util")) implementation(project(":util"))
implementation("com.google.guava:guava:${Versions.guava}") 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-server-netty:${Versions.ktor}")
implementation("io.ktor:ktor-thymeleaf:${Versions.ktor}") implementation("io.ktor:ktor-thymeleaf:${Versions.ktor}")
implementation("org.flywaydb:flyway-core:${Versions.flyway}") implementation("org.flywaydb:flyway-core:${Versions.flyway}")

@ -3,6 +3,7 @@ package org.openrs2.archive
import com.google.inject.AbstractModule import com.google.inject.AbstractModule
import com.google.inject.Scopes import com.google.inject.Scopes
import org.openrs2.buffer.BufferModule import org.openrs2.buffer.BufferModule
import org.openrs2.cache.CacheModule
import org.openrs2.db.Database import org.openrs2.db.Database
import org.openrs2.json.JsonModule import org.openrs2.json.JsonModule
import org.openrs2.net.NetworkModule import org.openrs2.net.NetworkModule
@ -10,6 +11,7 @@ import org.openrs2.net.NetworkModule
public object ArchiveModule : AbstractModule() { public object ArchiveModule : AbstractModule() {
override fun configure() { override fun configure() {
install(BufferModule) install(BufferModule)
install(CacheModule)
install(JsonModule) install(JsonModule)
install(NetworkModule) install(NetworkModule)

@ -5,6 +5,7 @@ import io.netty.buffer.Unpooled
import org.openrs2.buffer.use import org.openrs2.buffer.use
import org.openrs2.cache.Js5Archive import org.openrs2.cache.Js5Archive
import org.openrs2.cache.Store import org.openrs2.cache.Store
import org.openrs2.crypto.XteaKey
import org.openrs2.db.Database import org.openrs2.db.Database
import java.time.Instant import java.time.Instant
import javax.inject.Inject import javax.inject.Inject
@ -24,6 +25,14 @@ public class CacheExporter @Inject constructor(
val name: String? 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<Cache> { public suspend fun list(): List<Cache> {
return database.execute { connection -> return database.execute { connection ->
connection.prepareStatement( connection.prepareStatement(
@ -116,6 +125,56 @@ public class CacheExporter @Inject constructor(
} }
} }
public suspend fun exportKeys(id: Long): List<GroupKey> {
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<GroupKey>()
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 companion object {
private const val BATCH_SIZE = 1024 private const val BATCH_SIZE = 1024
} }

@ -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))
}
} }

@ -1,12 +1,17 @@
package org.openrs2.archive.web package org.openrs2.archive.web
import com.fasterxml.jackson.databind.ObjectMapper
import io.ktor.application.call import io.ktor.application.call
import io.ktor.application.install 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.get
import io.ktor.routing.routing import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty import io.ktor.server.netty.Netty
import io.ktor.thymeleaf.Thymeleaf import io.ktor.thymeleaf.Thymeleaf
import org.openrs2.json.Json
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect
import org.thymeleaf.templatemode.TemplateMode import org.thymeleaf.templatemode.TemplateMode
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
@ -16,10 +21,15 @@ import javax.inject.Singleton
@Singleton @Singleton
public class WebServer @Inject constructor( public class WebServer @Inject constructor(
private val cachesController: CachesController, private val cachesController: CachesController,
private val keysController: KeysController private val keysController: KeysController,
@Json private val mapper: ObjectMapper
) { ) {
public fun start() { public fun start() {
embeddedServer(Netty, port = 8000) { embeddedServer(Netty, port = 8000) {
install(ContentNegotiation) {
register(ContentType.Application.Json, JacksonConverter(mapper))
}
install(Thymeleaf) { install(Thymeleaf) {
addDialect(Java8TimeDialect()) addDialect(Java8TimeDialect())
@ -32,6 +42,7 @@ public class WebServer @Inject constructor(
routing { routing {
get("/caches") { cachesController.index(call) } get("/caches") { cachesController.index(call) }
get("/caches/{id}.zip") { cachesController.export(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 // ideally we'd use POST /keys here, but I want to be compatible with the RuneLite/OpenOSRS API
get("/keys/submit") { keysController.create(call) } get("/keys/submit") { keysController.create(call) }

@ -21,7 +21,8 @@
<td th:text="${#temporals.formatISO(cache.timestamp)}"></td> <td th:text="${#temporals.formatISO(cache.timestamp)}"></td>
<td th:text="${cache.name}"></td> <td th:text="${cache.name}"></td>
<td> <td>
<a th:href="${'/caches/' + cache.id + '.zip'}">Download</a> <a th:href="${'/caches/' + cache.id + '.zip'}">Cache</a>
<a th:href="${'/caches/' + cache.id + '.json'}">Keys</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>

Loading…
Cancel
Save