Add class for converting RuneLite flatcaches to other formats

Signed-off-by: Graham <gpe@openrs2.org>
Graham 3 years ago
parent 8c415023af
commit aa2784a9e6
  1. 162
      cache/src/main/kotlin/org/openrs2/cache/RuneLiteStore.kt

@ -0,0 +1,162 @@
package org.openrs2.cache
import io.netty.buffer.ByteBufAllocator
import io.netty.buffer.Unpooled
import org.openrs2.buffer.crc32
import org.openrs2.buffer.use
import java.nio.file.Files
import java.nio.file.Path
import java.util.Base64
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.io.path.name
@Singleton
public class RuneLiteStore @Inject constructor(
private val alloc: ByteBufAllocator
) {
public fun unpack(input: Path, output: Store) {
output.create(Store.ARCHIVESET)
for (path in Files.list(input)) {
val name = path.name
if (!name.endsWith(".flatcache")) {
continue
}
val archive = name.removeSuffix(".flatcache").toIntOrNull() ?: continue
unpackArchive(path, archive, output)
}
}
private fun unpackArchive(path: Path, archive: Int, output: Store) {
val index = Js5Index(Js5Protocol.ORIGINAL)
var indexChecksum = 0
Files.newBufferedReader(path).useLines { lines ->
var group: Js5Index.MutableGroup? = null
for (line in lines) {
val pair = line.split('=', limit = 2)
if (pair.size != 2) {
throw StoreCorruptException("Missing = in line")
}
val (key, value) = pair
if (group == null) {
when (key) {
"protocol" -> {
val protocolId = value.toIntOrNull()
?: throw StoreCorruptException("Protocol must be an integer")
index.protocol = Js5Protocol.fromId(protocolId)
?: throw StoreCorruptException("Protocol number not supported")
}
"revision" -> {
index.version = value.toIntOrNull()
?: throw StoreCorruptException("Revision must be an integer")
}
"compression" -> Unit
"crc" -> {
indexChecksum = value.toIntOrNull()
?: throw StoreCorruptException("Index CRC must be an integer")
}
"named" -> Unit
"id" -> {
val id = value.toIntOrNull()
?: throw StoreCorruptException("Group ID must be an integer")
group = index.createOrGet(id)
}
else -> throw StoreCorruptException("Unknown key in archive context: $key")
}
} else {
when (key) {
"namehash" -> {
group.nameHash = value.toIntOrNull()
?: throw StoreCorruptException("Group name hash must be an integer")
if (group.nameHash != 0) {
index.hasNames = true
}
}
"revision" -> {
group.version = value.toIntOrNull()
?: throw StoreCorruptException("Revision must be an integer")
}
"crc" -> {
group.checksum = value.toIntOrNull()
?: throw StoreCorruptException("Group CRC must be an integer")
}
"contents" -> {
Unpooled.wrappedBuffer(Base64.getDecoder().decode(value)).use { buf ->
output.write(archive, group!!.id, buf)
}
}
"compression" -> Unit
"file" -> {
val pair = value.split('=', limit = 2)
if (pair.size != 2) {
throw StoreCorruptException("Missing = in file line")
}
val id = pair[0].toIntOrNull()
?: throw StoreCorruptException("File ID must be an integer")
val file = group.createOrGet(id)
file.nameHash = pair[1].toIntOrNull()
?: throw StoreCorruptException("File name hash must be an integer")
if (file.nameHash != 0) {
index.hasNames = true
}
}
"id" -> {
val id = value.toIntOrNull()
?: throw StoreCorruptException("Group ID must be an integer")
group = index.createOrGet(id)
}
else -> throw StoreCorruptException("Unknown key in group context: $key")
}
}
}
}
alloc.buffer().use { uncompressed ->
index.write(uncompressed)
val matching = Js5CompressionType.values().count { type ->
Js5Compression.compress(uncompressed.slice(), type).use { compressed ->
val checksum = compressed.crc32()
if (checksum == indexChecksum) {
output.write(Store.ARCHIVESET, archive, compressed)
return@use true
}
return@use false
}
}
if (matching != 1) {
throw StoreCorruptException("Failed to reconstruct Js5Index")
}
}
}
}
Loading…
Cancel
Save