Open-source multiplayer game server compatible with the RuneScape client
https://www.openrs2.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
5.8 KiB
162 lines
5.8 KiB
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")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|