package org.openrs2.patcher.transform import com.github.michaelbull.logging.InlineLogger import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.InsnList import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.LabelNode import org.objectweb.asm.tree.LdcInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.VarInsnNode import org.openrs2.asm.InsnMatcher import org.openrs2.asm.MemberRef import org.openrs2.asm.classpath.ClassPath import org.openrs2.asm.classpath.Library import org.openrs2.asm.toAbstractInsnNode import org.openrs2.asm.transform.Transformer import org.openrs2.patcher.OperatingSystem import javax.inject.Singleton @Singleton public class PlatformDetectionTransformer : Transformer() { private var glBlocks = 0 private var miscBlocks = 0 override fun preTransform(classPath: ClassPath) { glBlocks = 0 miscBlocks = 0 } override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { var match = GL_PLATFORM_DETECTION_MATCHER.match(method).singleOrNull() if (match != null) { // find os.name, os.arch and platform ID variables val nameStore = match[3] as VarInsnNode val nameVar = nameStore.`var` val archStore = match[7] as VarInsnNode val archVar = archStore.`var` val platformLoad = match[match.size - 3] as VarInsnNode val platformVar = platformLoad.`var` // find unknown OS call val unknownOs = findUnknownOs(match) // generate our own platform detection code val list = InsnList() val end = LabelNode() var platform = 0 for (os in OperatingSystem.values()) { val nextOs = LabelNode() list.add(VarInsnNode(Opcodes.ALOAD, nameVar)) list.add(LdcInsnNode(os.needle)) list.add( MethodInsnNode( Opcodes.INVOKEVIRTUAL, STARTS_WITH.owner, STARTS_WITH.name, STARTS_WITH.desc ) ) list.add(JumpInsnNode(Opcodes.IFEQ, nextOs)) for ((i, arch) in os.architectures.withIndex()) { val matchingArch = LabelNode() val nextArch = LabelNode() if (i != os.architectures.size - 1) { for ((j, needle) in arch.needles.withIndex()) { list.add(VarInsnNode(Opcodes.ALOAD, archVar)) list.add(LdcInsnNode(needle)) list.add( MethodInsnNode( Opcodes.INVOKEVIRTUAL, STARTS_WITH.owner, STARTS_WITH.name, STARTS_WITH.desc ) ) if (j != arch.needles.size - 1) { list.add(JumpInsnNode(Opcodes.IFNE, matchingArch)) } else { list.add(JumpInsnNode(Opcodes.IFEQ, nextArch)) } } } list.add(matchingArch) list.add((platform++).toAbstractInsnNode()) list.add(VarInsnNode(Opcodes.ISTORE, platformVar)) list.add(JumpInsnNode(Opcodes.GOTO, end)) list.add(nextArch) } list.add(nextOs) } list.add(unknownOs) list.add(end) // replace existing platform detection code with our own for (i in (8 until match.size - 7)) { method.instructions.remove(match[i]) } method.instructions.insert(match[7], list) glBlocks++ } // adjust jagmisc platform IDs to account for the removal of MSJVM support match = MISC_PLATFORM_DETECTION_MATCHER.match(method).singleOrNull() if (match != null) { // find os.arch and platform ID variables val archStore = match[12] as VarInsnNode val archVar = archStore.`var` val platformStore = match[match.size - 5] as VarInsnNode val platformVar = platformStore.`var` // generate our own platform detection code val list = InsnList() val end = LabelNode() val os = OperatingSystem.WINDOWS for ((i, arch) in os.architectures.withIndex()) { val matchingArch = LabelNode() val nextArch = LabelNode() if (i != os.architectures.size - 1) { for ((j, needle) in arch.needles.withIndex()) { list.add(VarInsnNode(Opcodes.ALOAD, archVar)) list.add(LdcInsnNode(needle)) list.add( MethodInsnNode( Opcodes.INVOKEVIRTUAL, STARTS_WITH.owner, STARTS_WITH.name, STARTS_WITH.desc ) ) if (j != arch.needles.size - 1) { list.add(JumpInsnNode(Opcodes.IFNE, matchingArch)) } else { list.add(JumpInsnNode(Opcodes.IFEQ, nextArch)) } } } list.add(matchingArch) list.add(i.toAbstractInsnNode()) list.add(VarInsnNode(Opcodes.ISTORE, platformVar)) list.add(JumpInsnNode(Opcodes.GOTO, end)) list.add(nextArch) } list.add(end) // replace existing platform detection code with our own for (i in (13 until match.size - 4)) { method.instructions.remove(match[i]) } method.instructions.insert(match[12], list) miscBlocks++ } return false } override fun postTransform(classPath: ClassPath) { logger.info { "Replaced $glBlocks jaggl and $miscBlocks jagmisc platform detection blocks" } } private companion object { private val logger = InlineLogger() private val GL_PLATFORM_DETECTION_MATCHER = InsnMatcher.compile( """ LDC INVOKESTATIC INVOKEVIRTUAL ASTORE LDC INVOKESTATIC INVOKEVIRTUAL ASTORE .* ICONST ISTORE ILOAD GETSTATIC ILOAD AALOAD ARRAYLENGTH """ ) private val MISC_PLATFORM_DETECTION_MATCHER = InsnMatcher.compile( """ LDC INVOKESTATIC INVOKEVIRTUAL ASTORE ALOAD LDC INVOKEVIRTUAL IFNE GOTO LDC INVOKESTATIC INVOKEVIRTUAL ASTORE .* ICONST ISTORE ALOAD ALOAD LDC INVOKEVIRTUAL """ ) private val STARTS_WITH = MemberRef("java/lang/String", "startsWith", "(Ljava/lang/String;)Z") private fun findUnknownOs(match: List): InsnList { val aloadIndex = match.indexOfFirst { it is VarInsnNode && it.opcode == Opcodes.ALOAD && it.`var` == 0 } require(aloadIndex != -1) { "Missing ALOAD_0" } val list = InsnList() for (i in aloadIndex until match.size) { val insn = match[i] list.add(insn.clone(emptyMap())) if (insn.opcode == Opcodes.RETURN) { return list } } throw IllegalArgumentException("Missing RETURN") } } }