jartodynamiclibrary 0.1.0

Pack a Forge/NeoForge/Fabric mod .jar into a dynamic library (.dll/.so/.dylib) that loads it into a running Minecraft JVM
use core::ffi::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};

use jni::objects::{JByteArray, JClass, JObject, JValue};
use jni::sys::{jbyteArray, jclass, jobject};
use jni::{JNIEnv, NativeMethod};

use crate::error::{InjectorError, Result};

const DEFINE_CLASS0_SIGNATURE: &str =
    "(Ljava/lang/ClassLoader;[BLjava/security/ProtectionDomain;)Ljava/lang/Class;";
const DEFINE_CLASS_SIGNATURE: &str =
    "(Ljava/lang/String;[BIILjava/security/ProtectionDomain;)Ljava/lang/Class;";

fn define_class(
    env: &mut JNIEnv,
    loader: &JObject,
    data: &JByteArray,
    protection_domain: &JObject,
) -> jni::errors::Result<jobject> {
    let length = env.get_array_length(data)?;
    let class = env.call_method(
        loader,
        "defineClass",
        DEFINE_CLASS_SIGNATURE,
        &[
            JValue::Object(&JObject::null()),
            JValue::from(data),
            JValue::Int(0),
            JValue::Int(length),
            JValue::from(protection_domain),
        ],
    )?;
    Ok(class.l()?.into_raw())
}

unsafe extern "system" fn define_class0(
    raw_env: *mut jni::sys::JNIEnv,
    _injector_class: jclass,
    loader: jobject,
    data: jbyteArray,
    protection_domain: jobject,
) -> jobject {
    let Ok(mut env) = JNIEnv::from_raw(raw_env) else {
        return core::ptr::null_mut();
    };

    let outcome = catch_unwind(AssertUnwindSafe(|| {
        let loader = JObject::from_raw(loader);
        let data = JByteArray::from_raw(data);
        let protection_domain = JObject::from_raw(protection_domain);
        define_class(&mut env, &loader, &data, &protection_domain)
    }));

    match outcome {
        Ok(Ok(class)) => class,
        Ok(Err(_)) | Err(_) => {
            if !env.exception_check().unwrap_or(false) {
                let _ = env.throw_new("java/lang/InternalError", "native defineClass0 failed");
            }
            core::ptr::null_mut()
        }
    }
}

pub fn register(env: &mut JNIEnv, injector_class: &JClass) -> Result<()> {
    let methods = [NativeMethod {
        name: "defineClass0".into(),
        sig: DEFINE_CLASS0_SIGNATURE.into(),
        fn_ptr: define_class0 as *mut c_void,
    }];
    env.register_native_methods(injector_class, &methods)
        .map_err(|_| {
            let _ = env.exception_clear();
            InjectorError::new("Failed to register native methods")
        })
}