Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5152e6e0a6 | ||
|
|
0abefe7a04 | ||
|
|
036b24f2ae | ||
|
|
41d3e4a65e |
No files matched your search
@@ -19,3 +19,4 @@ openvape.zip
|
|||||||
/dist/
|
/dist/
|
||||||
/injector/target/
|
/injector/target/
|
||||||
/tools
|
/tools
|
||||||
|
/examples
|
||||||
@@ -5,8 +5,6 @@ A semi deobfuscated, IntelliJ-ready, runnable open sourced version of the Presti
|
|||||||
---
|
---
|
||||||
- we will work on removing the param obf and also renaming vars in the coming days. stay tuned!
|
- we will work on removing the param obf and also renaming vars in the coming days. stay tuned!
|
||||||
- if vikas wants to pay me to remove this, make ticket in discord and we can negotiate.
|
- if vikas wants to pay me to remove this, make ticket in discord and we can negotiate.
|
||||||
@ 1500 discord members, we will release injector
|
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/img/deobf-bird.png" width="260" alt="Deobf?">
|
<img src="docs/img/deobf-bird.png" width="260" alt="Deobf?">
|
||||||
|
|||||||
+17
-2
@@ -16,7 +16,7 @@ The injector and library must be built for the same architecture as the target J
|
|||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
1. **Injector** lists the running Fabric client JVMs, then loads the library into the chosen process.
|
1. **Injector** lists the running Minecraft JVMs, then loads the library into the chosen process.
|
||||||
- **Windows:** lists visible `java.exe`/`javaw.exe` windows (refreshed every 750 ms), then runs `LoadLibraryW` in the chosen
|
- **Windows:** lists visible `java.exe`/`javaw.exe` windows (refreshed every 750 ms), then runs `LoadLibraryW` in the chosen
|
||||||
process through `CreateRemoteThread` and confirms the module is mapped.
|
process through `CreateRemoteThread` and confirms the module is mapped.
|
||||||
- **Linux / macOS:** uses the HotSpot attach protocol (the same mechanism as `jcmd <pid> JVMTI.agent_load`). The injector
|
- **Linux / macOS:** uses the HotSpot attach protocol (the same mechanism as `jcmd <pid> JVMTI.agent_load`). The injector
|
||||||
@@ -29,7 +29,8 @@ The injector and library must be built for the same architecture as the target J
|
|||||||
unix, which also hands over the `JavaVM*`). The worker:
|
unix, which also hands over the `JavaVM*`). The worker:
|
||||||
- waits for the JVM (Windows: `jvm.dll`, `JNI_GetCreatedJavaVMs`), attaches, acquires the JVMTI redefine/retransform capabilities;
|
- waits for the JVM (Windows: `jvm.dll`, `JNI_GetCreatedJavaVMs`), attaches, acquires the JVMTI redefine/retransform capabilities;
|
||||||
- writes the embedded payload jar (the remapped `prestige-*.jar`) to the temp directory under `PrestigeLoader`;
|
- writes the embedded payload jar (the remapped `prestige-*.jar`) to the temp directory under `PrestigeLoader`;
|
||||||
- `FabricLauncherBase.getLauncher().addToClassPath(jar)` so payload and game classes share the Knot class loader;
|
- looks for Fabric through the render thread's context class loader, then the system and other thread loaders;
|
||||||
|
- `FabricLauncherBase.getLauncher().addToClassPath(jar)` so payload and game classes share the launcher's game class loader;
|
||||||
- registers `NativeBridge.classBytes` / `NativeBridge.redefine` and calls `Bootstrap.start()`.
|
- registers `NativeBridge.classBytes` / `NativeBridge.redefine` and calls `Bootstrap.start()`.
|
||||||
3. **`Bootstrap`** (Java) waits for the client window, then for every ClassTransform hook target gets the current class bytes
|
3. **`Bootstrap`** (Java) waits for the client window, then for every ClassTransform hook target gets the current class bytes
|
||||||
(JVMTI retransform capture), runs the hooks through `InPlaceTransformer` and redefines the class. The bundled ClassTransform
|
(JVMTI retransform capture), runs the hooks through `InPlaceTransformer` and redefines the class. The bundled ClassTransform
|
||||||
@@ -71,6 +72,20 @@ PrestigeInjector <pid> [lib path] # inject into a specific pid, optionally w
|
|||||||
|
|
||||||
On Windows this is `PrestigeInjector.exe`. By default the library is looked up beside the injector.
|
On Windows this is `PrestigeInjector.exe`. By default the library is looked up beside the injector.
|
||||||
|
|
||||||
|
### Dawn and Lunar Client 1.21.11
|
||||||
|
|
||||||
|
Use a 1.21.11 Fabric profile in Dawn or enable Lunar Client's Fabric add-on for 1.21.11. The target
|
||||||
|
profile needs Fabric Loader 0.19.x and Fabric API. Wait for the title screen before selecting the game
|
||||||
|
in the injector. The default Prestige menu key is Right Shift, which may overlap with a launcher keybind.
|
||||||
|
|
||||||
|
For Lunar, the native log should identify `GenesisClassLoader`. Check the game log for `hooks installed`
|
||||||
|
and `client initialised`. Hook counts refer to classes with changed bytecode. A skipped or failed hook
|
||||||
|
can leave its feature unavailable even if the menu opens.
|
||||||
|
|
||||||
|
An offline JVM replay accepted all 42 changed classes from Lunar's installed 1.21.11 cache after
|
||||||
|
recalculating stack and local limits on the cached classes. Live injection and Dawn's in-game profile
|
||||||
|
still need smoke testing.
|
||||||
|
|
||||||
## Install as a mod instead
|
## Install as a mod instead
|
||||||
|
|
||||||
The payload jar is also a normal Fabric mod. Put `prestige-*.jar` (from `dist/` or the CI `payload-jar` artifact) in the
|
The payload jar is also a normal Fabric mod. Put `prestige-*.jar` (from `dist/` or the CI `payload-jar` artifact) in the
|
||||||
|
|||||||
@@ -221,14 +221,12 @@ fn pick_process() -> Option<u32> {
|
|||||||
let mut selected = 0usize;
|
let mut selected = 0usize;
|
||||||
let mut processes = platform::java_processes();
|
let mut processes = platform::java_processes();
|
||||||
let mut last_refresh = Instant::now();
|
let mut last_refresh = Instant::now();
|
||||||
|
draw(&processes, selected);
|
||||||
loop {
|
loop {
|
||||||
if selected >= processes.len() {
|
|
||||||
selected = processes.len().saturating_sub(1);
|
|
||||||
}
|
|
||||||
draw(&processes, selected);
|
|
||||||
let deadline = Instant::now() + Duration::from_millis(50);
|
let deadline = Instant::now() + Duration::from_millis(50);
|
||||||
while Instant::now() < deadline {
|
while Instant::now() < deadline {
|
||||||
if let Some(key) = platform::poll_key() {
|
if let Some(key) = platform::poll_key() {
|
||||||
|
let previous = selected;
|
||||||
match key {
|
match key {
|
||||||
platform::Key::Up => selected = selected.saturating_sub(1),
|
platform::Key::Up => selected = selected.saturating_sub(1),
|
||||||
platform::Key::Down => selected = (selected + 1).min(processes.len().saturating_sub(1)),
|
platform::Key::Down => selected = (selected + 1).min(processes.len().saturating_sub(1)),
|
||||||
@@ -239,12 +237,19 @@ fn pick_process() -> Option<u32> {
|
|||||||
}
|
}
|
||||||
platform::Key::Escape => return None,
|
platform::Key::Escape => return None,
|
||||||
}
|
}
|
||||||
draw(&processes, selected);
|
if selected != previous {
|
||||||
|
draw(&processes, selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(10));
|
std::thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
if last_refresh.elapsed() >= Duration::from_millis(750) {
|
if last_refresh.elapsed() >= Duration::from_millis(750) {
|
||||||
processes = platform::java_processes();
|
let refreshed = platform::java_processes();
|
||||||
|
if refreshed != processes {
|
||||||
|
processes = refreshed;
|
||||||
|
selected = selected.min(processes.len().saturating_sub(1));
|
||||||
|
draw(&processes, selected);
|
||||||
|
}
|
||||||
last_refresh = Instant::now();
|
last_refresh = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// A running Minecraft JVM shown in the picker.
|
/// A running Minecraft JVM shown in the picker.
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub struct JavaProcess {
|
pub struct JavaProcess {
|
||||||
pub pid: u32,
|
pub pid: u32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ impl Jni {
|
|||||||
pub unsafe fn exception_clear(&self) {
|
pub unsafe fn exception_clear(&self) {
|
||||||
f!(self, 17, unsafe extern "system" fn(EnvPtr))(self.env)
|
f!(self, 17, unsafe extern "system" fn(EnvPtr))(self.env)
|
||||||
}
|
}
|
||||||
pub unsafe fn new_global_ref(&self, o: JObject) -> JObject {
|
|
||||||
f!(self, 21, unsafe extern "system" fn(EnvPtr, JObject) -> JObject)(self.env, o)
|
|
||||||
}
|
|
||||||
pub unsafe fn is_same_object(&self, a: JObject, b: JObject) -> bool {
|
pub unsafe fn is_same_object(&self, a: JObject, b: JObject) -> bool {
|
||||||
f!(self, 24, unsafe extern "system" fn(EnvPtr, JObject, JObject) -> JBool)(self.env, a, b) != 0
|
f!(self, 24, unsafe extern "system" fn(EnvPtr, JObject, JObject) -> JBool)(self.env, a, b) != 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ unsafe fn install() -> Result<(), String> {
|
|||||||
log!("payload written to {}", jar.display());
|
log!("payload written to {}", jar.display());
|
||||||
|
|
||||||
let loader = add_to_knot(&jni, &jar)?;
|
let loader = add_to_knot(&jni, &jar)?;
|
||||||
log!("payload appended to the Fabric Knot class loader");
|
log!("payload appended to the game class loader: {}", describe(&jni, loader));
|
||||||
|
|
||||||
register_natives(&jni, loader)?;
|
register_natives(&jni, loader)?;
|
||||||
log!("natives registered");
|
log!("natives registered");
|
||||||
@@ -111,7 +111,7 @@ fn write_payload() -> Result<PathBuf, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// FabricLauncherBase.getLauncher().addToClassPath(path) so the payload and the game classes share the Knot class loader.
|
/// 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.
|
/// Returns a local reference to the Fabric launcher's target class loader.
|
||||||
unsafe fn add_to_knot(jni: &Jni, jar: &std::path::Path) -> Result<JObject, String> {
|
unsafe fn add_to_knot(jni: &Jni, jar: &std::path::Path) -> Result<JObject, String> {
|
||||||
jni.ensure_local_capacity(4096);
|
jni.ensure_local_capacity(4096);
|
||||||
let (base, fabric_loader) = locate_fabric(jni)?;
|
let (base, fabric_loader) = locate_fabric(jni)?;
|
||||||
@@ -149,23 +149,30 @@ unsafe fn add_to_knot(jni: &Jni, jar: &std::path::Path) -> Result<JObject, Strin
|
|||||||
if loader.is_null() {
|
if loader.is_null() {
|
||||||
return Err("Knot target class loader is null".into());
|
return Err("Knot target class loader is null".into());
|
||||||
}
|
}
|
||||||
Ok(jni.new_global_ref(loader))
|
Ok(loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
const FABRIC_BASE: &str = "net.fabricmc.loader.impl.launch.FabricLauncherBase";
|
const FABRIC_BASE: &str = "net.fabricmc.loader.impl.launch.FabricLauncherBase";
|
||||||
|
|
||||||
/// Finds the class loader that can see fabric-loader. The system class loader is tried first (official launcher, most
|
/// Prefer the render thread's context loader when a launcher supplies its own loader hierarchy.
|
||||||
/// others); launchers that run the game from their own URLClassLoader (Prism/MultiMC, many modpack launchers) keep
|
/// Fall back to the system loader and then the other thread context loaders.
|
||||||
/// fabric-loader out of it, so every thread's context class loader is probed too. Returns the FabricLauncherBase class
|
|
||||||
/// and the loader it was found through (null = FindClass / system loader).
|
|
||||||
unsafe fn locate_fabric(jni: &Jni) -> Result<(JClass, JObject), String> {
|
unsafe fn locate_fabric(jni: &Jni) -> Result<(JClass, JObject), String> {
|
||||||
|
let (render, loaders) = thread_loaders(jni)?;
|
||||||
|
if let Some(loader) = render {
|
||||||
|
if let Ok(class) = class_for_name(jni, FABRIC_BASE, loader, false) {
|
||||||
|
log!("fabric-loader found through the render thread: {}", describe(jni, loader));
|
||||||
|
return Ok((class, loader));
|
||||||
|
}
|
||||||
|
}
|
||||||
let base = jni.find_class(&FABRIC_BASE.replace('.', "/"));
|
let base = jni.find_class(&FABRIC_BASE.replace('.', "/"));
|
||||||
if jni.take_exception().is_none() && !base.is_null() {
|
if jni.take_exception().is_none() && !base.is_null() {
|
||||||
return Ok((base, std::ptr::null_mut()));
|
return Ok((base, std::ptr::null_mut()));
|
||||||
}
|
}
|
||||||
log!("FabricLauncherBase is not visible to the system class loader, probing thread context class loaders");
|
log!("FabricLauncherBase is not visible to the system class loader, probing thread context class loaders");
|
||||||
let loaders = thread_loaders(jni)?;
|
|
||||||
for &loader in &loaders {
|
for &loader in &loaders {
|
||||||
|
if render.is_some_and(|r| jni.is_same_object(r, loader)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let Ok(class) = class_for_name(jni, FABRIC_BASE, loader, false) {
|
if let Ok(class) = class_for_name(jni, FABRIC_BASE, loader, false) {
|
||||||
log!("fabric-loader found through {}", describe(jni, loader));
|
log!("fabric-loader found through {}", describe(jni, loader));
|
||||||
return Ok((class, loader));
|
return Ok((class, loader));
|
||||||
@@ -180,10 +187,11 @@ unsafe fn locate_fabric(jni: &Jni) -> Result<(JClass, JObject), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Distinct context class loaders of all live threads (the game threads inherit the launcher's loader).
|
/// Distinct context class loaders of all live threads (the game threads inherit the launcher's loader).
|
||||||
unsafe fn thread_loaders(jni: &Jni) -> Result<Vec<JObject>, String> {
|
unsafe fn thread_loaders(jni: &Jni) -> Result<(Option<JObject>, Vec<JObject>), String> {
|
||||||
let thread_class = jni.find_class("java/lang/Thread");
|
let thread_class = jni.find_class("java/lang/Thread");
|
||||||
let get_all = jni.get_static_method_id(thread_class, "getAllStackTraces", "()Ljava/util/Map;");
|
let get_all = jni.get_static_method_id(thread_class, "getAllStackTraces", "()Ljava/util/Map;");
|
||||||
let get_context = jni.get_method_id(thread_class, "getContextClassLoader", "()Ljava/lang/ClassLoader;");
|
let get_context = jni.get_method_id(thread_class, "getContextClassLoader", "()Ljava/lang/ClassLoader;");
|
||||||
|
let get_name = jni.get_method_id(thread_class, "getName", "()Ljava/lang/String;");
|
||||||
let map_class = jni.find_class("java/util/Map");
|
let map_class = jni.find_class("java/util/Map");
|
||||||
let key_set = jni.get_method_id(map_class, "keySet", "()Ljava/util/Set;");
|
let key_set = jni.get_method_id(map_class, "keySet", "()Ljava/util/Set;");
|
||||||
let set_class = jni.find_class("java/util/Set");
|
let set_class = jni.find_class("java/util/Set");
|
||||||
@@ -195,6 +203,7 @@ unsafe fn thread_loaders(jni: &Jni) -> Result<Vec<JObject>, String> {
|
|||||||
let threads = jni.call_object(jni_non_null(set)?, to_array, &[]);
|
let threads = jni.call_object(jni_non_null(set)?, to_array, &[]);
|
||||||
jni.check("thread list")?;
|
jni.check("thread list")?;
|
||||||
let mut loaders: Vec<JObject> = Vec::new();
|
let mut loaders: Vec<JObject> = Vec::new();
|
||||||
|
let mut render = None;
|
||||||
for i in 0..jni.get_array_length(jni_non_null(threads)?) {
|
for i in 0..jni.get_array_length(jni_non_null(threads)?) {
|
||||||
let thread = jni.get_object_array_element(threads, i);
|
let thread = jni.get_object_array_element(threads, i);
|
||||||
if thread.is_null() {
|
if thread.is_null() {
|
||||||
@@ -204,11 +213,17 @@ unsafe fn thread_loaders(jni: &Jni) -> Result<Vec<JObject>, String> {
|
|||||||
if jni.take_exception().is_some() || loader.is_null() {
|
if jni.take_exception().is_some() || loader.is_null() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let name = jni.call_object(thread, get_name, &[]);
|
||||||
|
if jni.take_exception().is_none() && !name.is_null() {
|
||||||
|
if matches!(jni.get_string_utf(name).as_str(), "Render thread" | "Client thread") {
|
||||||
|
render = Some(loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
if !loaders.iter().any(|&known| jni.is_same_object(known, loader)) {
|
if !loaders.iter().any(|&known| jni.is_same_object(known, loader)) {
|
||||||
loaders.push(loader);
|
loaders.push(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(loaders)
|
Ok((render, loaders))
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn jni_non_null(o: JObject) -> Result<JObject, String> {
|
unsafe fn jni_non_null(o: JObject) -> Result<JObject, String> {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class Module extends SettingHolder implements MinecraftAccess {
|
|||||||
this.setLastEnabled(this.isEnabled());
|
this.setLastEnabled(this.isEnabled());
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
Prestige.eventBus.register(this);
|
Prestige.eventBus.register(this);
|
||||||
if (mc.world != null && mc.player != null) {
|
if (!requiresWorldOnEnable() || (mc.world != null && mc.player != null)) {
|
||||||
this.onEnable();
|
this.onEnable();
|
||||||
} else {
|
} else {
|
||||||
Prestige.moduleManager.queueEnable(this);
|
Prestige.moduleManager.queueEnable(this);
|
||||||
@@ -89,7 +89,7 @@ public class Module extends SettingHolder implements MinecraftAccess {
|
|||||||
public void applyState() {
|
public void applyState() {
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
Prestige.eventBus.register(this);
|
Prestige.eventBus.register(this);
|
||||||
if (mc.world != null && mc.player != null) {
|
if (!requiresWorldOnEnable() || (mc.world != null && mc.player != null)) {
|
||||||
this.onEnable();
|
this.onEnable();
|
||||||
} else {
|
} else {
|
||||||
Prestige.moduleManager.queueEnable(this);
|
Prestige.moduleManager.queueEnable(this);
|
||||||
@@ -123,6 +123,10 @@ public class Module extends SettingHolder implements MinecraftAccess {
|
|||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean requiresWorldOnEnable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,10 @@ import dev.zprestige.prestige.util.SimulatedClickManager;
|
|||||||
import dev.zprestige.prestige.util.FrameDeltaTimer;
|
import dev.zprestige.prestige.util.FrameDeltaTimer;
|
||||||
import dev.zprestige.prestige.util.NotificationManager;
|
import dev.zprestige.prestige.util.NotificationManager;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import net.fabricmc.api.ModInitializer;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.gui.screen.ChatScreen;
|
import net.minecraft.client.gui.screen.ChatScreen;
|
||||||
|
|
||||||
public class Prestige implements ModInitializer {
|
public class Prestige {
|
||||||
private static final AtomicBoolean initialized = new AtomicBoolean(false);
|
private static final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||||
private static final java.util.concurrent.atomic.AtomicInteger renderErrorCount = new java.util.concurrent.atomic.AtomicInteger();
|
private static final java.util.concurrent.atomic.AtomicInteger renderErrorCount = new java.util.concurrent.atomic.AtomicInteger();
|
||||||
public static EventBus eventBus;
|
public static EventBus eventBus;
|
||||||
@@ -160,8 +159,4 @@ public class Prestige implements ModInitializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInitialize() {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.zprestige.prestige.fabric;
|
||||||
|
|
||||||
|
import dev.zprestige.prestige.client.Prestige;
|
||||||
|
import dev.zprestige.prestige.ui.mc.Entry;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
|
|
||||||
|
public final class FabricEntrypoints implements ModInitializer, ClientModInitializer {
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
new Prestige().init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
Entry entry = new Entry();
|
||||||
|
entry.initialize();
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(entry::tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
package dev.zprestige.prestige.inject;
|
package dev.zprestige.prestige.inject;
|
||||||
|
|
||||||
import dev.zprestige.prestige.client.Prestige;
|
|
||||||
import dev.zprestige.prestige.engine.EnginePlugin;
|
import dev.zprestige.prestige.engine.EnginePlugin;
|
||||||
import dev.zprestige.prestige.engine.EngineState;
|
import dev.zprestige.prestige.engine.EngineState;
|
||||||
|
import dev.zprestige.prestige.fabric.FabricEntrypoints;
|
||||||
import dev.zprestige.prestige.hooks.Definer;
|
import dev.zprestige.prestige.hooks.Definer;
|
||||||
import dev.zprestige.prestige.ui.mc.Entry;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.ToIntBiFunction;
|
import java.util.function.ToIntBiFunction;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.lenni0451.classtransform.TransformerManager;
|
import net.lenni0451.classtransform.TransformerManager;
|
||||||
|
import net.lenni0451.classtransform.utils.mappings.SuperMappingFiller;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.FieldNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point of the injected payload. prestige_native.dll appends this jar to the Fabric (Knot) class path, registers the
|
* Entry point of the injected payload. prestige_native.dll appends this jar to the Fabric (Knot) class path, registers the
|
||||||
@@ -41,11 +48,12 @@ public final class Bootstrap {
|
|||||||
log("waiting for the Minecraft client");
|
log("waiting for the Minecraft client");
|
||||||
waitForClient();
|
waitForClient();
|
||||||
Report report = install(NativeBridge::classBytes, NativeBridge::redefine, nativeBatch());
|
Report report = install(NativeBridge::classBytes, NativeBridge::redefine, nativeBatch());
|
||||||
log("hooks installed: " + report.redefined + " classes, " + report.moved + " handlers moved" + (report.failed.isEmpty() ? "" : ", FAILED " + report.failed));
|
log("hooks installed: " + report.redefined + " classes, " + report.moved + " handlers moved"
|
||||||
|
+ ", " + report.unchanged + " unchanged" + (report.failed.isEmpty() ? "" : ", FAILED " + report.failed));
|
||||||
|
if (report.redefined == 0) throw new IllegalStateException("no hooks installed");
|
||||||
// Some clients (Lunar) name a few classes and methods differently. Keep going with the hooks that did apply: a missing
|
// Some clients (Lunar) name a few classes and methods differently. Keep going with the hooks that did apply: a missing
|
||||||
// hook only disables the features behind it, while stopping here would leave the client without Prestige at all.
|
// hook only disables the features behind it, while stopping here would leave the client without Prestige at all.
|
||||||
if (!report.failed.isEmpty()) {
|
if (!report.failed.isEmpty()) {
|
||||||
if (report.redefined == 0) throw new IllegalStateException("could not hook " + report.failed);
|
|
||||||
log("continuing with " + report.redefined + " hooked classes; skipped " + report.failed);
|
log("continuing with " + report.redefined + " hooked classes; skipped " + report.failed);
|
||||||
}
|
}
|
||||||
log("initialising Prestige on the client thread");
|
log("initialising Prestige on the client thread");
|
||||||
@@ -67,7 +75,7 @@ public final class Bootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final class Report {
|
public static final class Report {
|
||||||
public int redefined, moved;
|
public int redefined, moved, unchanged;
|
||||||
public final List<String> failed = new ArrayList<>();
|
public final List<String> failed = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +155,26 @@ public final class Bootstrap {
|
|||||||
}
|
}
|
||||||
long captured = System.nanoTime();
|
long captured = System.nanoTime();
|
||||||
|
|
||||||
|
// Genesis can expose different class resources from the definitions installed in its JVM.
|
||||||
|
// Resolve inherited Yarn members using the actual superclass and interface declarations.
|
||||||
|
var mappings = manager.getMapper().getRemapper();
|
||||||
|
if (!mappings.isEmpty()) {
|
||||||
|
Map<Class<?>, ClassNode> declarations = new HashMap<>();
|
||||||
|
for (Class<?> type : types) {
|
||||||
|
if (type == null) continue;
|
||||||
|
try {
|
||||||
|
Set<ClassNode> parents = new LinkedHashSet<>();
|
||||||
|
collectParents(type.getSuperclass(), declarations, parents);
|
||||||
|
for (Class<?> iface : type.getInterfaces()) collectParents(iface, declarations, parents);
|
||||||
|
ClassNode target = new ClassNode();
|
||||||
|
target.name = Type.getInternalName(type);
|
||||||
|
SuperMappingFiller.fillSuperMembers(target, parents, mappings);
|
||||||
|
} catch (LinkageError e) {
|
||||||
|
log("could not inspect inherited mappings for " + type.getName() + ": " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. ClassTransform, one class at a time (its mapper is not thread-safe: running it on several threads corrupts its state).
|
// 3. ClassTransform, one class at a time (its mapper is not thread-safe: running it on several threads corrupts its state).
|
||||||
// A result that is already in the disk cache skips the transformation, which is where nearly all of the time goes.
|
// A result that is already in the disk cache skips the transformation, which is where nearly all of the time goes.
|
||||||
InPlaceTransformer.Result[] results = new InPlaceTransformer.Result[total];
|
InPlaceTransformer.Result[] results = new InPlaceTransformer.Result[total];
|
||||||
@@ -167,6 +195,12 @@ public final class Bootstrap {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
results[i] = transformer.transform(target, originals[i]);
|
results[i] = transformer.transform(target, originals[i]);
|
||||||
|
if (results[i].target() == originals[i]) {
|
||||||
|
log("no bytecode changes for " + target);
|
||||||
|
report.unchanged++;
|
||||||
|
results[i] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (cache != null && results[i].helpers().isEmpty()) cache.put(target, originals[i], results[i].target());
|
if (cache != null && results[i].helpers().isEmpty()) cache.put(target, originals[i], results[i].target());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
fail(report, target, t);
|
fail(report, target, t);
|
||||||
@@ -211,6 +245,24 @@ public final class Bootstrap {
|
|||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void collectParents(Class<?> type, Map<Class<?>, ClassNode> declarations, Set<ClassNode> parents) {
|
||||||
|
if (type == null || !type.getName().startsWith("net.minecraft.")) return;
|
||||||
|
ClassNode declaration = declarations.computeIfAbsent(type, parent -> {
|
||||||
|
ClassNode node = new ClassNode();
|
||||||
|
node.name = Type.getInternalName(parent);
|
||||||
|
for (var method : parent.getDeclaredMethods()) {
|
||||||
|
node.methods.add(new MethodNode(method.getModifiers(), method.getName(), Type.getMethodDescriptor(method), null, null));
|
||||||
|
}
|
||||||
|
for (var field : parent.getDeclaredFields()) {
|
||||||
|
node.fields.add(new FieldNode(field.getModifiers(), field.getName(), Type.getDescriptor(field.getType()), null, null));
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
if (!parents.add(declaration)) return;
|
||||||
|
collectParents(type.getSuperclass(), declarations, parents);
|
||||||
|
for (Class<?> iface : type.getInterfaces()) collectParents(iface, declarations, parents);
|
||||||
|
}
|
||||||
|
|
||||||
private static void fail(Report report, String target, Throwable t) {
|
private static void fail(Report report, String target, Throwable t) {
|
||||||
report.failed.add(target);
|
report.failed.add(target);
|
||||||
log("hook failed for " + target + ": " + t);
|
log("hook failed for " + target + ": " + t);
|
||||||
@@ -244,8 +296,9 @@ public final class Bootstrap {
|
|||||||
|
|
||||||
private static void initClient() {
|
private static void initClient() {
|
||||||
try {
|
try {
|
||||||
new Prestige().onInitialize();
|
FabricEntrypoints entrypoints = new FabricEntrypoints();
|
||||||
new Entry().onInitializeClient();
|
entrypoints.onInitialize();
|
||||||
|
entrypoints.onInitializeClient();
|
||||||
log("client initialised");
|
log("client initialised");
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log("client init failed: " + t);
|
log("client init failed: " + t);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -11,8 +12,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import net.lenni0451.classtransform.TransformerManager;
|
import net.lenni0451.classtransform.TransformerManager;
|
||||||
|
import net.lenni0451.classtransform.utils.ASMUtils;
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
import org.objectweb.asm.ClassWriter;
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
@@ -48,7 +51,7 @@ public final class InPlaceTransformer {
|
|||||||
|
|
||||||
/** @param dottedName runtime name of the target class, @param current the bytes of the class as currently loaded */
|
/** @param dottedName runtime name of the target class, @param current the bytes of the class as currently loaded */
|
||||||
public Result transform(String dottedName, byte[] current) throws Exception {
|
public Result transform(String dottedName, byte[] current) throws Exception {
|
||||||
byte[] out = manager.transform(dottedName, current, true);
|
byte[] out = manager.transform(dottedName, current, false);
|
||||||
if (out == null) throw new IllegalStateException("ClassTransform failed for " + dottedName);
|
if (out == null) throw new IllegalStateException("ClassTransform failed for " + dottedName);
|
||||||
ClassNode before = read(current);
|
ClassNode before = read(current);
|
||||||
ClassNode after = read(out);
|
ClassNode after = read(out);
|
||||||
@@ -63,9 +66,9 @@ public final class InPlaceTransformer {
|
|||||||
before.methods.forEach(m -> existing.put(m.name + m.desc, m));
|
before.methods.forEach(m -> existing.put(m.name + m.desc, m));
|
||||||
Map<String, MethodNode> moved = new LinkedHashMap<>();
|
Map<String, MethodNode> moved = new LinkedHashMap<>();
|
||||||
for (MethodNode m : after.methods) if (!existing.containsKey(m.name + m.desc)) moved.put(m.name + m.desc, m);
|
for (MethodNode m : after.methods) if (!existing.containsKey(m.name + m.desc)) moved.put(m.name + m.desc, m);
|
||||||
if (moved.isEmpty()) return new Result(out, Map.of(), 0);
|
if (!after.interfaces.equals(before.interfaces))
|
||||||
if (after.interfaces.size() != before.interfaces.size())
|
|
||||||
throw new IllegalStateException(dottedName + ": transformer changed the interface list");
|
throw new IllegalStateException(dottedName + ": transformer changed the interface list");
|
||||||
|
if (moved.isEmpty()) return new Result(writeTarget(current, before, after, existing), Map.of(), 0);
|
||||||
|
|
||||||
String helperName = HOOK_PACKAGE + "H" + (sequence.getAndIncrement()) + "_" + after.name.substring(after.name.lastIndexOf('/') + 1).replace('$', '_');
|
String helperName = HOOK_PACKAGE + "H" + (sequence.getAndIncrement()) + "_" + after.name.substring(after.name.lastIndexOf('/') + 1).replace('$', '_');
|
||||||
ClassNode helper = new ClassNode();
|
ClassNode helper = new ClassNode();
|
||||||
@@ -100,13 +103,44 @@ public final class InPlaceTransformer {
|
|||||||
accessors.finish();
|
accessors.finish();
|
||||||
after.methods.removeIf(m -> moved.containsKey(m.name + m.desc));
|
after.methods.removeIf(m -> moved.containsKey(m.name + m.desc));
|
||||||
|
|
||||||
ClassWriter targetWriter = new ClassWriter(0);
|
|
||||||
after.accept(targetWriter);
|
|
||||||
ClassWriter helperWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
|
||||||
helper.accept(helperWriter);
|
|
||||||
Map<String, byte[]> helpers = new LinkedHashMap<>();
|
Map<String, byte[]> helpers = new LinkedHashMap<>();
|
||||||
helpers.put(helperName.replace('/', '.'), helperWriter.toByteArray());
|
helpers.put(helperName.replace('/', '.'), ASMUtils.toBytes(helper, manager.getClassTree(), manager.getClassProvider()));
|
||||||
return new Result(targetWriter.toByteArray(), helpers, moved.size());
|
return new Result(writeTarget(current, before, after, existing), helpers, moved.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute frames only in changed methods. Launchers can transform unrelated methods and
|
||||||
|
* supply valid frames for them even when their class resources are unavailable to ClassTransform.
|
||||||
|
*/
|
||||||
|
private byte[] writeTarget(byte[] current, ClassNode before, ClassNode after, Map<String, MethodNode> originals) {
|
||||||
|
List<MethodNode> changed = new ArrayList<>();
|
||||||
|
for (MethodNode method : after.methods) {
|
||||||
|
MethodNode original = originals.get(method.name + method.desc);
|
||||||
|
if (original == null) throw new IllegalStateException("Unexpected method " + method.name + method.desc);
|
||||||
|
if (!Arrays.equals(methodBytes(original), methodBytes(method))) changed.add(method);
|
||||||
|
}
|
||||||
|
if (changed.isEmpty()) return current;
|
||||||
|
after.methods = changed;
|
||||||
|
ClassNode computed = read(ASMUtils.toBytes(after, manager.getClassTree(), manager.getClassProvider()));
|
||||||
|
Map<String, MethodNode> replacements = new HashMap<>();
|
||||||
|
computed.methods.forEach(m -> replacements.put(m.name + m.desc, m));
|
||||||
|
before.methods.replaceAll(m -> replacements.getOrDefault(m.name + m.desc, m));
|
||||||
|
ClassWriter writer = new ClassWriter(new ClassReader(current), 0);
|
||||||
|
before.accept(writer);
|
||||||
|
return writer.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] methodBytes(MethodNode method) {
|
||||||
|
ClassWriter writer = new ClassWriter(0);
|
||||||
|
writer.visit(Opcodes.V21, Opcodes.ACC_PUBLIC, "MethodComparison", null, "java/lang/Object", null);
|
||||||
|
MethodVisitor visitor = writer.visitMethod(method.access, method.name, method.desc, method.signature,
|
||||||
|
method.exceptions.toArray(String[]::new));
|
||||||
|
method.accept(new MethodVisitor(Opcodes.ASM9, visitor) {
|
||||||
|
@Override public void visitFrame(int type, int locals, Object[] local, int stackSize, Object[] stack) {}
|
||||||
|
@Override public void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(0, 0); }
|
||||||
|
});
|
||||||
|
writer.visitEnd();
|
||||||
|
return writer.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClassNode read(byte[] bytes) {
|
private static ClassNode read(byte[] bytes) {
|
||||||
|
|||||||
@@ -7,20 +7,15 @@ import net.lenni0451.classtransform.annotations.CTarget;
|
|||||||
import net.lenni0451.classtransform.annotations.CTransformer;
|
import net.lenni0451.classtransform.annotations.CTransformer;
|
||||||
import net.lenni0451.classtransform.annotations.injection.CInject;
|
import net.lenni0451.classtransform.annotations.injection.CInject;
|
||||||
import net.minecraft.client.gui.hud.InGameOverlayRenderer;
|
import net.minecraft.client.gui.hud.InGameOverlayRenderer;
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
|
|
||||||
@CTransformer({InGameOverlayRenderer.class})
|
@CTransformer({InGameOverlayRenderer.class})
|
||||||
public class InGameOverlayRendererMixin {
|
public class InGameOverlayRendererMixin {
|
||||||
@CInject(
|
@CInject(
|
||||||
method = {"renderFireOverlay"},
|
method = {"renderFireOverlay"},
|
||||||
target = {@CTarget(
|
target = {@CTarget("HEAD")},
|
||||||
value = "INVOKE",
|
|
||||||
target = "Lnet/minecraft/client/util/math/MatrixStack;translate(FFF)V"
|
|
||||||
)},
|
|
||||||
cancellable = true
|
cancellable = true
|
||||||
)
|
)
|
||||||
private static void hideFireOverlayBeforeTranslate(MatrixStack matrices, VertexConsumerProvider vertexConsumers, InjectionCallback ci) {
|
private static void hideFireOverlayAtHead(InjectionCallback ci) {
|
||||||
FireOverlayEvent event = HookBridge.createFireOverlayEvent();
|
FireOverlayEvent event = HookBridge.createFireOverlayEvent();
|
||||||
if (HookBridge.postFireOverlayEvent(event)) {
|
if (HookBridge.postFireOverlayEvent(event)) {
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import net.lenni0451.classtransform.annotations.CTransformer;
|
|||||||
import net.lenni0451.classtransform.annotations.injection.CInject;
|
import net.lenni0451.classtransform.annotations.injection.CInject;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.entity.damage.DamageSource;
|
import net.minecraft.entity.damage.DamageSource;
|
||||||
|
import net.minecraft.particle.BlockParticleEffect;
|
||||||
import net.minecraft.particle.ParticleEffect;
|
import net.minecraft.particle.ParticleEffect;
|
||||||
import net.minecraft.registry.entry.RegistryEntry;
|
import net.minecraft.registry.entry.RegistryEntry;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.server.world.ServerWorld;
|
||||||
import net.minecraft.sound.SoundEvent;
|
import net.minecraft.sound.SoundEvent;
|
||||||
|
import net.minecraft.util.collection.Pool;
|
||||||
import net.minecraft.world.World.ExplosionSourceType;
|
import net.minecraft.world.World.ExplosionSourceType;
|
||||||
import net.minecraft.world.explosion.ExplosionBehavior;
|
import net.minecraft.world.explosion.ExplosionBehavior;
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ public class ServerWorldMixin {
|
|||||||
ExplosionSourceType explosionSourceType,
|
ExplosionSourceType explosionSourceType,
|
||||||
ParticleEffect smallParticle,
|
ParticleEffect smallParticle,
|
||||||
ParticleEffect largeParticle,
|
ParticleEffect largeParticle,
|
||||||
|
Pool<BlockParticleEffect> blockParticles,
|
||||||
RegistryEntry<SoundEvent> soundEvent,
|
RegistryEntry<SoundEvent> soundEvent,
|
||||||
InjectionCallback ci
|
InjectionCallback ci
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dev.zprestige.prestige.module.misc;
|
|||||||
|
|
||||||
import dev.zprestige.prestige.Module;
|
import dev.zprestige.prestige.Module;
|
||||||
import dev.zprestige.prestige.client.Prestige;
|
import dev.zprestige.prestige.client.Prestige;
|
||||||
|
import dev.zprestige.prestige.engine.EngineState;
|
||||||
import dev.zprestige.prestige.inject.Bootstrap;
|
import dev.zprestige.prestige.inject.Bootstrap;
|
||||||
import dev.zprestige.prestige.inject.NativeBridge;
|
import dev.zprestige.prestige.inject.NativeBridge;
|
||||||
import dev.zprestige.prestige.ui.mc.Overlay;
|
import dev.zprestige.prestige.ui.mc.Overlay;
|
||||||
@@ -22,9 +23,10 @@ public class SelfDestruct extends Module {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
if (Prestige.selfDestructed) return;
|
||||||
Prestige.selfDestructed = true;
|
Prestige.selfDestructed = true;
|
||||||
|
System.out.println("[Prestige] Self Destruct started");
|
||||||
Overlay.shutdown();
|
Overlay.shutdown();
|
||||||
mc.setScreen(null);
|
|
||||||
|
|
||||||
List<Module> modules = new ArrayList<>(Prestige.moduleManager.getModules());
|
List<Module> modules = new ArrayList<>(Prestige.moduleManager.getModules());
|
||||||
for (Module module : modules) {
|
for (Module module : modules) {
|
||||||
@@ -41,10 +43,13 @@ public class SelfDestruct extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Disposing this module now would null its own "Enabled" value while Module.toggle still has to read it.
|
// Disposing this module now would null its own "Enabled" value while Module.toggle still has to read it.
|
||||||
mc.execute(() -> dispose(modules));
|
EngineState.ready = false;
|
||||||
|
mc.send(() -> dispose(modules));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dispose(List<Module> modules) {
|
private void dispose(List<Module> modules) {
|
||||||
|
Prestige.eventBus.unregister(this);
|
||||||
|
Prestige.eventBus.unregister(Prestige.moduleManager);
|
||||||
for (Module module : modules) {
|
for (Module module : modules) {
|
||||||
try {
|
try {
|
||||||
module.dispose();
|
module.dispose();
|
||||||
@@ -56,15 +61,20 @@ public class SelfDestruct extends Module {
|
|||||||
Prestige.moduleManager.setModules(new ArrayList<>());
|
Prestige.moduleManager.setModules(new ArrayList<>());
|
||||||
|
|
||||||
if (Bootstrap.isInjected()) {
|
if (Bootstrap.isInjected()) {
|
||||||
// one more tick so no hook of the current tick is still being entered when the original classes come back
|
unhook();
|
||||||
mc.execute(() -> mc.execute(SelfDestruct::unhook));
|
} else {
|
||||||
|
System.out.println("[Prestige] Self Destruct finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void unhook() {
|
private static void unhook() {
|
||||||
try {
|
try {
|
||||||
Bootstrap.uninstall();
|
if (Bootstrap.uninstall() == 0) {
|
||||||
NativeBridge.release();
|
NativeBridge.release();
|
||||||
|
System.out.println("[Prestige] Self Destruct finished");
|
||||||
|
} else {
|
||||||
|
System.out.println("[Prestige] Self Destruct left hooks installed. Restart the game to clear them.");
|
||||||
|
}
|
||||||
} catch (UnsatisfiedLinkError ignored) {
|
} catch (UnsatisfiedLinkError ignored) {
|
||||||
// loader already gone
|
// loader already gone
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
@@ -73,6 +83,11 @@ public class SelfDestruct extends Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean requiresWorldOnEnable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPersistent() {
|
public boolean isPersistent() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class RenderCore {
|
|||||||
Render2D.currentBuffers = BUFFER_SOURCE;
|
Render2D.currentBuffers = BUFFER_SOURCE;
|
||||||
worldContext = context;
|
worldContext = context;
|
||||||
try {
|
try {
|
||||||
((RenderCallback)RenderEvents.WORLD_RENDER.invoker()).onRender(BUFFER_SOURCE, context);
|
RenderEvents.WORLD_RENDER.invoker().onRender(BUFFER_SOURCE, context);
|
||||||
BUFFER_SOURCE.flush(context);
|
BUFFER_SOURCE.flush(context);
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
LOGGER.error("World render failed", throwable);
|
LOGGER.error("World render failed", throwable);
|
||||||
@@ -102,9 +102,9 @@ public class RenderCore {
|
|||||||
Render2D.currentContext = context;
|
Render2D.currentContext = context;
|
||||||
Render2D.currentBuffers = BUFFER_SOURCE;
|
Render2D.currentBuffers = BUFFER_SOURCE;
|
||||||
try {
|
try {
|
||||||
((RenderCallback)RenderEvents.HUD_RENDER.invoker()).onRender(BUFFER_SOURCE, context);
|
RenderEvents.HUD_RENDER.invoker().onRender(BUFFER_SOURCE, context);
|
||||||
BUFFER_SOURCE.flush(context);
|
BUFFER_SOURCE.flush(context);
|
||||||
((RenderCallback)RenderEvents.AFTER_HUD_RENDER.invoker()).onRender(BUFFER_SOURCE, context);
|
RenderEvents.AFTER_HUD_RENDER.invoker().onRender(BUFFER_SOURCE, context);
|
||||||
BUFFER_SOURCE.flush(context);
|
BUFFER_SOURCE.flush(context);
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
LOGGER.error("HUD render failed", throwable);
|
LOGGER.error("HUD render failed", throwable);
|
||||||
|
|||||||
@@ -1,27 +1,34 @@
|
|||||||
package dev.zprestige.prestige.render;
|
package dev.zprestige.prestige.render;
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.event.Event;
|
import java.util.Objects;
|
||||||
import net.fabricmc.fabric.api.event.EventFactory;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public interface RenderEvents {
|
public interface RenderEvents {
|
||||||
Logger LOGGER = LoggerFactory.getLogger(RenderEvents.class);
|
Logger LOGGER = LoggerFactory.getLogger(RenderEvents.class);
|
||||||
Event HUD_RENDER = createEvent();
|
RenderEvent HUD_RENDER = new RenderEvent();
|
||||||
Event WORLD_RENDER = createEvent();
|
RenderEvent WORLD_RENDER = new RenderEvent();
|
||||||
Event AFTER_HUD_RENDER = createEvent();
|
RenderEvent AFTER_HUD_RENDER = new RenderEvent();
|
||||||
|
|
||||||
@NotNull
|
final class RenderEvent {
|
||||||
private static Event createEvent() {
|
private final CopyOnWriteArrayList<RenderCallback> listeners = new CopyOnWriteArrayList<>();
|
||||||
return EventFactory.createArrayBacked(RenderCallback.class, listeners -> (buffers, context) -> {
|
private final RenderCallback invoker = (buffers, context) -> {
|
||||||
for (RenderCallback listener : (listeners)) {
|
for (RenderCallback listener : listeners) {
|
||||||
try {
|
try {
|
||||||
listener.onRender(buffers, context);
|
listener.onRender(buffers, context);
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
LOGGER.error("Render listener failed", throwable);
|
LOGGER.error("Render listener failed", throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
public void register(RenderCallback listener) {
|
||||||
|
listeners.add(Objects.requireNonNull(listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderCallback invoker() {
|
||||||
|
return invoker;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
package dev.zprestige.prestige.ui.mc;
|
package dev.zprestige.prestige.ui.mc;
|
||||||
|
|
||||||
import dev.zprestige.prestige.ui.Input;
|
import dev.zprestige.prestige.ui.Input;
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
/** Opens the ported ClickGUI with the configured menu key (Input.menuKey, default RIGHT SHIFT); closing is handled by Input via the key mixin. */
|
/** Opens the ported ClickGUI with the configured menu key (Input.menuKey, default RIGHT SHIFT); closing is handled by Input via the key mixin. */
|
||||||
public final class Entry implements ClientModInitializer {
|
public final class Entry {
|
||||||
static boolean prev;
|
static boolean prev;
|
||||||
private static int cachedVk = -1, cachedGlfw;
|
private static int cachedVk = -1, cachedGlfw;
|
||||||
|
|
||||||
@Override public void onInitializeClient() {
|
public void initialize() {
|
||||||
try { Skid.run(); } catch (Throwable t) { System.out.println("[prestige-ui] splash " + t); }
|
try { Skid.run(); } catch (Throwable t) { System.out.println("[prestige-ui] splash " + t); }
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(this::tick);
|
|
||||||
System.out.println("[prestige-ui] loaded (menu key opens the menu)");
|
System.out.println("[prestige-ui] loaded (menu key opens the menu)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +27,7 @@ public final class Entry implements ClientModInitializer {
|
|||||||
|
|
||||||
static boolean bridgeTried;
|
static boolean bridgeTried;
|
||||||
|
|
||||||
void tick(MinecraftClient mc) {
|
public void tick(MinecraftClient mc) {
|
||||||
if (dev.zprestige.prestige.client.Prestige.selfDestructed) return;
|
if (dev.zprestige.prestige.client.Prestige.selfDestructed) return;
|
||||||
if (!bridgeTried) { bridgeTried = true; dev.zprestige.prestige.ui.bridge.Bridge.bind(); }
|
if (!bridgeTried) { bridgeTried = true; dev.zprestige.prestige.ui.bridge.Bridge.bind(); }
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
"description": "Prestige client with the imgui-java ClickGUI.",
|
"description": "Prestige client with the imgui-java ClickGUI.",
|
||||||
"environment": "client",
|
"environment": "client",
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"main": ["dev.zprestige.prestige.client.Prestige"],
|
"main": ["dev.zprestige.prestige.fabric.FabricEntrypoints"],
|
||||||
"client": ["dev.zprestige.prestige.ui.mc.Entry", "dev.zprestige.prestige.engine.EngineDev"]
|
"client": ["dev.zprestige.prestige.fabric.FabricEntrypoints", "dev.zprestige.prestige.engine.EngineDev"]
|
||||||
},
|
},
|
||||||
"mixins": ["prestige-engine.mixins.json", "prestige.mixins.json"],
|
"mixins": ["prestige-engine.mixins.json", "prestige.mixins.json"],
|
||||||
"accessWidener": "prestige-client.accesswidener",
|
"accessWidener": "prestige-client.accesswidener",
|
||||||
|
|||||||
Reference in new issue
Block a user