mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 03:14:40 -05:00
Update BytecodeEditor to use Java NIO Paths (#200)
This commit is contained in:
@@ -82,7 +82,7 @@ object PackageTools {
|
|||||||
)
|
)
|
||||||
handler.dump(errorFile, emptyArray<String>())
|
handler.dump(errorFile, emptyArray<String>())
|
||||||
} else {
|
} else {
|
||||||
BytecodeEditor.fixAndroidClasses(jarFilePath.toFile())
|
BytecodeEditor.fixAndroidClasses(jarFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,10 @@ import org.objectweb.asm.FieldVisitor
|
|||||||
import org.objectweb.asm.Handle
|
import org.objectweb.asm.Handle
|
||||||
import org.objectweb.asm.MethodVisitor
|
import org.objectweb.asm.MethodVisitor
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
import org.objectweb.asm.tree.ClassNode
|
import java.nio.file.FileSystems
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.use
|
import java.nio.file.Files
|
||||||
import java.io.File
|
import java.nio.file.Path
|
||||||
import java.io.IOException
|
import kotlin.streams.asSequence
|
||||||
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
|
|
||||||
|
|
||||||
object BytecodeEditor {
|
object BytecodeEditor {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
@@ -33,50 +28,31 @@ object BytecodeEditor {
|
|||||||
*
|
*
|
||||||
* @param jarFile The JarFile to replace class references in
|
* @param jarFile The JarFile to replace class references in
|
||||||
*/
|
*/
|
||||||
fun fixAndroidClasses(jarFile: File) {
|
fun fixAndroidClasses(jarFile: Path) {
|
||||||
val nodes = loadClasses(jarFile)
|
FileSystems.newFileSystem(jarFile, null as ClassLoader?)?.use {
|
||||||
.mapValues { (className, classFileBuffer) ->
|
Files.walk(it.getPath("/")).asSequence()
|
||||||
logger.trace { "Processing class $className" }
|
.filterNotNull()
|
||||||
transform(classFileBuffer)
|
.filterNot(Files::isDirectory)
|
||||||
} + loadNonClasses(jarFile)
|
.mapNotNull(::getClassBytes)
|
||||||
|
.map(::transform)
|
||||||
saveAsJar(nodes, jarFile)
|
.forEach(::write)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<String, ByteArray> {
|
|
||||||
return JarFile(jar).use { jarFile ->
|
|
||||||
jarFile.entries()
|
|
||||||
.asSequence()
|
|
||||||
.mapNotNull {
|
|
||||||
readJar(jarFile, it)
|
|
||||||
}
|
|
||||||
.toMap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get class file in [jar] for [entry]
|
* Get class bytes from a [Path]
|
||||||
*
|
*
|
||||||
* @param jar The jar to get the class from
|
* @param path The path entry to get the class bytes from
|
||||||
* @param entry The entry in the jar
|
|
||||||
*
|
*
|
||||||
* @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<String, ByteArray>? {
|
private fun getClassBytes(path: Path): Pair<Path, ByteArray>? {
|
||||||
return try {
|
return try {
|
||||||
jar.getInputStream(entry).use { stream ->
|
if (path.toString().endsWith(".class")) {
|
||||||
if (entry.name.endsWith(".class")) {
|
val bytes = Files.readAllBytes(path)
|
||||||
val bytes = stream.readBytes()
|
|
||||||
if (bytes.size < 4) {
|
if (bytes.size < 4) {
|
||||||
// Invalid class size
|
// Invalid class size
|
||||||
return@use null
|
return null
|
||||||
}
|
}
|
||||||
val cafebabe = String.format(
|
val cafebabe = String.format(
|
||||||
"%02X%02X%02X%02X",
|
"%02X%02X%02X%02X",
|
||||||
@@ -87,23 +63,17 @@ object BytecodeEditor {
|
|||||||
)
|
)
|
||||||
if (cafebabe.lowercase() != "cafebabe") {
|
if (cafebabe.lowercase() != "cafebabe") {
|
||||||
// Corrupted class
|
// Corrupted class
|
||||||
return@use null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode(bytes).name to bytes
|
path to bytes
|
||||||
} else null
|
} else null
|
||||||
}
|
} catch (e: Exception) {
|
||||||
} catch (e: IOException) {
|
logger.error(e) { "Error loading class from Path: $path" }
|
||||||
logger.error(e) { "Error loading jar file" }
|
|
||||||
null
|
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
|
* The path where replacement classes will reside
|
||||||
*/
|
*/
|
||||||
@@ -153,9 +123,9 @@ object BytecodeEditor {
|
|||||||
*
|
*
|
||||||
* @return [ByteArray] with modified bytecode
|
* @return [ByteArray] with modified bytecode
|
||||||
*/
|
*/
|
||||||
private fun transform(classfileBuffer: ByteArray): ByteArray {
|
private fun transform(pair: Pair<Path, ByteArray>): Pair<Path, ByteArray> {
|
||||||
// Read the class and prepare to modify it
|
// Read the class and prepare to modify it
|
||||||
val cr = ClassReader(classfileBuffer)
|
val cr = ClassReader(pair.second)
|
||||||
val cw = ClassWriter(cr, 0)
|
val cw = ClassWriter(cr, 0)
|
||||||
// Modify the class
|
// Modify the class
|
||||||
cr.accept(
|
cr.accept(
|
||||||
@@ -277,51 +247,10 @@ object BytecodeEditor {
|
|||||||
},
|
},
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
return cw.toByteArray()
|
return pair.first to cw.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun write(pair: Pair<Path, ByteArray>) {
|
||||||
* Load non-class files from the jar, such as icons and the manifest
|
Files.write(pair.first, pair.second)
|
||||||
*
|
|
||||||
* @param [jarFile] The file to load resources from
|
|
||||||
*
|
|
||||||
* @return [Map] of resources
|
|
||||||
*/
|
|
||||||
private fun loadNonClasses(jarFile: File): Map<String, ByteArray> {
|
|
||||||
val entries = mutableMapOf<String, ByteArray>()
|
|
||||||
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<String, ByteArray>, 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ object PackageTools {
|
|||||||
)
|
)
|
||||||
handler.dump(errorFile, emptyArray<String>())
|
handler.dump(errorFile, emptyArray<String>())
|
||||||
} else {
|
} else {
|
||||||
BytecodeEditor.fixAndroidClasses(jarFilePath.toFile())
|
BytecodeEditor.fixAndroidClasses(jarFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user