mod error;
pub use error::*;
pub use jni::{
AttachGuard, JNIEnv, JavaVM, NativeMethod,
errors::Error as JniError,
objects::{
GlobalRef, JBooleanArray, JByteArray, JClass, JObject, JObjectArray, JString, JValueGen,
ReleaseMode,
},
sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort, jsize},
};
use log::{debug, error, warn};
use parking_lot::ReentrantMutex;
use std::{
cell::RefCell,
collections::HashMap,
fmt::Debug,
hash::{DefaultHasher, Hash, Hasher},
str::FromStr,
sync::{Arc, LazyLock, OnceLock},
};
static HOOK_OBJECTS: LazyLock<
ReentrantMutex<
RefCell<
HashMap<
i32,
Arc<
dyn Fn(&mut JNIEnv<'_>, &JObject<'_>, &JObjectArray<'_>) -> Result<GlobalRef>
+ Send
+ Sync,
>,
>,
>,
>,
> = LazyLock::new(|| ReentrantMutex::new(RefCell::new(HashMap::new())));
static HOOK_OBJECTS_OTHER: LazyLock<ReentrantMutex<RefCell<HashMap<u64, i32>>>> =
LazyLock::new(|| ReentrantMutex::new(RefCell::new(HashMap::new())));
#[macro_export]
macro_rules! import {
() => {
use std::{
rc::Rc,
sync::{Arc, Mutex},
};
pub use $crate::Result;
use $crate::{
GlobalRef, JObject, impl_array, null_value, to_java_byte_array, to_java_object_array,
unbind_proxy_handler, vm_attach,
};
pub trait JObjRef {
fn java_ref(&self) -> Result<GlobalRef>;
}
pub trait JObjNew {
type Fields: Default;
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self>
where
Self: Sized;
fn null() -> Result<Self>
where
Self: Sized,
Self::Fields: Default,
{
let mut env = vm_attach()?;
Self::_new(null_value(&mut env)?.as_ref(), Default::default())
}
}
pub trait JType: JObjRef + JObjNew {
const CLASS: &'static str;
const OBJECT_SIG: &'static str;
const DIM: u8 = 0;
}
pub trait JProxy: JObjNew + JObjRef {
fn new(fields: Self::Fields) -> Result<Arc<Self>>;
fn release(&self) -> () {
if let Ok(ref r) = self.java_ref() {
unbind_proxy_handler(r)
}
}
}
impl<T: JObjRef> JObjRef for &T {
fn java_ref(&self) -> Result<GlobalRef> {
self.java_ref()
}
}
impl<T: JObjNew> JObjNew for &T {
type Fields = T::Fields;
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self> {
panic!("Reference types cannot be constructed.")
}
}
impl<T: JObjRef + JObjNew> JObjRef for Option<T>
where
<T as JObjNew>::Fields: Default,
{
fn java_ref(&self) -> Result<GlobalRef> {
match self {
None => T::null()?.java_ref(),
Some(v) => v.java_ref(),
}
}
}
impl<T: JObjRef + JObjNew> JObjNew for Option<T> {
type Fields = T::Fields;
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self> {
Ok(match this.is_null() {
true => None,
false => T::_new(this, fields).ok(),
})
}
}
impl<T: JType> JType for Arc<T> {
const CLASS: &'static str = T::CLASS;
const OBJECT_SIG: &'static str = T::OBJECT_SIG;
}
impl<T: JObjRef> JObjRef for Arc<T> {
fn java_ref(&self) -> Result<GlobalRef> {
self.as_ref().java_ref()
}
}
impl<T: JObjNew> JObjNew for Arc<T> {
type Fields = T::Fields;
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self> {
Ok(T::_new(this, fields)?.into())
}
}
impl<T: JType> JType for Rc<T> {
const CLASS: &'static str = T::CLASS;
const OBJECT_SIG: &'static str = T::OBJECT_SIG;
}
impl<T: JObjRef> JObjRef for Rc<T> {
fn java_ref(&self) -> Result<GlobalRef> {
self.as_ref().java_ref()
}
}
impl<T: JObjNew> JObjNew for Rc<T> {
type Fields = T::Fields;
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self> {
Ok(T::_new(this, fields)?.into())
}
}
impl<T: JType> JType for Mutex<T> {
const CLASS: &'static str = T::CLASS;
const OBJECT_SIG: &'static str = T::OBJECT_SIG;
}
impl<T: JObjNew> JObjNew for Mutex<T> {
type Fields = T::Fields;
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self> {
Ok(T::_new(this, fields)?.into())
}
}
impl<T: JObjRef> JObjRef for Mutex<T> {
fn java_ref(&self) -> Result<GlobalRef> {
self.java_ref()
}
}
impl_array!(u8, 1);
impl_array!(String, 1);
};
}
#[macro_export]
macro_rules! impl_array {
(String, $dim: expr) => {
impl JObjNew for &[String] {
type Fields = ();
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self> {
Ok(&[])
}
}
impl JObjRef for &[String] {
fn java_ref(&self) -> Result<GlobalRef> {
let mut env = vm_attach()?;
let arr = self
.iter()
.filter_map(|i| env.new_string(i).ok())
.collect::<Vec<_>>();
let sig = if Self::DIM <= 1 {
Self::CLASS.to_string()
} else {
"[".repeat((Self::DIM - 1) as _) + Self::OBJECT_SIG
};
let arr = to_java_object_array(&mut env, &arr, &sig)?;
Ok(env.new_global_ref(&arr)?)
}
}
impl JType for &[String] {
const CLASS: &'static str = <String as JType>::CLASS;
const OBJECT_SIG: &'static str = <String as JType>::OBJECT_SIG;
const DIM: u8 = $dim;
}
};
(u8, $dim:expr) => {
impl JObjNew for &[u8] {
type Fields = ();
fn _new(this: &GlobalRef, fields: Self::Fields) -> Result<Self> {
Ok(&[])
}
}
impl JObjRef for &[u8] {
fn java_ref(&self) -> Result<GlobalRef> {
let mut env = vm_attach()?;
let arr = self.iter().map(|i| *i as _).collect::<Vec<_>>();
let arr = to_java_byte_array(&mut env, &arr)?;
Ok(env.new_global_ref(&arr)?)
}
}
impl JType for &[u8] {
const CLASS: &'static str = "B";
const OBJECT_SIG: &'static str = "B";
const DIM: u8 = $dim;
}
};
}
pub fn android_vm<'a>() -> Result<&'static JavaVM> {
static JAVA_VM: LazyLock<Result<JavaVM>> = LazyLock::new(|| {
let ctx = ndk_context::android_context();
let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }?;
Ok(vm)
});
JAVA_VM.as_ref().map_err(|e| e.to_owned())
}
#[inline(always)]
pub fn vm_attach<'a>() -> Result<AttachGuard<'a>> {
let vm = android_vm()?;
vm.attach_current_thread().map_err(|e| e.into())
}
pub fn android_context<'a>() -> JObject<'a> {
let ctx = ndk_context::android_context();
unsafe { JObject::from_raw(ctx.context().cast()) }
}
pub fn new_proxy(interfaces: &[&str]) -> Result<GlobalRef> {
let class = load_rust_call_method_hook_class()?;
let mut env = vm_attach()?;
let obj = env.new_object(class, "()V", &[])?;
let faces = env.new_object_array(
interfaces.len() as jsize,
"java/lang/Class",
&JObject::null(),
)?;
for i in 0..interfaces.len() {
let class = env.new_string(interfaces[i])?;
let face = env
.call_static_method(
"java/lang/Class",
"forName",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[(&class).into()],
)?
.l()?;
env.set_object_array_element(&faces, i as jsize, &face)?;
}
let hash_code = env.call_method(&obj, "hashCode", "()I", &[])?.i()?;
let res = env.call_static_method(
"java/lang/reflect/Proxy",
"newProxyInstance",
"(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;",
&[
(&JObject::null()).into(),
(&faces).into(),
(&obj).into()
]
)?
.l()?;
let res = env.new_global_ref(&res)?;
let lock = HOOK_OBJECTS_OTHER.lock();
let mut hasher = DefaultHasher::new();
res.hash(&mut hasher);
lock.borrow_mut().insert(hasher.finish(), hash_code);
drop(lock);
Ok(res)
}
pub fn bind_proxy_handler(
proxy: &GlobalRef,
handler: impl Fn(&mut JNIEnv<'_>, &JObject<'_>, &JObjectArray<'_>) -> Result<GlobalRef>
+ Send
+ Sync
+ 'static,
) {
let hash_code = get_proxy_hash_code(proxy);
let lock = match HOOK_OBJECTS.try_lock() {
Some(lock) => lock,
None => {
error!("Can't bind proxy handler.");
return;
}
};
lock.borrow_mut().insert(hash_code, Arc::new(handler));
}
pub fn get_proxy_hash_code(proxy: &GlobalRef) -> i32 {
let mut hasher = DefaultHasher::new();
proxy.hash(&mut hasher);
let proxy_hash_code = hasher.finish();
let lock = HOOK_OBJECTS_OTHER.lock();
if let Some(code) = lock.borrow().get(&proxy_hash_code) {
return *code;
};
0
}
pub fn unbind_proxy_handler(proxy: &GlobalRef) {
let mut hasher = DefaultHasher::new();
proxy.hash(&mut hasher);
let proxy_hash_code = hasher.finish();
let lock = HOOK_OBJECTS_OTHER.lock();
if let Some(code) = lock.borrow().get(&proxy_hash_code) {
let lock = HOOK_OBJECTS.lock();
lock.borrow_mut().remove_entry(code);
debug!("Proxy `{}` is dropped.", proxy_hash_code);
};
}
pub trait ParseJObjectType<T: FromStr> {
fn parse(&self, env: &mut JNIEnv) -> Result<T>
where
<T as FromStr>::Err: Debug;
}
impl<T: FromStr> ParseJObjectType<T> for JObject<'_> {
fn parse(&self, env: &mut JNIEnv) -> Result<T>
where
<T as FromStr>::Err: Debug,
{
let s = env
.call_method(self, "toString", "()Ljava/lang/String;", &[])?
.l()?;
let s = env.get_string((&s).into())?;
let s = s.to_str()?;
Ok(s.parse()
.map_err(|_| DroidWrapError::FromStr(format!("Invalid value: {}", s)))?)
}
}
fn load_rust_call_method_hook_class<'a>() -> Result<&'a GlobalRef> {
#[cfg(target_os = "android")]
const BYTECODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/classes.dex"));
#[cfg(not(target_os = "android"))]
const BYTECODE: &[u8] = &[];
const LOADER_CLASS: &str = "dalvik/system/InMemoryDexClassLoader";
static INSTANCE: OnceLock<Result<GlobalRef>> = OnceLock::new();
INSTANCE.get_or_init(|| {
let mut env = vm_attach()?;
let byte_buffer = unsafe { env.new_direct_byte_buffer(BYTECODE.as_ptr() as *mut u8, BYTECODE.len()) }?;
let dex_class_loader = env
.new_object(
LOADER_CLASS,
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
&[
JValueGen::Object(&JObject::from(byte_buffer)),
JValueGen::Object(&JObject::null()),
],
)?;
let class = env.new_string("rust/CallMethodHook")?;
let class = env
.call_method(
&dex_class_loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[(&class).into()],
)?
.l()?;
let m = NativeMethod {
name: "invoke".into(),
sig: "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;".into(),
fn_ptr: rust_callback as *mut _,
};
env.register_native_methods(Into::<&JClass<'_>>::into(&class), &[m])?;
Ok(env.new_global_ref(&class)?)
}).as_ref().map_err(|e| e.clone())
}
unsafe extern "C" fn rust_callback<'a>(
mut env: JNIEnv<'a>,
this: JObject<'a>,
_: JObject<'a>,
method: JObject<'a>,
args: JObjectArray<'a>,
) -> JObject<'a> {
fn get_hash_code<'a>(env: &mut JNIEnv<'a>, this: &JObject<'a>) -> Result<i32> {
Ok::<_, DroidWrapError>(env.call_method(this, "hashCode", "()I", &[])?.i()?)
}
fn get_name<'a>(env: &mut JNIEnv<'a>, method: &JObject<'a>) -> Result<String> {
let name = env
.call_method(method, "getName", "()Ljava/lang/String;", &[])?
.l()?;
Ok::<_, DroidWrapError>(env.get_string((&name).into())?.to_str()?.to_string())
}
let hash_code = get_hash_code(&mut env, &this).unwrap_or_default();
match get_name(&mut env, &method).unwrap_or_default().as_str() {
"toString" => {
return match env.new_string(format!("Proxy@{:x}", hash_code).as_str()) {
Ok(r) => r.into(),
_ => JObject::null(),
};
}
"equals" | "hashCode" => {
return match env.call_method(
&method,
"invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
&[(&this).into(), (&args).into()],
) {
Ok(r) => match r.l() {
Ok(r) => r,
Err(e) => {
error!("{}", e);
return JObject::null();
}
},
Err(e) => {
error!("{}", e);
return JObject::null();
}
};
}
_ => (),
}
let lock = HOOK_OBJECTS.lock();
let func = if let Some(f) = lock.borrow().get(&hash_code) {
f.to_owned()
} else {
warn!(
"The method call has reached, but it appears that the proxy object has been dropped."
);
return JObject::null();
};
drop(lock);
match func(&mut env, &method, &args) {
Ok(ret) => match env.new_local_ref(ret.as_obj()) {
Ok(r) => return r,
Err(e) => {
error!("{}", e);
JObject::null()
}
},
Err(e) => {
error!("{}", e);
JObject::null()
}
}
}
pub fn to_vec<'a>(env: &mut JNIEnv<'a>, arr: &JObjectArray) -> Result<Vec<JObject<'a>>> {
let size = env.get_array_length(arr)?;
let mut arr2 = Vec::with_capacity(size as usize);
for i in 0..size {
arr2.push(env.get_object_array_element(arr, i)?);
}
Ok(arr2)
}
pub fn to_java_object_array<'a, O: AsRef<JObject<'a>>>(
env: &mut JNIEnv<'a>,
arr: &[O],
element_class: &str,
) -> Result<JObjectArray<'a>> {
let arr2 = env.new_object_array(arr.len() as _, element_class, JObject::null())?;
for (i, j) in arr.iter().enumerate() {
env.set_object_array_element(&arr2, i as _, j)?;
}
Ok(arr2)
}
pub fn to_java_byte_array<'a>(
env: &mut JNIEnv<'a>,
arr: &[jbyte],
) -> Result<JByteArray<'a>> {
let arr2 = env.new_byte_array(arr.len() as _)?;
env.set_byte_array_region(&arr2, 0, arr)?;
Ok(arr2)
}
pub fn null_value(env: &mut JNIEnv) -> Result<GlobalRef> {
let obj = JObject::null();
Ok(env.new_global_ref(&obj)?)
}
pub fn wrapper_bool_value(value: bool, env: &mut JNIEnv) -> Result<GlobalRef> {
let obj = env.new_object("java/lang/Boolean", "(Z)V", &[(value as jboolean).into()])?;
Ok(env.new_global_ref(&obj)?)
}
pub fn wrapper_integer_value(value: i32, env: &mut JNIEnv) -> Result<GlobalRef> {
let obj = env.new_object("java/lang/Integer", "(I)V", &[value.into()])?;
Ok(env.new_global_ref(&obj)?)
}
pub fn wrapper_long_value(value: i64, env: &mut JNIEnv) -> Result<GlobalRef> {
let obj = env.new_object("java/lang/Long", "(J)V", &[value.into()])?;
Ok(env.new_global_ref(&obj)?)
}
pub fn java_object_equals<'a, O: AsRef<JObject<'a>>>(a: O, b: O) -> Result<bool> {
let a = a.as_ref();
let b = b.as_ref();
if a.is_null() && a.is_null() {
return Ok(true);
}
if a.is_null() {
return Ok(false);
}
let mut env = vm_attach()?;
let res = env
.call_method(a, "equals", "(Ljava/lang/Object;)Z", &[b.into()])?
.z()?;
Ok(res)
}
pub fn java_object_to_string<'a, O: AsRef<JObject<'a>>>(obj: O) -> Result<String> {
let obj = obj.as_ref();
if obj.is_null() {
return Err(DroidWrapError::Jni(JniError::NullPtr("to_string")));
}
let mut env = vm_attach()?;
let s = env
.call_method(obj, "toString", "()Ljava/lang/String;", &[])?
.l()?;
let s = env.get_string((&s).into())?;
Ok(s.to_str()?.to_string())
}