This commit is contained in:
bbldrizzy committed 2026-10-23 00:28:57 +01:00
1 parent 83aea53e57
commit aa4f6b6ddc
842 files changed
+47284 -91563

No files matched your search

+175
View File
@@ -0,0 +1,175 @@
//! The loader worker: JVM discovery, payload installation into Fabric's Knot class loader, native registration, bootstrap.
use crate::bridge::{init_jvmti, native_class_bytes, native_log, native_redefine};
use crate::jni::*;
use crate::log;
use std::ffi::{c_char, c_void};
use std::path::PathBuf;
use std::time::Duration;
extern "system" {
fn GetModuleHandleA(name: *const c_char) -> *mut c_void;
fn GetProcAddress(module: *mut c_void, name: *const c_char) -> *mut c_void;
fn GetCurrentProcessId() -> u32;
}
static PAYLOAD: &[u8] = include_bytes!(env!("PRESTIGE_PAYLOAD_JAR"));
const NATIVE_BRIDGE: &str = "dev.zprestige.prestige.inject.NativeBridge";
const BOOTSTRAP: &str = "dev.zprestige.prestige.inject.Bootstrap";
pub fn run() {
log!("worker started, pid {}", unsafe { GetCurrentProcessId() });
match unsafe { install() } {
Ok(()) => log!("bootstrap returned"),
Err(e) => log!("FAILED: {e}"),
}
}
unsafe fn install() -> Result<(), String> {
let vm = find_vm()?;
let env = attach_current_thread(vm)?;
let jni = Jni::new(env);
log!("attached to the JVM");
init_jvmti(vm)?;
log!("JVMTI ready");
let jar = write_payload()?;
log!("payload written to {}", jar.display());
let loader = add_to_knot(&jni, &jar)?;
log!("payload appended to the Fabric Knot class loader");
register_natives(&jni, loader)?;
log!("natives registered");
let bootstrap = class_for_name(&jni, BOOTSTRAP, loader)?;
let start = jni.get_static_method_id(bootstrap, "start", "()V");
jni.check("resolve Bootstrap.start")?;
// Bootstrap.start waits for the client, installs the hooks and schedules the client initialisation.
jni.call_static_void(bootstrap, start, &[]);
jni.check("Bootstrap.start")?;
Ok(())
}
/// Waits for jvm.dll and a created JavaVM (the injector may run before Minecraft has started its JVM).
unsafe fn find_vm() -> Result<VmPtr, String> {
let mut module = std::ptr::null_mut();
for _ in 0..600 {
module = GetModuleHandleA(c"jvm.dll".as_ptr());
if !module.is_null() {
break;
}
std::thread::sleep(Duration::from_millis(100));
}
if module.is_null() {
return Err("jvm.dll is not loaded in this process".into());
}
let get_vms = GetProcAddress(module, c"JNI_GetCreatedJavaVMs".as_ptr());
if get_vms.is_null() {
return Err("JNI_GetCreatedJavaVMs export not found".into());
}
let get_vms: unsafe extern "system" fn(*mut VmPtr, JInt, *mut JInt) -> JInt = std::mem::transmute(get_vms);
for _ in 0..600 {
let mut vm: VmPtr = std::ptr::null_mut();
let mut count: JInt = 0;
if get_vms(&mut vm, 1, &mut count) == JNI_OK && count > 0 && !vm.is_null() {
return Ok(vm);
}
std::thread::sleep(Duration::from_millis(100));
}
Err("no JavaVM was created".into())
}
fn write_payload() -> Result<PathBuf, String> {
let dir = std::env::temp_dir().join("PrestigeLoader");
std::fs::create_dir_all(&dir).map_err(|e| format!("create {}: {e}", dir.display()))?;
let path = dir.join(format!("prestige-payload-{}.jar", unsafe { GetCurrentProcessId() }));
std::fs::write(&path, PAYLOAD).map_err(|e| format!("write {}: {e}", path.display()))?;
Ok(path)
}
/// FabricLauncherBase.getLauncher().addToClassPath(path) so the payload and the game classes share the Knot class loader.
/// Returns a global reference to the Knot target class loader.
unsafe fn add_to_knot(jni: &Jni, jar: &std::path::Path) -> Result<JObject, String> {
// A thread attached from native code resolves classes through the system class loader, which holds fabric-loader.
let base = jni.find_class("net/fabricmc/loader/impl/launch/FabricLauncherBase");
jni.check("FabricLauncherBase (is this a Fabric instance?)")?;
let get_launcher = jni.get_static_method_id(base, "getLauncher", "()Lnet/fabricmc/loader/impl/launch/FabricLauncher;");
jni.check("FabricLauncherBase.getLauncher")?;
let launcher = jni.call_static_object(base, get_launcher, &[]);
jni.check("getLauncher()")?;
if launcher.is_null() {
return Err("Fabric launcher is not available yet".into());
}
let launcher_class = jni.find_class("net/fabricmc/loader/impl/launch/FabricLauncher");
jni.check("FabricLauncher")?;
let add = jni.get_method_id(launcher_class, "addToClassPath", "(Ljava/nio/file/Path;[Ljava/lang/String;)V");
jni.check("FabricLauncher.addToClassPath")?;
let target = jni.get_method_id(launcher_class, "getTargetClassLoader", "()Ljava/lang/ClassLoader;");
jni.check("FabricLauncher.getTargetClassLoader")?;
// Path path = new java.io.File(jar).toPath()
let file_class = jni.find_class("java/io/File");
let file_ctor = jni.get_method_id(file_class, "<init>", "(Ljava/lang/String;)V");
let to_path = jni.get_method_id(file_class, "toPath", "()Ljava/nio/file/Path;");
jni.check("java.io.File")?;
let jar_string = jni.new_string(&jar.to_string_lossy());
let file = jni.new_object(file_class, file_ctor, &[obj(jar_string)]);
let path = jni.call_object(file, to_path, &[]);
jni.check("File.toPath")?;
let string_class = jni.find_class("java/lang/String");
let prefixes = jni.new_object_array(0, string_class, std::ptr::null_mut());
jni.call_void(launcher, add, &[obj(path), obj(prefixes)]);
jni.check("addToClassPath")?;
let loader = jni.call_object(launcher, target, &[]);
jni.check("getTargetClassLoader()")?;
if loader.is_null() {
return Err("Knot target class loader is null".into());
}
Ok(jni.new_global_ref(loader))
}
/// Class.forName(name, true, loader)
unsafe fn class_for_name(jni: &Jni, name: &str, loader: JObject) -> Result<JClass, String> {
let class_class = jni.find_class("java/lang/Class");
let for_name = jni.get_static_method_id(class_class, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
jni.check("Class.forName")?;
let name_string = jni.new_string(name);
let class = jni.call_static_object(class_class, for_name, &[obj(name_string), boolean(true), obj(loader)]);
jni.check(&format!("load {name}"))?;
if class.is_null() {
return Err(format!("{name} not found"));
}
Ok(class)
}
unsafe fn register_natives(jni: &Jni, loader: JObject) -> Result<(), String> {
let class = class_for_name(jni, NATIVE_BRIDGE, loader)?;
let methods = [
NativeMethod {
name: c"classBytes".as_ptr(),
signature: c"(Ljava/lang/Class;)[B".as_ptr(),
fn_ptr: native_class_bytes as *const () as usize as *mut c_void,
},
NativeMethod {
name: c"redefine".as_ptr(),
signature: c"(Ljava/lang/Class;[B)I".as_ptr(),
fn_ptr: native_redefine as *const () as usize as *mut c_void,
},
NativeMethod {
name: c"log".as_ptr(),
signature: c"(Ljava/lang/String;)V".as_ptr(),
fn_ptr: native_log as *const () as usize as *mut c_void,
},
];
let rc = jni.register_natives(class, &methods);
if rc != JNI_OK {
let _ = jni.take_exception();
return Err(format!("RegisterNatives failed ({rc})"));
}
Ok(())
}