first commit
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package dev.zerozipp.agent
|
||||
|
||||
import dev.zerozipp.loader.event.*
|
||||
import java.io.File
|
||||
|
||||
@Suppress("unused") class Client {
|
||||
private val base = Client::class
|
||||
private var mods = modules()
|
||||
|
||||
private fun modules(): List<Module> = runCatching {
|
||||
val source = base.java.protectionDomain.codeSource
|
||||
val location = File(source.location.toURI())
|
||||
val mods = File(location.parentFile, "mods")
|
||||
val loader = base.java.classLoader
|
||||
|
||||
if(!mods.isDirectory) return emptyList()
|
||||
val files = mods.listFiles { it.extension == "jar" }
|
||||
files?.map { Module(it, loader) }?: emptyList()
|
||||
}.getOrDefault(emptyList())
|
||||
|
||||
fun reload() = run { mods = modules() }
|
||||
fun event(event: Event) = mods.forEach {
|
||||
module -> module.event(this, event)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package dev.zerozipp.agent
|
||||
|
||||
import dev.zerozipp.loader.event.*
|
||||
import java.util.jar.JarFile
|
||||
import java.io.File
|
||||
import java.net.*
|
||||
|
||||
class Module(jar: File, parent: ClassLoader) {
|
||||
private val instance: Any? = runCatching {
|
||||
val urls = arrayOf(jar.toURI().toURL())
|
||||
val manifest = JarFile(jar).use { it.manifest }
|
||||
val attributes = manifest.mainAttributes
|
||||
val name = attributes.getValue("Mod-Class")
|
||||
|
||||
val loader = URLClassLoader(urls, parent)
|
||||
val base = Class.forName(name, true, loader)
|
||||
base.getConstructor().newInstance()
|
||||
}.getOrNull()
|
||||
|
||||
fun event(client: Client, event: Event) = runCatching {
|
||||
val (c, e) = Client::class.java to Event::class.java
|
||||
instance?.javaClass?.getDeclaredMethod("event", c, e)
|
||||
}.getOrNull()?.invoke(instance, client, event)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
@file:JvmName("Main")
|
||||
package dev.zerozipp.injector
|
||||
|
||||
import com.sun.tools.attach.*
|
||||
import dev.zerozipp.loader.Agent
|
||||
import java.io.File
|
||||
|
||||
val targets = setOf(
|
||||
"net.minecraft.client.main.Main",
|
||||
"org.prismlauncher.EntryPoint",
|
||||
"org.multimc.EntryPoint"
|
||||
)
|
||||
|
||||
fun main() {
|
||||
val domain = Agent::class.java.protectionDomain
|
||||
val jar = File(domain.codeSource.location.toURI())
|
||||
val machines = VirtualMachine.list()
|
||||
|
||||
for(desc in machines) if(targets.any {
|
||||
desc.displayName().startsWith(it)
|
||||
}) VirtualMachine.attach(desc).also {
|
||||
it.loadAgent(jar.absolutePath)
|
||||
it.detach()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@file:JvmName("Agent")
|
||||
package dev.zerozipp.loader
|
||||
|
||||
import dev.zerozipp.agent.Client
|
||||
import dev.zerozipp.loader.inject.*
|
||||
import net.bytebuddy.agent.builder.AgentBuilder
|
||||
import net.bytebuddy.agent.builder.AgentBuilder.*
|
||||
import net.bytebuddy.matcher.ElementMatchers.*
|
||||
import net.bytebuddy.description.method.*
|
||||
import net.bytebuddy.asm.Advice
|
||||
import java.lang.instrument.*
|
||||
import java.util.jar.JarFile
|
||||
import java.nio.file.Path
|
||||
import java.io.File
|
||||
|
||||
@Suppress("unused") object Agent {
|
||||
private const val KNOT = "net.fabricmc.loader.impl.launch.knot"
|
||||
@JvmStatic fun premain(args: String?, inst: Instrumentation?) = attach(inst!!)
|
||||
@JvmStatic fun agentmain(args: String?, inst: Instrumentation?) = attach(inst!!)
|
||||
val injections = listOf(Tick::class, Render::class, Gui::class)
|
||||
val client: Client by lazy { Client() }
|
||||
|
||||
fun AgentBuilder.inject(base: Class<*>): AgentBuilder {
|
||||
val inject = base.getAnnotation(Inject::class.java)
|
||||
val name = named<MethodDescription>(inject.method)
|
||||
val descriptor = if(inject.desc.isEmpty()) { name }
|
||||
else { name.and(hasDescriptor(inject.desc)) }
|
||||
|
||||
val advice = Advice.to(base).on(descriptor)
|
||||
return type(named(inject.target)).transform {
|
||||
builder, _, _, _, _ -> builder.visit(advice)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getKnotDelegate(): Any? {
|
||||
val base = Class.forName("$KNOT.KnotClassLoader")
|
||||
val delegate = base.getDeclaredField("delegate")
|
||||
val threads = Thread.getAllStackTraces().keys
|
||||
val loaders = threads.mapNotNull { it.contextClassLoader }
|
||||
val knot = loaders.firstOrNull { it.javaClass == base }
|
||||
return delegate.apply { isAccessible = true }.get(knot)
|
||||
}
|
||||
|
||||
private fun exposeToKnot(jar: Path) {
|
||||
val delegate = getKnotDelegate()
|
||||
val base = Class.forName("$KNOT.KnotClassDelegate")
|
||||
val source = base.getMethod("addCodeSource", Path::class.java)
|
||||
source.apply { isAccessible = true }.invoke(delegate, jar)
|
||||
}
|
||||
|
||||
private fun attach(inst: Instrumentation) {
|
||||
val domain = Client::class.java.protectionDomain
|
||||
val location = File(domain.codeSource.location.toURI())
|
||||
inst.appendToSystemClassLoaderSearch(JarFile(location))
|
||||
runCatching { exposeToKnot(location.toPath()) }
|
||||
|
||||
var builder: AgentBuilder = Default()
|
||||
builder = builder.disableClassFormatChanges()
|
||||
builder = builder.with(RedefinitionStrategy.RETRANSFORMATION)
|
||||
for(base in injections) builder = builder.inject(base.java)
|
||||
builder.installOn(inst)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dev.zerozipp.loader
|
||||
|
||||
@Suppress("unused")
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Inject(
|
||||
val target: String,
|
||||
val method: String,
|
||||
val desc: String
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package dev.zerozipp.loader.event
|
||||
|
||||
data class Event(
|
||||
val target: String,
|
||||
val state: State
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package dev.zerozipp.loader.event
|
||||
|
||||
enum class State {
|
||||
ENTER, EXIT
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package dev.zerozipp.loader.inject
|
||||
|
||||
import dev.zerozipp.loader.event.Event
|
||||
import dev.zerozipp.loader.event.State.*
|
||||
import dev.zerozipp.loader.Agent.client
|
||||
import dev.zerozipp.loader.Inject
|
||||
import net.bytebuddy.asm.Advice.*
|
||||
|
||||
@Suppress("unused") @Inject(
|
||||
"net.minecraft.client.gui.Gui",
|
||||
method = "extractRenderState", desc = "") object Gui {
|
||||
@OnMethodEnter @JvmStatic fun enter() = client.event(enter)
|
||||
@OnMethodExit @JvmStatic fun exit() = client.event(exit)
|
||||
@JvmField val enter = Event("gui", ENTER)
|
||||
@JvmField val exit = Event("gui", EXIT)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package dev.zerozipp.loader.inject
|
||||
|
||||
import dev.zerozipp.loader.event.Event
|
||||
import dev.zerozipp.loader.event.State.*
|
||||
import dev.zerozipp.loader.Agent.client
|
||||
import dev.zerozipp.loader.Inject
|
||||
import net.bytebuddy.asm.Advice.*
|
||||
|
||||
@Suppress("unused") @Inject(
|
||||
"net.minecraft.client.renderer.LevelRenderer",
|
||||
method = "extractLevel", desc = "") object Render {
|
||||
@OnMethodEnter @JvmStatic fun enter() = client.event(enter)
|
||||
@OnMethodExit @JvmStatic fun exit() = client.event(exit)
|
||||
@JvmField val enter = Event("render", ENTER)
|
||||
@JvmField val exit = Event("render", EXIT)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package dev.zerozipp.loader.inject
|
||||
|
||||
import dev.zerozipp.loader.event.Event
|
||||
import dev.zerozipp.loader.event.State.*
|
||||
import dev.zerozipp.loader.Agent.client
|
||||
import dev.zerozipp.loader.Inject
|
||||
import net.bytebuddy.asm.Advice.*
|
||||
|
||||
@Suppress("unused") @Inject(
|
||||
"net.minecraft.client.Minecraft",
|
||||
method = "tick", desc = "") object Tick {
|
||||
@OnMethodEnter @JvmStatic fun enter() = client.event(enter)
|
||||
@OnMethodExit @JvmStatic fun exit() = client.event(exit)
|
||||
@JvmField val enter = Event("tick", ENTER)
|
||||
@JvmField val exit = Event("tick", EXIT)
|
||||
}
|
||||
Reference in New Issue
Block a user