mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-11-22 21:52:46 +00:00
Merge pull request #183 from doukutsu-rs/horizon-os
merge Horizon branch with portability fixes
This commit is contained in:
commit
2cb36de715
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -9,6 +9,9 @@
|
|||
debug/
|
||||
target/
|
||||
|
||||
# Shader binary files
|
||||
*.dksh
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
@ -60,3 +63,5 @@ ehthumbs_vista.db
|
|||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
*.log
|
||||
|
|
20
Cargo.toml
20
Cargo.toml
|
@ -21,22 +21,24 @@ panic = "abort"
|
|||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "doukutsu-rs"
|
||||
identifier = "io.github.doukutsu_rs"
|
||||
version = "0.100.0"
|
||||
resources = ["data"]
|
||||
copyright = "Copyright (c) 2020-2022 doukutsu-rs dev team"
|
||||
copyright = "Copyright (c) 2020-2022 doukutsu-rs contributors"
|
||||
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"]
|
||||
backend-glutin = ["winit", "glutin", "render-opengl"]
|
||||
backend-horizon = []
|
||||
render-opengl = []
|
||||
scripting-lua = ["lua-ffi"]
|
||||
netplay = ["serde_cbor"]
|
||||
|
@ -50,10 +52,12 @@ android = []
|
|||
#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"] }
|
||||
#cpal = { path = "./3rdparty/cpal" }
|
||||
byteorder = "1.4"
|
||||
case_insensitive_hashmap = "1.0.0"
|
||||
chrono = "0.4"
|
||||
cpal = "0.14"
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
|
||||
#cpal = "0.14"
|
||||
cpal = { git = "https://github.com/doukutsu-rs/cpal", branch = "horizon" }
|
||||
directories = "3"
|
||||
downcast = "0.11"
|
||||
#glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] }
|
||||
|
@ -68,7 +72,7 @@ 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"
|
||||
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"
|
||||
|
@ -81,7 +85,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 +104,7 @@ ndk = "0.7"
|
|||
ndk-glue = "0.7"
|
||||
ndk-sys = "0.4"
|
||||
jni = "0.20"
|
||||
|
||||
[target.'cfg(target_os = "horizon")'.dependencies]
|
||||
#deko3d = { path = "./3rdparty/deko3d" }
|
||||
deko3d = { git = "https://github.com/doukutsu-rs/deko3d-rs", branch = "master" }
|
||||
|
|
|
@ -122,6 +122,14 @@ Extract the `data` folder (contained in `romfs`) from your console using tool su
|
|||
- doukutsu-rs doesn't rely on the original ROM or executable, you just need the data files, go to `RomFS options` menu to just extract the files to SD card so you don't need to do any extra steps.
|
||||
- Ensure you're dumping the files **with update included** (`Use update/DLC` option), as 1.0 isn't supported.
|
||||
|
||||
**Nintendo Switch homebrew port specific info**
|
||||
|
||||
If you're running the homebrew port (drshorizon.nro) on your Switch, you can avoid the dumping step, doukutsu-rs will
|
||||
automatically detect and mount the data files if you run it over Cave Story+ in Title Override mode (hold `R` while starting CS+ and launch d-rs from hbmenu).
|
||||
|
||||
You can put your own data files in `/switch/doukutsu-rs/data` directory on SD Card, which will be overlayed over RomFS if
|
||||
you run it in setup described above.
|
||||
|
||||
</details>
|
||||
|
||||
#### Controls
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
9
drshorizon/.cargo/config
Normal file
9
drshorizon/.cargo/config
Normal file
|
@ -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"
|
21
drshorizon/Cargo.toml
Normal file
21
drshorizon/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "drshorizon"
|
||||
description = "doukutsu-rs targeted for Nintendo Switch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
#incremental = true
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
incremental = true
|
||||
[profile.dev.package."doukutsu-rs"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
incremental = true
|
||||
|
||||
[dependencies]
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-horizon"] }
|
3
drshorizon/README.md
Normal file
3
drshorizon/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
Experimental. Nothing to see there yet.
|
||||
|
||||
ld script and .specs taken from devkitPro
|
45
drshorizon/aarch64-nintendo-switch.json
Normal file
45
drshorizon/aarch64-nintendo-switch.json
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"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,
|
||||
"has-thread-local": 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",
|
||||
"-I",
|
||||
"/opt/devkitpro/libnx/include"
|
||||
]
|
||||
},
|
||||
"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"
|
||||
}
|
200
drshorizon/aarch64_nintendo_switch.ld
Normal file
200
drshorizon/aarch64_nintendo_switch.ld
Normal file
|
@ -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) }
|
||||
}
|
8
drshorizon/aarch64_nintendo_switch.specs
Normal file
8
drshorizon/aarch64_nintendo_switch.specs
Normal file
|
@ -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
|
||||
|
5
drshorizon/build.rs
Normal file
5
drshorizon/build.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
println!("cargo:rustc-link-lib=dylib=nx");
|
||||
}
|
33
drshorizon/build_debug.sh
Executable file
33
drshorizon/build_debug.sh
Executable file
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")" || exit
|
||||
set -e
|
||||
|
||||
DARK_GRAY=$(tput setaf 8)
|
||||
YELLOW=$(tput bold)$(tput setaf 3)
|
||||
RESET=$(tput sgr0)
|
||||
|
||||
function message() {
|
||||
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
|
||||
}
|
||||
|
||||
message "Compiling shaders..."
|
||||
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
|
||||
|
||||
message "Building crate..."
|
||||
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
|
||||
|
||||
message "Creating NACP..."
|
||||
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.100.0' target/aarch64-nintendo-switch/debug/drshorizon.nacp
|
||||
|
||||
message "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
|
||||
|
||||
message "done!"
|
33
drshorizon/build_release.sh
Executable file
33
drshorizon/build_release.sh
Executable file
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")" || exit
|
||||
set -e
|
||||
|
||||
DARK_GRAY=$(tput setaf 8)
|
||||
YELLOW=$(tput bold)$(tput setaf 3)
|
||||
RESET=$(tput sgr0)
|
||||
|
||||
function message() {
|
||||
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
|
||||
}
|
||||
|
||||
message "Compiling shaders..."
|
||||
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
|
||||
|
||||
message "Building crate..."
|
||||
rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json --release
|
||||
|
||||
rm -f target/aarch64-nintendo-switch/release/drshorizon.nro
|
||||
rm -f target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "Creating NACP..."
|
||||
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.100.0' target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "Running elf2nro..."
|
||||
elf2nro target/aarch64-nintendo-switch/release/drshorizon.elf target/aarch64-nintendo-switch/release/drshorizon.nro \
|
||||
--icon=../res/nx_icon.jpg \
|
||||
--nacp=target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "done!"
|
47
drshorizon/src/main.rs
Normal file
47
drshorizon/src/main.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
//#![feature(restricted_std)]
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PrintConsole {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ApmCpuBoostMode {
|
||||
Normal = 0,
|
||||
FastLoad = 1,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn consoleInit(unk: *mut PrintConsole) -> *mut PrintConsole;
|
||||
fn consoleUpdate(unk: *mut PrintConsole);
|
||||
|
||||
fn socketInitialize(unk: *const std::ffi::c_void) -> u32;
|
||||
fn nxlinkConnectToHost(redir_stdout: bool, redir_stderr: bool) -> i32;
|
||||
|
||||
fn appletSetCpuBoostMode(mode: ApmCpuBoostMode) -> u32;
|
||||
|
||||
static __text_start: u32;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
// if socketInitialize(std::ptr::null()) == 0 {
|
||||
// nxlinkConnectToHost(true, true);
|
||||
// }
|
||||
|
||||
// appletSetCpuBoostMode(ApmCpuBoostMode::FastLoad);
|
||||
|
||||
std::env::set_var("RUST_BACKTRACE", "full");
|
||||
|
||||
println!("__text_start = {:#x}", (&__text_start) as *const _ as usize);
|
||||
|
||||
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 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
drshorizon/symbolize.js
Normal file
51
drshorizon/symbolize.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
// <reference types="node" />
|
||||
|
||||
const readline = require('readline');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
terminal: false
|
||||
});
|
||||
|
||||
let textStart = 0;
|
||||
const textStartRegex = /__text_start = 0x([0-9a-f]+)/i;
|
||||
let symbolize = false;
|
||||
|
||||
if (process.argv.length <= 2) {
|
||||
console.error('Usage: node symbolize.js <path to ELF file>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const elfPath = process.argv[2];
|
||||
|
||||
rl.on('line', (line) => {
|
||||
if (textStart === 0) {
|
||||
const match = textStartRegex.exec(line);
|
||||
if (match) {
|
||||
textStart = parseInt(match[1], 16);
|
||||
}
|
||||
}
|
||||
|
||||
if (line.includes("stack backtrace:")) {
|
||||
symbolize = true;
|
||||
}
|
||||
|
||||
if (symbolize) {
|
||||
const match = /0x([0-9a-f]+) - \<unknown\>/.exec(line);
|
||||
if (match) {
|
||||
const addr = parseInt(match[1], 16);
|
||||
const relative = addr - textStart;
|
||||
// run addr2line on the address
|
||||
const addr2line = childProcess.spawnSync('addr2line', ['-e', elfPath, '-j', '.text', '-f', '-C', '0x' + relative.toString(16)]);
|
||||
if (addr2line.status === 0) {
|
||||
const output = addr2line.stdout.toString();
|
||||
const lines = output.split('\n');
|
||||
const [func, file] = lines;
|
||||
line = line.replace(match[0], `0x${addr.toString(16)} - ${func} (${file})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(line);
|
||||
});
|
BIN
res/nx_icon.jpg
Normal file
BIN
res/nx_icon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
BIN
res/nx_icon.png
Normal file
BIN
res/nx_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
|
@ -1,4 +1,4 @@
|
|||
edition = "2018"
|
||||
edition = "2021"
|
||||
max_width = 120
|
||||
use_small_heuristics = "Max"
|
||||
newline_style = "Unix"
|
||||
|
|
|
@ -107,15 +107,20 @@ pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn
|
|||
return crate::framework::backend_null::NullBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend-horizon"))]
|
||||
{
|
||||
return crate::framework::backend_horizon::HorizonBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend-glutin"))]
|
||||
{
|
||||
return crate::framework::backend_glutin::GlutinBackend::new();
|
||||
}
|
||||
{
|
||||
return crate::framework::backend_glutin::GlutinBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend-sdl")]
|
||||
{
|
||||
return crate::framework::backend_sdl2::SDL2Backend::new(size_hint);
|
||||
}
|
||||
{
|
||||
return crate::framework::backend_sdl2::SDL2Backend::new(size_hint);
|
||||
}
|
||||
|
||||
log::warn!("No backend compiled in, using null backend instead.");
|
||||
crate::framework::backend_null::NullBackend::new()
|
||||
|
|
1378
src/framework/backend_horizon.rs
Normal file
1378
src/framework/backend_horizon.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,6 +3,8 @@
|
|||
pub mod backend;
|
||||
#[cfg(feature = "backend-glutin")]
|
||||
pub mod backend_glutin;
|
||||
#[cfg(feature = "backend-horizon")]
|
||||
pub mod backend_horizon;
|
||||
pub mod backend_null;
|
||||
#[cfg(feature = "backend-sdl")]
|
||||
pub mod backend_sdl2;
|
||||
|
|
|
@ -301,149 +301,14 @@ fn check_shader_compile_status(shader: u32, gl: &Gl) -> GameResult {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
const VERTEX_SHADER_BASIC: &str = r"
|
||||
#version 110
|
||||
const VERTEX_SHADER_BASIC: &str = include_str!("shaders/opengl/vertex_basic_110.glsl");
|
||||
const FRAGMENT_SHADER_TEXTURED: &str = include_str!("shaders/opengl/fragment_textured_110.glsl");
|
||||
const FRAGMENT_SHADER_COLOR: &str = include_str!("shaders/opengl/fragment_color_110.glsl");
|
||||
const FRAGMENT_SHADER_WATER: &str = include_str!("shaders/opengl/fragment_water_110.glsl");
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
attribute vec2 Position;
|
||||
attribute vec2 UV;
|
||||
attribute vec4 Color;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
const FRAGMENT_SHADER_TEXTURED: &str = r"
|
||||
#version 110
|
||||
|
||||
uniform sampler2D Texture;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
const FRAGMENT_SHADER_COLOR: &str = r"
|
||||
#version 110
|
||||
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color;
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
const FRAGMENT_SHADER_WATER: &str = r"
|
||||
#version 110
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
uniform sampler2D Texture;
|
||||
uniform float Time;
|
||||
uniform float Scale;
|
||||
uniform vec2 FrameOffset;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 resolution_inv = vec2(ProjMtx[0][0], -ProjMtx[1][1]) * 0.5;
|
||||
vec2 uv = gl_FragCoord.xy * resolution_inv;
|
||||
uv.y += 1.0;
|
||||
vec2 wave = uv;
|
||||
wave.x += sin((-FrameOffset.y * resolution_inv.y + uv.x * 16.0) + Time / 20.0) * Scale * resolution_inv.x;
|
||||
wave.y -= cos((-FrameOffset.x * resolution_inv.x + uv.y * 16.0) + Time / 5.0) * Scale * resolution_inv.y;
|
||||
float off = 0.35 * Scale * resolution_inv.y;
|
||||
float off2 = 2.0 * off;
|
||||
|
||||
vec3 color = texture2D(Texture, wave).rgb * 0.25;
|
||||
color += texture2D(Texture, wave + vec2(0, off)).rgb * 0.125;
|
||||
color += texture2D(Texture, wave + vec2(0, -off)).rgb * 0.125;
|
||||
|
||||
color.rg += texture2D(Texture, wave + vec2(-off, -off)).rg * 0.0625;
|
||||
color.rg += texture2D(Texture, wave + vec2(-off, 0)).rg * 0.125;
|
||||
color.rg += texture2D(Texture, wave + vec2(-off, off)).rg * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(-off2, -off)).b * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(-off2, 0)).b * 0.125;
|
||||
color.b += texture2D(Texture, wave + vec2(-off2, off)).b * 0.0625;
|
||||
|
||||
color.rg += texture2D(Texture, wave + vec2(off, off)).gb * 0.0625;
|
||||
color.rg += texture2D(Texture, wave + vec2(off, 0)).gb * 0.125;
|
||||
color.rg += texture2D(Texture, wave + vec2(off, -off)).gb * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(off2, off)).r * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(off2, 0)).r * 0.125;
|
||||
color.b += texture2D(Texture, wave + vec2(off2, -off)).r * 0.0625;
|
||||
|
||||
color *= (1.0 - Frag_Color.a);
|
||||
color += Frag_Color.rgb * Frag_Color.a;
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
const VERTEX_SHADER_BASIC_GLES: &str = r"
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
attribute vec2 Position;
|
||||
attribute vec2 UV;
|
||||
attribute vec4 Color;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
const FRAGMENT_SHADER_TEXTURED_GLES: &str = r"
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D Texture;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
const FRAGMENT_SHADER_COLOR_GLES: &str = r"
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color;
|
||||
}
|
||||
|
||||
";
|
||||
const VERTEX_SHADER_BASIC_GLES: &str = include_str!("shaders/opengles/vertex_basic_100.glsl");
|
||||
const FRAGMENT_SHADER_TEXTURED_GLES: &str = include_str!("shaders/opengles/fragment_textured_100.glsl");
|
||||
const FRAGMENT_SHADER_COLOR_GLES: &str = include_str!("shaders/opengles/fragment_color_100.glsl");
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct RenderShader {
|
||||
|
|
11
src/framework/shaders/deko3d/fragment_color.glsl
Normal file
11
src/framework/shaders/deko3d/fragment_color.glsl
Normal file
|
@ -0,0 +1,11 @@
|
|||
#version 460
|
||||
|
||||
layout (location = 0) in vec2 Frag_UV;
|
||||
layout (location = 1) in vec4 Frag_Color;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColor = Frag_Color;
|
||||
}
|
13
src/framework/shaders/deko3d/fragment_textured.glsl
Normal file
13
src/framework/shaders/deko3d/fragment_textured.glsl
Normal file
|
@ -0,0 +1,13 @@
|
|||
#version 460
|
||||
|
||||
layout (location = 0) in vec2 Frag_UV;
|
||||
layout (location = 1) in vec4 Frag_Color;
|
||||
|
||||
layout (binding = 0) uniform sampler2D Texture;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColor = Frag_Color * texture(Texture, Frag_UV);
|
||||
}
|
20
src/framework/shaders/deko3d/vertex_basic.glsl
Normal file
20
src/framework/shaders/deko3d/vertex_basic.glsl
Normal file
|
@ -0,0 +1,20 @@
|
|||
#version 460
|
||||
|
||||
layout (location = 0) in vec2 Position;
|
||||
layout (location = 1) in vec2 UV;
|
||||
layout (location = 2) in vec4 Color;
|
||||
|
||||
layout (location = 0) out vec2 Frag_UV;
|
||||
layout (location = 1) out vec4 Frag_Color;
|
||||
|
||||
layout (std140, binding = 0) uniform VertUBO
|
||||
{
|
||||
mat4 proj;
|
||||
} ProjMtx;
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx.proj * vec4(Position.xy, 0.0, 1.0);
|
||||
}
|
9
src/framework/shaders/opengl/fragment_color_110.glsl
Normal file
9
src/framework/shaders/opengl/fragment_color_110.glsl
Normal file
|
@ -0,0 +1,9 @@
|
|||
#version 110
|
||||
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color;
|
||||
}
|
10
src/framework/shaders/opengl/fragment_textured_110.glsl
Normal file
10
src/framework/shaders/opengl/fragment_textured_110.glsl
Normal file
|
@ -0,0 +1,10 @@
|
|||
#version 110
|
||||
|
||||
uniform sampler2D Texture;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
|
||||
}
|
42
src/framework/shaders/opengl/fragment_water_110.glsl
Normal file
42
src/framework/shaders/opengl/fragment_water_110.glsl
Normal file
|
@ -0,0 +1,42 @@
|
|||
#version 110
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
uniform sampler2D Texture;
|
||||
uniform float Time;
|
||||
uniform float Scale;
|
||||
uniform vec2 FrameOffset;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 resolution_inv = vec2(ProjMtx[0][0], -ProjMtx[1][1]) * 0.5;
|
||||
vec2 uv = gl_FragCoord.xy * resolution_inv;
|
||||
uv.y += 1.0;
|
||||
vec2 wave = uv;
|
||||
wave.x += sin((-FrameOffset.y * resolution_inv.y + uv.x * 16.0) + Time / 20.0) * Scale * resolution_inv.x;
|
||||
wave.y -= cos((-FrameOffset.x * resolution_inv.x + uv.y * 16.0) + Time / 5.0) * Scale * resolution_inv.y;
|
||||
float off = 0.35 * Scale * resolution_inv.y;
|
||||
float off2 = 2.0 * off;
|
||||
|
||||
vec3 color = texture2D(Texture, wave).rgb * 0.25;
|
||||
color += texture2D(Texture, wave + vec2(0, off)).rgb * 0.125;
|
||||
color += texture2D(Texture, wave + vec2(0, -off)).rgb * 0.125;
|
||||
|
||||
color.rg += texture2D(Texture, wave + vec2(-off, -off)).rg * 0.0625;
|
||||
color.rg += texture2D(Texture, wave + vec2(-off, 0)).rg * 0.125;
|
||||
color.rg += texture2D(Texture, wave + vec2(-off, off)).rg * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(-off2, -off)).b * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(-off2, 0)).b * 0.125;
|
||||
color.b += texture2D(Texture, wave + vec2(-off2, off)).b * 0.0625;
|
||||
|
||||
color.rg += texture2D(Texture, wave + vec2(off, off)).gb * 0.0625;
|
||||
color.rg += texture2D(Texture, wave + vec2(off, 0)).gb * 0.125;
|
||||
color.rg += texture2D(Texture, wave + vec2(off, -off)).gb * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(off2, off)).r * 0.0625;
|
||||
color.b += texture2D(Texture, wave + vec2(off2, 0)).r * 0.125;
|
||||
color.b += texture2D(Texture, wave + vec2(off2, -off)).r * 0.0625;
|
||||
|
||||
color *= (1.0 - Frag_Color.a);
|
||||
color += Frag_Color.rgb * Frag_Color.a;
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
15
src/framework/shaders/opengl/vertex_basic_110.glsl
Normal file
15
src/framework/shaders/opengl/vertex_basic_110.glsl
Normal file
|
@ -0,0 +1,15 @@
|
|||
#version 110
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
attribute vec2 Position;
|
||||
attribute vec2 UV;
|
||||
attribute vec4 Color;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
|
||||
}
|
11
src/framework/shaders/opengles/fragment_color_100.glsl
Normal file
11
src/framework/shaders/opengles/fragment_color_100.glsl
Normal file
|
@ -0,0 +1,11 @@
|
|||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color;
|
||||
}
|
12
src/framework/shaders/opengles/fragment_textured_100.glsl
Normal file
12
src/framework/shaders/opengles/fragment_textured_100.glsl
Normal file
|
@ -0,0 +1,12 @@
|
|||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D Texture;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
|
||||
}
|
17
src/framework/shaders/opengles/vertex_basic_100.glsl
Normal file
17
src/framework/shaders/opengles/vertex_basic_100.glsl
Normal file
|
@ -0,0 +1,17 @@
|
|||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
attribute vec2 Position;
|
||||
attribute vec2 UV;
|
||||
attribute vec4 Color;
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
|
||||
}
|
|
@ -128,7 +128,7 @@ pub trait VFS: Debug {
|
|||
fn metadata(&self, path: &Path) -> GameResult<Box<dyn VMetadata>>;
|
||||
|
||||
/// Retrieve all file and directory entries in the given directory.
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>>;
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>>;
|
||||
|
||||
/// Retrieve the actual location of the VFS root, if available.
|
||||
fn to_path_buf(&self) -> Option<PathBuf>;
|
||||
|
@ -156,6 +156,7 @@ pub trait VMetadata {
|
|||
pub struct PhysicalFS {
|
||||
root: PathBuf,
|
||||
readonly: bool,
|
||||
lowercase: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -214,7 +215,11 @@ fn sanitize_path(path: &path::Path) -> Option<PathBuf> {
|
|||
impl PhysicalFS {
|
||||
/// Creates a new PhysicalFS
|
||||
pub fn new(root: &Path, readonly: bool) -> Self {
|
||||
PhysicalFS { root: root.into(), readonly }
|
||||
PhysicalFS { root: root.into(), readonly, lowercase: false }
|
||||
}
|
||||
|
||||
pub fn new_lowercase(root: &Path) -> Self {
|
||||
PhysicalFS { root: root.into(), readonly: true, lowercase: true }
|
||||
}
|
||||
|
||||
/// Takes a given path (&str) and returns
|
||||
|
@ -222,7 +227,11 @@ impl PhysicalFS {
|
|||
/// absolute path you get when appending it
|
||||
/// to this filesystem's root.
|
||||
fn to_absolute(&self, p: &Path) -> GameResult<PathBuf> {
|
||||
if let Some(safe_path) = sanitize_path(p) {
|
||||
if let Some(mut safe_path) = sanitize_path(p) {
|
||||
if self.lowercase {
|
||||
safe_path = PathBuf::from(p.to_string_lossy().to_lowercase())
|
||||
}
|
||||
|
||||
let mut root_path = self.root.clone();
|
||||
root_path.push(safe_path.clone());
|
||||
|
||||
|
@ -376,7 +385,7 @@ impl VFS for PhysicalFS {
|
|||
}
|
||||
|
||||
/// Retrieve the path entries in this path
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>> {
|
||||
self.create_root()?;
|
||||
let p = self.to_absolute(path)?;
|
||||
// This is inconvenient because path() returns the full absolute
|
||||
|
@ -511,7 +520,7 @@ impl VFS for OverlayFS {
|
|||
}
|
||||
|
||||
/// Retrieve the path entries in this path
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>> {
|
||||
// This is tricky 'cause we have to actually merge iterators together...
|
||||
// Doing it the simple and stupid way works though.
|
||||
let mut v = Vec::new();
|
||||
|
|
130
src/game/mod.rs
130
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)));
|
||||
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(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,36 +278,54 @@ 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)));
|
||||
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(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");
|
||||
#[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)));
|
||||
}
|
||||
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()));
|
||||
|
||||
if options.server_mode {
|
||||
|
@ -315,15 +333,15 @@ pub fn init(options: LaunchOptions) -> GameResult {
|
|||
context.headless = true;
|
||||
}
|
||||
|
||||
let game = UnsafeCell::new(Game::new(&mut context)?);
|
||||
let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) };
|
||||
let mut game = Box::pin(Game::new(&mut context)?);
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
{
|
||||
state_ref.lua.update_refs(unsafe { (&*game.get()).state.get() }, &mut context as *mut Context);
|
||||
}
|
||||
{
|
||||
game.state.get().lua.update_refs(unsafe { &mut *game.state.get() }, &mut context as *mut Context);
|
||||
}
|
||||
|
||||
state_ref.next_scene = Some(Box::new(LoadingScene::new()));
|
||||
context.run(unsafe { &mut *game.get() })?;
|
||||
game.state.get_mut().next_scene = Some(Box::new(LoadingScene::new()));
|
||||
log::info!("Starting main loop...");
|
||||
context.run(game.as_mut().get_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ pub struct Settings {
|
|||
pub pause_on_focus_loss: bool,
|
||||
#[serde(default = "default_interpolation")]
|
||||
pub organya_interpolation: InterpolationMode,
|
||||
#[serde(default = "default_controller_type")]
|
||||
#[serde(default = "default_p1_controller_type")]
|
||||
pub player1_controller_type: ControllerType,
|
||||
#[serde(default = "default_controller_type")]
|
||||
#[serde(default = "default_p2_controller_type")]
|
||||
pub player2_controller_type: ControllerType,
|
||||
#[serde(default = "p1_default_keymap")]
|
||||
pub player1_key_map: PlayerKeyMap,
|
||||
|
@ -133,8 +133,21 @@ fn default_screen_shake_intensity() -> ScreenShakeIntensity {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn default_controller_type() -> ControllerType {
|
||||
ControllerType::Keyboard
|
||||
fn default_p1_controller_type() -> ControllerType {
|
||||
if cfg!(any(target_os = "horizon")) {
|
||||
ControllerType::Gamepad(0)
|
||||
} else {
|
||||
ControllerType::Keyboard
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn default_p2_controller_type() -> ControllerType {
|
||||
if cfg!(any(target_os = "horizon")) {
|
||||
ControllerType::Gamepad(1)
|
||||
} else {
|
||||
ControllerType::Keyboard
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -216,8 +229,8 @@ impl Settings {
|
|||
|
||||
if self.version == 11 {
|
||||
self.version = 12;
|
||||
self.player1_controller_type = default_controller_type();
|
||||
self.player2_controller_type = default_controller_type();
|
||||
self.player1_controller_type = default_p1_controller_type();
|
||||
self.player2_controller_type = default_p2_controller_type();
|
||||
self.player1_controller_button_map = player_default_controller_button_map();
|
||||
self.player2_controller_button_map = player_default_controller_button_map();
|
||||
self.player1_controller_axis_sensitivity = default_controller_axis_sensitivity();
|
||||
|
@ -396,8 +409,8 @@ impl Default for Settings {
|
|||
timing_mode: default_timing(),
|
||||
pause_on_focus_loss: default_pause_on_focus_loss(),
|
||||
organya_interpolation: InterpolationMode::Linear,
|
||||
player1_controller_type: default_controller_type(),
|
||||
player2_controller_type: default_controller_type(),
|
||||
player1_controller_type: default_p1_controller_type(),
|
||||
player2_controller_type: default_p2_controller_type(),
|
||||
player1_key_map: p1_default_keymap(),
|
||||
player2_key_map: p2_default_keymap(),
|
||||
player1_controller_button_map: player_default_controller_button_map(),
|
||||
|
|
|
@ -345,12 +345,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//#![cfg_attr(target_os = "horizon", feature(restricted_std))]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate strum;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::common::Color;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::graphics;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::graphics::font::Font;
|
||||
use crate::scene::Scene;
|
||||
|
@ -29,6 +31,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 +42,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();
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +51,8 @@ impl Scene for NoDataScene {
|
|||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
graphics::clear(ctx, Color::from_rgb(30, 0, 0));
|
||||
|
||||
state.font.builder().center(state.canvas_size.0).y(10.0).color((255, 100, 100, 255)).draw(
|
||||
"doukutsu-rs internal error",
|
||||
ctx,
|
||||
|
@ -62,36 +67,57 @@ impl Scene for NoDataScene {
|
|||
&mut state.texture_set,
|
||||
)?;
|
||||
|
||||
let mut y = 60.0;
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
let yellow = (255, 255, 0, 255);
|
||||
state.font.builder().center(state.canvas_size.0).y(60.0).color(yellow).draw(
|
||||
state.font.builder().center(state.canvas_size.0).y(y).color(yellow).draw(
|
||||
"It's likely that you haven't extracted the game data properly.",
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
)?;
|
||||
state.font.builder().center(state.canvas_size.0).y(80.0).color(yellow).draw(
|
||||
y += 20.0;
|
||||
state.font.builder().center(state.canvas_size.0).y(y).color(yellow).draw(
|
||||
"Click here to open the guide.",
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
)?;
|
||||
state.font.builder().center(state.canvas_size.0).y(100.0).color(yellow).draw(
|
||||
y += 20.0;
|
||||
state.font.builder().center(state.canvas_size.0).y(y).color(yellow).draw(
|
||||
REL_URL,
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
)?;
|
||||
y += 20.0;
|
||||
}
|
||||
|
||||
{
|
||||
state.font.builder().center(state.canvas_size.0).y(140.0).draw(
|
||||
&self.err,
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
)?;
|
||||
// put max 80 chars per line
|
||||
let mut lines = Vec::new();
|
||||
let mut line = String::new();
|
||||
|
||||
for word in self.err.split(' ') {
|
||||
if line.len() + word.len() > 80 {
|
||||
lines.push(line);
|
||||
line = String::new();
|
||||
}
|
||||
line.push_str(word);
|
||||
line.push(' ');
|
||||
}
|
||||
lines.push(line);
|
||||
|
||||
for line in lines {
|
||||
state.font.builder().center(state.canvas_size.0).y(y).draw(
|
||||
&line,
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
)?;
|
||||
y += 20.0;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
13
src/util/browser.rs
Normal file
13
src/util/browser.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
#[cfg(feature = "webbrowser")]
|
||||
pub use webbrowser::open;
|
||||
|
||||
// stub for platforms webbrowser doesn't support, such as Horizon OS
|
||||
#[cfg(not(any(feature = "webbrowser", target_os = "horizon")))]
|
||||
pub fn open(_url: &str) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "horizon")]
|
||||
pub fn open(url: &str) -> std::io::Result<()> {
|
||||
crate::framework::backend_horizon::web_open(url)
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod bitvec;
|
||||
pub mod encoding;
|
||||
pub mod rng;
|
||||
pub mod browser;
|
||||
|
|
Loading…
Reference in a new issue