batbox_android/
lib.rs

1#![cfg(target_os = "android")]
2
3use std::path::PathBuf;
4
5pub use android_activity as activity;
6pub type App = android_activity::AndroidApp;
7
8static APP: std::sync::OnceLock<App> = std::sync::OnceLock::new();
9
10pub fn init(app: App) {
11    APP.set(app).unwrap();
12}
13
14pub fn app() -> &'static App {
15    APP.get().expect("Android app was not set")
16}
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
19pub enum FileMode {
20    FileSystem,
21    Assets,
22}
23
24static FILE_MODE: std::sync::OnceLock<std::sync::Mutex<FileMode>> = std::sync::OnceLock::new();
25
26fn file_mode_mutex() -> &'static std::sync::Mutex<FileMode> {
27    FILE_MODE.get_or_init(|| std::sync::Mutex::new(FileMode::Assets))
28}
29
30pub fn set_file_mode(file_mode: FileMode) {
31    *file_mode_mutex().lock().unwrap() = file_mode;
32}
33
34pub fn file_mode() -> FileMode {
35    *file_mode_mutex().lock().unwrap()
36}
37
38mod make_it_work {
39    use jni::{JNIEnv, JavaVM};
40    use std::ffi::c_void;
41
42    #[no_mangle]
43    pub extern "C" fn JNI_OnLoad(
44        vm: jni::JavaVM,
45        res: *mut std::os::raw::c_void,
46    ) -> jni::sys::jint {
47        // Wait for debugger to connect OMEGALUL
48        std::thread::sleep(std::time::Duration::from_secs(3));
49        let env = vm.get_env().unwrap();
50        let vm = vm.get_java_vm_pointer() as *mut c_void;
51        unsafe {
52            ndk_context::initialize_android_context(vm, res);
53        }
54        jni::JNIVersion::V6.into()
55    }
56
57    #[no_mangle]
58    pub unsafe extern "C" fn __cxa_pure_virtual() {
59        loop {}
60    }
61}
62
63pub fn copy_assets_to_filesystem(
64    asset_dirs: impl IntoIterator<Item = impl AsRef<std::path::Path>>,
65    destination: impl AsRef<std::path::Path>,
66) {
67    copy(asset_dirs, destination).unwrap();
68
69    use std::{
70        error::Error,
71        ffi::{CStr, CString},
72        fs, io,
73        path::{Path, PathBuf},
74    };
75
76    use jni::{
77        objects::{JObject, JObjectArray, JValueGen},
78        JNIEnv, JavaVM,
79    };
80    use ndk::asset::Asset;
81
82    pub type CopyResult<T> = Result<T, Box<dyn Error>>;
83
84    pub fn copy(
85        asset_dirs: impl IntoIterator<Item = impl AsRef<Path>>,
86        destination: impl AsRef<Path>,
87    ) -> CopyResult<()> {
88        // Create a VM for executing Java calls
89        let ctx = ndk_context::android_context();
90        let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }?;
91        let mut env = vm.attach_current_thread()?;
92
93        // Query the Asset Manager
94        let asset_manager = env
95            .call_method(
96                unsafe { JObject::from_raw(ctx.context().cast()) },
97                "getAssets",
98                "()Landroid/content/res/AssetManager;",
99                &[],
100            )?
101            .l()?;
102
103        // Copy assets
104        for asset_dir in asset_dirs {
105            copy_recursively(
106                &mut *env,
107                &asset_manager,
108                asset_dir.as_ref().to_path_buf(),
109                destination.as_ref().join(asset_dir),
110            )?;
111        }
112
113        Ok(())
114    }
115
116    fn copy_recursively(
117        env: &mut JNIEnv,
118        asset_manager: &JObject,
119        asset_dir: PathBuf,
120        destination_dir: PathBuf,
121    ) -> CopyResult<()> {
122        for asset_filename in list(env, &asset_manager, &asset_dir)? {
123            let asset_path = asset_dir.join(&asset_filename);
124            if let Some(asset) = open_asset(&CString::new(asset_path.to_string_lossy().as_ref())?) {
125                copy_asset(asset, asset_filename, &destination_dir)?;
126            } else {
127                copy_recursively(
128                    env,
129                    &asset_manager,
130                    asset_path,
131                    destination_dir.join(asset_filename),
132                )?;
133            }
134        }
135        Ok(())
136    }
137
138    fn list(
139        env: &mut JNIEnv,
140        asset_manager: &JObject,
141        asset_dir: &Path,
142    ) -> CopyResult<Vec<String>> {
143        let asset_array: JObjectArray = env
144            .call_method(
145                asset_manager,
146                "list",
147                "(Ljava/lang/String;)[Ljava/lang/String;",
148                &[JValueGen::Object(
149                    &env.new_string(asset_dir.to_string_lossy())?.into(),
150                )],
151            )?
152            .l()?
153            .into();
154
155        let mut assets = Vec::new();
156        for index in 0..env.get_array_length(&asset_array)? {
157            let elem = env.get_object_array_element(&asset_array, index)?.into();
158            let asset: String = env.get_string(&elem)?.into();
159            assets.push(asset);
160        }
161
162        Ok(assets)
163    }
164
165    fn open_asset(asset_path: &CStr) -> Option<Asset> {
166        app().asset_manager().open(asset_path)
167    }
168
169    fn copy_asset(
170        mut asset: Asset,
171        filename: impl AsRef<Path>,
172        destination_dir: impl AsRef<Path>,
173    ) -> CopyResult<()> {
174        fs::create_dir_all(destination_dir.as_ref())?;
175        let mut file = fs::File::options()
176            .create(true)
177            .write(true)
178            .open(destination_dir.as_ref().join(filename))?;
179
180        io::copy(&mut asset, &mut file)?;
181        Ok(())
182    }
183}