package org.openrs2.bundler.transform import com.github.michaelbull.logging.InlineLogger import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.InsnList import org.objectweb.asm.tree.InsnNode import org.objectweb.asm.tree.IntInsnNode import org.objectweb.asm.tree.LdcInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.TypeInsnNode import org.openrs2.asm.InsnMatcher 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.bundler.Resource public class ResourceTransformer( private val resources: List? = null, private val glResources: List> = Resource.compressGlNatives(), private val miscResources: List = Resource.compressMiscNatives() ) : 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 { if (resources != null) { for ((i, match) in RESOURCE_MATCHER.match(method).withIndex()) { val resource = resources[i] // sanity check that the destination matches up val destination = match[2] as LdcInsnNode require(destination.cst == resource.destination) // update the source (the CRC may have changed) val source = match[3] as LdcInsnNode source.cst = resource.sourceWithChecksum // update file sizes method.instructions.set(match[22], resource.uncompressedSize.toAbstractInsnNode()) method.instructions.set(match[23], resource.compressedSize.toAbstractInsnNode()) // update digest for ((j, byte) in resource.digest.withIndex()) { method.instructions.set(match[28 + 4 * j], byte.toInt().toAbstractInsnNode()) } } } val match = GL_RESOURCES_MATCHER.match(method).singleOrNull() if (match != null) { // remove everything but the PUTSTATIC for (i in 0 until match.size - 1) { method.instructions.remove(match[i]) } // find the type of the resource class val new = match[8] as TypeInsnNode val type = new.desc // create our own resource array val list = InsnList() create2dResourceArray(list, type, glResources, GL_LOADING_MESSAGES) // insert our own resource array before the PUTSTATIC method.instructions.insertBefore(match[match.size - 1], list) glBlocks++ } val miscMatch = MISC_RESOURCES_MATCHER.match(method).singleOrNull() if (miscMatch != null) { // remove everything but the PUTSTATIC for (i in 0 until miscMatch.size - 1) { method.instructions.remove(miscMatch[i]) } // find the type of the resource class val new = miscMatch[4] as TypeInsnNode val type = new.desc // create our own resource array val list = InsnList() createResourceArray(list, type, miscResources, MISC_LOADING_MESSAGES) // insert our own resource array before the PUTSTATIC method.instructions.insertBefore(miscMatch[miscMatch.size - 1], list) miscBlocks++ } return false } private fun create2dResourceArray( list: InsnList, type: String, resources: List>, messages: List ) { list.add(resources.size.toAbstractInsnNode()) list.add(TypeInsnNode(Opcodes.ANEWARRAY, "[L$type;")) for ((i, innerResources) in resources.withIndex()) { list.add(InsnNode(Opcodes.DUP)) list.add(i.toAbstractInsnNode()) createResourceArray(list, type, innerResources, messages, progressSufix = true) list.add(InsnNode(Opcodes.AASTORE)) } } private fun createResourceArray( list: InsnList, type: String, resources: List, messages: List, progressSufix: Boolean = false ) { list.add(resources.size.toAbstractInsnNode()) list.add(TypeInsnNode(Opcodes.ANEWARRAY, type)) for ((i, resource) in resources.withIndex()) { list.add(InsnNode(Opcodes.DUP)) list.add(i.toAbstractInsnNode()) list.add(TypeInsnNode(Opcodes.NEW, type)) list.add(InsnNode(Opcodes.DUP)) list.add(LdcInsnNode(resource.destination)) list.add(LdcInsnNode(resource.sourceWithChecksum)) list.add(messages.size.toAbstractInsnNode()) list.add(TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/String")) for ((j, message) in messages.withIndex()) { val messageWithSuffix = if (progressSufix && resources.size > 1) { "$message (${i + 1}/${resources.size})" } else { message } list.add(InsnNode(Opcodes.DUP)) list.add(j.toAbstractInsnNode()) list.add(LdcInsnNode(messageWithSuffix)) list.add(InsnNode(Opcodes.AASTORE)) } list.add(resource.uncompressedSize.toAbstractInsnNode()) list.add(resource.compressedSize.toAbstractInsnNode()) list.add(resource.digest.size.toAbstractInsnNode()) list.add(IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_INT)) for ((j, byte) in resource.digest.withIndex()) { list.add(InsnNode(Opcodes.DUP)) list.add(j.toAbstractInsnNode()) list.add(byte.toInt().toAbstractInsnNode()) list.add(InsnNode(Opcodes.IASTORE)) } list.add( MethodInsnNode( Opcodes.INVOKESPECIAL, type, "", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;II[I)V" ) ) list.add(InsnNode(Opcodes.AASTORE)) } } override fun postTransform(classPath: ClassPath) { logger.info { "Replaced $glBlocks jaggl and $miscBlocks jagmisc resource constructor blocks" } } private companion object { private val logger = InlineLogger() private const val RESOURCE_CONSTRUCTOR = """ NEW DUP LDC LDC ICONST_4 ANEWARRAY (DUP ICONST LDC AASTORE)+ (ICONST | BIPUSH | SIPUSH | LDC) (ICONST | BIPUSH | SIPUSH | LDC) BIPUSH NEWARRAY (DUP (ICONST | BIPUSH) (ICONST | BIPUSH) IASTORE)+ INVOKESPECIAL """ private const val RESOURCE_ARRAY_CONSTRUCTOR = "ICONST ANEWARRAY (DUP ICONST $RESOURCE_CONSTRUCTOR AASTORE)+" private val RESOURCE_MATCHER = InsnMatcher.compile("$RESOURCE_CONSTRUCTOR PUTSTATIC") private val GL_RESOURCES_MATCHER = InsnMatcher.compile( """ ICONST ANEWARRAY ( DUP ICONST $RESOURCE_ARRAY_CONSTRUCTOR AASTORE )+ PUTSTATIC """ ) private val MISC_RESOURCES_MATCHER = InsnMatcher.compile("$RESOURCE_ARRAY_CONSTRUCTOR PUTSTATIC") private val GL_LOADING_MESSAGES = listOf( "Loading 3D library", "Lade 3D-Softwarebibliothek", "Chargement de la librairie 3D", "Carregando biblioteca 3D" ) private val MISC_LOADING_MESSAGES = listOf( "Loading utility library", "Lade Utility-Softwarebibliothek", "Chargement de la librairie utilitaire", "Carregando biblioteca de ferramentas" ) } }