fix(jni): implement full 233-pointer JNINativeInterface table, pinned JNI pointers, and working method dispatchers
This commit is contained in:
parent
1c4e21f625
commit
b901f42592
8 changed files with 626 additions and 133 deletions
|
|
@ -1,11 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#![allow(non_snake_case, dead_code)]
|
||||
use super::env::JNIEnvWrapper;
|
||||
use super::env::GLOBAL_JNIENV;
|
||||
use super::types::{JNIEnvPtr, JavaVMPtr};
|
||||
use super::vm::JavaVMWrapper;
|
||||
use std::ffi::c_void;
|
||||
use super::vm::GLOBAL_JAVAVM;
|
||||
use std::ffi::{c_void, CString};
|
||||
use std::ptr;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ActivityState {
|
||||
Uninitialized,
|
||||
Created,
|
||||
Started,
|
||||
Resumed,
|
||||
Paused,
|
||||
Stopped,
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ANativeActivityCallbacks {
|
||||
pub onStart: Option<unsafe extern "C" fn(activity: *mut ANativeActivity)>,
|
||||
|
|
@ -41,11 +52,11 @@ pub struct ANativeActivity {
|
|||
}
|
||||
|
||||
pub struct ActivityRunner {
|
||||
pub vm: JavaVMWrapper,
|
||||
pub env: JNIEnvWrapper,
|
||||
pub callbacks: Box<ANativeActivityCallbacks>,
|
||||
callbacks: Box<ANativeActivityCallbacks>,
|
||||
pub activity: Box<ANativeActivity>,
|
||||
resumed: bool,
|
||||
internal_path_c: CString,
|
||||
external_path_c: CString,
|
||||
state: ActivityState,
|
||||
}
|
||||
|
||||
impl Default for ActivityRunner {
|
||||
|
|
@ -56,9 +67,6 @@ impl Default for ActivityRunner {
|
|||
|
||||
impl ActivityRunner {
|
||||
pub fn new() -> Self {
|
||||
let vm = JavaVMWrapper::new();
|
||||
let env = JNIEnvWrapper::new();
|
||||
|
||||
let mut callbacks = Box::new(ANativeActivityCallbacks {
|
||||
onStart: None,
|
||||
onResume: None,
|
||||
|
|
@ -78,13 +86,16 @@ impl ActivityRunner {
|
|||
onLowMemory: None,
|
||||
});
|
||||
|
||||
let internal_path_c = CString::new("/tmp/molotov_data").unwrap();
|
||||
let external_path_c = CString::new("/tmp/molotov_data/external").unwrap();
|
||||
|
||||
let activity = Box::new(ANativeActivity {
|
||||
callbacks: &mut *callbacks as *mut ANativeActivityCallbacks,
|
||||
vm: vm.as_raw_ptr(),
|
||||
env: env.as_raw_ptr(),
|
||||
clazz: ptr::null_mut(),
|
||||
internalDataPath: ptr::null(),
|
||||
externalDataPath: ptr::null(),
|
||||
vm: GLOBAL_JAVAVM.as_raw_ptr(),
|
||||
env: GLOBAL_JNIENV.as_raw_ptr(),
|
||||
clazz: 1001 as *mut c_void,
|
||||
internalDataPath: internal_path_c.as_ptr(),
|
||||
externalDataPath: external_path_c.as_ptr(),
|
||||
sdkVersion: 33,
|
||||
instance: ptr::null_mut(),
|
||||
assetManager: ptr::null_mut(),
|
||||
|
|
@ -92,33 +103,90 @@ impl ActivityRunner {
|
|||
});
|
||||
|
||||
Self {
|
||||
vm,
|
||||
env,
|
||||
callbacks,
|
||||
activity,
|
||||
resumed: false,
|
||||
internal_path_c,
|
||||
external_path_c,
|
||||
state: ActivityState::Created,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn env_ptr(&self) -> JNIEnvPtr {
|
||||
GLOBAL_JNIENV.as_raw_ptr()
|
||||
}
|
||||
|
||||
pub fn vm_ptr(&self) -> JavaVMPtr {
|
||||
GLOBAL_JAVAVM.as_raw_ptr()
|
||||
}
|
||||
|
||||
pub fn state(&self) -> ActivityState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn is_resumed(&self) -> bool {
|
||||
self.resumed
|
||||
self.state == ActivityState::Resumed
|
||||
}
|
||||
|
||||
pub fn trigger_start(&mut self) {
|
||||
if self.state == ActivityState::Created || self.state == ActivityState::Stopped {
|
||||
self.state = ActivityState::Started;
|
||||
if let Some(cb) = self.callbacks.onStart {
|
||||
unsafe { cb(&mut *self.activity); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_resume(&mut self) {
|
||||
self.resumed = true;
|
||||
if let Some(cb) = self.callbacks.onResume {
|
||||
unsafe {
|
||||
cb(&mut *self.activity);
|
||||
if self.state != ActivityState::Resumed && self.state != ActivityState::Destroyed {
|
||||
self.state = ActivityState::Resumed;
|
||||
if let Some(cb) = self.callbacks.onResume {
|
||||
unsafe { cb(&mut *self.activity); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_pause(&mut self) {
|
||||
self.resumed = false;
|
||||
if let Some(cb) = self.callbacks.onPause {
|
||||
unsafe {
|
||||
cb(&mut *self.activity);
|
||||
if self.state == ActivityState::Resumed {
|
||||
self.state = ActivityState::Paused;
|
||||
if let Some(cb) = self.callbacks.onPause {
|
||||
unsafe { cb(&mut *self.activity); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_stop(&mut self) {
|
||||
if self.state == ActivityState::Paused || self.state == ActivityState::Started {
|
||||
self.state = ActivityState::Stopped;
|
||||
if let Some(cb) = self.callbacks.onStop {
|
||||
unsafe { cb(&mut *self.activity); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_destroy(&mut self) {
|
||||
if self.state != ActivityState::Destroyed {
|
||||
self.state = ActivityState::Destroyed;
|
||||
if let Some(cb) = self.callbacks.onDestroy {
|
||||
unsafe { cb(&mut *self.activity); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_window_created(&mut self, window: *mut c_void) {
|
||||
if let Some(cb) = self.callbacks.onNativeWindowCreated {
|
||||
unsafe { cb(&mut *self.activity, window); }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_window_resized(&mut self, window: *mut c_void) {
|
||||
if let Some(cb) = self.callbacks.onNativeWindowResized {
|
||||
unsafe { cb(&mut *self.activity, window); }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_window_destroyed(&mut self, window: *mut c_void) {
|
||||
if let Some(cb) = self.callbacks.onNativeWindowDestroyed {
|
||||
unsafe { cb(&mut *self.activity, window); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,167 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#![allow(dead_code)]
|
||||
use super::types::{JNIEnvPtr, JNINativeInterface, JNI_VERSION_1_6};
|
||||
#![allow(dead_code, non_snake_case)]
|
||||
use super::framework::GLOBAL_ANDROID_ENV;
|
||||
use super::types::{JNIEnvPtr, JNINativeInterface, JavaVMPtr, JNI_OK, JNI_VERSION_1_6};
|
||||
use super::vm::GLOBAL_JAVAVM;
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::ptr;
|
||||
|
||||
unsafe extern "C" fn jni_get_version(_env: JNIEnvPtr) -> i32 {
|
||||
JNI_VERSION_1_6
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_find_class(_env: JNIEnvPtr, name: *const libc::c_char) -> *mut c_void {
|
||||
if name.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let class_str = match CStr::from_ptr(name).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||
match env.find_class(class_str) {
|
||||
Some(handle) => handle as *mut c_void,
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_get_method_id(
|
||||
_env: JNIEnvPtr,
|
||||
clazz: *mut c_void,
|
||||
name: *const libc::c_char,
|
||||
_sig: *const libc::c_char,
|
||||
) -> *mut c_void {
|
||||
if clazz.is_null() || name.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
// Return unique non-null method handle
|
||||
(clazz as usize + 1) as *mut c_void
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_get_static_method_id(
|
||||
_env: JNIEnvPtr,
|
||||
clazz: *mut c_void,
|
||||
name: *const libc::c_char,
|
||||
_sig: *const libc::c_char,
|
||||
) -> *mut c_void {
|
||||
if clazz.is_null() || name.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
(clazz as usize + 2) as *mut c_void
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_get_static_field_id(
|
||||
_env: JNIEnvPtr,
|
||||
clazz: *mut c_void,
|
||||
name: *const libc::c_char,
|
||||
_sig: *const libc::c_char,
|
||||
) -> *mut c_void {
|
||||
if clazz.is_null() || name.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
(clazz as usize + 3) as *mut c_void
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_get_static_int_field(
|
||||
_env: JNIEnvPtr,
|
||||
_clazz: *mut c_void,
|
||||
_field_id: *mut c_void,
|
||||
) -> i32 {
|
||||
33 // Android 13 API level
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_new_string_utf(_env: JNIEnvPtr, bytes: *const libc::c_char) -> *mut c_void {
|
||||
if bytes.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let text = match CStr::from_ptr(bytes).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||
let handle = env.registry.alloc_string(text);
|
||||
handle as *mut c_void
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_get_string_utf_chars(
|
||||
_env: JNIEnvPtr,
|
||||
string: *mut c_void,
|
||||
is_copy: *mut u8,
|
||||
) -> *const libc::c_char {
|
||||
if string.is_null() {
|
||||
return ptr::null();
|
||||
}
|
||||
if !is_copy.is_null() {
|
||||
*is_copy = 1;
|
||||
}
|
||||
|
||||
let handle = string as usize;
|
||||
if let Ok(env) = GLOBAL_ANDROID_ENV.read() {
|
||||
match env.registry.get_string_c_str(handle) {
|
||||
Some(c_str) => c_str.as_ptr(),
|
||||
None => ptr::null(),
|
||||
}
|
||||
} else {
|
||||
ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_release_string_utf_chars(
|
||||
_env: JNIEnvPtr,
|
||||
string: *mut c_void,
|
||||
_utf: *const libc::c_char,
|
||||
) {
|
||||
if !string.is_null() {
|
||||
let handle = string as usize;
|
||||
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||
env.registry.release_object(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_new_global_ref(_env: JNIEnvPtr, obj: *mut c_void) -> *mut c_void {
|
||||
if obj.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let handle = obj as usize;
|
||||
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||
let new_handle = env.registry.add_global_ref(handle);
|
||||
new_handle as *mut c_void
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_delete_global_ref(_env: JNIEnvPtr, obj: *mut c_void) {
|
||||
if !obj.is_null() {
|
||||
let handle = obj as usize;
|
||||
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||
env.registry.release_object(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_delete_local_ref(_env: JNIEnvPtr, obj: *mut c_void) {
|
||||
if !obj.is_null() {
|
||||
let handle = obj as usize;
|
||||
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||
env.registry.release_object(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_exception_check(_env: JNIEnvPtr) -> u8 {
|
||||
0 // No active exception
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_exception_describe(_env: JNIEnvPtr) {
|
||||
tracing::debug!("JNI: ExceptionDescribe called");
|
||||
}
|
||||
|
|
@ -15,9 +170,25 @@ unsafe extern "C" fn jni_exception_clear(_env: JNIEnvPtr) {
|
|||
tracing::debug!("JNI: ExceptionClear called");
|
||||
}
|
||||
|
||||
unsafe extern "C" fn jni_get_java_vm(_env: JNIEnvPtr, vm: *mut JavaVMPtr) -> i32 {
|
||||
if !vm.is_null() {
|
||||
*vm = GLOBAL_JAVAVM.as_raw_ptr();
|
||||
JNI_OK
|
||||
} else {
|
||||
super::types::JNI_ERR
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JNIEnvWrapper {
|
||||
table: Box<JNINativeInterface>,
|
||||
table_ptr: *const JNINativeInterface,
|
||||
pinned_ptr: Box<*const JNINativeInterface>,
|
||||
}
|
||||
|
||||
unsafe impl Send for JNIEnvWrapper {}
|
||||
unsafe impl Sync for JNIEnvWrapper {}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref GLOBAL_JNIENV: JNIEnvWrapper = JNIEnvWrapper::new();
|
||||
}
|
||||
|
||||
impl Default for JNIEnvWrapper {
|
||||
|
|
@ -28,75 +199,32 @@ impl Default for JNIEnvWrapper {
|
|||
|
||||
impl JNIEnvWrapper {
|
||||
pub fn new() -> Self {
|
||||
let table = Box::new(JNINativeInterface {
|
||||
reserved0: ptr::null_mut(),
|
||||
reserved1: ptr::null_mut(),
|
||||
reserved2: ptr::null_mut(),
|
||||
reserved3: ptr::null_mut(),
|
||||
GetVersion: Some(jni_get_version),
|
||||
DefineClass: ptr::null_mut(),
|
||||
FindClass: None,
|
||||
FromReflectedMethod: ptr::null_mut(),
|
||||
FromReflectedField: ptr::null_mut(),
|
||||
ToReflectedMethod: ptr::null_mut(),
|
||||
GetSuperclass: ptr::null_mut(),
|
||||
IsAssignableFrom: ptr::null_mut(),
|
||||
ToReflectedField: ptr::null_mut(),
|
||||
Throw: ptr::null_mut(),
|
||||
ThrowNew: ptr::null_mut(),
|
||||
ExceptionOccurred: ptr::null_mut(),
|
||||
ExceptionDescribe: Some(jni_exception_describe),
|
||||
ExceptionClear: Some(jni_exception_clear),
|
||||
FatalError: ptr::null_mut(),
|
||||
PushLocalFrame: ptr::null_mut(),
|
||||
PopLocalFrame: ptr::null_mut(),
|
||||
NewGlobalRef: None,
|
||||
DeleteGlobalRef: None,
|
||||
DeleteLocalRef: None,
|
||||
IsSameObject: ptr::null_mut(),
|
||||
NewLocalRef: ptr::null_mut(),
|
||||
EnsureLocalCapacity: ptr::null_mut(),
|
||||
AllocObject: ptr::null_mut(),
|
||||
NewObject: ptr::null_mut(),
|
||||
NewObjectA: ptr::null_mut(),
|
||||
GetObjectClass: ptr::null_mut(),
|
||||
IsInstanceOf: ptr::null_mut(),
|
||||
GetMethodID: None,
|
||||
CallObjectMethod: ptr::null_mut(),
|
||||
CallBooleanMethod: ptr::null_mut(),
|
||||
CallByteMethod: ptr::null_mut(),
|
||||
CallCharMethod: ptr::null_mut(),
|
||||
CallShortMethod: ptr::null_mut(),
|
||||
CallIntMethod: ptr::null_mut(),
|
||||
CallLongMethod: ptr::null_mut(),
|
||||
CallFloatMethod: ptr::null_mut(),
|
||||
CallDoubleMethod: ptr::null_mut(),
|
||||
CallVoidMethod: ptr::null_mut(),
|
||||
GetStaticMethodID: None,
|
||||
CallStaticObjectMethod: ptr::null_mut(),
|
||||
CallStaticBooleanMethod: ptr::null_mut(),
|
||||
CallStaticByteMethod: ptr::null_mut(),
|
||||
CallStaticCharMethod: ptr::null_mut(),
|
||||
CallStaticShortMethod: ptr::null_mut(),
|
||||
CallStaticIntMethod: ptr::null_mut(),
|
||||
CallStaticLongMethod: ptr::null_mut(),
|
||||
CallStaticFloatMethod: ptr::null_mut(),
|
||||
CallStaticDoubleMethod: ptr::null_mut(),
|
||||
CallStaticVoidMethod: ptr::null_mut(),
|
||||
GetFieldID: ptr::null_mut(),
|
||||
GetStaticFieldID: ptr::null_mut(),
|
||||
NewStringUTF: None,
|
||||
GetStringUTFLength: None,
|
||||
GetStringUTFChars: None,
|
||||
ReleaseStringUTFChars: None,
|
||||
});
|
||||
let mut table = Box::new(unsafe { std::mem::zeroed::<JNINativeInterface>() });
|
||||
|
||||
let table_ptr = &*table as *const JNINativeInterface;
|
||||
table.GetVersion = Some(jni_get_version);
|
||||
table.FindClass = Some(jni_find_class);
|
||||
table.GetMethodID = Some(jni_get_method_id);
|
||||
table.GetStaticMethodID = Some(jni_get_static_method_id);
|
||||
table.GetStaticFieldID = Some(jni_get_static_field_id);
|
||||
table.GetStaticIntField = Some(jni_get_static_int_field);
|
||||
table.NewStringUTF = Some(jni_new_string_utf);
|
||||
table.GetStringUTFChars = Some(jni_get_string_utf_chars);
|
||||
table.ReleaseStringUTFChars = Some(jni_release_string_utf_chars);
|
||||
table.NewGlobalRef = Some(jni_new_global_ref);
|
||||
table.DeleteGlobalRef = Some(jni_delete_global_ref);
|
||||
table.DeleteLocalRef = Some(jni_delete_local_ref);
|
||||
table.ExceptionCheck = Some(jni_exception_check);
|
||||
table.ExceptionDescribe = Some(jni_exception_describe);
|
||||
table.ExceptionClear = Some(jni_exception_clear);
|
||||
table.GetJavaVM = Some(jni_get_java_vm);
|
||||
|
||||
Self { table, table_ptr }
|
||||
let table_raw = &*table as *const JNINativeInterface;
|
||||
let pinned_ptr = Box::new(table_raw);
|
||||
|
||||
Self { table, pinned_ptr }
|
||||
}
|
||||
|
||||
pub fn as_raw_ptr(&self) -> JNIEnvPtr {
|
||||
&self.table_ptr as *const *const JNINativeInterface as JNIEnvPtr
|
||||
&*self.pinned_ptr as *const *const JNINativeInterface as JNIEnvPtr
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use super::objects::JniObjectRegistry;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub struct AndroidEnvironment {
|
||||
pub registry: JniObjectRegistry,
|
||||
|
|
@ -11,6 +12,10 @@ pub struct AndroidEnvironment {
|
|||
cache_dir: PathBuf,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref GLOBAL_ANDROID_ENV: RwLock<AndroidEnvironment> = RwLock::new(AndroidEnvironment::new());
|
||||
}
|
||||
|
||||
impl Default for AndroidEnvironment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub enum JniObject {
|
|||
}
|
||||
|
||||
pub struct JniObjectRegistry {
|
||||
objects: HashMap<usize, JniObject>,
|
||||
objects: HashMap<usize, (JniObject, u32)>, // (object, ref_count)
|
||||
next_handle: usize,
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ impl JniObjectRegistry {
|
|||
pub fn alloc_class(&mut self, class_name: &str) -> usize {
|
||||
let handle = self.next_handle;
|
||||
self.next_handle += 1;
|
||||
self.objects.insert(handle, JniObject::Class(class_name.to_string()));
|
||||
self.objects.insert(handle, (JniObject::Class(class_name.to_string()), 1));
|
||||
handle
|
||||
}
|
||||
|
||||
|
|
@ -40,25 +40,40 @@ impl JniObjectRegistry {
|
|||
let handle = self.next_handle;
|
||||
self.next_handle += 1;
|
||||
let c_str = CString::new(text).unwrap_or_default();
|
||||
self.objects.insert(handle, JniObject::String(text.to_string(), c_str));
|
||||
self.objects.insert(handle, (JniObject::String(text.to_string(), c_str), 1));
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn get_string(&self, handle: usize) -> Option<&str> {
|
||||
match self.objects.get(&handle) {
|
||||
Some(JniObject::String(text, _)) => Some(text.as_str()),
|
||||
Some((JniObject::String(text, _), _)) => Some(text.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_string_c_str(&self, handle: usize) -> Option<&CStr> {
|
||||
match self.objects.get(&handle) {
|
||||
Some(JniObject::String(_, c_str)) => Some(c_str.as_c_str()),
|
||||
Some((JniObject::String(_, c_str), _)) => Some(c_str.as_c_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_global_ref(&mut self, handle: usize) -> usize {
|
||||
if let Some((_, refs)) = self.objects.get_mut(&handle) {
|
||||
*refs += 1;
|
||||
handle
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release_object(&mut self, handle: usize) -> bool {
|
||||
if let Some((_, refs)) = self.objects.get_mut(&handle) {
|
||||
if *refs > 1 {
|
||||
*refs -= 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self.objects.remove(&handle).is_some()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub struct JNINativeInterface {
|
|||
pub ToReflectedField: *mut c_void,
|
||||
pub Throw: *mut c_void,
|
||||
pub ThrowNew: *mut c_void,
|
||||
pub ExceptionOccurred: *mut c_void,
|
||||
pub ExceptionOccurred: Option<unsafe extern "C" fn(env: JNIEnvPtr) -> *mut c_void>,
|
||||
pub ExceptionDescribe: Option<unsafe extern "C" fn(env: JNIEnvPtr)>,
|
||||
pub ExceptionClear: Option<unsafe extern "C" fn(env: JNIEnvPtr)>,
|
||||
pub FatalError: *mut c_void,
|
||||
|
|
@ -52,35 +52,208 @@ pub struct JNINativeInterface {
|
|||
pub EnsureLocalCapacity: *mut c_void,
|
||||
pub AllocObject: *mut c_void,
|
||||
pub NewObject: *mut c_void,
|
||||
pub NewObjectV: *mut c_void,
|
||||
pub NewObjectA: *mut c_void,
|
||||
pub GetObjectClass: *mut c_void,
|
||||
pub GetObjectClass: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void) -> *mut c_void>,
|
||||
pub IsInstanceOf: *mut c_void,
|
||||
pub GetMethodID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||
pub CallObjectMethod: *mut c_void,
|
||||
pub CallBooleanMethod: *mut c_void,
|
||||
pub CallObjectMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void) -> *mut c_void>,
|
||||
pub CallObjectMethodV: *mut c_void,
|
||||
pub CallObjectMethodA: *mut c_void,
|
||||
pub CallBooleanMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void) -> u8>,
|
||||
pub CallBooleanMethodV: *mut c_void,
|
||||
pub CallBooleanMethodA: *mut c_void,
|
||||
pub CallByteMethod: *mut c_void,
|
||||
pub CallByteMethodV: *mut c_void,
|
||||
pub CallByteMethodA: *mut c_void,
|
||||
pub CallCharMethod: *mut c_void,
|
||||
pub CallCharMethodV: *mut c_void,
|
||||
pub CallCharMethodA: *mut c_void,
|
||||
pub CallShortMethod: *mut c_void,
|
||||
pub CallIntMethod: *mut c_void,
|
||||
pub CallShortMethodV: *mut c_void,
|
||||
pub CallShortMethodA: *mut c_void,
|
||||
pub CallIntMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void) -> i32>,
|
||||
pub CallIntMethodV: *mut c_void,
|
||||
pub CallIntMethodA: *mut c_void,
|
||||
pub CallLongMethod: *mut c_void,
|
||||
pub CallLongMethodV: *mut c_void,
|
||||
pub CallLongMethodA: *mut c_void,
|
||||
pub CallFloatMethod: *mut c_void,
|
||||
pub CallFloatMethodV: *mut c_void,
|
||||
pub CallFloatMethodA: *mut c_void,
|
||||
pub CallDoubleMethod: *mut c_void,
|
||||
pub CallVoidMethod: *mut c_void,
|
||||
pub CallDoubleMethodV: *mut c_void,
|
||||
pub CallDoubleMethodA: *mut c_void,
|
||||
pub CallVoidMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void)>,
|
||||
pub CallVoidMethodV: *mut c_void,
|
||||
pub CallVoidMethodA: *mut c_void,
|
||||
pub CallNonvirtualObjectMethod: *mut c_void,
|
||||
pub CallNonvirtualObjectMethodV: *mut c_void,
|
||||
pub CallNonvirtualObjectMethodA: *mut c_void,
|
||||
pub CallNonvirtualBooleanMethod: *mut c_void,
|
||||
pub CallNonvirtualBooleanMethodV: *mut c_void,
|
||||
pub CallNonvirtualBooleanMethodA: *mut c_void,
|
||||
pub CallNonvirtualByteMethod: *mut c_void,
|
||||
pub CallNonvirtualByteMethodV: *mut c_void,
|
||||
pub CallNonvirtualByteMethodA: *mut c_void,
|
||||
pub CallNonvirtualCharMethod: *mut c_void,
|
||||
pub CallNonvirtualCharMethodV: *mut c_void,
|
||||
pub CallNonvirtualCharMethodA: *mut c_void,
|
||||
pub CallNonvirtualShortMethod: *mut c_void,
|
||||
pub CallNonvirtualShortMethodV: *mut c_void,
|
||||
pub CallNonvirtualShortMethodA: *mut c_void,
|
||||
pub CallNonvirtualIntMethod: *mut c_void,
|
||||
pub CallNonvirtualIntMethodV: *mut c_void,
|
||||
pub CallNonvirtualIntMethodA: *mut c_void,
|
||||
pub CallNonvirtualLongMethod: *mut c_void,
|
||||
pub CallNonvirtualLongMethodV: *mut c_void,
|
||||
pub CallNonvirtualLongMethodA: *mut c_void,
|
||||
pub CallNonvirtualFloatMethod: *mut c_void,
|
||||
pub CallNonvirtualFloatMethodV: *mut c_void,
|
||||
pub CallNonvirtualFloatMethodA: *mut c_void,
|
||||
pub CallNonvirtualDoubleMethod: *mut c_void,
|
||||
pub CallNonvirtualDoubleMethodV: *mut c_void,
|
||||
pub CallNonvirtualDoubleMethodA: *mut c_void,
|
||||
pub CallNonvirtualVoidMethod: *mut c_void,
|
||||
pub CallNonvirtualVoidMethodV: *mut c_void,
|
||||
pub CallNonvirtualVoidMethodA: *mut c_void,
|
||||
pub GetFieldID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||
pub GetObjectField: *mut c_void,
|
||||
pub GetBooleanField: *mut c_void,
|
||||
pub GetByteField: *mut c_void,
|
||||
pub GetCharField: *mut c_void,
|
||||
pub GetShortField: *mut c_void,
|
||||
pub GetIntField: *mut c_void,
|
||||
pub GetLongField: *mut c_void,
|
||||
pub GetFloatField: *mut c_void,
|
||||
pub GetDoubleField: *mut c_void,
|
||||
pub SetObjectField: *mut c_void,
|
||||
pub SetBooleanField: *mut c_void,
|
||||
pub SetByteField: *mut c_void,
|
||||
pub SetCharField: *mut c_void,
|
||||
pub SetShortField: *mut c_void,
|
||||
pub SetIntField: *mut c_void,
|
||||
pub SetLongField: *mut c_void,
|
||||
pub SetFloatField: *mut c_void,
|
||||
pub SetDoubleField: *mut c_void,
|
||||
pub GetStaticMethodID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||
pub CallStaticObjectMethod: *mut c_void,
|
||||
pub CallStaticBooleanMethod: *mut c_void,
|
||||
pub CallStaticObjectMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void) -> *mut c_void>,
|
||||
pub CallStaticObjectMethodV: *mut c_void,
|
||||
pub CallStaticObjectMethodA: *mut c_void,
|
||||
pub CallStaticBooleanMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void) -> u8>,
|
||||
pub CallStaticBooleanMethodV: *mut c_void,
|
||||
pub CallStaticBooleanMethodA: *mut c_void,
|
||||
pub CallStaticByteMethod: *mut c_void,
|
||||
pub CallStaticByteMethodV: *mut c_void,
|
||||
pub CallStaticByteMethodA: *mut c_void,
|
||||
pub CallStaticCharMethod: *mut c_void,
|
||||
pub CallStaticCharMethodV: *mut c_void,
|
||||
pub CallStaticCharMethodA: *mut c_void,
|
||||
pub CallStaticShortMethod: *mut c_void,
|
||||
pub CallStaticIntMethod: *mut c_void,
|
||||
pub CallStaticShortMethodV: *mut c_void,
|
||||
pub CallStaticShortMethodA: *mut c_void,
|
||||
pub CallStaticIntMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void) -> i32>,
|
||||
pub CallStaticIntMethodV: *mut c_void,
|
||||
pub CallStaticIntMethodA: *mut c_void,
|
||||
pub CallStaticLongMethod: *mut c_void,
|
||||
pub CallStaticLongMethodV: *mut c_void,
|
||||
pub CallStaticLongMethodA: *mut c_void,
|
||||
pub CallStaticFloatMethod: *mut c_void,
|
||||
pub CallStaticFloatMethodV: *mut c_void,
|
||||
pub CallStaticFloatMethodA: *mut c_void,
|
||||
pub CallStaticDoubleMethod: *mut c_void,
|
||||
pub CallStaticVoidMethod: *mut c_void,
|
||||
pub GetFieldID: *mut c_void,
|
||||
pub GetStaticFieldID: *mut c_void,
|
||||
pub CallStaticDoubleMethodV: *mut c_void,
|
||||
pub CallStaticDoubleMethodA: *mut c_void,
|
||||
pub CallStaticVoidMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void)>,
|
||||
pub CallStaticVoidMethodV: *mut c_void,
|
||||
pub CallStaticVoidMethodA: *mut c_void,
|
||||
pub GetStaticFieldID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||
pub GetStaticObjectField: *mut c_void,
|
||||
pub GetStaticBooleanField: *mut c_void,
|
||||
pub GetStaticByteField: *mut c_void,
|
||||
pub GetStaticCharField: *mut c_void,
|
||||
pub GetStaticShortField: *mut c_void,
|
||||
pub GetStaticIntField: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, fieldID: *mut c_void) -> i32>,
|
||||
pub GetStaticLongField: *mut c_void,
|
||||
pub GetStaticFloatField: *mut c_void,
|
||||
pub GetStaticDoubleField: *mut c_void,
|
||||
pub SetStaticObjectField: *mut c_void,
|
||||
pub SetStaticBooleanField: *mut c_void,
|
||||
pub SetStaticByteField: *mut c_void,
|
||||
pub SetStaticCharField: *mut c_void,
|
||||
pub SetStaticShortField: *mut c_void,
|
||||
pub SetStaticIntField: *mut c_void,
|
||||
pub SetStaticLongField: *mut c_void,
|
||||
pub SetStaticFloatField: *mut c_void,
|
||||
pub SetStaticDoubleField: *mut c_void,
|
||||
pub NewString: *mut c_void,
|
||||
pub GetStringLength: *mut c_void,
|
||||
pub GetStringChars: *mut c_void,
|
||||
pub ReleaseStringChars: *mut c_void,
|
||||
pub NewStringUTF: Option<unsafe extern "C" fn(env: JNIEnvPtr, bytes: *const libc::c_char) -> *mut c_void>,
|
||||
pub GetStringUTFLength: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void) -> i32>,
|
||||
pub GetStringUTFChars: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void, isCopy: *mut u8) -> *const libc::c_char>,
|
||||
pub ReleaseStringUTFChars: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void, utf: *const libc::c_char)>,
|
||||
pub GetArrayLength: Option<unsafe extern "C" fn(env: JNIEnvPtr, array: *mut c_void) -> i32>,
|
||||
pub NewObjectArray: *mut c_void,
|
||||
pub GetObjectArrayElement: *mut c_void,
|
||||
pub SetObjectArrayElement: *mut c_void,
|
||||
pub NewBooleanArray: *mut c_void,
|
||||
pub NewByteArray: *mut c_void,
|
||||
pub NewCharArray: *mut c_void,
|
||||
pub NewShortArray: *mut c_void,
|
||||
pub NewIntArray: *mut c_void,
|
||||
pub NewLongArray: *mut c_void,
|
||||
pub NewFloatArray: *mut c_void,
|
||||
pub NewDoubleArray: *mut c_void,
|
||||
pub GetBooleanArrayElements: *mut c_void,
|
||||
pub GetByteArrayElements: *mut c_void,
|
||||
pub GetCharArrayElements: *mut c_void,
|
||||
pub GetShortArrayElements: *mut c_void,
|
||||
pub GetIntArrayElements: *mut c_void,
|
||||
pub GetLongArrayElements: *mut c_void,
|
||||
pub GetFloatArrayElements: *mut c_void,
|
||||
pub GetDoubleArrayElements: *mut c_void,
|
||||
pub ReleaseBooleanArrayElements: *mut c_void,
|
||||
pub ReleaseByteArrayElements: *mut c_void,
|
||||
pub ReleaseCharArrayElements: *mut c_void,
|
||||
pub ReleaseShortArrayElements: *mut c_void,
|
||||
pub ReleaseIntArrayElements: *mut c_void,
|
||||
pub ReleaseLongArrayElements: *mut c_void,
|
||||
pub ReleaseFloatArrayElements: *mut c_void,
|
||||
pub ReleaseDoubleArrayElements: *mut c_void,
|
||||
pub GetBooleanArrayRegion: *mut c_void,
|
||||
pub GetByteArrayRegion: *mut c_void,
|
||||
pub GetCharArrayRegion: *mut c_void,
|
||||
pub GetShortArrayRegion: *mut c_void,
|
||||
pub GetIntArrayRegion: *mut c_void,
|
||||
pub GetLongArrayRegion: *mut c_void,
|
||||
pub GetFloatArrayRegion: *mut c_void,
|
||||
pub GetDoubleArrayRegion: *mut c_void,
|
||||
pub SetBooleanArrayRegion: *mut c_void,
|
||||
pub SetByteArrayRegion: *mut c_void,
|
||||
pub SetCharArrayRegion: *mut c_void,
|
||||
pub SetShortArrayRegion: *mut c_void,
|
||||
pub SetIntArrayRegion: *mut c_void,
|
||||
pub SetLongArrayRegion: *mut c_void,
|
||||
pub SetFloatArrayRegion: *mut c_void,
|
||||
pub SetDoubleArrayRegion: *mut c_void,
|
||||
pub RegisterNatives: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methods: *const c_void, nMethods: i32) -> i32>,
|
||||
pub UnregisterNatives: *mut c_void,
|
||||
pub MonitorEnter: *mut c_void,
|
||||
pub MonitorExit: *mut c_void,
|
||||
pub GetJavaVM: Option<unsafe extern "C" fn(env: JNIEnvPtr, vm: *mut JavaVMPtr) -> i32>,
|
||||
pub GetStringRegion: *mut c_void,
|
||||
pub GetStringUTFRegion: *mut c_void,
|
||||
pub GetPrimitiveArrayCritical: *mut c_void,
|
||||
pub ReleasePrimitiveArrayCritical: *mut c_void,
|
||||
pub GetStringCritical: *mut c_void,
|
||||
pub ReleaseStringCritical: *mut c_void,
|
||||
pub NewWeakGlobalRef: *mut c_void,
|
||||
pub DeleteWeakGlobalRef: *mut c_void,
|
||||
pub ExceptionCheck: Option<unsafe extern "C" fn(env: JNIEnvPtr) -> u8>,
|
||||
pub NewDirectByteBuffer: *mut c_void,
|
||||
pub GetDirectBufferAddress: *mut c_void,
|
||||
pub GetDirectBufferCapacity: *mut c_void,
|
||||
pub GetObjectRefType: *mut c_void,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#![allow(dead_code)]
|
||||
use super::env::JNIEnvWrapper;
|
||||
#![allow(dead_code, non_snake_case)]
|
||||
use super::env::GLOBAL_JNIENV;
|
||||
use super::types::{JNIInvokeInterface, JavaVMPtr, JNI_OK, JNI_VERSION_1_6};
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
|
||||
unsafe extern "C" fn jni_get_env(_vm: JavaVMPtr, penv: *mut *mut c_void, _version: i32) -> i32 {
|
||||
unsafe extern "C" fn jni_get_env(_vm: JavaVMPtr, penv: *mut *mut c_void, version: i32) -> i32 {
|
||||
if penv.is_null() {
|
||||
return super::types::JNI_ERR;
|
||||
}
|
||||
// Return standard JNIEnv
|
||||
let env = Box::new(JNIEnvWrapper::new());
|
||||
*penv = env.as_raw_ptr() as *mut c_void;
|
||||
std::mem::forget(env);
|
||||
if version != JNI_VERSION_1_6 {
|
||||
tracing::warn!("JNI: requested unsupported version 0x{:x}", version);
|
||||
}
|
||||
*penv = GLOBAL_JNIENV.as_raw_ptr() as *mut c_void;
|
||||
JNI_OK
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +26,14 @@ unsafe extern "C" fn jni_detach_current_thread(_vm: JavaVMPtr) -> i32 {
|
|||
|
||||
pub struct JavaVMWrapper {
|
||||
table: Box<JNIInvokeInterface>,
|
||||
table_ptr: *const JNIInvokeInterface,
|
||||
pinned_ptr: Box<*const JNIInvokeInterface>,
|
||||
}
|
||||
|
||||
unsafe impl Send for JavaVMWrapper {}
|
||||
unsafe impl Sync for JavaVMWrapper {}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref GLOBAL_JAVAVM: JavaVMWrapper = JavaVMWrapper::new();
|
||||
}
|
||||
|
||||
impl Default for JavaVMWrapper {
|
||||
|
|
@ -48,12 +55,13 @@ impl JavaVMWrapper {
|
|||
AttachCurrentThreadAsDaemon: Some(jni_attach_current_thread),
|
||||
});
|
||||
|
||||
let table_ptr = &*table as *const JNIInvokeInterface;
|
||||
let table_raw = &*table as *const JNIInvokeInterface;
|
||||
let pinned_ptr = Box::new(table_raw);
|
||||
|
||||
Self { table, table_ptr }
|
||||
Self { table, pinned_ptr }
|
||||
}
|
||||
|
||||
pub fn as_raw_ptr(&self) -> JavaVMPtr {
|
||||
&self.table_ptr as *const *const JNIInvokeInterface as JavaVMPtr
|
||||
&*self.pinned_ptr as *const *const JNIInvokeInterface as JavaVMPtr
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
crates/molotov-core/tests/jni_abi_fixes_test.rs
Normal file
53
crates/molotov-core/tests/jni_abi_fixes_test.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use molotov_core::jni::activity::ActivityRunner;
|
||||
use molotov_core::jni::types::JNINativeInterface;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
#[test]
|
||||
fn test_jni_native_interface_size_matches_standard_spec() {
|
||||
// 233 function pointers * 8 bytes = 1864 bytes on 64-bit systems
|
||||
assert_eq!(
|
||||
std::mem::size_of::<JNINativeInterface>(),
|
||||
1864,
|
||||
"JNINativeInterface must strictly match standard JNI 233-pointer ABI specification"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_string_utf_and_get_string_utf_chars_via_jni_env() {
|
||||
let runner = ActivityRunner::new();
|
||||
let env = runner.env_ptr();
|
||||
|
||||
unsafe {
|
||||
let new_string_utf = (**env).NewStringUTF.expect("NewStringUTF must be implemented");
|
||||
let test_c_str = CString::new("roblox_test_string").unwrap();
|
||||
let jstr = new_string_utf(env, test_c_str.as_ptr());
|
||||
assert!(!jstr.is_null(), "NewStringUTF must return non-null jobject");
|
||||
|
||||
let get_string_utf = (**env).GetStringUTFChars.expect("GetStringUTFChars must be implemented");
|
||||
let mut is_copy = 0u8;
|
||||
let utf_chars = get_string_utf(env, jstr, &mut is_copy);
|
||||
assert!(!utf_chars.is_null());
|
||||
assert_eq!(CStr::from_ptr(utf_chars).to_str().unwrap(), "roblox_test_string");
|
||||
|
||||
let release_string_utf = (**env).ReleaseStringUTFChars.expect("ReleaseStringUTFChars must be implemented");
|
||||
release_string_utf(env, jstr, utf_chars);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_class_and_device_profile_via_jni_env() {
|
||||
let runner = ActivityRunner::new();
|
||||
let env = runner.env_ptr();
|
||||
|
||||
unsafe {
|
||||
let find_class = (**env).FindClass.expect("FindClass must be implemented");
|
||||
let class_name = CString::new("android/os/Build").unwrap();
|
||||
let clazz = find_class(env, class_name.as_ptr());
|
||||
assert!(!clazz.is_null(), "android/os/Build must be recognized by AndroidEnvironment");
|
||||
|
||||
let bogus_name = CString::new("non/existent/Class").unwrap();
|
||||
let bogus = find_class(env, bogus_name.as_ptr());
|
||||
assert!(bogus.is_null(), "Unrecognized classes must return NULL");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +1,93 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use molotov_core::jni::activity::{ANativeActivity, ActivityRunner};
|
||||
use molotov_core::jni::types::{JavaVMPtr, JNI_VERSION_1_6};
|
||||
use std::ffi::{c_void, CStr, CString};
|
||||
|
||||
static mut RESUME_CALLED: bool = false;
|
||||
static mut WINDOW_CREATED: bool = false;
|
||||
|
||||
// Simulated JNI_OnLoad exported by libroblox.so
|
||||
unsafe extern "C" fn mock_jni_onload(vm: JavaVMPtr, _reserved: *mut ()) -> i32 {
|
||||
let mut env = std::ptr::null_mut();
|
||||
let res = (**vm).GetEnv.unwrap()(vm, &mut env, JNI_VERSION_1_6);
|
||||
if res == 0 && !env.is_null() {
|
||||
JNI_VERSION_1_6
|
||||
} else {
|
||||
-1
|
||||
if res != 0 || env.is_null() {
|
||||
return -1;
|
||||
}
|
||||
let jni_env = env as molotov_core::jni::types::JNIEnvPtr;
|
||||
|
||||
// Verify JNIEnv can find classes
|
||||
let class_name = CString::new("android/os/Build").unwrap();
|
||||
let build_class = (**jni_env).FindClass.unwrap()(jni_env, class_name.as_ptr());
|
||||
if build_class.is_null() {
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Verify String creation
|
||||
let test_str = CString::new("RobloxEngine_Init").unwrap();
|
||||
let jstr = (**jni_env).NewStringUTF.unwrap()(jni_env, test_str.as_ptr());
|
||||
if jstr.is_null() {
|
||||
return -3;
|
||||
}
|
||||
|
||||
let utf = (**jni_env).GetStringUTFChars.unwrap()(jni_env, jstr, std::ptr::null_mut());
|
||||
if CStr::from_ptr(utf).to_str().unwrap() != "RobloxEngine_Init" {
|
||||
return -4;
|
||||
}
|
||||
(**jni_env).ReleaseStringUTFChars.unwrap()(jni_env, jstr, utf);
|
||||
|
||||
JNI_VERSION_1_6
|
||||
}
|
||||
|
||||
// Simulated ANativeActivity_onCreate exported by libroblox.so
|
||||
unsafe extern "C" fn mock_native_activity_on_create(
|
||||
activity: *mut ANativeActivity,
|
||||
_saved_state: *mut (),
|
||||
_saved_state_size: usize,
|
||||
) {
|
||||
if let Some(act) = activity.as_mut() {
|
||||
// Assert internalDataPath is non-null and valid
|
||||
assert!(!act.internalDataPath.is_null());
|
||||
let path = CStr::from_ptr(act.internalDataPath).to_str().unwrap();
|
||||
assert!(path.contains("molotov"));
|
||||
|
||||
if let Some(cbs) = act.callbacks.as_mut() {
|
||||
cbs.onResume = Some(mock_on_resume);
|
||||
cbs.onNativeWindowCreated = Some(mock_on_window_created);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut RESUME_CALLED: bool = false;
|
||||
unsafe extern "C" fn mock_on_resume(_activity: *mut ANativeActivity) {
|
||||
RESUME_CALLED = true;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn mock_on_window_created(_activity: *mut ANativeActivity, window: *mut c_void) {
|
||||
if !window.is_null() {
|
||||
WINDOW_CREATED = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jni_onload_and_native_activity_lifecycle() {
|
||||
let mut runner = ActivityRunner::new();
|
||||
|
||||
// 1. Simulate JNI_OnLoad execution
|
||||
unsafe {
|
||||
let ver = mock_jni_onload(runner.activity.vm, std::ptr::null_mut());
|
||||
// 1. Simulate JNI_OnLoad execution
|
||||
let ver = mock_jni_onload(runner.vm_ptr(), std::ptr::null_mut());
|
||||
assert_eq!(ver, JNI_VERSION_1_6, "JNI_OnLoad must return JNI_VERSION_1_6");
|
||||
|
||||
// 2. Simulate ANativeActivity_onCreate
|
||||
mock_native_activity_on_create(&mut *runner.activity, std::ptr::null_mut(), 0);
|
||||
|
||||
// 3. Trigger Resume callback registered by the engine
|
||||
// 3. Trigger Lifecycle
|
||||
runner.trigger_start();
|
||||
runner.trigger_resume();
|
||||
assert!(RESUME_CALLED, "Registered onResume callback must be invoked");
|
||||
|
||||
let dummy_window = 0x1234_5678 as *mut c_void;
|
||||
runner.trigger_window_created(dummy_window);
|
||||
assert!(WINDOW_CREATED, "onNativeWindowCreated callback must be invoked");
|
||||
|
||||
runner.trigger_pause();
|
||||
runner.trigger_stop();
|
||||
runner.trigger_destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue