Skip to content

Commit

Permalink
feat(wip): forge transformer implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Zxnii committed Aug 1, 2024
1 parent e946676 commit df32ef1
Show file tree
Hide file tree
Showing 15 changed files with 370 additions and 205 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
package org.polyfrost.spice.util

import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassReader.SKIP_CODE
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.InvokeDynamicInsnNode
import org.objectweb.asm.tree.LdcInsnNode
import org.objectweb.asm.tree.MethodNode

object ClassUtil

private val chainCache = mutableMapOf<String, List<String>>()

fun readClass(name: String): ClassNode? =
ClassNode().also { node ->
ClassReader(
ClassUtil::class.java
.getResourceAsStream("${name.replace(".", "/")}.class")
?.use { it.readBytes() } ?: return null
).accept(node, SKIP_CODE)
}

fun getStrings(node: ClassNode): Set<String> =
node.methods.map {
getStrings(it as MethodNode)
Expand All @@ -22,3 +37,25 @@ fun getStrings(node: MethodNode): Set<String> =
}
.flatten().toSet()

@Suppress("NAME_SHADOWING")
fun classChain(clazz: Class<*>): List<Class<*>> {
val chain = mutableListOf<Class<*>>()
var clazz = clazz

while (clazz.superclass != null) {
chain.add(clazz)
clazz = clazz.superclass
}

return chain
}

fun classChain(node: ClassNode): List<String> {
return chainCache.computeIfAbsent(node.name) {
val chain = mutableListOf<String>()
val newNode = readClass(node.superName) ?: return@computeIfAbsent chain

chain.addAll(classChain(newNode))
chain
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.polyfrost.spice.util

import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter

class SpiceClassWriter : ClassWriter {
@Suppress("unused")
constructor(flags: Int) : super(flags)

@Suppress("unused")
constructor(reader: ClassReader, flags: Int)
: super(reader, flags)

override fun getCommonSuperClass(a: String, b: String): String? {
val chainA = classChain(readClass(a) ?: return "java/lang/Object")
val chainB = classChain(readClass(b) ?: return "java/lang/Object")

val commonSuperClasses = mutableSetOf<String>()

chainA.forEach { if (chainB.contains(it)) commonSuperClasses += it }

return commonSuperClasses.firstOrNull()
}
}
108 changes: 56 additions & 52 deletions modules/core/src/main/kotlin/org/polyfrost/spice/patcher/Cache.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package org.polyfrost.spice.patcher

import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
import org.polyfrost.spice.patcher.lwjgl.LibraryTransformer
import org.polyfrost.spice.patcher.lwjgl.LwjglTransformer
import org.polyfrost.spice.spiceDirectory
import org.spongepowered.asm.transformers.MixinClassWriter
import org.polyfrost.spice.util.SpiceClassWriter
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
Expand Down Expand Up @@ -51,72 +51,76 @@ fun loadCacheBuffers(hash: String): Map<String, ByteArray> {
}
}

suspend fun buildCache(hash: String, `in`: List<ClassNode>): Map<String, ClassNode> {
fun buildCache(hash: String, `in`: List<ClassNode>): Pair<Map<String, ClassNode>, Map<String, ByteArray>> {
// todo: abuse coroutines.
return coroutineScope {
val transformers = arrayOf(
LwjglTransformer,
LibraryTransformer
)
val transformers = arrayOf(
LwjglTransformer,
LibraryTransformer
)

val transformable = mutableSetOf<String>()
val provider = LwjglTransformer.provider
val transformable = mutableSetOf<String>()
val provider = LwjglTransformer.provider

val transformed = mutableMapOf<String, ClassNode>()
val buffers = mutableMapOf<String, ByteArray>()
val transformed = mutableMapOf<String, ClassNode>()
val buffers = mutableMapOf<String, ByteArray>()

`in`.forEach { node ->
transformable.add(node.name)
`in`.forEach { node ->
transformable.add(node.name)
transformers.forEach transform@{ transformer ->
val targets = transformer.targets

transformers.forEach transform@{ transformer ->
val targets = transformer.getClassNames()
if (targets != null
&& !targets.contains(node.name.replace("/", "."))
) return@transform

if (targets != null
&& !targets.contains(node.name.replace("/", "."))
) return@transform
transformer.transform(node)
}

transformer.transform(node)
}
node.methods.forEach { method ->
method as MethodNode

transformed[node.name] = node
buffers["${node.name}.class"] =
MixinClassWriter(COMPUTE_FRAMES)
.also { node.accept(it) }
.toByteArray()
// this fixes stuff because sometimes exceptions are null (????)
if (method.exceptions == null) method.exceptions = mutableListOf<String>()
}

provider.allEntries.forEach { entry ->
if (!entry.endsWith(".class") || !entry.startsWith("org/lwjgl/")) return@forEach
transformed[node.name] = node
buffers["${node.name}.class"] =
SpiceClassWriter(COMPUTE_FRAMES)
.also { node.accept(it) }
.toByteArray()
}

provider.allEntries.forEach { entry ->
if (!entry.endsWith(".class") || !entry.startsWith("org/lwjgl/")) return@forEach

if (!buffers.contains(entry)) {
buffers[entry] =
provider.readFile(entry) ?: return@forEach
}
if (!buffers.contains(entry)) {
buffers[entry] =
provider.readFile(entry) ?: return@forEach
}
}

JarOutputStream(cachePath(hash).outputStream())
.use { out ->
out.putNextEntry(ZipEntry("cache-manifest.json"))
out.write(
Json.encodeToString<CacheManifest>(
CacheManifest(
transformable.toList()
)
).toByteArray(Charsets.UTF_8)
)
JarOutputStream(cachePath(hash).outputStream())
.use { out ->
out.putNextEntry(ZipEntry("cache-manifest.json"))
out.write(
Json.encodeToString<CacheManifest>(
CacheManifest(
transformable.toList()
)
).toByteArray(Charsets.UTF_8)
)
out.closeEntry()

buffers.forEach { (name, buffer) ->
out.putNextEntry(ZipEntry(name))
out.write(buffer)
out.closeEntry()

buffers.forEach { (name, buffer) ->
out.putNextEntry(ZipEntry(name))
out.write(buffer)
out.closeEntry()
}

out.finish()
}

transformed
}
out.finish()
}

return Pair(transformed, buffers)
}

private fun cachePath(hash: String) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import org.polyfrost.spice.platform.api.IClassTransformer
import org.polyfrost.spice.util.getStrings

object LunarTransformer : IClassTransformer {
override fun getClassNames(): Array<String>? {
return null
}
override val targets = null

override fun transform(node: ClassNode) {
if (!node.name.startsWith("com/moonsworth/lunar/")) return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import org.objectweb.asm.tree.MethodNode
import org.polyfrost.spice.platform.api.IClassTransformer

object OptifineTransformer : IClassTransformer {
override fun getClassNames(): Array<String> {
return arrayOf("net.optifine.shaders.Shaders")
}
override val targets = arrayOf("net.optifine.shaders.Shaders")

override fun transform(node: ClassNode) {
node.methods.forEach { method ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import org.objectweb.asm.tree.MethodNode
import org.polyfrost.spice.platform.api.IClassTransformer

object LibraryTransformer : IClassTransformer {
override fun getClassNames(): Array<String> {
return arrayOf("org.lwjgl.system.Library")
}
override val targets = arrayOf("org.lwjgl.system.Library")

override fun transform(node: ClassNode) {
node.methods.forEach { method ->
Expand All @@ -19,4 +17,4 @@ object LibraryTransformer : IClassTransformer {
.forEach { (it as LdcInsnNode).cst = "spice.library.path" }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.polyfrost.spice.patcher.lwjgl

import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.objectweb.asm.ClassReader
import org.objectweb.asm.tree.ClassNode
import org.polyfrost.spice.util.UrlByteArrayConnection
Expand All @@ -14,7 +11,6 @@ import java.security.MessageDigest
import java.util.jar.JarInputStream

class LwjglProvider {
private val cacheLock = Mutex()
private val fileCache = mutableMapOf<String, ByteArray>()

private val jar by lazy { JarInputStream(openStream() ?: return@lazy null) }
Expand Down Expand Up @@ -73,44 +69,36 @@ class LwjglProvider {
?.openStream()

private fun readEntryUntil(path: String?): ByteArray? {
return runBlocking {
if (closed || jar == null) return@runBlocking null

while (true) {
val entry = jar!!.nextEntry ?: run {
jar!!.close()
closed = true

return@runBlocking null
}

if (entry.isDirectory) continue
if (closed || jar == null) return null

val length = entry.size.toInt()
val entryBuffer = ByteArray(length)
while (true) {
val entry = jar!!.nextEntry ?: run {
jar!!.close()
closed = true

var offset = 0
return null
}

while (true) {
val read = jar!!.read(entryBuffer, offset, length - offset)
val length = entry.size.toInt()

offset += read
if (entry.isDirectory) continue
if (length == -1) continue

if (offset == length) break
}
val entryBuffer = ByteArray(length)
var offset = 0

jar!!.closeEntry()
while (true) {
val read = jar!!.read(entryBuffer, offset, length - offset)

cacheLock.withLock {
fileCache[entry.name] = entryBuffer
}
offset += read

if (path != null && entry.name == path) return@runBlocking entryBuffer
if (offset == length) break
}

// compiler isn't smart enough
@Suppress("UNREACHABLE_CODE")
null
jar!!.closeEntry()
fileCache[entry.name] = entryBuffer

if (path != null && entry.name == path) return entryBuffer
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@ import org.polyfrost.spice.platform.api.IClassTransformer

object LwjglTransformer : IClassTransformer {
private val logger = LogManager.getLogger("Spice/Transformer")!!
override val targets = null

val provider = LwjglProvider()

override fun getClassNames(): Array<String>? {
return null
}

override fun transform(node: ClassNode) {
if (!node.name.startsWith("org/lwjgl")) return
if (node.name == "org/lwjgl/opengl/PixelFormat") return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface IClassTransformer {
* If null, the transformer will transform all classes
* Format like net.minecraft.client.Minecraft, not like net/minecraft/client/Minecraft
*/
fun getClassNames(): Array<String>?
val targets: Array<String>?

fun transform(node: ClassNode)
}
Loading

0 comments on commit df32ef1

Please sign in to comment.