diff --git a/src/main/kotlin/app/revanced/patcher/util/Io.kt b/src/main/kotlin/app/revanced/patcher/util/Io.kt index b51db352..b0ca581a 100644 --- a/src/main/kotlin/app/revanced/patcher/util/Io.kt +++ b/src/main/kotlin/app/revanced/patcher/util/Io.kt @@ -1,6 +1,5 @@ package app.revanced.patcher.util -import app.revanced.patcher.writer.SafeClassWriter import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.tree.ClassNode @@ -50,21 +49,21 @@ internal class Io( fun saveAsJar() { val jis = ZipInputStream(bufferedInputStream) val jos = ZipOutputStream(output) + val classReaders = mutableMapOf() // first write all non .class zip entries from the original input stream to the output stream // we read it first to close the input stream as fast as possible // TODO(oSumAtrIX): There is currently no way to remove non .class files. lateinit var zipEntry: ZipEntry while (jis.nextEntry.also { if (it != null) zipEntry = it } != null) { - // skip all class files because we added them in the loop above - // TODO(oSumAtrIX): Check for zipEntry.isDirectory - if (zipEntry.name.endsWith(".class")) continue + if (zipEntry.name.endsWith(".class")) { + classReaders[zipEntry.name] = ClassReader(jis.readBytes()) + continue + } - // create a new zipEntry and write the contents of the zipEntry to the output stream + // create a new zipEntry and write the contents of the zipEntry to the output stream and close it jos.putNextEntry(ZipEntry(zipEntry)) jos.write(jis.readBytes()) - - // close the newly created zipEntry jos.closeEntry() } @@ -76,10 +75,12 @@ internal class Io( // now write all the patched classes to the output stream for (patchedClass in classes) { // create a new entry of the patched class - jos.putNextEntry(JarEntry(patchedClass.name + ".class")) + val name = patchedClass.name + ".class" + jos.putNextEntry(JarEntry(name)) // parse the patched class to a byte array and write it to the output stream - val cw: ClassWriter = SafeClassWriter( + val cw = ClassWriter( + classReaders[name]!!, ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS ) patchedClass.accept(cw) diff --git a/src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt b/src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt deleted file mode 100644 index c200a2ea..00000000 --- a/src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt +++ /dev/null @@ -1,140 +0,0 @@ -package app.revanced.patcher.writer - -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.Opcodes -import java.io.IOException - -/** - * A ClassWriter that computes the common super class of two classes without - * actually loading them with a ClassLoader. - * - * @author Eric Bruneton - */ -// TODO(Sculas): should we add the ClassReader parameter back? -class SafeClassWriter(flags: Int) : ClassWriter(flags) { - override fun getCommonSuperClass(type1: String, type2: String): String { - try { - val info1 = typeInfo(type1) - val info2 = typeInfo(type2) - if (info1.access and Opcodes.ACC_INTERFACE != 0) { - return if (typeImplements(type2, info2, type1)) { - type1 - } else { - "java/lang/Object" - } - } - if (info2.access and Opcodes.ACC_INTERFACE != 0) { - return if (typeImplements(type1, info1, type2)) { - type2 - } else { - "java/lang/Object" - } - } - val b1 = typeAncestors(type1, info1) - val b2 = typeAncestors(type2, info2) - var result = "java/lang/Object" - var end1 = b1.length - var end2 = b2.length - while (true) { - val start1 = b1.lastIndexOf(";", end1 - 1) - val start2 = b2.lastIndexOf(";", end2 - 1) - if (start1 != -1 && start2 != -1 && end1 - start1 == end2 - start2) { - val p1 = b1.substring(start1 + 1, end1) - val p2 = b2.substring(start2 + 1, end2) - if (p1 == p2) { - result = p1 - end1 = start1 - end2 = start2 - } else { - return result - } - } else { - return result - } - } - } catch (e: IOException) { - throw RuntimeException(e.toString()) - } - } - - /** - * Returns the internal names of the ancestor classes of the given type. - * - * @param _type - * the internal name of a class or interface. - * @param _info - * the ClassReader corresponding to 'type'. - * @return a StringBuilder containing the ancestor classes of 'type', - * separated by ';'. The returned string has the following format: - * ";type1;type2 ... ;typeN", where type1 is 'type', and typeN is a - * direct subclass of Object. If 'type' is Object, the returned - * string is empty. - * @throws IOException - * if the bytecode of 'type' or of some of its ancestor class - * cannot be loaded. - */ - @Throws(IOException::class) - private fun typeAncestors(_type: String, _info: ClassReader): StringBuilder { - var type = _type - var info = _info - val b = StringBuilder() - while ("java/lang/Object" != type) { - b.append(';').append(type) - type = info.superName - info = typeInfo(type) - } - return b - } - - /** - * Returns true if the given type implements the given interface. - * - * @param _type - * the internal name of a class or interface. - * @param _info - * the ClassReader corresponding to 'type'. - * @param itf - * the internal name of a interface. - * @return true if 'type' implements directly or indirectly 'itf' - * @throws IOException - * if the bytecode of 'type' or of some of its ancestor class - * cannot be loaded. - */ - @Throws(IOException::class) - private fun typeImplements(_type: String, _info: ClassReader, itf: String): Boolean { - var type = _type - var info = _info - while ("java/lang/Object" != type) { - info.interfaces.forEach { - if (it == itf) { - return true - } - } - info.interfaces.forEach { - if (typeImplements(it, typeInfo(it), itf)) { - return true - } - } - type = info.superName - info = typeInfo(type) - } - return false - } - - /** - * Returns a ClassReader corresponding to the given class or interface. - * - * @param type - * the internal name of a class or interface. - * @return the ClassReader corresponding to 'type'. - * @throws IOException - * if the bytecode of 'type' cannot be loaded. - */ - @Throws(IOException::class) - private fun typeInfo(type: String): ClassReader { - val input = ClassLoader.getSystemClassLoader().getResourceAsStream("$type.class") - ?: throw IOException("Cannot create ClassReader for type $type") - return input.use(::ClassReader) - } -} \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/ReaderTest.kt b/src/test/kotlin/app/revanced/patcher/ReaderTest.kt index 4087d892..becd7f61 100644 --- a/src/test/kotlin/app/revanced/patcher/ReaderTest.kt +++ b/src/test/kotlin/app/revanced/patcher/ReaderTest.kt @@ -7,6 +7,6 @@ internal class ReaderTest { @Test fun `read jar containing multiple classes`() { val testData = PatcherTest::class.java.getResourceAsStream("/test2.jar")!! - Patcher(testData, ByteArrayOutputStream(), PatcherTest.testSignatures) // reusing test sigs from PatcherTest + Patcher(testData, ByteArrayOutputStream(), PatcherTest.testSignatures).save() // reusing test sigs from PatcherTest } } \ No newline at end of file