mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 01:25:20 +02:00
Run rom android (#956)
* run rom android * more * more * more * more * more * more * more * more
This commit is contained in:
@@ -27,6 +27,7 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs["debug"]
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package io.github.gopher64.gopher64
|
||||
|
||||
import android.content.Intent
|
||||
import org.libsdl.app.SDLActivity
|
||||
|
||||
class N64Activity : SDLActivity() {
|
||||
companion object {
|
||||
const val CONFIGURE_INPUT_PROFILE = 2
|
||||
const val RUN_ROM = 3
|
||||
}
|
||||
|
||||
override fun getLibraries(): Array<String> = arrayOf(
|
||||
@@ -35,6 +37,36 @@ class N64Activity : SDLActivity() {
|
||||
}
|
||||
setResult(RESULT_OK) // so that the profiles are updated in the GUI
|
||||
return args.toTypedArray()
|
||||
} else if (request_code == RUN_ROM) {
|
||||
val file_path = intent.getStringExtra("file_path") ?: return super.getArguments()
|
||||
val overclock = intent.getBooleanExtra("overclock", false)
|
||||
val disable_expansion_pak = intent.getBooleanExtra("disable_expansion_pak", false)
|
||||
val args = mutableListOf(
|
||||
file_path,
|
||||
"--fullscreen",
|
||||
"--overclock",
|
||||
overclock.toString(),
|
||||
"--disable-expansion-pak",
|
||||
disable_expansion_pak.toString())
|
||||
|
||||
val dataIntent = Intent()
|
||||
|
||||
val netplay_peer_addr = intent.getStringExtra("netplay_peer_addr")
|
||||
val cheats = intent.getStringExtra("cheats")
|
||||
if (netplay_peer_addr != null && cheats != null) {
|
||||
args.add("--netplay-peer-addr")
|
||||
args.add(netplay_peer_addr)
|
||||
args.add("--netplay-player-number")
|
||||
args.add(intent.getIntExtra("netplay_player_number", 4).toString())
|
||||
args.add("--cheats")
|
||||
args.add(cheats)
|
||||
dataIntent.putExtra("cheats_path", cheats)
|
||||
}
|
||||
|
||||
dataIntent.putExtra("file_path", file_path)
|
||||
|
||||
setResult(RESULT_OK, dataIntent)
|
||||
return args.toTypedArray()
|
||||
} else {
|
||||
return super.getArguments()
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) {
|
||||
callback.paused = !callback.paused;
|
||||
}
|
||||
break;
|
||||
case SDL_SCANCODE_AC_BACK:
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
if (gfx_info.fullscreen) {
|
||||
SDL_zero(user_event);
|
||||
|
||||
+81
-3
@@ -14,7 +14,86 @@ use std::io::Error;
|
||||
#[cfg(target_os = "android")]
|
||||
use ui::android;
|
||||
|
||||
pub async fn run() -> std::io::Result<()> {
|
||||
/// N64 emulator
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version=env!("GIT_DESCRIBE"), about, long_about = None, arg_required_else_help = if cfg!(feature = "gui") { false } else { true })]
|
||||
pub struct Args {
|
||||
pub game: Option<String>,
|
||||
#[arg(short, long)]
|
||||
pub fullscreen: bool,
|
||||
#[arg(long)]
|
||||
pub overclock: Option<bool>,
|
||||
#[arg(long)]
|
||||
pub disable_expansion_pak: Option<bool>,
|
||||
#[arg(long, value_name = "CHEATS_FILE", hide = true)]
|
||||
pub cheats: Option<String>,
|
||||
#[arg(long, value_name = "NETPLAY_PEER_ADDR", hide = true)]
|
||||
pub netplay_peer_addr: Option<String>,
|
||||
#[arg(long, value_name = "NETPLAY_PLAYER_NUMBER", hide = true)]
|
||||
pub netplay_player_number: Option<u8>,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PROFILE_NAME",
|
||||
help = "Create a new input profile (keyboard/gamepad mappings)"
|
||||
)]
|
||||
pub configure_input_profile: Option<String>,
|
||||
#[arg(long, help = "Use DirectInput when configuring a new input profile")]
|
||||
pub use_dinput: bool,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "DEADZONE_PERCENTAGE",
|
||||
help = "Used along with --configure-input-profile to set the deadzone for analog sticks"
|
||||
)]
|
||||
pub deadzone: Option<i32>,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PROFILE_NAME",
|
||||
help = "Must also specify --port. Used to bind a previously created profile to a port"
|
||||
)]
|
||||
pub bind_input_profile: Option<String>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Lists connected controllers which can be used in --assign-controller"
|
||||
)]
|
||||
pub list_controllers: bool,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "CONTROLLER_NUMBER",
|
||||
help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port"
|
||||
)]
|
||||
pub assign_controller: Option<i32>,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PORT",
|
||||
help = "Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller"
|
||||
)]
|
||||
pub port: Option<usize>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Clear all input profile bindings and controller assignments"
|
||||
)]
|
||||
pub clear_input_bindings: bool,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "SLOT",
|
||||
help = "Load savestate from slot 0-9 when starting the game"
|
||||
)]
|
||||
pub load_state: Option<u32>,
|
||||
#[arg(
|
||||
long = "ra-username",
|
||||
value_name = "USERNAME",
|
||||
help = "Username for RetroAchievements"
|
||||
)]
|
||||
pub ra_username: Option<String>,
|
||||
#[arg(
|
||||
long = "ra-password",
|
||||
value_name = "PASSWORD",
|
||||
help = "Password for RetroAchievements"
|
||||
)]
|
||||
pub ra_password: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
let dirs = ui::get_dirs();
|
||||
|
||||
std::fs::create_dir_all(&dirs.config_dir)?;
|
||||
@@ -24,7 +103,6 @@ pub async fn run() -> std::io::Result<()> {
|
||||
|
||||
ui::sdl_hints();
|
||||
|
||||
let args = ui::Args::parse();
|
||||
if let Some(game) = args.game {
|
||||
let file_path = std::path::Path::new(&game).to_path_buf();
|
||||
let Some(rom_contents) = device::get_rom_contents(&file_path) else {
|
||||
@@ -167,7 +245,7 @@ pub async fn run() -> std::io::Result<()> {
|
||||
if let Some(shutdown_tx) = &shutdown_tx {
|
||||
ui::usb::close(shutdown_tx, usb_handle).await;
|
||||
}
|
||||
} else if std::env::args().count() > 1 {
|
||||
} else if arg_count > 1 {
|
||||
let mut config = ui::config::Config::new();
|
||||
|
||||
if let Some(profile) = args.configure_input_profile {
|
||||
|
||||
+4
-1
@@ -3,7 +3,10 @@
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[tokio::main(worker_threads = 4)]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
gopher64::run().await
|
||||
let args = gopher64::Args::parse();
|
||||
gopher64::run(args, std::env::args().count()).await
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ pub mod usb;
|
||||
pub mod video;
|
||||
#[cfg(all(feature = "gui", not(target_os = "android")))]
|
||||
pub mod vru;
|
||||
use clap::Parser;
|
||||
|
||||
pub static WEB_CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
|
||||
reqwest::Client::builder()
|
||||
@@ -29,85 +28,6 @@ pub static WEB_CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLoc
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
/// N64 emulator
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version=env!("GIT_DESCRIBE"), about, long_about = None, arg_required_else_help = if cfg!(feature = "gui") { false } else { true })]
|
||||
pub struct Args {
|
||||
pub game: Option<String>,
|
||||
#[arg(short, long)]
|
||||
pub fullscreen: bool,
|
||||
#[arg(long)]
|
||||
pub overclock: Option<bool>,
|
||||
#[arg(long)]
|
||||
pub disable_expansion_pak: Option<bool>,
|
||||
#[arg(long, value_name = "CHEATS_FILE", hide = true)]
|
||||
pub cheats: Option<String>,
|
||||
#[arg(long, value_name = "NETPLAY_PEER_ADDR", hide = true)]
|
||||
pub netplay_peer_addr: Option<String>,
|
||||
#[arg(long, value_name = "NETPLAY_PLAYER_NUMBER", hide = true)]
|
||||
pub netplay_player_number: Option<u8>,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PROFILE_NAME",
|
||||
help = "Create a new input profile (keyboard/gamepad mappings)"
|
||||
)]
|
||||
pub configure_input_profile: Option<String>,
|
||||
#[arg(long, help = "Use DirectInput when configuring a new input profile")]
|
||||
pub use_dinput: bool,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "DEADZONE_PERCENTAGE",
|
||||
help = "Used along with --configure-input-profile to set the deadzone for analog sticks"
|
||||
)]
|
||||
pub deadzone: Option<i32>,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PROFILE_NAME",
|
||||
help = "Must also specify --port. Used to bind a previously created profile to a port"
|
||||
)]
|
||||
pub bind_input_profile: Option<String>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Lists connected controllers which can be used in --assign-controller"
|
||||
)]
|
||||
pub list_controllers: bool,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "CONTROLLER_NUMBER",
|
||||
help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port"
|
||||
)]
|
||||
pub assign_controller: Option<i32>,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PORT",
|
||||
help = "Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller"
|
||||
)]
|
||||
pub port: Option<usize>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Clear all input profile bindings and controller assignments"
|
||||
)]
|
||||
pub clear_input_bindings: bool,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "SLOT",
|
||||
help = "Load savestate from slot 0-9 when starting the game"
|
||||
)]
|
||||
pub load_state: Option<u32>,
|
||||
#[arg(
|
||||
long = "ra-username",
|
||||
value_name = "USERNAME",
|
||||
help = "Username for RetroAchievements"
|
||||
)]
|
||||
pub ra_username: Option<String>,
|
||||
#[arg(
|
||||
long = "ra-password",
|
||||
value_name = "PASSWORD",
|
||||
help = "Password for RetroAchievements"
|
||||
)]
|
||||
pub ra_password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Dirs {
|
||||
pub config_dir: std::path::PathBuf,
|
||||
|
||||
+134
-22
@@ -1,3 +1,5 @@
|
||||
use crate::Args;
|
||||
use crate::run;
|
||||
use crate::ui;
|
||||
use clap::Parser;
|
||||
use jni::objects::{JClass, JObject, JString};
|
||||
@@ -8,6 +10,7 @@ use std::os::fd::FromRawFd;
|
||||
|
||||
const REQUEST_SELECT_ROM: jint = 1;
|
||||
const CONFIGURE_INPUT_PROFILE: jint = 2;
|
||||
const RUN_ROM: jint = 3;
|
||||
|
||||
pub static ANDROID_APP: std::sync::Mutex<Option<slint::android::AndroidApp>> =
|
||||
std::sync::Mutex::new(None);
|
||||
@@ -82,6 +85,7 @@ bind_java_type! {
|
||||
sig = (extra: JString, value: jint) -> AndroidIntent,
|
||||
name = "putExtra",
|
||||
},
|
||||
fn get_string_extra(name: JString) -> JString,
|
||||
fn set_class_name(package_name: JString, class_name: JString) -> AndroidIntent,
|
||||
},
|
||||
}
|
||||
@@ -111,6 +115,7 @@ bind_java_type! {
|
||||
methods {
|
||||
static fn parse(uri_string: JString) -> AndroidUri,
|
||||
fn to_string() -> JString,
|
||||
static fn decode(path: JString) -> JString,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -163,20 +168,11 @@ pub async extern "C" fn gopher64_sdl_main(
|
||||
argc: std::ffi::c_int,
|
||||
argv: *mut *mut std::ffi::c_char,
|
||||
) -> std::ffi::c_int {
|
||||
ui::sdl_hints();
|
||||
|
||||
let raw = argv_to_strings(argc, argv);
|
||||
let args = ui::Args::try_parse_from(raw).unwrap();
|
||||
if let Some(profile) = args.configure_input_profile {
|
||||
let mut config = ui::config::Config::new();
|
||||
ui::input::configure_input_profile(
|
||||
&mut config,
|
||||
profile,
|
||||
args.use_dinput,
|
||||
args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT),
|
||||
);
|
||||
|
||||
ui::sdl_close();
|
||||
let args = Args::try_parse_from(raw).unwrap();
|
||||
if let Err(err) = run(args, argc as usize).await {
|
||||
eprintln!("Error running game: {err:?}");
|
||||
return 1;
|
||||
}
|
||||
0
|
||||
}
|
||||
@@ -188,14 +184,14 @@ pub fn spawn_configure_input_profile(
|
||||
) {
|
||||
if let Some(vm) = get_vm() {
|
||||
if let Err(err) = vm.attach_current_thread(|env| {
|
||||
start_n64_activity_on_jvm(env, profile_name.to_string(), dinput, deadzone)
|
||||
start_configure_input_profile_on_jvm(env, profile_name.to_string(), dinput, deadzone)
|
||||
}) {
|
||||
eprintln!("JNI error while starting N64Activity: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_n64_activity_on_jvm(
|
||||
fn start_configure_input_profile_on_jvm(
|
||||
env: &mut Env<'_>,
|
||||
profile_name: String,
|
||||
dinput: bool,
|
||||
@@ -232,15 +228,82 @@ fn start_n64_activity_on_jvm(
|
||||
}
|
||||
|
||||
pub fn run_rom(
|
||||
_file_path: std::path::PathBuf,
|
||||
_game_settings: ui::GameSettings,
|
||||
file_path: std::path::PathBuf,
|
||||
game_settings: ui::GameSettings,
|
||||
netplay: Option<ui::gui::NetplayDevice>,
|
||||
weak: slint::Weak<ui::gui::AppWindow>,
|
||||
) {
|
||||
if let Some(netplay) = netplay {
|
||||
println!(
|
||||
"Netplay peer addr: {} player number: {}",
|
||||
netplay.peer_addr, netplay.player_number
|
||||
);
|
||||
if let Some(vm) = get_vm() {
|
||||
if let Err(err) = vm.attach_current_thread(|env| {
|
||||
start_run_rom_on_jvm(env, file_path, game_settings, netplay, weak)
|
||||
}) {
|
||||
eprintln!("JNI error while starting N64Activity: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_run_rom_on_jvm(
|
||||
env: &mut Env<'_>,
|
||||
file_path: std::path::PathBuf,
|
||||
game_settings: ui::GameSettings,
|
||||
netplay: Option<ui::gui::NetplayDevice>,
|
||||
weak: slint::Weak<ui::gui::AppWindow>,
|
||||
) -> 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::<Global<AndroidActivity>>(&raw_activity_global)? };
|
||||
|
||||
let package_name = JString::from_str(env, "io.github.gopher64.gopher64")?;
|
||||
let class_name = JString::from_str(env, "io.github.gopher64.gopher64.N64Activity")?;
|
||||
|
||||
let file_path_key = JString::from_str(env, "file_path")?;
|
||||
let file_path_value = JString::from_str(env, file_path.to_str().unwrap())?;
|
||||
let overclock_key = JString::from_str(env, "overclock")?;
|
||||
let disable_expansion_pak_key = JString::from_str(env, "disable_expansion_pak")?;
|
||||
let request_code_key = JString::from_str(env, "request_code")?;
|
||||
let mut intent = AndroidIntent::new(env)?
|
||||
.set_class_name(env, &package_name, &class_name)?
|
||||
.put_extra_int(env, &request_code_key, RUN_ROM)?
|
||||
.put_extra_string(env, &file_path_key, &file_path_value)?
|
||||
.put_extra_boolean(env, &overclock_key, game_settings.overclock)?
|
||||
.put_extra_boolean(
|
||||
env,
|
||||
&disable_expansion_pak_key,
|
||||
game_settings.disable_expansion_pak,
|
||||
)?;
|
||||
|
||||
if let Some(netplay) = netplay {
|
||||
let netplay_peer_addr_key = JString::from_str(env, "netplay_peer_addr")?;
|
||||
let netplay_player_number_key = JString::from_str(env, "netplay_player_number")?;
|
||||
let netplay_peer_addr_value = JString::from_str(env, &netplay.peer_addr.to_string())?;
|
||||
let cheats_key = JString::from_str(env, "cheats")?;
|
||||
let cheats_path = ui::get_dirs().cache_dir.join("cheats.json");
|
||||
let cheats_value = JString::from_str(env, cheats_path.to_str().unwrap())?;
|
||||
|
||||
let f = std::fs::File::create(cheats_path).unwrap();
|
||||
serde_json::to_writer_pretty(f, &game_settings.cheats).unwrap();
|
||||
|
||||
intent = intent
|
||||
.put_extra_string(env, &netplay_peer_addr_key, &netplay_peer_addr_value)?
|
||||
.put_extra_int(
|
||||
env,
|
||||
&netplay_player_number_key,
|
||||
netplay.player_number as jint,
|
||||
)?
|
||||
.put_extra_string(env, &cheats_key, &cheats_value)?;
|
||||
}
|
||||
|
||||
weak.upgrade_in_event_loop(move |handle| handle.set_game_running(true))
|
||||
.unwrap();
|
||||
|
||||
activity
|
||||
.as_ref()
|
||||
.start_activity_for_result(env, &intent, RUN_ROM)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(jni::errors::Error::UninitializedJavaVM)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +317,27 @@ fn get_vm() -> Option<JavaVM> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_path(path: &str) -> String {
|
||||
if let Some(vm) = get_vm() {
|
||||
match vm.attach_current_thread(|env| decode_path_on_jvm(env, path)) {
|
||||
Ok(decoded_path) => decoded_path,
|
||||
Err(err) => {
|
||||
eprintln!("JNI error while decoding path: {err:?}");
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Android app not initialized");
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_path_on_jvm(env: &mut Env<'_>, path: &str) -> jni::errors::Result<String> {
|
||||
let path = JString::from_str(env, path)?;
|
||||
let decoded_path = AndroidUri::decode(env, &path)?;
|
||||
Ok(decoded_path.try_to_string(env)?)
|
||||
}
|
||||
|
||||
/// Lists connected gamepads and joysticks using the Android framework.
|
||||
pub fn list_controllers() -> Vec<ControllerInfo> {
|
||||
if let Some(vm) = get_vm() {
|
||||
@@ -536,6 +620,34 @@ pub extern "system" fn Java_io_github_gopher64_gopher64_SlintActivity_nativeOnAc
|
||||
ui::gui::update_input_profiles(&weak_app_window, &config);
|
||||
}
|
||||
}
|
||||
RUN_ROM => {
|
||||
let result_intent = unsafe { env.as_cast_raw::<AndroidIntent>(&intent_data)? };
|
||||
|
||||
let file_path_key = JString::from_str(env, "file_path")?;
|
||||
let file_path = result_intent
|
||||
.as_ref()
|
||||
.get_string_extra(env, &file_path_key)?
|
||||
.try_to_string(env)?;
|
||||
|
||||
let cheats_path_key = JString::from_str(env, "cheats_path")?;
|
||||
if let Ok(cheats_path) = result_intent
|
||||
.as_ref()
|
||||
.get_string_extra(env, &cheats_path_key)
|
||||
&& let Ok(cheats_path) = cheats_path.try_to_string(env)
|
||||
{
|
||||
let _ = std::fs::remove_file(cheats_path);
|
||||
}
|
||||
if let Ok(weak_app_window) = WEAK_SLINT_WINDOW.lock()
|
||||
&& let Some(weak_app_window) = weak_app_window.as_ref()
|
||||
{
|
||||
weak_app_window
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
ui::gui::update_recent_roms(&handle, file_path.into());
|
||||
handle.set_game_running(false)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-21
@@ -111,7 +111,7 @@ fn local_game_window(app: &AppWindow, config: &ui::config::Config) {
|
||||
.map(|x| {
|
||||
(
|
||||
x.into(),
|
||||
std::path::Path::new(x)
|
||||
std::path::Path::new(&decode_path(x))
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
@@ -541,11 +541,10 @@ pub fn run_rom(
|
||||
file_path: std::path::PathBuf,
|
||||
game_settings: ui::GameSettings,
|
||||
netplay: Option<NetplayDevice>,
|
||||
#[cfg(target_os = "android")] _weak: slint::Weak<AppWindow>,
|
||||
#[cfg(not(target_os = "android"))] weak: slint::Weak<AppWindow>,
|
||||
weak: slint::Weak<AppWindow>,
|
||||
) {
|
||||
#[cfg(target_os = "android")]
|
||||
ui::android::run_rom(file_path, game_settings, netplay);
|
||||
ui::android::run_rom(file_path, game_settings, netplay, weak);
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
tokio::spawn(async move {
|
||||
@@ -571,7 +570,7 @@ pub fn run_rom(
|
||||
]);
|
||||
let cheats_path = ui::get_dirs().cache_dir.join("cheats.json");
|
||||
if let Some(netplay_device) = netplay {
|
||||
let f = std::fs::File::create(cheats_path.to_str().unwrap()).unwrap();
|
||||
let f = std::fs::File::create(&cheats_path).unwrap();
|
||||
serde_json::to_writer_pretty(f, &game_settings.cheats).unwrap();
|
||||
|
||||
command.args([
|
||||
@@ -595,28 +594,14 @@ pub fn run_rom(
|
||||
eprintln!("Failed to run game");
|
||||
}
|
||||
|
||||
let _ = std::fs::remove_file(cheats_path.to_str().unwrap());
|
||||
let _ = std::fs::remove_file(cheats_path);
|
||||
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
if let Some(rom_dir) = file_path.parent().unwrap().to_str() {
|
||||
handle.set_rom_dir(rom_dir.into());
|
||||
}
|
||||
if success {
|
||||
let recent_roms = slint::VecModel::default();
|
||||
recent_roms.push((
|
||||
file_path.to_str().unwrap().into(),
|
||||
file_path.file_name().unwrap().to_str().unwrap().into(),
|
||||
));
|
||||
|
||||
for rom in handle.get_recent_roms().iter() {
|
||||
if rom.0 != file_path.to_str().unwrap()
|
||||
&& recent_roms.row_count() < 5
|
||||
&& rom_exists(&rom.0)
|
||||
{
|
||||
recent_roms.push(rom);
|
||||
}
|
||||
}
|
||||
handle.set_recent_roms(slint::ModelRc::from(std::rc::Rc::new(recent_roms)));
|
||||
update_recent_roms(&handle, file_path);
|
||||
}
|
||||
handle.set_game_running(false);
|
||||
})
|
||||
@@ -624,6 +609,34 @@ pub fn run_rom(
|
||||
});
|
||||
}
|
||||
|
||||
fn decode_path(path: &str) -> String {
|
||||
#[cfg(target_os = "android")]
|
||||
return ui::android::decode_path(path);
|
||||
#[cfg(not(target_os = "android"))]
|
||||
return path.to_string();
|
||||
}
|
||||
|
||||
pub fn update_recent_roms(app: &AppWindow, file_path: std::path::PathBuf) {
|
||||
let recent_roms = slint::VecModel::default();
|
||||
recent_roms.push((
|
||||
file_path.to_str().unwrap().into(),
|
||||
std::path::Path::new(&decode_path(file_path.to_str().unwrap()))
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.into(),
|
||||
));
|
||||
|
||||
for rom in app.get_recent_roms().iter() {
|
||||
if rom.0 != file_path.to_str().unwrap() && recent_roms.row_count() < 5 && rom_exists(&rom.0)
|
||||
{
|
||||
recent_roms.push(rom);
|
||||
}
|
||||
}
|
||||
app.set_recent_roms(slint::ModelRc::from(std::rc::Rc::new(recent_roms)));
|
||||
}
|
||||
|
||||
pub async fn select_rom(rom_dir: slint::SharedString) -> Option<std::path::PathBuf> {
|
||||
#[cfg(target_os = "android")]
|
||||
return ui::android::select_rom(rom_dir).await;
|
||||
|
||||
Reference in New Issue
Block a user