diff --git a/Cargo.toml b/Cargo.toml index b67b3c0..1ae5eb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ category = "Game" osx_minimum_system_version = "10.12" [features] -default = ["default-base", "backend-sdl", "render-opengl", "exe"] +default = ["default-base", "backend-sdl", "render-opengl", "exe", "webbrowser"] default-base = ["ogg-playback"] ogg-playback = ["lewton"] backend-sdl = ["sdl2", "sdl2-sys"] @@ -48,11 +48,11 @@ android = [] #glutin = { path = "./3rdparty/glutin/glutin", optional = true } #lua-ffi = { path = "./3rdparty/luajit-rs", optional = true } #winit = { path = "./3rdparty/winit", optional = true, default_features = false, features = ["x11"] } -#sdl2 = { path = "./3rdparty/rust-sdl2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } -#sdl2-sys = { path = "./3rdparty/rust-sdl2/sdl2-sys", optional = true, features = ["bundled", "static-link"] } +sdl2 = { path = "./3rdparty/rust-sdl2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } +sdl2-sys = { path = "./3rdparty/rust-sdl2/sdl2-sys", optional = true, features = ["bundled", "static-link"] } byteorder = "1.4" case_insensitive_hashmap = "1.0.0" -chrono = "0.4" +chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } cpal = "0.14" directories = "3" downcast = "0.11" @@ -68,9 +68,9 @@ lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "e0b2ff596 num-derive = "0.3" num-traits = "0.2" paste = "1.0" -pelite = ">=0.9.2" -sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } -sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["bundled", "static-link"] } +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"] } +#sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["bundled", "static-link"] } rc-box = "1.2.0" serde = { version = "1", features = ["derive"] } serde_derive = "1" @@ -81,7 +81,7 @@ strum = "0.24" strum_macros = "0.24" # remove and replace when drain_filter is in stable vec_mut_scan = "0.4" -webbrowser = "0.8" +webbrowser = { version = "0.8", optional = true } #winit = { git = "https://github.com/alula/winit.git", rev = "6acf76ff192dd8270aaa119b9f35716c03685f9f", optional = true, default_features = false, features = ["x11"] } winit = { version = "0.27", optional = true, default_features = false, features = ["x11"] } xmltree = "0.10" @@ -100,3 +100,5 @@ ndk = "0.7" ndk-glue = "0.7" ndk-sys = "0.4" jni = "0.20" + +[target.'cfg(target_os = "horizon")'.dependencies] diff --git a/drsandroid/Cargo.toml b/drsandroid/Cargo.toml index 27d87e4..348588a 100644 --- a/drsandroid/Cargo.toml +++ b/drsandroid/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "drsandroid" +description = "doukutsu-rs targeted for Android" version = "0.1.0" -authors = ["Alula"] -edition = "2018" +edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] -ndk = "0.3" -ndk-glue = "0.3" -ndk-sys = "0.2" -doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin"] } +ndk = "0.7" +ndk-glue = "0.7" +ndk-sys = "0.4" +doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin", "webbrowser"] } diff --git a/drsandroid/src/lib.rs b/drsandroid/src/lib.rs index 4b72e92..2b0addc 100644 --- a/drsandroid/src/lib.rs +++ b/drsandroid/src/lib.rs @@ -1,7 +1,7 @@ #[cfg(target_os = "android")] #[cfg_attr(target_os = "android", ndk_glue::main())] pub fn android_main() { - let options = doukutsu_rs::LaunchOptions { server_mode: false, editor: false }; + let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false }; doukutsu_rs::init(options).unwrap(); } diff --git a/drshorizon/.cargo/config b/drshorizon/.cargo/config new file mode 100644 index 0000000..6b56e8a --- /dev/null +++ b/drshorizon/.cargo/config @@ -0,0 +1,9 @@ +[build] +target = ["aarch64-nintendo-switch"] + +[target.aarch64-nintendo-switch] +cc = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc"} +cxx = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-g++"} +ar = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-ar" +ranlib = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-ranlib"} +linker = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc" diff --git a/drshorizon/Cargo.toml b/drshorizon/Cargo.toml new file mode 100644 index 0000000..effae96 --- /dev/null +++ b/drshorizon/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "drshorizon" +description = "doukutsu-rs targeted for Nintendo Switch" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +doukutsu-rs = { path = "../", default-features = false, features = ["default-base"] } diff --git a/drshorizon/README.md b/drshorizon/README.md new file mode 100644 index 0000000..84d0240 --- /dev/null +++ b/drshorizon/README.md @@ -0,0 +1,3 @@ +Experimental. Nothing to see there yet. + +ld script and .specs taken from devkitPro diff --git a/drshorizon/aarch64-nintendo-switch.json b/drshorizon/aarch64-nintendo-switch.json new file mode 100644 index 0000000..28a30a5 --- /dev/null +++ b/drshorizon/aarch64-nintendo-switch.json @@ -0,0 +1,40 @@ +{ + "arch": "aarch64", + "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", + "dynamic-linking": true, + "disable-redzone": true, + "env": "newlib", + "executables": true, + "exe-suffix": ".elf", + "features": "+a57,+strict-align,+crc,+crypto", + "has-rpath": false, + "linker": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc", + "linker-flavor": "gcc", + "llvm-target": "aarch64-unknown-none", + "max-atomic-width": 128, + "no-default-libraries": false, + "os": "horizon", + "panic-strategy": "abort", + "position-independent-executables": true, + "pre-link-args": { + "gcc": [ + "-fPIC", + "-specs", + "aarch64_nintendo_switch.specs", + "-T", + "aarch64_nintendo_switch.ld", + "-L", + "/opt/devkitpro/portlibs/switch/lib", + "-L", + "/opt/devkitpro/libnx/lib" + ] + }, + "relocation-model": "pic", + "requires-uwtable": true, + "target-c-int-width": "32", + "target-endian": "little", + "target-family": ["unix"], + "target-pointer-width": "64", + "trap-unreachable": true, + "vendor": "nintendo" +} diff --git a/drshorizon/aarch64_nintendo_switch.ld b/drshorizon/aarch64_nintendo_switch.ld new file mode 100644 index 0000000..2fade78 --- /dev/null +++ b/drshorizon/aarch64_nintendo_switch.ld @@ -0,0 +1,200 @@ +OUTPUT_ARCH(aarch64) +ENTRY(_start) + +PHDRS +{ + code PT_LOAD FLAGS(5) /* Read | Execute */; + rodata PT_LOAD FLAGS(4) /* Read */; + data PT_LOAD FLAGS(6) /* Read | Write */; + dyn PT_DYNAMIC; +} + +SECTIONS +{ + /* =========== CODE section =========== */ + PROVIDE(__start__ = 0x0); + . = __start__; + __code_start = . ; + + .text : + { + HIDDEN(__text_start = .); + KEEP (*(.crt0)) + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .stub .text.* .gnu.linkonce.t.*) + . = ALIGN(8); + } :code + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } :code + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } :code + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } :code + + /* =========== RODATA section =========== */ + . = ALIGN(0x1000); + __rodata_start = . ; + + .nx-module-name : { KEEP (*(.nx-module-name)) } :rodata + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } :rodata + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } :rodata + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } :rodata + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } :rodata + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } : rodata + + HIDDEN(__dynamic_start = .); + .dynamic : { *(.dynamic) } :rodata :dyn + .dynsym : { *(.dynsym) } :rodata + .dynstr : { *(.dynstr) } :rodata + .rela.dyn : { *(.rela.*) } :rodata + .interp : { *(.interp) } :rodata + .hash : { *(.hash) } :rodata + .gnu.hash : { *(.gnu.hash) } :rodata + .gnu.version : { *(.gnu.version) } :rodata + .gnu.version_d : { *(.gnu.version_d) } :rodata + .gnu.version_r : { *(.gnu.version_r) } :rodata + .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata + + /* =========== DATA section =========== */ + . = ALIGN(0x1000); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } :data + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } :data + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } : data + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } :data + + .tdata ALIGN(8) : + { + __tdata_lma = .; + *(.tdata .tdata.* .gnu.linkonce.td.*) + . = ALIGN(8); + __tdata_lma_end = .; + } :data + + .tbss ALIGN(8) : + { + *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) + . = ALIGN(8); + } :data + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } :data + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP( *(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)) ) + KEEP( *(.init_array .ctors) ) + PROVIDE (__init_array_end = .); + } :data + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP( *(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)) ) + KEEP( *(.fini_array .dtors) ) + PROVIDE (__fini_array_end = .); + } :data + + __got_start__ = .; + + .got : { *(.got) *(.igot) } :data + .got.plt : { *(.got.plt) *(.igot.plt) } :data + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } :data + + __bss_start__ = .; + .bss ALIGN(8) : + { + HIDDEN(__bss_start = .); + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(8); + + /* Reserve space for the TLS segment of the main thread */ + __tls_start = .; + . += + SIZEOF(.tdata) + SIZEOF(.tbss); + __tls_end = .; + HIDDEN(__bss_end = .); + } :data + __bss_end__ = .; + + __end__ = ABSOLUTE(.) ; + + . = ALIGN(0x1000); + __argdata__ = ABSOLUTE(.) ; + + /* ================== + ==== Metadata ==== + ================== */ + + /* Discard sections that difficult post-processing */ + /DISCARD/ : { *(.group .comment .note) } + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } +} diff --git a/drshorizon/aarch64_nintendo_switch.specs b/drshorizon/aarch64_nintendo_switch.specs new file mode 100644 index 0000000..861bec4 --- /dev/null +++ b/drshorizon/aarch64_nintendo_switch.specs @@ -0,0 +1,8 @@ +%rename link old_link + +*link: +%(old_link) -pie --no-dynamic-linker --spare-dynamic-tags=0 -z text -z nodynamic-undefined-weak --build-id=sha1 --nx-module-name + +*startfile: +crti%O%s crtbegin%O%s --require-defined=main + diff --git a/drshorizon/build.rs b/drshorizon/build.rs new file mode 100644 index 0000000..1f061c1 --- /dev/null +++ b/drshorizon/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + println!("cargo:rustc-link-lib=dylib=nx"); +} diff --git a/drshorizon/build_debug.sh b/drshorizon/build_debug.sh new file mode 100755 index 0000000..0fa9bf8 --- /dev/null +++ b/drshorizon/build_debug.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +cd "$(dirname "$0")" || exit +set -e + +rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json + +rm -f target/aarch64-nintendo-switch/debug/drshorizon.nro +rm -f target/aarch64-nintendo-switch/debug/drshorizon.nacp + +echo "Creating NACP..." +nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.100.0' target/aarch64-nintendo-switch/debug/drshorizon.nacp + +echo "Running elf2nro..." +elf2nro target/aarch64-nintendo-switch/debug/drshorizon.elf target/aarch64-nintendo-switch/debug/drshorizon.nro \ + --icon=../res/nx_icon.jpg \ + --nacp=target/aarch64-nintendo-switch/debug/drshorizon.nacp + +echo "done." diff --git a/drshorizon/src/main.rs b/drshorizon/src/main.rs new file mode 100644 index 0000000..28cf722 --- /dev/null +++ b/drshorizon/src/main.rs @@ -0,0 +1,27 @@ +//#![feature(restricted_std)] + +#[repr(C)] +pub struct PrintConsole {} + +extern "C" { + pub fn consoleInit(unk: *mut PrintConsole) -> *mut PrintConsole; + + pub fn consoleUpdate(unk: *mut PrintConsole); +} + +fn main() { + unsafe { + consoleInit(std::ptr::null_mut()); + + let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false }; + let result = doukutsu_rs::game::init(options); + + if let Err(e) = result { + println!("Initialization error: {}", e); + loop { + consoleUpdate(std::ptr::null_mut()); + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + } +} diff --git a/res/nx_icon.jpg b/res/nx_icon.jpg new file mode 100644 index 0000000..4cd2aa9 Binary files /dev/null and b/res/nx_icon.jpg differ diff --git a/res/nx_icon.png b/res/nx_icon.png new file mode 100644 index 0000000..73d5f38 Binary files /dev/null and b/res/nx_icon.png differ diff --git a/src/framework/backend_null.rs b/src/framework/backend_null.rs index 4b41b6b..d33e71a 100644 --- a/src/framework/backend_null.rs +++ b/src/framework/backend_null.rs @@ -27,10 +27,23 @@ impl Backend for NullBackend { } } +#[cfg(target_os = "horizon")] +#[repr(C)] +pub struct PrintConsole {} + +#[cfg(target_os = "horizon")] +extern "C" { fn consoleUpdate(unk: *mut PrintConsole); } + pub struct NullEventLoop; impl BackendEventLoop for NullEventLoop { fn run(&mut self, game: &mut Game, ctx: &mut Context) { + println!("BackendEventLoop::run"); + #[cfg(target_os = "horizon")] + unsafe { + consoleUpdate(std::ptr::null_mut()); + } + let state_ref = unsafe { &mut *game.state.get() }; ctx.screen_size = (640.0, 480.0); @@ -54,6 +67,11 @@ impl BackendEventLoop for NullEventLoop { std::thread::sleep(std::time::Duration::from_millis(10)); game.draw(ctx).unwrap(); + + #[cfg(target_os = "horizon")] + unsafe { + consoleUpdate(std::ptr::null_mut()); + } } } diff --git a/src/game/mod.rs b/src/game/mod.rs index 9a85a22..b8e69b2 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -221,8 +221,8 @@ pub fn init(options: LaunchOptions) -> GameResult { .with_level(log::Level::Info.to_level_filter()) .init(); - #[cfg(not(target_os = "android"))] - let resource_dir = if let Ok(data_dir) = std::env::var("CAVESTORY_DATA_DIR") { + #[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()?; @@ -231,46 +231,46 @@ pub fn init(options: LaunchOptions) -> GameResult { } #[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"); + { + 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"); + 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 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; - } + 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(target_os = "android"))] + #[cfg(not(any(target_os = "android", target_os = "horizon")))] log::info!("Resource directory: {:?}", resource_dir); log::info!("Initializing engine..."); let mut context = Context::new(); - #[cfg(not(target_os = "android"))] - mount_vfs(&mut context, Box::new(PhysicalFS::new(&resource_dir, true))); + #[cfg(not(any(target_os = "android", target_os = "horizon")))] + mount_vfs(&mut context, Box::new(PhysicalFS::new(&resource_dir, true))); - #[cfg(not(target_os = "android"))] - let project_dirs = match directories::ProjectDirs::from("", "", "doukutsu-rs") { + #[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; @@ -278,35 +278,35 @@ pub fn init(options: LaunchOptions) -> GameResult { } }; #[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(); + { + 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"); + data_path.push("data"); + user_path.push("saves"); - let _ = std::fs::create_dir_all(&data_path); - let _ = std::fs::create_dir_all(&user_path); + 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); + 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(not(target_os = "android"))] - { - 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))); - } + mount_vfs(&mut context, Box::new(PhysicalFS::new(&data_path, true))); + mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_path, false))); + } + + #[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))); } + } mount_vfs(&mut context, Box::new(BuiltinFS::new())); @@ -318,9 +318,9 @@ pub fn init(options: LaunchOptions) -> GameResult { let game = UnsafeCell::new(Game::new(&mut context)?); let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) }; #[cfg(feature = "scripting-lua")] - { - state_ref.lua.update_refs(unsafe { (&*game.get()).state.get() }, &mut context as *mut Context); - } + { + state_ref.lua.update_refs(unsafe { (&*game.get()).state.get() }, &mut context as *mut Context); + } state_ref.next_scene = Some(Box::new(LoadingScene::new())); context.run(unsafe { &mut *game.get() })?; diff --git a/src/game/shared_game_state.rs b/src/game/shared_game_state.rs index 64e8df2..4a8fe35 100644 --- a/src/game/shared_game_state.rs +++ b/src/game/shared_game_state.rs @@ -327,12 +327,13 @@ impl SharedGameState { None => "data", }; - let vanilla_extractor = - VanillaExtractor::from(ctx, vanilla_ext_exe.to_string(), vanilla_ext_outdir.to_string()); - if vanilla_extractor.is_some() { - let result = vanilla_extractor.unwrap().extract_data(); - if result.is_err() { - log::error!("Failed to extract vanilla data: {}", result.unwrap_err()); + #[cfg(not(target_os = "horizon"))] + if let Some(vanilla_extractor) = + VanillaExtractor::from(ctx, vanilla_ext_exe.to_string(), vanilla_ext_outdir.to_string()) + { + let result = vanilla_extractor.extract_data(); + if let Err(e) = result { + log::error!("Failed to extract vanilla data: {}", e); } } diff --git a/src/lib.rs b/src/lib.rs index 29588b7..0d11d29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +//#![cfg_attr(target_os = "horizon", feature(restricted_std))] + #[macro_use] extern crate log; extern crate strum; diff --git a/src/menu/settings_menu.rs b/src/menu/settings_menu.rs index f6609c4..ff3ae59 100644 --- a/src/menu/settings_menu.rs +++ b/src/menu/settings_menu.rs @@ -11,6 +11,7 @@ use crate::menu::MenuEntry; use crate::menu::{Menu, MenuSelectionResult}; use crate::scene::title_scene::TitleScene; use crate::sound::InterpolationMode; +use crate::util::browser; use super::controls_menu::ControlsMenu; @@ -295,13 +296,18 @@ impl SettingsMenu { self.graphics.push_entry(GraphicsMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned())); + self.main.push_entry( + MainMenuEntry::Graphics, + MenuEntry::Active(state.loc.t("menus.options_menu.graphics").to_owned()), + ); self.main - .push_entry(MainMenuEntry::Graphics, MenuEntry::Active(state.loc.t("menus.options_menu.graphics").to_owned())); - self.main.push_entry(MainMenuEntry::Sound, MenuEntry::Active(state.loc.t("menus.options_menu.sound").to_owned())); + .push_entry(MainMenuEntry::Sound, MenuEntry::Active(state.loc.t("menus.options_menu.sound").to_owned())); #[cfg(not(target_os = "android"))] - self.main - .push_entry(MainMenuEntry::Controls, MenuEntry::Active(state.loc.t("menus.options_menu.controls").to_owned())); + self.main.push_entry( + MainMenuEntry::Controls, + MenuEntry::Active(state.loc.t("menus.options_menu.controls").to_owned()), + ); self.language.push_entry( LanguageMenuEntry::Title, @@ -322,10 +328,13 @@ impl SettingsMenu { ); } - self.main - .push_entry(MainMenuEntry::Behavior, MenuEntry::Active(state.loc.t("menus.options_menu.behavior").to_owned())); + self.main.push_entry( + MainMenuEntry::Behavior, + MenuEntry::Active(state.loc.t("menus.options_menu.behavior").to_owned()), + ); - self.main.push_entry(MainMenuEntry::Links, MenuEntry::Active(state.loc.t("menus.options_menu.links").to_owned())); + self.main + .push_entry(MainMenuEntry::Links, MenuEntry::Active(state.loc.t("menus.options_menu.links").to_owned())); self.links .push_entry(LinksMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.options_menu.links").to_owned())); @@ -345,11 +354,17 @@ impl SettingsMenu { self.sound.push_entry( SoundMenuEntry::MusicVolume, - MenuEntry::OptionsBar(state.loc.t("menus.options_menu.sound_menu.music_volume").to_owned(), state.settings.bgm_volume), + MenuEntry::OptionsBar( + state.loc.t("menus.options_menu.sound_menu.music_volume").to_owned(), + state.settings.bgm_volume, + ), ); self.sound.push_entry( SoundMenuEntry::EffectsVolume, - MenuEntry::OptionsBar(state.loc.t("menus.options_menu.sound_menu.effects_volume").to_owned(), state.settings.sfx_volume), + MenuEntry::OptionsBar( + state.loc.t("menus.options_menu.sound_menu.effects_volume").to_owned(), + state.settings.sfx_volume, + ), ); self.sound.push_entry( @@ -845,7 +860,7 @@ impl SettingsMenu { }, CurrentMenu::LinksMenu => match self.links.tick(controller, state) { MenuSelectionResult::Selected(LinksMenuEntry::Link(url), _) => { - if let Err(e) = webbrowser::open(&url) { + if let Err(e) = browser::open(&url) { log::warn!("Error opening web browser: {}", e); } } diff --git a/src/scene/no_data_scene.rs b/src/scene/no_data_scene.rs index d6da88b..9c4a209 100644 --- a/src/scene/no_data_scene.rs +++ b/src/scene/no_data_scene.rs @@ -29,6 +29,7 @@ impl Scene for NoDataScene { #[cfg(target_os = "android")] { use crate::common::Rect; + use crate::util::browser; if !self.flag { self.flag = true; @@ -39,7 +40,7 @@ impl Scene for NoDataScene { let screen = Rect::new(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize); if state.touch_controls.consume_click_in(screen) { - if let Err(err) = webbrowser::open(REL_URL) { + if let Err(err) = browser::open(REL_URL) { self.err = err.to_string(); } } diff --git a/src/util/browser.rs b/src/util/browser.rs new file mode 100644 index 0000000..25366ee --- /dev/null +++ b/src/util/browser.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "webbrowser")] +pub use webbrowser::open; + +// stub for platforms webbrowser doesn't support, such as Horizon OS +#[cfg(not(feature = "webbrowser"))] +pub fn open(_url: &str) -> std::io::Result<()> { + Ok(()) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 66084cd..a36d623 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,4 @@ pub mod bitvec; pub mod encoding; pub mod rng; +pub mod browser;