Extract links from pre-HD new engine loaders

Signed-off-by: Graham <gpe@openrs2.org>
Graham 1 year 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.ElfFile
import net.fornwall.jelf.ElfSymbol import net.fornwall.jelf.ElfSymbol
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LdcInsnNode import org.objectweb.asm.tree.LdcInsnNode
import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.TypeInsnNode 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.archive.cache.CacheImporter
import org.openrs2.asm.InsnMatcher import org.openrs2.asm.InsnMatcher
import org.openrs2.asm.classpath.Library import org.openrs2.asm.classpath.Library
import org.openrs2.asm.getArgumentExpressions
import org.openrs2.asm.hasCode import org.openrs2.asm.hasCode
import org.openrs2.asm.intConstant import org.openrs2.asm.intConstant
import org.openrs2.asm.io.CabLibraryReader 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.LibraryReader
import org.openrs2.asm.io.Pack200LibraryReader import org.openrs2.asm.io.Pack200LibraryReader
import org.openrs2.asm.io.PackClassLibraryReader import org.openrs2.asm.io.PackClassLibraryReader
import org.openrs2.asm.nextReal
import org.openrs2.buffer.use import org.openrs2.buffer.use
import org.openrs2.compress.gzip.Gzip import org.openrs2.compress.gzip.Gzip
import org.openrs2.db.Database 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() 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 { private fun ByteBuf.hasPrefix(bytes: ByteArray): Boolean {
Unpooled.wrappedBuffer(bytes).use { prefix -> Unpooled.wrappedBuffer(bytes).use { prefix ->
val len = prefix.readableBytes() val len = prefix.readableBytes()
@ -830,5 +981,10 @@ public class ClientImporter @Inject constructor(
private const val SHA1_BYTES = 20 private const val SHA1_BYTES = 20
private val SHA1_MATCHER = private val SHA1_MATCHER =
InsnMatcher.compile("BIPUSH NEWARRAY (DUP (ICONST | BIPUSH) (ICONST | BIPUSH | SIPUSH) IASTORE)+") 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