add menu options for opening user and data dirs

This commit is contained in:
József Sallai 2023-02-17 16:05:38 +02:00
parent 30c85c2f8b
commit d6715bccea
7 changed files with 258 additions and 110 deletions

View File

@ -73,6 +73,7 @@ log = "0.4"
lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "e0b2ff5960f7ef9974aa9675cebe4907bee0134f", optional = true }
num-derive = "0.3"
num-traits = "0.2"
open = "3.2"
paste = "1.0"
pelite = { version = ">=0.9.2", default-features = false, features = ["std"] }
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }

View File

@ -128,7 +128,12 @@
"fastforward": "Fast-Forward"
}
},
"links": "Links..."
"links": "Links...",
"advanced": "Advanced...",
"advanced_menu": {
"open_user_data": "Open user data directory",
"open_game_data": "Open game data directory"
}
},
"controls_menu": {
"select_player": {

View File

@ -128,7 +128,12 @@
"fastforward": "はやおくり"
}
},
"links": "リンク"
"links": "リンク",
"advanced": "詳細設定",
"advanced_menu": {
"open_user_data": "ユーザープロファイルを開く",
"open_game_data": "ゲームファイルを開く"
}
},
"controls_menu": {
"select_player": {

View File

@ -0,0 +1,171 @@
use std::path::PathBuf;
use crate::{
data::builtin_fs::BuiltinFS,
framework::{
context::Context,
error::GameResult,
filesystem::{mount_user_vfs, mount_vfs},
vfs::PhysicalFS,
},
};
pub struct FilesystemContainer {
pub user_path: PathBuf,
pub game_path: PathBuf,
}
impl FilesystemContainer {
pub fn new() -> Self {
Self { user_path: PathBuf::new(), game_path: PathBuf::new() }
}
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")))]
{
if crate::framework::filesystem::open(&context, "/.drs_localstorage").is_ok() {
let mut user_dir = resource_dir.clone();
user_dir.push("_drs_profile");
let _ = std::fs::create_dir_all(&user_dir);
mount_user_vfs(context, Box::new(PhysicalFS::new(&user_dir, false)));
self.user_path = user_dir.clone();
} 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())
}
fn open_directory(&self, path: PathBuf) -> GameResult {
#[cfg(target_os = "horizon")]
return Ok(()); // can't open directories on switch
#[cfg(target_os = "android")]
return Ok(()); // TODO: figure out how to do this on android
#[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))
})
}
}

View File

@ -1,5 +1,4 @@
use std::cell::UnsafeCell;
use std::path::PathBuf;
use std::sync::Mutex;
use std::time::{Duration, Instant};
@ -7,20 +6,19 @@ use lazy_static::lazy_static;
use scripting::tsc::text_script::ScriptMode;
use crate::data::builtin_fs::BuiltinFS;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem::{mount_user_vfs, mount_vfs};
use crate::framework::graphics;
use crate::framework::graphics::VSyncMode;
use crate::framework::ui::UI;
use crate::framework::vfs::PhysicalFS;
use crate::game::filesystem_container::FilesystemContainer;
use crate::game::shared_game_state::{Fps, SharedGameState, TimingMode};
use crate::graphics::texture_set::{G_MAG, I_MAG};
use crate::scene::loading_scene::LoadingScene;
use crate::scene::Scene;
pub mod caret;
pub mod filesystem_container;
pub mod frame;
pub mod inventory;
pub mod map;
@ -221,112 +219,10 @@ pub fn init(options: LaunchOptions) -> GameResult {
.with_level(log::Level::Info.to_level_filter())
.init();
#[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...");
let mut context = Box::pin(Context::new());
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
mount_vfs(&mut context, Box::new(PhysicalFS::new(&resource_dir, true)));
#[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(&mut context, Box::new(PhysicalFS::new(&data_path, true)));
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_path, false)));
}
#[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(&mut context, Box::new(PhysicalFS::new(&data_path, true)));
if crate::framework::backend_horizon::mount_romfs() {
mount_vfs(&mut context, Box::new(PhysicalFS::new_lowercase(&PathBuf::from("romfs:/data"))));
}
log::info!("Mounting user VFS");
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_path, false)));
log::info!("ok");
}
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
{
if crate::framework::filesystem::open(&context, "/.drs_localstorage").is_ok() {
let mut user_dir = resource_dir.clone();
user_dir.push("_drs_profile");
let _ = std::fs::create_dir_all(&user_dir);
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_dir, false)));
} else {
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(project_dirs.data_local_dir(), false)));
}
}
log::info!("Mounting built-in FS");
mount_vfs(&mut context, Box::new(BuiltinFS::new()));
let mut fs_container = FilesystemContainer::new();
fs_container.mount_fs(&mut context)?;
if options.server_mode {
log::info!("Running in server mode...");
@ -339,6 +235,8 @@ pub fn init(options: LaunchOptions) -> GameResult {
game.state.get().lua.update_refs(unsafe { &mut *game.state.get() }, &mut context as *mut Context);
}
game.state.get_mut().fs_container = Some(fs_container);
game.state.get_mut().next_scene = Some(Box::new(LoadingScene::new()));
log::info!("Starting main loop...");
context.run(game.as_mut().get_mut())?;

View File

@ -34,6 +34,8 @@ use crate::sound::SoundManager;
use crate::util::bitvec::BitVec;
use crate::util::rng::XorShift;
use super::filesystem_container::FilesystemContainer;
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum TimingMode {
_50Hz,
@ -314,6 +316,7 @@ pub struct SharedGameState {
pub lightmap_canvas: Option<Box<dyn BackendTexture>>,
pub season: Season,
pub menu_character: MenuCharacter,
pub fs_container: Option<FilesystemContainer>,
pub constants: EngineConstants,
pub font: BMFont,
pub texture_set: TextureSet,
@ -468,6 +471,7 @@ impl SharedGameState {
lightmap_canvas: None,
season,
menu_character: MenuCharacter::Quote,
fs_container: None,
constants,
font,
texture_set: TextureSet::new(),

View File

@ -27,6 +27,7 @@ enum CurrentMenu {
LanguageMenu,
BehaviorMenu,
LinksMenu,
AdvancedMenu,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
@ -37,6 +38,7 @@ enum MainMenuEntry {
Language,
Behavior,
Links,
Advanced,
Back,
}
@ -134,6 +136,20 @@ impl Default for LinksMenuEntry {
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum AdvancedMenuEntry {
Title,
OpenUserData,
OpenGameData,
Back,
}
impl Default for AdvancedMenuEntry {
fn default() -> Self {
AdvancedMenuEntry::OpenUserData
}
}
pub struct SettingsMenu {
current: CurrentMenu,
main: Menu<MainMenuEntry>,
@ -143,6 +159,7 @@ pub struct SettingsMenu {
language: Menu<LanguageMenuEntry>,
behavior: Menu<BehaviorMenuEntry>,
links: Menu<LinksMenuEntry>,
advanced: Menu<AdvancedMenuEntry>,
controls_menu: ControlsMenu,
pub on_title: bool,
}
@ -164,6 +181,7 @@ impl SettingsMenu {
let language = Menu::new(0, 0, 120, 0);
let behavior = Menu::new(0, 0, 220, 0);
let links = Menu::new(0, 0, 220, 0);
let advanced = Menu::new(0, 0, 220, 0);
let controls_menu = ControlsMenu::new();
@ -176,6 +194,7 @@ impl SettingsMenu {
language,
behavior,
links,
advanced,
controls_menu,
on_title: false,
}
@ -349,6 +368,26 @@ impl SettingsMenu {
);
self.links.push_entry(LinksMenuEntry::Link(GETPLUS_LINK), MenuEntry::Active("Get Cave Story+".to_owned()));
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
self.main.push_entry(
MainMenuEntry::Advanced,
MenuEntry::Active(state.loc.t("menus.options_menu.advanced").to_owned()),
);
self.advanced.push_entry(
AdvancedMenuEntry::Title,
MenuEntry::Disabled(state.loc.t("menus.options_menu.advanced").to_owned()),
);
self.advanced.push_entry(
AdvancedMenuEntry::OpenUserData,
MenuEntry::Active(state.loc.t("menus.options_menu.advanced_menu.open_user_data").to_owned()),
);
self.advanced.push_entry(
AdvancedMenuEntry::OpenGameData,
MenuEntry::Active(state.loc.t("menus.options_menu.advanced_menu.open_game_data").to_owned()),
);
self.advanced.push_entry(AdvancedMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.sound.push_entry(
@ -507,6 +546,11 @@ impl SettingsMenu {
self.links.update_height();
self.links.x = ((state.canvas_size.0 - self.links.width as f32) / 2.0).floor() as isize;
self.links.y = 30 + ((state.canvas_size.1 - self.links.height as f32) / 2.0).floor() as isize;
self.advanced.update_width(state);
self.advanced.update_height();
self.advanced.x = ((state.canvas_size.0 - self.advanced.width as f32) / 2.0).floor() as isize;
self.advanced.y = 30 + ((state.canvas_size.1 - self.advanced.height as f32) / 2.0).floor() as isize;
}
pub fn tick(
@ -538,6 +582,9 @@ impl SettingsMenu {
MenuSelectionResult::Selected(MainMenuEntry::Links, _) => {
self.current = CurrentMenu::LinksMenu;
}
MenuSelectionResult::Selected(MainMenuEntry::Advanced, _) => {
self.current = CurrentMenu::AdvancedMenu;
}
MenuSelectionResult::Selected(MainMenuEntry::Back, _) | MenuSelectionResult::Canceled => exit_action(),
_ => (),
},
@ -868,6 +915,22 @@ impl SettingsMenu {
}
_ => (),
},
CurrentMenu::AdvancedMenu => match self.advanced.tick(controller, state) {
MenuSelectionResult::Selected(AdvancedMenuEntry::OpenUserData, _) => {
if let Some(fs_container) = &state.fs_container {
fs_container.open_user_directory()?;
}
}
MenuSelectionResult::Selected(AdvancedMenuEntry::OpenGameData, _) => {
if let Some(fs_container) = &state.fs_container {
fs_container.open_game_directory()?;
}
}
MenuSelectionResult::Selected(AdvancedMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
self.current = CurrentMenu::MainMenu;
}
_ => {}
},
}
Ok(())
}
@ -882,6 +945,7 @@ impl SettingsMenu {
CurrentMenu::LanguageMenu => self.language.draw(state, ctx)?,
CurrentMenu::BehaviorMenu => self.behavior.draw(state, ctx)?,
CurrentMenu::LinksMenu => self.links.draw(state, ctx)?,
CurrentMenu::AdvancedMenu => self.advanced.draw(state, ctx)?,
}
Ok(())