Extract links from pre-HD new engine loaders

Signed-off-by: Graham <gpe@openrs2.org>
master
Graham 6 months ago
parent 71c6f5821b
commit 2aff6e56b7
  1. 158
      archive/src/main/kotlin/org/openrs2/archive/client/ClientImporter.kt

@ -18,7 +18,9 @@ import jakarta.inject.Singleton
import net.fornwall.jelf.ElfFile
import net.fornwall.jelf.ElfSymbol
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LdcInsnNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.TypeInsnNode
@ -26,6 +28,7 @@ import org.openrs2.archive.cache.CacheExporter
import org.openrs2.archive.cache.CacheImporter
import org.openrs2.asm.InsnMatcher
import org.openrs2.asm.classpath.Library
import org.openrs2.asm.getArgumentExpressions
import org.openrs2.asm.hasCode
import org.openrs2.asm.intConstant
import org.openrs2.asm.io.CabLibraryReader
@ -33,6 +36,7 @@ import org.openrs2.asm.io.JarLibraryReader
import org.openrs2.asm.io.LibraryReader
import org.openrs2.asm.io.Pack200LibraryReader
import org.openrs2.asm.io.PackClassLibraryReader
import org.openrs2.asm.nextReal
import org.openrs2.buffer.use
import org.openrs2.compress.gzip.Gzip
import org.openrs2.db.Database
@ -780,10 +784,157 @@ public class ClientImporter @Inject constructor(
)
}
// TODO(gpe): new engine support
val loader = library["loader"]
if (loader != null) {
val links = mutableListOf<ArtifactLink>()
val paths = mutableSetOf<String>()
for (method in loader.methods) {
if (method.name != "run" || method.desc != "()V") {
continue
}
for (insn in method.instructions) {
if (insn !is MethodInsnNode || insn.owner != loader.name || !insn.desc.endsWith(")[B")) {
continue
}
// TODO(gpe): extract file size too (tricky due to dummy arguments)
val exprs = getArgumentExpressions(insn) ?: continue
for (expr in exprs) {
val single = expr.singleOrNull() ?: continue
if (single !is LdcInsnNode) {
continue
}
val cst = single.cst
if (cst is String && FILE_NAME_REGEX.matches(cst)) {
paths += cst
}
}
}
}
val hashes = mutableMapOf<AbstractInsnNode, ByteArray>()
for (method in loader.methods) {
for (match in SHA1_CMP_MATCHER.match(method)) {
val sha1 = ByteArray(SHA1_BYTES)
var i = 0
while (i < match.size) {
var n = match[i++].intConstant
if (n != null) {
i++ // ALOAD
}
val index = match[i++].intConstant!!
i++ // BALOAD
var xor = false
if (i + 1 < match.size && match[i + 1].opcode == Opcodes.IXOR) {
i += 2 // ICONST_M1, IXOR
xor = true
}
if (match[i].opcode == Opcodes.IFNE) {
n = 0
i++
} else {
if (n == null) {
n = match[i++].intConstant!!
}
i++ // ICMP_IFNE
}
if (xor) {
n = n.inv()
}
sha1[index] = n.toByte()
}
hashes[match[0]] = sha1
}
}
for (method in loader.methods) {
for (match in PATH_CMP_MATCHER.match(method)) {
val first = match[0]
val ldc = if (first is LdcInsnNode) {
first
} else {
match[1] as LdcInsnNode
}
val path = ldc.cst
if (path !is String) {
continue
}
val acmp = match[2] as JumpInsnNode
val target = if (acmp.opcode == Opcodes.IF_ACMPNE) {
acmp.nextReal
} else {
acmp.label.nextReal
}
val hash = hashes.remove(target) ?: continue
if (!paths.remove(path)) {
continue
}
links += parseLink(path, hash)
}
}
if (paths.size != hashes.size || paths.size > 1) {
throw IllegalArgumentException()
} else if (paths.size == 1) {
links += parseLink(paths.single(), hashes.values.single())
}
return links
}
// TODO(gpe)
return emptyList()
}
private fun parseLink(path: String, sha1: ByteArray): ArtifactLink {
val m = FILE_NAME_REGEX.matchEntire(path) ?: throw IllegalArgumentException()
val (name, crc1, ext, crc2) = m.destructured
val type = when (name) {
// TODO(gpe): funorb loaders
"runescape", "client" -> ArtifactType.CLIENT
"unpackclass" -> ArtifactType.UNPACKCLASS
else -> throw IllegalArgumentException()
}
val format = when (ext) {
"pack200" -> ArtifactFormat.PACK200
"js5" -> ArtifactFormat.PACKCLASS
"jar", "pack" -> ArtifactFormat.JAR
else -> throw IllegalArgumentException()
}
val crc = crc1.toIntOrNull() ?: crc2.toIntOrNull() ?: throw IllegalArgumentException()
return ArtifactLink(
type,
format,
OperatingSystem.INDEPENDENT,
Architecture.INDEPENDENT,
Jvm.INDEPENDENT,
crc,
sha1,
null
)
}
private fun ByteBuf.hasPrefix(bytes: ByteArray): Boolean {
Unpooled.wrappedBuffer(bytes).use { prefix ->
val len = prefix.readableBytes()
@ -830,5 +981,10 @@ public class ClientImporter @Inject constructor(
private const val SHA1_BYTES = 20
private val SHA1_MATCHER =
InsnMatcher.compile("BIPUSH NEWARRAY (DUP (ICONST | BIPUSH) (ICONST | BIPUSH | SIPUSH) IASTORE)+")
private val FILE_NAME_REGEX = Regex("([a-z]+)(?:_(-?[0-9]+))?[.]([a-z0-9]+)(?:\\?crc=(-?[0-9]+))?")
private val SHA1_CMP_MATCHER =
InsnMatcher.compile("((ICONST | BIPUSH)? ALOAD (ICONST | BIPUSH) BALOAD (ICONST IXOR)? (ICONST | BIPUSH)? (IF_ICMPEQ | IF_ICMPNE | IFEQ | IFNE))+")
private val PATH_CMP_MATCHER = InsnMatcher.compile("(LDC ALOAD | ALOAD LDC) (IF_ACMPEQ | IF_ACMPNE)")
}
}

Loading…
Cancel
Save