From 77e057f244abe00214a91935da93eb48429e6ee4 Mon Sep 17 00:00:00 2001 From: Mitchell Syer Date: Sat, 18 Sep 2021 13:27:15 -0400 Subject: [PATCH] Update BytecodeEditor to use Java NIO Paths (#200) --- .../tachidesk/anime/impl/util/PackageTools.kt | 2 +- .../manga/impl/util/BytecodeEditor.kt | 155 +++++------------- .../tachidesk/manga/impl/util/PackageTools.kt | 2 +- 3 files changed, 44 insertions(+), 115 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/anime/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/anime/impl/util/PackageTools.kt index 746d1fcf..373dab9e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/anime/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/anime/impl/util/PackageTools.kt @@ -82,7 +82,7 @@ object PackageTools { ) handler.dump(errorFile, emptyArray()) } else { - BytecodeEditor.fixAndroidClasses(jarFilePath.toFile()) + BytecodeEditor.fixAndroidClasses(jarFilePath) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt index 2e2d8bfb..59d0a861 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt @@ -15,15 +15,10 @@ import org.objectweb.asm.FieldVisitor import org.objectweb.asm.Handle import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.ClassNode -import suwayomi.tachidesk.manga.impl.util.storage.use -import java.io.File -import java.io.IOException -import java.util.jar.JarEntry -import java.util.jar.JarFile -import java.util.jar.JarOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path +import kotlin.streams.asSequence object BytecodeEditor { private val logger = KotlinLogging.logger {} @@ -33,77 +28,52 @@ object BytecodeEditor { * * @param jarFile The JarFile to replace class references in */ - fun fixAndroidClasses(jarFile: File) { - val nodes = loadClasses(jarFile) - .mapValues { (className, classFileBuffer) -> - logger.trace { "Processing class $className" } - transform(classFileBuffer) - } + loadNonClasses(jarFile) - - saveAsJar(nodes, jarFile) - } - - /** - * Load all classes inside the [jar] [File] - * - * @param jar The JarFile to load classes from - * - * @return [Map] with class names and [ByteArray]s of bytecode - */ - private fun loadClasses(jar: File): Map { - return JarFile(jar).use { jarFile -> - jarFile.entries() - .asSequence() - .mapNotNull { - readJar(jarFile, it) - } - .toMap() + fun fixAndroidClasses(jarFile: Path) { + FileSystems.newFileSystem(jarFile, null as ClassLoader?)?.use { + Files.walk(it.getPath("/")).asSequence() + .filterNotNull() + .filterNot(Files::isDirectory) + .mapNotNull(::getClassBytes) + .map(::transform) + .forEach(::write) } } /** - * Get class file in [jar] for [entry] + * Get class bytes from a [Path] * - * @param jar The jar to get the class from - * @param entry The entry in the jar + * @param path The path entry to get the class bytes from * - * @return [Pair] of the class name plus the class [ByteArray], or null if it's not a valid class + * @return [Pair] of the [Path] plus the class [ByteArray], or null if it's not a valid class */ - private fun readJar(jar: JarFile, entry: JarEntry): Pair? { + private fun getClassBytes(path: Path): Pair? { return try { - jar.getInputStream(entry).use { stream -> - if (entry.name.endsWith(".class")) { - val bytes = stream.readBytes() - if (bytes.size < 4) { - // Invalid class size - return@use null - } - val cafebabe = String.format( - "%02X%02X%02X%02X", - bytes[0], - bytes[1], - bytes[2], - bytes[3] - ) - if (cafebabe.lowercase() != "cafebabe") { - // Corrupted class - return@use null - } + if (path.toString().endsWith(".class")) { + val bytes = Files.readAllBytes(path) + if (bytes.size < 4) { + // Invalid class size + return null + } + val cafebabe = String.format( + "%02X%02X%02X%02X", + bytes[0], + bytes[1], + bytes[2], + bytes[3] + ) + if (cafebabe.lowercase() != "cafebabe") { + // Corrupted class + return null + } - getNode(bytes).name to bytes - } else null - } - } catch (e: IOException) { - logger.error(e) { "Error loading jar file" } + path to bytes + } else null + } catch (e: Exception) { + logger.error(e) { "Error loading class from Path: $path" } null } } - private fun getNode(bytes: ByteArray): ClassNode { - val cr = ClassReader(bytes) - return ClassNode().also { cr.accept(it, ClassReader.EXPAND_FRAMES) } - } - /** * The path where replacement classes will reside */ @@ -153,9 +123,9 @@ object BytecodeEditor { * * @return [ByteArray] with modified bytecode */ - private fun transform(classfileBuffer: ByteArray): ByteArray { + private fun transform(pair: Pair): Pair { // Read the class and prepare to modify it - val cr = ClassReader(classfileBuffer) + val cr = ClassReader(pair.second) val cw = ClassWriter(cr, 0) // Modify the class cr.accept( @@ -277,51 +247,10 @@ object BytecodeEditor { }, 0 ) - return cw.toByteArray() + return pair.first to cw.toByteArray() } - /** - * Load non-class files from the jar, such as icons and the manifest - * - * @param [jarFile] The file to load resources from - * - * @return [Map] of resources - */ - private fun loadNonClasses(jarFile: File): Map { - val entries = mutableMapOf() - ZipInputStream(jarFile.inputStream()).use { stream -> - var nextEntry: ZipEntry? - while (stream.nextEntry.also { nextEntry = it } != null) { - nextEntry?.use(stream) { entry -> - // If it ends with class or is a directory ignore it - if (!entry.name.endsWith(".class") && !entry.isDirectory) { - val bytes = stream.readBytes() - entries[entry.name] = bytes - } - } - } - } - return entries - } - - /** - * Save jar with modified content - * - * @param outBytes [Map] of names and [ByteArray]s of content to save inside the jar - * @param file JarFile to save to - */ - private fun saveAsJar(outBytes: Map, file: File) { - JarOutputStream(file.outputStream()).use { out -> - outBytes.forEach { (entry, value) -> - // Append extension to class entries - out.putNextEntry( - ZipEntry( - entry + if (entry.contains(".")) "" else ".class" - ) - ) - out.write(value) - out.closeEntry() - } - } + private fun write(pair: Pair) { + Files.write(pair.first, pair.second) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt index 1da66e38..19938d17 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt @@ -82,7 +82,7 @@ object PackageTools { ) handler.dump(errorFile, emptyArray()) } else { - BytecodeEditor.fixAndroidClasses(jarFilePath.toFile()) + BytecodeEditor.fixAndroidClasses(jarFilePath) } }