From 3f9d62248a78901856d03a3095ccce3a176fbd85 Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Fri, 22 May 2026 16:42:08 +0200 Subject: [PATCH] rom reading on android (#937) --- src/device.rs | 20 ++++++++++++----- src/ui/android.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/device.rs b/src/device.rs index 53f5b7ea..ae4d4d30 100644 --- a/src/device.rs +++ b/src/device.rs @@ -16,7 +16,7 @@ const N64_EXTENSIONS_UNCOMPRESSED: [&str; 8] = use rand::{Rng, SeedableRng}; use crate::{cheats, netplay, retroachievements, ui}; -use std::{collections::HashMap, fs, io::Read}; +use std::{collections::HashMap, io::Read}; pub mod ai; pub mod cache; @@ -156,7 +156,10 @@ pub fn get_rom_contents(file_path: &std::path::PathBuf) -> Option> { .unwrap_or_default() .eq_ignore_ascii_case("zip") { - let zip_file = fs::File::open(file_path).unwrap(); + #[cfg(target_os = "android")] + let zip_file = ui::android::get_file_from_uri(file_path).unwrap(); + #[cfg(not(target_os = "android"))] + let zip_file = std::fs::File::open(file_path).unwrap(); let mut archive = zip::ZipArchive::new(zip_file).unwrap(); for i in 0..archive.len() { let mut file = archive.by_index(i).unwrap(); @@ -173,8 +176,12 @@ pub fn get_rom_contents(file_path: &std::path::PathBuf) -> Option> { .unwrap_or_default() .eq_ignore_ascii_case("7z") { + #[cfg(target_os = "android")] + let sevenz_file = ui::android::get_file_from_uri(file_path).unwrap(); + #[cfg(not(target_os = "android"))] + let sevenz_file = std::fs::File::open(file_path).unwrap(); let mut archive = - sevenz_rust2::ArchiveReader::open(file_path, sevenz_rust2::Password::empty()).unwrap(); + sevenz_rust2::ArchiveReader::new(sevenz_file, sevenz_rust2::Password::empty()).unwrap(); let mut found = false; archive @@ -197,8 +204,11 @@ pub fn get_rom_contents(file_path: &std::path::PathBuf) -> Option> { ) .expect("ok"); } else { - contents = fs::read(file_path) - .unwrap_or_else(|_| panic!("Could not read ROM file: {}", file_path.display())); + #[cfg(target_os = "android")] + let mut file = ui::android::get_file_from_uri(file_path).unwrap(); + #[cfg(not(target_os = "android"))] + let mut file = std::fs::File::open(file_path).unwrap(); + file.read_to_end(&mut contents).unwrap(); } if contents.is_empty() { diff --git a/src/ui/android.rs b/src/ui/android.rs index 7e479c0d..4ca22d15 100644 --- a/src/ui/android.rs +++ b/src/ui/android.rs @@ -1,9 +1,9 @@ +use crate::ui; use jni::objects::{JClass, JObject, JString}; use jni::refs::Global; use jni::sys::jint; use jni::{Env, EnvUnowned, JavaVM, bind_java_type}; - -use crate::ui; +use std::os::fd::FromRawFd; pub const REQUEST_SELECT_ROM: jint = 1; @@ -72,6 +72,7 @@ bind_java_type! { ParcelFileDescriptor => "android.os.ParcelFileDescriptor", methods { fn close() -> (), + fn detach_fd() -> jint, }, } @@ -371,6 +372,57 @@ fn rom_exists_on_jvm(env: &mut Env<'_>, path: String) -> jni::errors::Result<()> } } +pub fn get_file_from_uri(path: &std::path::PathBuf) -> Option { + let path = path.to_str().unwrap().into(); + + let vm = if let Ok(app) = ANDROID_APP.lock() + && let Some(app) = app.as_ref() + { + unsafe { JavaVM::from_raw(app.vm_as_ptr().cast()) } + } else { + eprintln!("Android app not initialized"); + return None; + }; + match vm.attach_current_thread(|env| get_file_from_uri_on_jvm(env, path)) { + Ok(file) => file, + Err(err) => { + eprintln!("JNI error while opening URI: {err:?}"); + return None; + } + } +} + +fn get_file_from_uri_on_jvm( + env: &mut Env<'_>, + path: String, +) -> jni::errors::Result> { + if let Ok(app) = ANDROID_APP.lock() + && let Some(app) = app.as_ref() + { + let raw_activity_global = app.activity_as_ptr() as jni::sys::jobject; + let activity = unsafe { env.as_cast_raw::>(&raw_activity_global)? }; + let path = JString::from_str(env, path)?; + let mode = JString::from_str(env, "r")?; + let uri = AndroidUri::parse(env, &path)?; + + let content_resolver = activity.as_ref().get_content_resolver(env)?; + let parcel_file_descriptor = content_resolver.open_file_descriptor(env, &uri, &mode); + if let Ok(descriptor) = parcel_file_descriptor + && !descriptor.is_null() + { + let owned_fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(descriptor.detach_fd(env)?) }; + let file = std::fs::File::from(owned_fd); + descriptor.close(env)?; + return Ok(Some(file)); + } else { + return Ok(None); + } + } else { + eprintln!("Android app not initialized"); + return Err(jni::errors::Error::UninitializedJavaVM); + } +} + #[unsafe(no_mangle)] pub extern "system" fn Java_io_github_gopher64_gopher64_SlintActivity_nativeOnActivityResult< 'caller,