doukutsu-rs/src/game/filesystem_container.rs

222 lines
7.6 KiB
Rust

use std::path::PathBuf;
use crate::{
data::builtin_fs::BuiltinFS,
framework::{
context::Context,
error::GameResult,
filesystem::{mount_user_vfs, mount_vfs, unmount_user_vfs},
vfs::PhysicalFS,
},
};
pub struct FilesystemContainer {
pub user_path: PathBuf,
pub game_path: PathBuf,
pub is_portable: bool,
}
impl FilesystemContainer {
pub fn new() -> Self {
Self { user_path: PathBuf::new(), game_path: PathBuf::new(), is_portable: false }
}
pub fn mount_fs(&mut self, context: &mut Context) -> GameResult {
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
let resource_dir = if let Ok(data_dir) = std::env::var("CAVESTORY_DATA_DIR") {
PathBuf::from(data_dir)
} else {
let mut resource_dir = std::env::current_exe()?;
if resource_dir.file_name().is_some() {
let _ = resource_dir.pop();
}
#[cfg(target_os = "macos")]
{
let mut bundle_dir = resource_dir.clone();
let _ = bundle_dir.pop();
let mut bundle_exec_dir = bundle_dir.clone();
let mut csplus_data_dir = bundle_dir.clone();
let _ = csplus_data_dir.pop();
let _ = csplus_data_dir.pop();
let mut csplus_data_base_dir = csplus_data_dir.clone();
csplus_data_base_dir.push("data");
csplus_data_base_dir.push("base");
bundle_exec_dir.push("MacOS");
bundle_dir.push("Resources");
if bundle_exec_dir.is_dir() && bundle_dir.is_dir() {
log::info!("Running in macOS bundle mode");
if csplus_data_base_dir.is_dir() {
log::info!("Cave Story+ Steam detected");
resource_dir = csplus_data_dir;
} else {
resource_dir = bundle_dir;
}
}
}
resource_dir.push("data");
resource_dir
};
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
log::info!("Resource directory: {:?}", resource_dir);
log::info!("Initializing engine...");
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
{
mount_vfs(context, Box::new(PhysicalFS::new(&resource_dir, true)));
self.game_path = resource_dir.clone();
}
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
let project_dirs = match directories::ProjectDirs::from("", "", "doukutsu-rs") {
Some(dirs) => dirs,
None => {
use crate::framework::error::GameError;
return Err(GameError::FilesystemError(String::from(
"No valid home directory path could be retrieved.",
)));
}
};
#[cfg(target_os = "android")]
{
let mut data_path =
PathBuf::from(ndk_glue::native_activity().internal_data_path().to_string_lossy().to_string());
let mut user_path = data_path.clone();
data_path.push("data");
user_path.push("saves");
let _ = std::fs::create_dir_all(&data_path);
let _ = std::fs::create_dir_all(&user_path);
log::info!("Android data directories: data_path={:?} user_path={:?}", &data_path, &user_path);
mount_vfs(context, Box::new(PhysicalFS::new(&data_path, true)));
mount_user_vfs(context, Box::new(PhysicalFS::new(&user_path, false)));
self.user_path = user_path.clone();
self.game_path = data_path.clone();
}
#[cfg(target_os = "horizon")]
{
let mut data_path = PathBuf::from("sdmc:/switch/doukutsu-rs/data");
let mut user_path = PathBuf::from("sdmc:/switch/doukutsu-rs/user");
let _ = std::fs::create_dir_all(&data_path);
let _ = std::fs::create_dir_all(&user_path);
log::info!("Mounting VFS");
mount_vfs(context, Box::new(PhysicalFS::new(&data_path, true)));
if crate::framework::backend_horizon::mount_romfs() {
mount_vfs(context, Box::new(PhysicalFS::new_lowercase(&PathBuf::from("romfs:/data"))));
}
log::info!("Mounting user VFS");
mount_user_vfs(context, Box::new(PhysicalFS::new(&user_path, false)));
log::info!("ok");
self.user_path = user_path.clone();
self.game_path = data_path.clone();
}
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
{
let mut user_dir = resource_dir.clone();
user_dir.pop();
user_dir.push("user");
if user_dir.is_dir() {
// portable mode
mount_user_vfs(context, Box::new(PhysicalFS::new(&user_dir, false)));
self.user_path = user_dir.clone();
self.is_portable = true;
} else {
let user_dir = project_dirs.data_local_dir();
mount_user_vfs(context, Box::new(PhysicalFS::new(user_dir, false)));
self.user_path = user_dir.to_path_buf();
}
}
log::info!("Mounting built-in FS");
mount_vfs(context, Box::new(BuiltinFS::new()));
Ok(())
}
pub fn open_user_directory(&self) -> GameResult {
self.open_directory(self.user_path.clone())
}
pub fn open_game_directory(&self) -> GameResult {
self.open_directory(self.game_path.clone())
}
pub fn make_portable_user_directory(&mut self, ctx: &mut Context) -> GameResult {
let mut user_dir = self.game_path.clone();
user_dir.pop();
user_dir.push("user");
if user_dir.is_dir() {
return Ok(()); // portable directory already exists
}
let _ = std::fs::create_dir_all(user_dir.clone());
// copy user data from current user dir
for entry in std::fs::read_dir(&self.user_path)? {
let entry = entry?;
let path = entry.path();
let file_name = path.file_name().unwrap().to_str().unwrap();
let mut new_path = user_dir.clone();
new_path.push(file_name);
std::fs::copy(path, new_path)?;
}
// unmount old user dir
unmount_user_vfs(ctx, &self.user_path);
// mount new user dir
mount_user_vfs(ctx, Box::new(PhysicalFS::new(&user_dir, false)));
self.user_path = user_dir.clone();
self.is_portable = true;
Ok(())
}
fn open_directory(&self, path: PathBuf) -> GameResult {
#[cfg(target_os = "horizon")]
return Ok(()); // can't open directories on switch
#[cfg(target_os = "android")]
unsafe {
use jni::objects::{JObject, JValue};
use jni::JavaVM;
let vm_ptr = ndk_glue::native_activity().vm();
let vm = JavaVM::from_raw(vm_ptr)?;
let vm_env = vm.attach_current_thread()?;
let class = vm_env.new_global_ref(JObject::from_raw(ndk_glue::native_activity().activity()))?;
let method = vm_env.call_method(class.as_obj(), "openDir", "(Ljava/lang/String;)V", &[
JValue::from(vm_env.new_string(path.to_str().unwrap()).unwrap())
])?;
return Ok(());
}
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
open::that(path).map_err(|e| {
use crate::framework::error::GameError;
GameError::FilesystemError(format!("Failed to open directory: {}", e))
})
}
}