Desktop build: settings and progress kept as files, savegames in a saves folder, system file dialog for loading

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 23:46:11 +01:00
1 parent 1df97b4af2
commit db521ed2ee
4 files changed
+413 -18

No files matched your search

Generated
+274 -1
View File
@@ -26,6 +26,27 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.25.2" version = "1.25.2"
@@ -68,11 +89,24 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.13.2",
"block2",
"libc",
"objc2",
]
[[package]] [[package]]
name = "dungeoncraft" name = "dungeoncraft"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"macroquad", "macroquad",
"rfd",
"serde", "serde",
"serde_json", "serde_json",
] ]
@@ -119,6 +153,30 @@ dependencies = [
"ttf-parser", "ttf-parser",
] ]
[[package]]
name = "futures-core"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-task"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]] [[package]]
name = "glam" name = "glam"
version = "0.27.0" version = "0.27.0"
@@ -155,6 +213,17 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.191" version = "0.2.191"
@@ -167,6 +236,12 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "log"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
[[package]] [[package]]
name = "macroquad" name = "macroquad"
version = "0.4.16" version = "0.4.16"
@@ -258,19 +333,86 @@ dependencies = [
"malloc_buf", "malloc_buf",
] ]
[[package]]
name = "objc2"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.13.2",
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.13.2",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.13.2",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]] [[package]]
name = "png" name = "png"
version = "0.17.16" version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"crc32fast", "crc32fast",
"fdeflate", "fdeflate",
"flate2", "flate2",
"miniz_oxide 0.8.9", "miniz_oxide 0.8.9",
] ]
[[package]]
name = "pollster"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.107" version = "1.0.107"
@@ -295,6 +437,42 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "raw-window-handle"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "rfd"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
dependencies = [
"block2",
"dispatch2",
"js-sys",
"libc",
"log",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation",
"percent-encoding",
"pollster",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-sys",
]
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.229" version = "1.0.229"
@@ -344,6 +522,12 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.9" version = "3.0.9"
@@ -355,6 +539,15 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tokio"
version = "1.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
dependencies = [
"pin-project-lite",
]
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.25.1" version = "0.25.1"
@@ -370,6 +563,71 @@ version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "wasm-bindgen"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c"
dependencies = [
"cfg-if",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501"
dependencies = [
"js-sys",
"tokio",
"wasm-bindgen",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@@ -392,6 +650,21 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]] [[package]]
name = "zlib-rs" name = "zlib-rs"
version = "0.6.8" version = "0.6.8"
+4
View File
@@ -17,3 +17,7 @@ codegen-units = 1
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1
# desktop builds: system file dialog (xdg portal on Linux) and sound output
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rfd = { version = "0.17", default-features = false, features = ["xdg-portal"] }
+7 -3
View File
@@ -1497,7 +1497,7 @@ async fn main() {
MenuAction::Save | MenuAction::FileSave => { MenuAction::Save | MenuAction::FileSave => {
let name = save_file_name(); let name = save_file_name();
platform::download(&name, app.autosave_json().as_bytes()); platform::download(&name, app.autosave_json().as_bytes());
app.set_status(i18n::trf("save.saved", &[name]), now); app.set_status(i18n::trf("save.saved", &[platform::saved_as(&name)]), now);
} }
MenuAction::CloudSave(id, name) => { MenuAction::CloudSave(id, name) => {
if cloud.save(id.as_deref(), &name, &app.autosave_json()) { if cloud.save(id.as_deref(), &name, &app.autosave_json()) {
@@ -1544,7 +1544,7 @@ async fn main() {
MenuAction::CampaignSave => { MenuAction::CampaignSave => {
let name = menu.campaign_doc.file_name(); let name = menu.campaign_doc.file_name();
platform::download(&name, menu.campaign_doc.save().as_bytes()); platform::download(&name, menu.campaign_doc.save().as_bytes());
app.set_status(i18n::trf("campaign.saved", &[name]), now); app.set_status(i18n::trf("campaign.saved", &[platform::saved_as(&name)]), now);
} }
MenuAction::CampaignEditName => { MenuAction::CampaignEditName => {
if let Some(value) = prompt_line(&i18n::tr("campaign.prompt_name"), &menu.campaign_doc.name, 60) { if let Some(value) = prompt_line(&i18n::tr("campaign.prompt_name"), &menu.campaign_doc.name, 60) {
@@ -2349,7 +2349,11 @@ app.pending_briefing = briefing_of(&ed.doc, i);
ui.show_help = false; ui.show_help = false;
r3d.invalidate(); r3d.invalidate();
} }
editor::EdAction::SaveFile => platform::download(&ed.doc.file_name(), ed.doc.save().as_bytes()), editor::EdAction::SaveFile => {
let name = ed.doc.file_name();
platform::download(&name, ed.doc.save().as_bytes());
ed.set_status(i18n::trf("campaign.saved", &[platform::saved_as(&name)]));
}
editor::EdAction::OpenFile => platform::open_file(), editor::EdAction::OpenFile => platform::open_file(),
editor::EdAction::ServerSave => { editor::EdAction::ServerSave => {
if !cloud.available() { if !cloud.available() {
+128 -14
View File
@@ -1,7 +1,7 @@
// Browser integration via our own miniquad JS plugin (www/dc_plugin.js): // Browser integration via our own miniquad JS plugin (www/dc_plugin.js):
// persistent key/value storage (localStorage), a cache that is written to storage when the // persistent key/value storage (localStorage), a cache that is written to storage when the
// page is hidden or closed, file download, file upload and HTTP requests. // page is hidden or closed, file download, file upload and HTTP requests.
// Native builds use simple fallbacks. // Native builds keep the storage as files in the data directory (see imp below).
// Result of an HTTP request: status 0 means the service could not be reached // Result of an HTTP request: status 0 means the service could not be reached
pub struct HttpResult { pub struct HttpResult {
@@ -113,6 +113,11 @@ mod imp {
unsafe { dc_cache_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) } unsafe { dc_cache_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) }
} }
// where download() put a file, for the status line: the browser's download folder
pub fn saved_as(name: &str) -> String {
name.to_string()
}
pub fn download(name: &str, data: &[u8]) { pub fn download(name: &str, data: &[u8]) {
unsafe { dc_download(name.as_ptr(), name.len() as u32, data.as_ptr(), data.len() as u32) } unsafe { dc_download(name.as_ptr(), name.len() as u32, data.as_ptr(), data.len() as u32) }
} }
@@ -188,14 +193,84 @@ mod imp {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
mod imp { mod imp {
use std::cell::RefCell; // Desktop builds: the storage keys are files in the data directory, savegames and
// campaigns are written to its "saves" folder, the system file dialog opens files.
use std::collections::HashMap; use std::collections::HashMap;
thread_local! { use std::path::PathBuf;
static STORE: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new()); use std::sync::Mutex;
static STORE: Mutex<Option<HashMap<String, String>>> = Mutex::new(None);
static PICKED: Mutex<Option<Vec<u8>>> = Mutex::new(None);
static PICKING: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
// DUNGEONCRAFT_DATA, else the usual per-user application data folder
pub fn data_dir() -> PathBuf {
let env = |k: &str| std::env::var_os(k).filter(|v| !v.is_empty()).map(PathBuf::from);
if let Some(d) = env("DUNGEONCRAFT_DATA") {
return d;
} }
if cfg!(target_os = "windows") {
if let Some(d) = env("APPDATA") {
return d.join("DungeonCraft");
}
} else if cfg!(target_os = "macos") {
if let Some(h) = env("HOME") {
return h.join("Library/Application Support/DungeonCraft");
}
} else if let Some(d) = env("XDG_DATA_HOME").or_else(|| env("HOME").map(|h| h.join(".local/share"))) {
return d.join("dungeoncraft");
}
PathBuf::from("dungeoncraft-data")
}
pub fn saves_dir() -> PathBuf {
data_dir().join("saves")
}
// a storage key as a file name: only harmless characters
pub(super) fn key_file(key: &str) -> String {
let name: String = key.chars().map(|c| if c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_') { c } else { '_' }).collect();
if name.is_empty() || name.starts_with('.') {
format!("_{}", name)
} else {
name
}
}
// writes through a temporary file, so a crash never leaves half a file
fn write_file(path: &std::path::Path, data: &[u8]) -> std::io::Result<()> {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir)?;
}
let tmp = path.with_extension("tmp");
std::fs::write(&tmp, data)?;
std::fs::rename(&tmp, path)
}
pub fn storage_get(key: &str) -> Option<String> { pub fn storage_get(key: &str) -> Option<String> {
STORE.with(|s| s.borrow().get(key).cloned()) let mut store = STORE.lock().unwrap_or_else(|e| e.into_inner());
let store = store.get_or_insert_with(HashMap::new);
if let Some(v) = store.get(key) {
return Some(v.clone());
} }
let v = std::fs::read_to_string(data_dir().join("store").join(key_file(key))).ok()?;
store.insert(key.to_string(), v.clone());
Some(v)
}
pub fn storage_set(key: &str, val: &str) {
STORE.lock().unwrap_or_else(|e| e.into_inner()).get_or_insert_with(HashMap::new).insert(key.to_string(), val.to_string());
let path = data_dir().join("store").join(key_file(key));
if let Err(e) = write_file(&path, val.as_bytes()) {
eprintln!("DungeonCraft: cannot write {}: {}", path.display(), e);
}
}
// there is no "page left" moment on the desktop: write at once
pub fn cache_on_leave(key: &str, val: &str) {
storage_set(key, val);
}
pub fn prompt(_title: &str, _default: &str) -> Option<String> { pub fn prompt(_title: &str, _default: &str) -> Option<String> {
None None
} }
@@ -208,19 +283,47 @@ mod imp {
pub fn fetch_download_state() -> i32 { pub fn fetch_download_state() -> i32 {
0 0
} }
pub fn storage_set(key: &str, val: &str) {
STORE.with(|s| s.borrow_mut().insert(key.to_string(), val.to_string())); // a "download" lands in the saves folder
}
pub fn cache_on_leave(key: &str, val: &str) {
storage_set(key, val);
}
pub fn download(name: &str, data: &[u8]) { pub fn download(name: &str, data: &[u8]) {
let _ = std::fs::write(name, data); let path = saves_dir().join(key_file(name));
if let Err(e) = write_file(&path, data) {
eprintln!("DungeonCraft: cannot write {}: {}", path.display(), e);
} }
pub fn open_file() {} }
// where download() put a file, for the status line
pub fn saved_as(name: &str) -> String {
saves_dir().join(key_file(name)).display().to_string()
}
fn pick() {
let dir = saves_dir();
let _ = std::fs::create_dir_all(&dir);
let file = rfd::FileDialog::new().set_directory(&dir).pick_file();
if let Some(bytes) = file.and_then(|f| std::fs::read(f).ok()) {
*PICKED.lock().unwrap_or_else(|e| e.into_inner()) = Some(bytes);
}
PICKING.store(false, std::sync::atomic::Ordering::SeqCst);
}
// opens the system file dialog (one at a time); poll with take_file()
pub fn open_file() {
if PICKING.swap(true, std::sync::atomic::Ordering::SeqCst) {
return;
}
// macOS dialogs must run on the main thread; elsewhere the game keeps running
if cfg!(target_os = "macos") {
pick();
} else if std::thread::Builder::new().name("file dialog".into()).spawn(pick).is_err() {
PICKING.store(false, std::sync::atomic::Ordering::SeqCst);
}
}
pub fn take_file() -> Option<Vec<u8>> { pub fn take_file() -> Option<Vec<u8>> {
None PICKED.lock().unwrap_or_else(|e| e.into_inner()).take()
} }
// no savegame service in native builds: every request fails like an unreachable server // no savegame service in native builds: every request fails like an unreachable server
pub fn http(_method: &str, _url: &str, _body: &[u8]) -> u32 { pub fn http(_method: &str, _url: &str, _body: &[u8]) -> u32 {
0 0
@@ -270,3 +373,14 @@ pub use imp::*;
pub extern "C" fn dungeoncraft_crate_version() -> u32 { pub extern "C" fn dungeoncraft_crate_version() -> u32 {
1 1
} }
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
#[test]
fn storage_keys_become_harmless_file_names() {
assert_eq!(super::imp::key_file("dc.settings"), "dc.settings");
assert_eq!(super::imp::key_file("../etc/passwd"), "_.._etc_passwd");
assert_eq!(super::imp::key_file("a b/c"), "a_b_c");
assert_eq!(super::imp::key_file(""), "_");
}
}