From aa802d709e6a6b8861759b10293a0b082d886f1b Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 22:25:54 +0200 Subject: [PATCH 01/19] Decompose app::run into game parsing, installation and launching steps --- bdgm/bdgm-play/src/app.rs | 244 ++-------------------------------- bdgm/bdgm-play/src/args.rs | 34 ++++- bdgm/bdgm-play/src/dirs.rs | 57 ++++++++ bdgm/bdgm-play/src/dump.rs | 4 +- bdgm/bdgm-play/src/error.rs | 2 +- bdgm/bdgm-play/src/image.rs | 60 +++++++++ bdgm/bdgm-play/src/install.rs | 23 ++++ bdgm/bdgm-play/src/launch.rs | 110 +++++++++++++++ bdgm/bdgm-play/src/lib.rs | 19 +++ bdgm/bdgm-play/src/main.rs | 4 - bdgm/bdgm-play/src/server.rs | 88 ++++++++++-- 11 files changed, 394 insertions(+), 251 deletions(-) create mode 100644 bdgm/bdgm-play/src/dirs.rs create mode 100644 bdgm/bdgm-play/src/image.rs create mode 100644 bdgm/bdgm-play/src/install.rs create mode 100644 bdgm/bdgm-play/src/launch.rs create mode 100644 bdgm/bdgm-play/src/lib.rs diff --git a/bdgm/bdgm-play/src/app.rs b/bdgm/bdgm-play/src/app.rs index ef780ca..723cbce 100644 --- a/bdgm/bdgm-play/src/app.rs +++ b/bdgm/bdgm-play/src/app.rs @@ -1,248 +1,28 @@ -use std::{collections::HashMap, fs::File, io::Read, process::Command, string::String}; -use bdgm::{ - error::BDGMError, - game::{Game, ValidatedGame}, +use bdgm::game::{Game, ValidatedGame}; +use bdgm_play::{ + args::Args, error::AppError, image::resolve_image_args, install::install, launch::launch_game, + read_manifest, }; use clap::Parser; -use fs_extra::dir::{self, CopyOptions}; -use hadris_udf::UdfVolume; -use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode}; use platform_dirs::AppDirs; -use tempfile::tempdir; - -use crate::{ - args::Args, - dump::extract_udf_dir, - error::AppError, - server::{ - acquire_portlist_lock, create_listener, get_file, get_portlist_file_path, load_ports, - save_ports, serve, - }, -}; - -#[cfg(windows)] -use crate::dump::dump_disc; -#[cfg(windows)] -use tempfile::NamedTempFile; pub(crate) async fn run() -> anyhow::Result<()> { let app_dirs = AppDirs::new(Some("bdgm-play"), true).ok_or(AppError::NoAppDirs)?; - let extract_dir = tempdir()?; - - let args = { - let args = Args::parse(); - println!("Playing {}", args.location.to_string_lossy()); - - #[allow(unused_assignments)] - #[allow(unused_mut)] - let mut raw_disc = false; - #[cfg(windows)] - { - raw_disc = args.raw_disc; - } - - if args.image || raw_disc { - let file = if args.image { - File::open(&args.location)? - } else { - #[cfg(windows)] - { - let dump_file = NamedTempFile::new()?; - dump_disc( - &args.location.to_string_lossy(), - &dump_file.path().to_string_lossy(), - )?; - File::open(dump_file.path())? - } - #[cfg(not(windows))] - { - File::open("")? - } - }; + let args = Args::parse(); + let args = resolve_image_args(args)?; - let udf = UdfVolume::open(file)?; - extract_udf_dir(&udf, &udf.root_dir()?, extract_dir.path())?; - #[cfg(windows)] - { - Args { - location: extract_dir.path().to_path_buf(), - image: false, - raw_disc: false, - runtime: None, - } - } - #[cfg(not(windows))] - { - Args { - location: extract_dir.path().to_path_buf(), - image: false, - runtime: None, - } - } - } else { - args - } - }; + let manifest = read_manifest(&args)?; - let manifest_path = args.location.join("BDGM").join("DISC.BDGM"); - - if !manifest_path.try_exists()? { - return Err(BDGMError::DiscFileMissing.into()); - } - - let mut manifest = File::open(manifest_path)?; - let mut contents = String::new(); - - println!("Reading manifest..."); - manifest.read_to_string(&mut contents)?; - let game = Game::from_str(&contents)?; + let game = Game::from_str(&manifest)?; let game = ValidatedGame::validate(game)?; - let app_dir_path = args.location.join("BDGM").join("APP"); - let executable_str = game.executable(); - let executable_path = app_dir_path.join(&executable_str); - - if !executable_path.try_exists()? { - return Err(AppError::InvalidGameFile(BDGMError::ExecutableMissing( - executable_path.to_string_lossy().into_owned(), - )) - .into()); - } - - let id = game.id(); - let game_dir = app_dirs.data_dir.join(&id); - let cache_dir = app_dirs.cache_dir.join(&id); - let data_dir = game_dir.join("data"); - let install_dir = game_dir.join("app").join(game.version()); - if !install_dir.try_exists()? { - println!("Copying files..."); - dir::create_all(&install_dir, false)?; - dir::copy( - &app_dir_path, - &install_dir, - &CopyOptions::new().overwrite(true).content_only(true), - )?; - println!("Copied!"); - } - - let mut envvars = HashMap::new(); - envvars.insert("BDGM_DATA", data_dir.to_string_lossy().into_owned()); - envvars.insert("XDG_DATA_DIRS", data_dir.to_string_lossy().into_owned()); - envvars.insert("BDGM_CACHE", cache_dir.to_string_lossy().into_owned()); - envvars.insert("BDGM_APP", app_dir_path.to_string_lossy().into_owned()); - envvars.insert("BDGM_DISC", args.location.to_string_lossy().into_owned()); - envvars.insert("BDGM_VERSION", game.bdgm_version().to_string()); + println!("Found: {} version {}", game.name(), game.version()); + install(&game, &app_dirs, &args)?; - let runtime_path = args.runtime.map(|x| x.to_string_lossy().to_string()); - - let runtime = game.runtime(); - - let status = match runtime { - bdgm::runtime::Runtime::Java => Command::new(runtime_path.as_deref().unwrap_or("java")) - .args(game.runtime_args()) - .arg("--jar") - .arg(install_dir.join(executable_str)) - .arg("--") - .args(game.args()) - .envs(envvars) - .current_dir(&install_dir) - .status()?, - bdgm::runtime::Runtime::Dotnet => Command::new(runtime_path.as_deref().unwrap_or("dotnet")) - .args(game.runtime_args()) - .arg(install_dir.join(executable_str)) - .arg("--") - .args(game.args()) - .envs(envvars) - .current_dir(&install_dir) - .status()?, - bdgm::runtime::Runtime::Python => Command::new(runtime_path.as_deref().unwrap_or("python")) - .args(game.runtime_args()) - .arg(install_dir.join(executable_str)) - .arg("--") - .args(game.args()) - .envs(envvars) - .current_dir(&install_dir) - .status()?, - bdgm::runtime::Runtime::HTML => { - println!("Reading saved ports..."); - let lock = acquire_portlist_lock(&app_dirs.data_dir)?; - let mut file = get_file(&get_portlist_file_path(&app_dirs.data_dir))?; - let mut ports = load_ports(&mut file)?; - let port = ports.get_by_left(game.id()); - - let listener = match port { - Some(port) => { - drop(file); - drop(lock); - create_listener(Some(*port)).await? - } - None => { - let mut listener = create_listener(None).await?; - let mut port = listener.local_addr()?.port(); - - let mut tries = 0; - while ports.get_by_right(&&port).is_some() { - listener = create_listener(None).await?; - port = listener.local_addr()?.port(); - - tries += 1; - if tries >= 5000 { - return Err(AppError::CouldNotFindUnclaimedPort.into()); - } - } - - ports.insert(game.id().to_string(), listener.local_addr()?.port()); - println!("Persisting port {}...", listener.local_addr()?.port()); - drop(file); - save_ports(ports, lock, &app_dirs.data_dir)?; - listener - } - }; - - let executable = executable_str.to_string_lossy(); - let encoded = utf8_percent_encode(&executable, &ENCODE_SET); - - let address = format!("http://{}/{}", listener.local_addr()?, encoded); - println!("Opening {address}"); - webbrowser::open(&address)?; - - println!("Running server, press Ctrl + C to stop."); - serve(listener, install_dir).await?; - - std::process::ExitStatus::default() - } - bdgm::runtime::Runtime::Windows => { - if cfg!(target_os = "windows") { - Command::new(install_dir.join(executable_str)) - .args(game.args()) - .envs(envvars) - .current_dir(&install_dir) - .status()? - } else { - Command::new(runtime_path.as_deref().unwrap_or("wine")) - .args(game.runtime_args()) - .arg(install_dir.join(executable_str)) - .args(game.args()) - .envs(envvars) - .env("WINEPREFIX", game_dir.join("wineprefix")) - .current_dir(&install_dir) - .status()? - } - } - }; - if !status.success() { - eprintln!("Your game crashed: {status}"); - eprintln!("Setting a runtime with `--runtime /path/to/runtime` may fix your issue."); - } + println!("Launching..."); + launch_game(&game, &args, &app_dirs, true).await?; Ok(()) } - -const ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC - .remove(b'/') - .remove(b'-') - .remove(b'.') - .remove(b'_') - .remove(b'~'); diff --git a/bdgm/bdgm-play/src/args.rs b/bdgm/bdgm-play/src/args.rs index 7964d83..dc3deaf 100644 --- a/bdgm/bdgm-play/src/args.rs +++ b/bdgm/bdgm-play/src/args.rs @@ -5,7 +5,7 @@ use clap::Parser; /// BDGM disc image builder #[derive(Parser, Debug)] #[command(version, about, long_about = None)] -pub(crate) struct Args { +pub struct Args { /// The mounted root of the game disc. This is the parent of the BDGM directory. pub location: PathBuf, @@ -22,3 +22,35 @@ pub(crate) struct Args { #[arg(long)] pub runtime: Option, } + +impl Args { + pub fn new_imageless(location: PathBuf, runtime: Option) -> Self { + #[cfg(windows)] + { + Args { + location, + image: false, + raw_disc: false, + runtime, + } + } + #[cfg(not(windows))] + { + Args { + location, + image: false, + runtime, + } + } + } + + pub fn is_raw_disc(&self) -> bool { + #[cfg(windows)] + { + return self.raw_disc; + } + + #[allow(unreachable_code)] + false + } +} diff --git a/bdgm/bdgm-play/src/dirs.rs b/bdgm/bdgm-play/src/dirs.rs new file mode 100644 index 0000000..3325abe --- /dev/null +++ b/bdgm/bdgm-play/src/dirs.rs @@ -0,0 +1,57 @@ +use std::path::PathBuf; + +use bdgm::{error::BDGMError, game::ValidatedGame}; +use platform_dirs::AppDirs; + +use crate::{args::Args, error::AppError}; + +pub struct GameDirs { + pub root: PathBuf, + pub install: PathBuf, + pub cache: PathBuf, + pub data: PathBuf, +} + +impl GameDirs { + pub fn from(game: &ValidatedGame, app_dirs: &AppDirs) -> Self { + let root = app_dirs.data_dir.join(&game.id()); + let install = root.join("app").join(game.version()); + let cache = app_dirs.cache_dir.join(&game.id()); + let data = root.join("data"); + + GameDirs { + root, + install, + cache, + data, + } + } +} + +pub fn get_app_dir_path(args: &Args) -> PathBuf { + args.location.join("BDGM").join("APP") +} + +pub fn try_get_executable_path(args: &Args, game: &ValidatedGame) -> anyhow::Result { + let app_dir_path = get_app_dir_path(args); + let executable_path = app_dir_path.join(&game.executable()); + + if !executable_path.try_exists()? { + return Err(AppError::InvalidGameFile(BDGMError::ExecutableMissing( + executable_path.to_string_lossy().into_owned(), + )) + .into()); + } + + Ok(executable_path) +} + +pub fn try_get_manifest_path(args: &Args) -> anyhow::Result { + let manifest_path = args.location.join("BDGM").join("DISC.BDGM"); + + if !manifest_path.try_exists()? { + return Err(BDGMError::DiscFileMissing.into()); + } + + Ok(manifest_path) +} diff --git a/bdgm/bdgm-play/src/dump.rs b/bdgm/bdgm-play/src/dump.rs index 25dcdbc..48e3f3c 100644 --- a/bdgm/bdgm-play/src/dump.rs +++ b/bdgm/bdgm-play/src/dump.rs @@ -118,7 +118,7 @@ fn disc_size(file: &File) -> io::Result { } #[cfg(windows)] -pub(crate) fn dump_disc(drive: &str, output: &str) -> io::Result<()> { +pub fn dump_disc(drive: &str, output: &str) -> io::Result<()> { let mut drive = OpenOptions::new() .read(true) .custom_flags(FILE_FLAG_NO_BUFFERING) @@ -178,7 +178,7 @@ pub(crate) fn dump_disc(drive: &str, output: &str) -> io::Result<()> { Ok(()) } -pub(crate) fn extract_udf_dir( +pub fn extract_udf_dir( udf: &UdfVolume, dir: &UdfDir, output_path: &Path, diff --git a/bdgm/bdgm-play/src/error.rs b/bdgm/bdgm-play/src/error.rs index 0af0e03..f38e696 100644 --- a/bdgm/bdgm-play/src/error.rs +++ b/bdgm/bdgm-play/src/error.rs @@ -4,7 +4,7 @@ use bdgm::error::BDGMError; use thiserror::Error; #[derive(Debug, Error, PartialEq, Eq)] -pub(crate) enum AppError { +pub enum AppError { NoAppDirs, InvalidGameFile(BDGMError), CouldNotFindUnclaimedPort, diff --git a/bdgm/bdgm-play/src/image.rs b/bdgm/bdgm-play/src/image.rs new file mode 100644 index 0000000..63e290c --- /dev/null +++ b/bdgm/bdgm-play/src/image.rs @@ -0,0 +1,60 @@ +use std::fs::File; + +use hadris_udf::UdfVolume; +use tempfile::tempdir; + +use crate::{args::Args, dump::extract_udf_dir}; + +#[cfg(windows)] +use crate::dump::dump_disc; +#[cfg(windows)] +use tempfile::NamedTempFile; + +enum ImageState { + MountedDirectory, + Image, + #[cfg(windows)] + RawDisc, +} + +impl ImageState { + pub fn from(args: &Args) -> Self { + #[cfg(windows)] + if args.is_raw_disc() { + return Self::RawDisc; + } + if args.image { + Self::Image + } else { + Self::MountedDirectory + } + } +} + +pub fn resolve_image_args(args: Args) -> anyhow::Result { + let image_state = ImageState::from(&args); + + let file = match image_state { + ImageState::MountedDirectory => return Ok(args), + ImageState::Image => File::open(&args.location)?, + #[cfg(windows)] + ImageState::RawDisc => { + let dump_file = NamedTempFile::new()?; + dump_disc( + &args.location.to_string_lossy(), + &dump_file.path().to_string_lossy(), + )?; + + File::open(dump_file.path())? + } + }; + + let extract_dir = tempdir()?; + let udf = UdfVolume::open(file)?; + extract_udf_dir(&udf, &udf.root_dir()?, extract_dir.path())?; + + Ok(Args::new_imageless( + extract_dir.path().to_path_buf(), + args.runtime, + )) +} diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs new file mode 100644 index 0000000..31bdbee --- /dev/null +++ b/bdgm/bdgm-play/src/install.rs @@ -0,0 +1,23 @@ +use bdgm::game::ValidatedGame; +use fs_extra::dir::{self, CopyOptions}; +use platform_dirs::AppDirs; + +use crate::{args::Args, dirs::get_app_dir_path}; + +pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow::Result<()> { + let id = game.id(); + let game_dir = app_dirs.data_dir.join(&id); + let install_dir = game_dir.join("app").join(game.version()); + if !install_dir.try_exists()? { + println!("Copying files..."); + dir::create_all(&install_dir, false)?; + dir::copy( + &get_app_dir_path(args), + &install_dir, + &CopyOptions::new().overwrite(true).content_only(true), + )?; + println!("Copied!"); + } + + Ok(()) +} diff --git a/bdgm/bdgm-play/src/launch.rs b/bdgm/bdgm-play/src/launch.rs new file mode 100644 index 0000000..07bac72 --- /dev/null +++ b/bdgm/bdgm-play/src/launch.rs @@ -0,0 +1,110 @@ +use std::{ + collections::HashMap, + process::{Command, ExitStatus}, +}; + +use bdgm::game::ValidatedGame; +use platform_dirs::AppDirs; + +use crate::{ + args::Args, + dirs::{GameDirs, get_app_dir_path}, + server::play_html_game, +}; + +pub fn get_envvars( + game: &ValidatedGame, + args: &Args, + app_dirs: &AppDirs, +) -> HashMap<&'static str, String> { + let game_dirs = GameDirs::from(game, app_dirs); + + let mut envvars = HashMap::new(); + envvars.insert("BDGM_DATA", game_dirs.data.to_string_lossy().into_owned()); + envvars.insert( + "XDG_DATA_DIRS", + game_dirs.data.to_string_lossy().into_owned(), + ); + envvars.insert("BDGM_CACHE", game_dirs.cache.to_string_lossy().into_owned()); + envvars.insert( + "BDGM_APP", + get_app_dir_path(args).to_string_lossy().into_owned(), + ); + envvars.insert("BDGM_DISC", args.location.to_string_lossy().into_owned()); + envvars.insert("BDGM_VERSION", game.bdgm_version().to_string()); + + envvars +} + +pub async fn launch_game( + game: &ValidatedGame, + args: &Args, + app_dirs: &AppDirs, + verbose: bool, +) -> anyhow::Result { + let envvars = get_envvars(&game, &args, &app_dirs); + let runtime = game.runtime(); + let game_dirs = GameDirs::from(&game, &app_dirs); + let runtime_path = args + .runtime + .as_ref() + .map(|x| x.to_string_lossy().to_string()); + + let status = match runtime { + bdgm::runtime::Runtime::Java => Command::new(runtime_path.as_deref().unwrap_or("java")) + .args(game.runtime_args()) + .arg("--jar") + .arg(game_dirs.install.join(game.executable())) + .arg("--") + .args(game.args()) + .envs(envvars) + .current_dir(&game_dirs.install) + .status()?, + bdgm::runtime::Runtime::Dotnet => Command::new(runtime_path.as_deref().unwrap_or("dotnet")) + .args(game.runtime_args()) + .arg(game_dirs.install.join(game.executable())) + .arg("--") + .args(game.args()) + .envs(envvars) + .current_dir(&game_dirs.install) + .status()?, + bdgm::runtime::Runtime::Python => Command::new(runtime_path.as_deref().unwrap_or("python")) + .args(game.runtime_args()) + .arg(game_dirs.install.join(game.executable())) + .arg("--") + .args(game.args()) + .envs(envvars) + .current_dir(&game_dirs.install) + .status()?, + bdgm::runtime::Runtime::HTML => { + play_html_game(&game, &game_dirs.install, &game_dirs.data, verbose).await?; + std::process::ExitStatus::default() + } + bdgm::runtime::Runtime::Windows => { + if cfg!(target_os = "windows") { + Command::new(game_dirs.install.join(game.executable())) + .args(game.args()) + .envs(envvars) + .current_dir(&game_dirs.install) + .status()? + } else { + Command::new(runtime_path.as_deref().unwrap_or("wine")) + .args(game.runtime_args()) + .arg(game_dirs.install.join(game.executable())) + .args(game.args()) + .envs(envvars) + .env("WINEPREFIX", game_dirs.root.join("wineprefix")) + .current_dir(&game_dirs.install) + .status()? + } + } + }; + if !status.success() { + if verbose { + eprintln!("Your game crashed: {status}"); + eprintln!("Setting a runtime with `--runtime /path/to/runtime` may fix your issue."); + } + } + + Ok(status) +} diff --git a/bdgm/bdgm-play/src/lib.rs b/bdgm/bdgm-play/src/lib.rs new file mode 100644 index 0000000..aa61e26 --- /dev/null +++ b/bdgm/bdgm-play/src/lib.rs @@ -0,0 +1,19 @@ +use std::{fs::File, io::Read}; + +use crate::{args::Args, dirs::try_get_manifest_path}; +pub mod args; +pub mod dirs; +pub mod dump; +pub mod error; +pub mod image; +pub mod install; +pub mod launch; +pub mod server; + +pub fn read_manifest(args: &Args) -> anyhow::Result { + let path = try_get_manifest_path(args)?; + let mut manifest = File::open(path)?; + let mut contents = String::new(); + manifest.read_to_string(&mut contents)?; + Ok(contents) +} diff --git a/bdgm/bdgm-play/src/main.rs b/bdgm/bdgm-play/src/main.rs index fd7fdff..371e5f2 100644 --- a/bdgm/bdgm-play/src/main.rs +++ b/bdgm/bdgm-play/src/main.rs @@ -1,10 +1,6 @@ use crate::app::run; mod app; -mod args; -mod dump; -mod error; -mod server; #[tokio::main] async fn main() { diff --git a/bdgm/bdgm-play/src/server.rs b/bdgm/bdgm-play/src/server.rs index b696085..1bd81f5 100644 --- a/bdgm/bdgm-play/src/server.rs +++ b/bdgm/bdgm-play/src/server.rs @@ -6,11 +6,15 @@ use std::{ use anyhow::{Context, Result}; use axum::Router; +use bdgm::game::ValidatedGame; use bimap::BiMap; +use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode}; use tokio::net::TcpListener; use tower_http::services::ServeDir; -pub(crate) async fn create_listener(port: Option) -> Result { +use crate::error::AppError; + +pub async fn create_listener(port: Option) -> Result { match port { Some(port) => Ok(TcpListener::bind(("127.0.0.1", port)) .await @@ -21,7 +25,7 @@ pub(crate) async fn create_listener(port: Option) -> Result { } } -pub(crate) async fn serve(listener: TcpListener, directory: PathBuf) -> Result<()> { +pub async fn serve(listener: TcpListener, directory: &PathBuf) -> Result<()> { let app = Router::new().fallback_service(ServeDir::new(directory)); axum::serve(listener, app).await.context("Server crashed")?; @@ -31,7 +35,7 @@ pub(crate) async fn serve(listener: TcpListener, directory: PathBuf) -> Result<( const PORTLIST_FILE_NAME: &'static str = "ports.json"; -pub(crate) fn get_file(path: &PathBuf) -> Result { +pub fn get_file(path: &PathBuf) -> Result { Ok(File::options() .write(true) .read(true) @@ -39,18 +43,18 @@ pub(crate) fn get_file(path: &PathBuf) -> Result { .open(path)?) } -pub(crate) fn acquire_portlist_lock(data_dir: &PathBuf) -> Result { +pub fn acquire_portlist_lock(data_dir: &PathBuf) -> Result { let path = get_portlist_file_path(data_dir).with_added_extension("lock"); let file = get_file(&path)?; file.lock()?; Ok(file) } -pub(crate) fn get_portlist_file_path(data_dir: &PathBuf) -> PathBuf { +pub fn get_portlist_file_path(data_dir: &PathBuf) -> PathBuf { data_dir.join(PORTLIST_FILE_NAME) } -pub(crate) fn load_ports(file: &mut File) -> Result> { +pub fn load_ports(file: &mut File) -> Result> { let mut json = String::new(); match file.read_to_string(&mut json) { Ok(_) => { @@ -75,11 +79,7 @@ fn truncate(file: &mut File) -> Result<()> { Ok(()) } -pub(crate) fn save_ports( - ports: BiMap, - lock_file: File, - data_dir: &PathBuf, -) -> Result<()> { +pub fn save_ports(ports: BiMap, lock_file: File, data_dir: &PathBuf) -> Result<()> { let json = serde_json::to_string_pretty(&ports)?; let path = get_portlist_file_path(data_dir); @@ -99,3 +99,69 @@ pub(crate) fn save_ports( Ok(()) } + +pub async fn play_html_game( + game: &ValidatedGame, + install_dir: &PathBuf, + data_dir: &PathBuf, + verbose: bool, +) -> anyhow::Result<()> { + let lock = acquire_portlist_lock(data_dir)?; + let mut file = get_file(&get_portlist_file_path(data_dir))?; + let mut ports = load_ports(&mut file)?; + let port = ports.get_by_left(game.id()); + + let listener = match port { + Some(port) => { + drop(file); + drop(lock); + create_listener(Some(*port)).await? + } + None => { + let mut listener = create_listener(None).await?; + let mut port = listener.local_addr()?.port(); + + let mut tries = 0; + while ports.get_by_right(&&port).is_some() { + listener = create_listener(None).await?; + port = listener.local_addr()?.port(); + + tries += 1; + if tries >= 5000 { + return Err(AppError::CouldNotFindUnclaimedPort.into()); + } + } + + ports.insert(game.id().to_string(), listener.local_addr()?.port()); + if verbose { + println!("Persisting port {}...", listener.local_addr()?.port()); + } + drop(file); + save_ports(ports, lock, data_dir)?; + listener + } + }; + + let executable = game.executable().to_string_lossy(); + let encoded = utf8_percent_encode(&executable, &ENCODE_SET); + + let address = format!("http://{}/{}", listener.local_addr()?, encoded); + if verbose { + println!("Opening {address}"); + } + webbrowser::open(&address)?; + + if verbose { + println!("Running server, press Ctrl + C to stop."); + } + serve(listener, install_dir).await?; + + Ok(()) +} + +const ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC + .remove(b'/') + .remove(b'-') + .remove(b'.') + .remove(b'_') + .remove(b'~'); From 157f57f10a842f42166bcfa778f5b462df403dc9 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 22:26:13 +0200 Subject: [PATCH 02/19] Exit with status code when something goes wrong --- bdgm/bdgm-play/src/app.rs | 7 ++++--- bdgm/bdgm-play/src/main.rs | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/bdgm/bdgm-play/src/app.rs b/bdgm/bdgm-play/src/app.rs index 723cbce..28bd319 100644 --- a/bdgm/bdgm-play/src/app.rs +++ b/bdgm/bdgm-play/src/app.rs @@ -1,3 +1,4 @@ +use std::process::ExitStatus; use bdgm::game::{Game, ValidatedGame}; use bdgm_play::{ @@ -7,7 +8,7 @@ use bdgm_play::{ use clap::Parser; use platform_dirs::AppDirs; -pub(crate) async fn run() -> anyhow::Result<()> { +pub(crate) async fn run() -> anyhow::Result { let app_dirs = AppDirs::new(Some("bdgm-play"), true).ok_or(AppError::NoAppDirs)?; let args = Args::parse(); @@ -22,7 +23,7 @@ pub(crate) async fn run() -> anyhow::Result<()> { install(&game, &app_dirs, &args)?; println!("Launching..."); - launch_game(&game, &args, &app_dirs, true).await?; + let status = launch_game(&game, &args, &app_dirs, true).await?; - Ok(()) + Ok(status) } diff --git a/bdgm/bdgm-play/src/main.rs b/bdgm/bdgm-play/src/main.rs index 371e5f2..fedf15f 100644 --- a/bdgm/bdgm-play/src/main.rs +++ b/bdgm/bdgm-play/src/main.rs @@ -5,7 +5,14 @@ mod app; #[tokio::main] async fn main() { match run().await { - Ok(_) => {} - Err(e) => eprintln!("{e}"), + Ok(status) => { + if let Some(code) = status.code() { + std::process::exit(code); + } + } + Err(e) => { + eprintln!("{e}"); + std::process::exit(1); + } } } From 2a60c842e821efd46e2e2442939b2e2e9065f057 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 22:43:06 +0200 Subject: [PATCH 03/19] Create bdgm workspace --- .github/workflows/test.yml | 14 +- bdgm/Cargo.lock | 1469 ++++++++++++++++++++++++++++++-- bdgm/Cargo.toml | 17 +- bdgm/bdgm-build/Cargo.toml | 2 +- bdgm/bdgm-play/Cargo.toml | 2 +- bdgm/bdgm/Cargo.lock | 441 ++++++++++ bdgm/bdgm/Cargo.toml | 15 + bdgm/{ => bdgm}/DISC_read.BDGM | 0 bdgm/{ => bdgm}/src/disc.rs | 2 +- bdgm/{ => bdgm}/src/error.rs | 0 bdgm/{ => bdgm}/src/game.rs | 0 bdgm/{ => bdgm}/src/lib.rs | 0 bdgm/{ => bdgm}/src/runtime.rs | 0 13 files changed, 1854 insertions(+), 108 deletions(-) create mode 100644 bdgm/bdgm/Cargo.lock create mode 100644 bdgm/bdgm/Cargo.toml rename bdgm/{ => bdgm}/DISC_read.BDGM (100%) rename bdgm/{ => bdgm}/src/disc.rs (99%) rename bdgm/{ => bdgm}/src/error.rs (100%) rename bdgm/{ => bdgm}/src/game.rs (100%) rename bdgm/{ => bdgm}/src/lib.rs (100%) rename bdgm/{ => bdgm}/src/runtime.rs (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 83e02b8..8ac6108 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,12 @@ jobs: test: strategy: matrix: - os: [blacksmith-4vcpu-ubuntu-2404, windows-latest, blacksmith-6vcpu-macos-15] + os: + [ + blacksmith-4vcpu-ubuntu-2404, + windows-latest, + blacksmith-6vcpu-macos-15, + ] runs-on: ${{matrix.os}} @@ -30,10 +35,3 @@ jobs: - name: Run tests run: cargo test --verbose working-directory: ./bdgm - - - name: Build bdgm-play - run: cargo build - working-directory: ./bdgm/bdgm-play - - name: Build bdgm-build - run: cargo build - working-directory: ./bdgm/bdgm-build diff --git a/bdgm/Cargo.lock b/bdgm/Cargo.lock index 3a99900..60db7ae 100644 --- a/bdgm/Cargo.lock +++ b/bdgm/Cargo.lock @@ -2,18 +2,126 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "bdgm" version = "0.2.0" @@ -22,7 +130,48 @@ dependencies = [ "hadris-udf", "serde", "serde_json", - "thiserror", + "thiserror 2.0.21", +] + +[[package]] +name = "bdgm-build" +version = "0.3.0" +dependencies = [ + "anyhow", + "bdgm", + "clap", +] + +[[package]] +name = "bdgm-play" +version = "0.3.0" +dependencies = [ + "anyhow", + "axum", + "bdgm", + "bimap", + "clap", + "fs_extra", + "hadris-udf", + "percent-encoding", + "platform-dirs", + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.21", + "tokio", + "tower-http", + "webbrowser", + "windows-sys", +] + +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +dependencies = [ + "serde", ] [[package]] @@ -34,6 +183,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytemuck" version = "1.25.2" @@ -60,6 +215,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + [[package]] name = "cfg-if" version = "1.0.5" @@ -86,6 +247,62 @@ dependencies = [ "num-traits", ] +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "cpufeatures" version = "0.3.1" @@ -110,6 +327,48 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" +[[package]] +name = "dirs-next" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "embedded-io" version = "0.7.1" @@ -125,6 +384,87 @@ dependencies = [ "embedded-io", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[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]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -203,7 +543,7 @@ dependencies = [ "hadris-macros", "hadris-path", "spin", - "thiserror", + "thiserror 2.0.21", ] [[package]] @@ -226,146 +566,691 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.18" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "libc" -version = "0.2.189" +name = "http" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] [[package]] -name = "lock_api" -version = "0.4.14" +name = "http-body" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ - "scopeguard", + "bytes", + "http", ] [[package]] -name = "memchr" -version = "2.8.3" +name = "http-body-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] [[package]] -name = "num-traits" -version = "0.2.19" +name = "http-range-header" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] -name = "proc-macro2" -version = "1.0.107" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ - "unicode-ident", + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", ] [[package]] -name = "quote" -version = "1.0.47" +name = "hyper-util" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "proc-macro2", + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", ] [[package]] -name = "r-efi" -version = "6.0.0" +name = "icu_collections" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "rand" -version = "0.10.2" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ - "chacha20", - "getrandom", - "rand_core", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "rand_core" -version = "0.10.1" +name = "icu_normalizer" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "icu_normalizer_data" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] -name = "serde" -version = "1.0.229" +name = "icu_properties" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ - "serde_core", - "serde_derive", + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", ] [[package]] -name = "serde_core" -version = "1.0.229" +name = "icu_properties_data" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] -name = "serde_derive" -version = "1.0.229" +name = "icu_provider" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "serde_json" -version = "1.0.151" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "spin" -version = "0.12.3" +name = "idna_adapter" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ - "lock_api", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "stable_deref_trait" +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +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", + "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", + "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", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[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]] +name = "platform-dirs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e188d043c1a692985f78b5464853a263f1a27e5bd6322bad3a4078ee3c998a38" +dependencies = [ + "dirs-next", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.119" @@ -388,52 +1273,470 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.21", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", "syn 3.0.6", ] +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" +dependencies = [ + "jni", + "log", + "ndk-context", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + [[package]] name = "zerocopy" -version = "0.8.57" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.57" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", "syn 2.0.119", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "zmij" version = "1.0.23" diff --git a/bdgm/Cargo.toml b/bdgm/Cargo.toml index de0601f..14ab740 100644 --- a/bdgm/Cargo.toml +++ b/bdgm/Cargo.toml @@ -1,15 +1,4 @@ -[package] -name = "bdgm" -description = "A library for handling Blu-Ray Disc Game discs" -version = "0.2.0" -edition = "2024" -license = "MIT" -repository = "https://github.com/yesser-studios/bdgm" -readme = "../README.md" +[workspace] +resolver = "3" +members = ["bdgm", "bdgm-build", "bdgm-play"] -[dependencies] -serde = {version = "1", features = ["derive"]} -serde_json = "1" -anyhow = "1" -thiserror = "2" -hadris-udf = { version = "2.1.0", features = ["write"] } diff --git a/bdgm/bdgm-build/Cargo.toml b/bdgm/bdgm-build/Cargo.toml index d2a14a9..52458bf 100644 --- a/bdgm/bdgm-build/Cargo.toml +++ b/bdgm/bdgm-build/Cargo.toml @@ -8,6 +8,6 @@ repository = "https://github.com/yesser-studios/bdgm" readme = "../../README.md" [dependencies] -bdgm = {version = "0.2", path = ".."} +bdgm = {version = "0.2", path = "../bdgm"} clap = {version = "4", features = ["derive"]} anyhow = "1" diff --git a/bdgm/bdgm-play/Cargo.toml b/bdgm/bdgm-play/Cargo.toml index 1fce13d..2461b15 100644 --- a/bdgm/bdgm-play/Cargo.toml +++ b/bdgm/bdgm-play/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/yesser-studios/bdgm" readme = "../../README.md" [dependencies] -bdgm = {version = "0.2", path = ".."} +bdgm = {version = "0.2", path = "../bdgm"} clap = {version = "4", features = ["derive"]} anyhow = "1" thiserror = "2" diff --git a/bdgm/bdgm/Cargo.lock b/bdgm/bdgm/Cargo.lock new file mode 100644 index 0000000..3a99900 --- /dev/null +++ b/bdgm/bdgm/Cargo.lock @@ -0,0 +1,441 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bdgm" +version = "0.2.0" +dependencies = [ + "anyhow", + "hadris-udf", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "num-traits", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "hadris-common" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2419641e2ce38205ac40e8c127d5d635f700f4b6bd79494f570141296b7682e9" +dependencies = [ + "bytemuck", + "chrono", + "crc", + "hadris-fixed", + "hadris-io", + "hadris-path", + "heapless", + "rand", + "zerocopy", +] + +[[package]] +name = "hadris-fixed" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f979ce5abdf45f06df09e7fede7b7ac54aae20b63407e3f5f9595999f5cb96" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "hadris-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85372a1e658e46128d98b0b2221485dd72ab6adaca1b643d7f9bfc0fa42bc290" +dependencies = [ + "bytemuck", + "cfg-if", + "embedded-io", + "embedded-io-async", +] + +[[package]] +name = "hadris-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793233288d1b5f23451f75b2c57696808b544eabd45919cceed46b5403c7df8e" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "hadris-path" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25620d0760ae6cdcf56a5c269d29f2e380a712355924143c199552cba895de" + +[[package]] +name = "hadris-udf" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff80abdac13b09d30c90aca9ecd69e61388a8c19c12192d9e5e244d800125fde" +dependencies = [ + "bitflags", + "bytemuck", + "hadris-common", + "hadris-io", + "hadris-macros", + "hadris-path", + "spin", + "thiserror", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "spin" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "zerocopy" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/bdgm/bdgm/Cargo.toml b/bdgm/bdgm/Cargo.toml new file mode 100644 index 0000000..de0601f --- /dev/null +++ b/bdgm/bdgm/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bdgm" +description = "A library for handling Blu-Ray Disc Game discs" +version = "0.2.0" +edition = "2024" +license = "MIT" +repository = "https://github.com/yesser-studios/bdgm" +readme = "../README.md" + +[dependencies] +serde = {version = "1", features = ["derive"]} +serde_json = "1" +anyhow = "1" +thiserror = "2" +hadris-udf = { version = "2.1.0", features = ["write"] } diff --git a/bdgm/DISC_read.BDGM b/bdgm/bdgm/DISC_read.BDGM similarity index 100% rename from bdgm/DISC_read.BDGM rename to bdgm/bdgm/DISC_read.BDGM diff --git a/bdgm/src/disc.rs b/bdgm/bdgm/src/disc.rs similarity index 99% rename from bdgm/src/disc.rs rename to bdgm/bdgm/src/disc.rs index b35a1cb..3a7fa42 100644 --- a/bdgm/src/disc.rs +++ b/bdgm/bdgm/src/disc.rs @@ -194,7 +194,7 @@ mod test { #[test] fn sample_dir_written() { - let entry = Entry::scan_dir("../disc").unwrap(); + let entry = Entry::scan_dir("../../disc").unwrap(); entry .write_udf("./result.udf".into(), UdfRevision::V2_50) .unwrap(); diff --git a/bdgm/src/error.rs b/bdgm/bdgm/src/error.rs similarity index 100% rename from bdgm/src/error.rs rename to bdgm/bdgm/src/error.rs diff --git a/bdgm/src/game.rs b/bdgm/bdgm/src/game.rs similarity index 100% rename from bdgm/src/game.rs rename to bdgm/bdgm/src/game.rs diff --git a/bdgm/src/lib.rs b/bdgm/bdgm/src/lib.rs similarity index 100% rename from bdgm/src/lib.rs rename to bdgm/bdgm/src/lib.rs diff --git a/bdgm/src/runtime.rs b/bdgm/bdgm/src/runtime.rs similarity index 100% rename from bdgm/src/runtime.rs rename to bdgm/bdgm/src/runtime.rs From e7c0c8e308777f635634fb17e4d1cc3f386ace06 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 22:48:08 +0200 Subject: [PATCH 04/19] Remove '--' arg from java, dotnet, and python launches --- bdgm/bdgm-play/src/launch.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/bdgm/bdgm-play/src/launch.rs b/bdgm/bdgm-play/src/launch.rs index 07bac72..2fe4692 100644 --- a/bdgm/bdgm-play/src/launch.rs +++ b/bdgm/bdgm-play/src/launch.rs @@ -55,7 +55,6 @@ pub async fn launch_game( .args(game.runtime_args()) .arg("--jar") .arg(game_dirs.install.join(game.executable())) - .arg("--") .args(game.args()) .envs(envvars) .current_dir(&game_dirs.install) @@ -63,7 +62,6 @@ pub async fn launch_game( bdgm::runtime::Runtime::Dotnet => Command::new(runtime_path.as_deref().unwrap_or("dotnet")) .args(game.runtime_args()) .arg(game_dirs.install.join(game.executable())) - .arg("--") .args(game.args()) .envs(envvars) .current_dir(&game_dirs.install) @@ -71,7 +69,6 @@ pub async fn launch_game( bdgm::runtime::Runtime::Python => Command::new(runtime_path.as_deref().unwrap_or("python")) .args(game.runtime_args()) .arg(game_dirs.install.join(game.executable())) - .arg("--") .args(game.args()) .envs(envvars) .current_dir(&game_dirs.install) From b7667953d5cf19356fe267dd4f4a8268fc140d30 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 22:48:44 +0200 Subject: [PATCH 05/19] Fix incorrect -jar argument --- bdgm/bdgm-play/src/launch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdgm/bdgm-play/src/launch.rs b/bdgm/bdgm-play/src/launch.rs index 2fe4692..e622843 100644 --- a/bdgm/bdgm-play/src/launch.rs +++ b/bdgm/bdgm-play/src/launch.rs @@ -53,7 +53,7 @@ pub async fn launch_game( let status = match runtime { bdgm::runtime::Runtime::Java => Command::new(runtime_path.as_deref().unwrap_or("java")) .args(game.runtime_args()) - .arg("--jar") + .arg("-jar") .arg(game_dirs.install.join(game.executable())) .args(game.args()) .envs(envvars) From 9b01c7ada295aaf970a4e4a2a47abc8cadabd505 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 22:56:57 +0200 Subject: [PATCH 06/19] Install game atomically --- bdgm/bdgm-play/src/install.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index 31bdbee..b07caaa 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -1,3 +1,5 @@ +use std::io; + use bdgm::game::ValidatedGame; use fs_extra::dir::{self, CopyOptions}; use platform_dirs::AppDirs; @@ -8,14 +10,25 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: let id = game.id(); let game_dir = app_dirs.data_dir.join(&id); let install_dir = game_dir.join("app").join(game.version()); + let install_part_dir = game_dir + .join("app") + .join(game.version()) + .with_added_extension("part"); if !install_dir.try_exists()? { + if install_part_dir.try_exists()? { + return Err(io::Error::new( + io::ErrorKind::AlreadyExists, + format!("Installation directory does not exist but installation part directory (path: {}) does. An installation may already be in progress for this game.", install_part_dir.display()) + ).into()); + } println!("Copying files..."); - dir::create_all(&install_dir, false)?; + dir::create_all(&install_part_dir, false)?; dir::copy( &get_app_dir_path(args), - &install_dir, + &install_part_dir, &CopyOptions::new().overwrite(true).content_only(true), )?; + std::fs::rename(&install_part_dir, install_dir)?; println!("Copied!"); } From 89d231984df033986310306f7bdc098f4699bf14 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 23:03:31 +0200 Subject: [PATCH 07/19] Keep image extraction temp dir until after launch --- bdgm/bdgm-play/src/app.rs | 3 ++- bdgm/bdgm-play/src/image.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bdgm/bdgm-play/src/app.rs b/bdgm/bdgm-play/src/app.rs index 28bd319..a46aa08 100644 --- a/bdgm/bdgm-play/src/app.rs +++ b/bdgm/bdgm-play/src/app.rs @@ -12,7 +12,7 @@ pub(crate) async fn run() -> anyhow::Result { let app_dirs = AppDirs::new(Some("bdgm-play"), true).ok_or(AppError::NoAppDirs)?; let args = Args::parse(); - let args = resolve_image_args(args)?; + let (args, tempdir) = resolve_image_args(args)?; let manifest = read_manifest(&args)?; @@ -24,6 +24,7 @@ pub(crate) async fn run() -> anyhow::Result { println!("Launching..."); let status = launch_game(&game, &args, &app_dirs, true).await?; + drop(tempdir); Ok(status) } diff --git a/bdgm/bdgm-play/src/image.rs b/bdgm/bdgm-play/src/image.rs index 63e290c..aa6b235 100644 --- a/bdgm/bdgm-play/src/image.rs +++ b/bdgm/bdgm-play/src/image.rs @@ -1,7 +1,7 @@ use std::fs::File; use hadris_udf::UdfVolume; -use tempfile::tempdir; +use tempfile::{TempDir, tempdir}; use crate::{args::Args, dump::extract_udf_dir}; @@ -31,11 +31,11 @@ impl ImageState { } } -pub fn resolve_image_args(args: Args) -> anyhow::Result { +pub fn resolve_image_args(args: Args) -> anyhow::Result<(Args, Option)> { let image_state = ImageState::from(&args); let file = match image_state { - ImageState::MountedDirectory => return Ok(args), + ImageState::MountedDirectory => return Ok((args, None)), ImageState::Image => File::open(&args.location)?, #[cfg(windows)] ImageState::RawDisc => { @@ -53,8 +53,8 @@ pub fn resolve_image_args(args: Args) -> anyhow::Result { let udf = UdfVolume::open(file)?; extract_udf_dir(&udf, &udf.root_dir()?, extract_dir.path())?; - Ok(Args::new_imageless( - extract_dir.path().to_path_buf(), - args.runtime, + Ok(( + Args::new_imageless(extract_dir.path().to_path_buf(), args.runtime), + Some(extract_dir), )) } From 214a51af72a1e4883853c50fd23f3c58a0bb8528 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 23:07:03 +0200 Subject: [PATCH 08/19] Exit with code 1 if app fails but does not have an exit code --- bdgm/bdgm-play/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bdgm/bdgm-play/src/main.rs b/bdgm/bdgm-play/src/main.rs index fedf15f..06a9c2f 100644 --- a/bdgm/bdgm-play/src/main.rs +++ b/bdgm/bdgm-play/src/main.rs @@ -8,6 +8,9 @@ async fn main() { Ok(status) => { if let Some(code) = status.code() { std::process::exit(code); + } else if !status.success() { + // For example on SIGKILL + std::process::exit(1); } } Err(e) => { From 794d86d681b6b1027c1697638378ee0fb2481795 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Thu, 1 Oct 2026 23:10:35 +0200 Subject: [PATCH 09/19] Fix portlist files being created per-game --- bdgm/bdgm-play/src/launch.rs | 2 +- bdgm/bdgm-play/src/server.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bdgm/bdgm-play/src/launch.rs b/bdgm/bdgm-play/src/launch.rs index e622843..34d6188 100644 --- a/bdgm/bdgm-play/src/launch.rs +++ b/bdgm/bdgm-play/src/launch.rs @@ -74,7 +74,7 @@ pub async fn launch_game( .current_dir(&game_dirs.install) .status()?, bdgm::runtime::Runtime::HTML => { - play_html_game(&game, &game_dirs.install, &game_dirs.data, verbose).await?; + play_html_game(&game, &game_dirs.install, app_dirs, verbose).await?; std::process::ExitStatus::default() } bdgm::runtime::Runtime::Windows => { diff --git a/bdgm/bdgm-play/src/server.rs b/bdgm/bdgm-play/src/server.rs index 1bd81f5..d3c4070 100644 --- a/bdgm/bdgm-play/src/server.rs +++ b/bdgm/bdgm-play/src/server.rs @@ -9,6 +9,7 @@ use axum::Router; use bdgm::game::ValidatedGame; use bimap::BiMap; use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode}; +use platform_dirs::AppDirs; use tokio::net::TcpListener; use tower_http::services::ServeDir; @@ -103,11 +104,11 @@ pub fn save_ports(ports: BiMap, lock_file: File, data_dir: &PathBuf pub async fn play_html_game( game: &ValidatedGame, install_dir: &PathBuf, - data_dir: &PathBuf, + app_dirs: &AppDirs, verbose: bool, ) -> anyhow::Result<()> { - let lock = acquire_portlist_lock(data_dir)?; - let mut file = get_file(&get_portlist_file_path(data_dir))?; + let lock = acquire_portlist_lock(&app_dirs.data_dir)?; + let mut file = get_file(&get_portlist_file_path(&app_dirs.data_dir))?; let mut ports = load_ports(&mut file)?; let port = ports.get_by_left(game.id()); @@ -137,7 +138,7 @@ pub async fn play_html_game( println!("Persisting port {}...", listener.local_addr()?.port()); } drop(file); - save_ports(ports, lock, data_dir)?; + save_ports(ports, lock, &app_dirs.data_dir)?; listener } }; From e12e9df783d4cd65a09e188b5015614da46b90ef Mon Sep 17 00:00:00 2001 From: yesseruser Date: Fri, 2 Oct 2026 10:44:15 +0200 Subject: [PATCH 10/19] Move file operations to fs and lock installation part directory --- bdgm/bdgm-play/src/fs.rs | 31 ++++++++++++++++++++++++++++++ bdgm/bdgm-play/src/install.rs | 20 ++++++++++--------- bdgm/bdgm-play/src/lib.rs | 1 + bdgm/bdgm-play/src/server.rs | 36 ++++++++--------------------------- 4 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 bdgm/bdgm-play/src/fs.rs diff --git a/bdgm/bdgm-play/src/fs.rs b/bdgm/bdgm-play/src/fs.rs new file mode 100644 index 0000000..569217a --- /dev/null +++ b/bdgm/bdgm-play/src/fs.rs @@ -0,0 +1,31 @@ +use std::{ + fs::File, + io::{self, Seek, SeekFrom}, + path::PathBuf, +}; + +pub fn acquire_lock(path: &PathBuf) -> io::Result { + let file = get_file(&path.with_added_extension("lock"))?; + file.lock()?; + Ok(file) +} + +pub fn get_file(path: &PathBuf) -> io::Result { + File::options() + .write(true) + .read(true) + .create(true) + .open(path) +} + +pub fn get_part_file(path: &PathBuf) -> io::Result<(File, PathBuf)> { + let path = path.with_added_extension("part"); + Ok((get_file(&path)?, path)) +} + +pub fn truncate(file: &mut File) -> io::Result<()> { + file.set_len(0)?; + file.seek(SeekFrom::Start(0))?; + + Ok(()) +} diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index b07caaa..bbdcebf 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -4,22 +4,23 @@ use bdgm::game::ValidatedGame; use fs_extra::dir::{self, CopyOptions}; use platform_dirs::AppDirs; -use crate::{args::Args, dirs::get_app_dir_path}; +use crate::{args::Args, dirs::get_app_dir_path, fs::acquire_lock}; pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow::Result<()> { let id = game.id(); let game_dir = app_dirs.data_dir.join(&id); let install_dir = game_dir.join("app").join(game.version()); - let install_part_dir = game_dir - .join("app") - .join(game.version()) - .with_added_extension("part"); + let install_part_dir = game_dir.with_added_extension("part"); if !install_dir.try_exists()? { + let lock = acquire_lock(&install_dir).map_err(|_| { + io::Error::new( + io::ErrorKind::ResourceBusy, + "An installation is in progress.", + ) + }); if install_part_dir.try_exists()? { - return Err(io::Error::new( - io::ErrorKind::AlreadyExists, - format!("Installation directory does not exist but installation part directory (path: {}) does. An installation may already be in progress for this game.", install_part_dir.display()) - ).into()); + println!("Installation part directory exists but lock is not held. Removing..."); + std::fs::remove_dir_all(&install_part_dir)?; } println!("Copying files..."); dir::create_all(&install_part_dir, false)?; @@ -29,6 +30,7 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: &CopyOptions::new().overwrite(true).content_only(true), )?; std::fs::rename(&install_part_dir, install_dir)?; + drop(lock); println!("Copied!"); } diff --git a/bdgm/bdgm-play/src/lib.rs b/bdgm/bdgm-play/src/lib.rs index aa61e26..c7e9966 100644 --- a/bdgm/bdgm-play/src/lib.rs +++ b/bdgm/bdgm-play/src/lib.rs @@ -5,6 +5,7 @@ pub mod args; pub mod dirs; pub mod dump; pub mod error; +pub mod fs; pub mod image; pub mod install; pub mod launch; diff --git a/bdgm/bdgm-play/src/server.rs b/bdgm/bdgm-play/src/server.rs index d3c4070..e6eb06f 100644 --- a/bdgm/bdgm-play/src/server.rs +++ b/bdgm/bdgm-play/src/server.rs @@ -1,6 +1,6 @@ use std::{ fs::{self, File}, - io::{Read, Seek, SeekFrom, Write}, + io::{Read, Write}, path::PathBuf, }; @@ -13,7 +13,10 @@ use platform_dirs::AppDirs; use tokio::net::TcpListener; use tower_http::services::ServeDir; -use crate::error::AppError; +use crate::{ + error::AppError, + fs::{acquire_lock, get_file, get_part_file, truncate}, +}; pub async fn create_listener(port: Option) -> Result { match port { @@ -36,21 +39,6 @@ pub async fn serve(listener: TcpListener, directory: &PathBuf) -> Result<()> { const PORTLIST_FILE_NAME: &'static str = "ports.json"; -pub fn get_file(path: &PathBuf) -> Result { - Ok(File::options() - .write(true) - .read(true) - .create(true) - .open(path)?) -} - -pub fn acquire_portlist_lock(data_dir: &PathBuf) -> Result { - let path = get_portlist_file_path(data_dir).with_added_extension("lock"); - let file = get_file(&path)?; - file.lock()?; - Ok(file) -} - pub fn get_portlist_file_path(data_dir: &PathBuf) -> PathBuf { data_dir.join(PORTLIST_FILE_NAME) } @@ -73,25 +61,17 @@ pub fn load_ports(file: &mut File) -> Result> { } } -fn truncate(file: &mut File) -> Result<()> { - file.set_len(0)?; - file.seek(SeekFrom::Start(0))?; - - Ok(()) -} - pub fn save_ports(ports: BiMap, lock_file: File, data_dir: &PathBuf) -> Result<()> { let json = serde_json::to_string_pretty(&ports)?; let path = get_portlist_file_path(data_dir); - let temp_path = path.with_added_extension("tmp"); - let mut temp_file = get_file(&temp_path)?; + let (mut temp_file, part_file_path) = get_part_file(&path)?; truncate(&mut temp_file)?; temp_file.write_all(json.as_bytes())?; temp_file.sync_all()?; drop(temp_file); - fs::rename(&temp_path, &path)?; + fs::rename(&part_file_path, &path)?; #[cfg(unix)] File::open(data_dir)?.sync_all()?; @@ -107,7 +87,7 @@ pub async fn play_html_game( app_dirs: &AppDirs, verbose: bool, ) -> anyhow::Result<()> { - let lock = acquire_portlist_lock(&app_dirs.data_dir)?; + let lock = acquire_lock(&get_portlist_file_path(&app_dirs.data_dir))?; let mut file = get_file(&get_portlist_file_path(&app_dirs.data_dir))?; let mut ports = load_ports(&mut file)?; let port = ports.get_by_left(game.id()); From b409727760c53ddc8ec0a235f12c08252fdf4a75 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Fri, 2 Oct 2026 11:06:13 +0200 Subject: [PATCH 11/19] Fix install part dir not being in correct location --- bdgm/bdgm-play/src/install.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index bbdcebf..4b47b83 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -10,7 +10,7 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: let id = game.id(); let game_dir = app_dirs.data_dir.join(&id); let install_dir = game_dir.join("app").join(game.version()); - let install_part_dir = game_dir.with_added_extension("part"); + let install_part_dir = install_dir.with_added_extension("part"); if !install_dir.try_exists()? { let lock = acquire_lock(&install_dir).map_err(|_| { io::Error::new( From 5a049b964cc6ac1c64946c6d44ad5167e8a6ab8c Mon Sep 17 00:00:00 2001 From: yesseruser Date: Fri, 2 Oct 2026 11:06:45 +0200 Subject: [PATCH 12/19] Check lock result --- bdgm/bdgm-play/src/install.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index 4b47b83..40fe5f0 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -17,7 +17,7 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: io::ErrorKind::ResourceBusy, "An installation is in progress.", ) - }); + })?; if install_part_dir.try_exists()? { println!("Installation part directory exists but lock is not held. Removing..."); std::fs::remove_dir_all(&install_part_dir)?; From 33fc7e228bac574b2a5953fc71cb132edf36f5d6 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Fri, 2 Oct 2026 22:47:21 +0200 Subject: [PATCH 13/19] Try acquiring lock instead of blocking --- bdgm/bdgm-play/src/fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdgm/bdgm-play/src/fs.rs b/bdgm/bdgm-play/src/fs.rs index 569217a..86e8e47 100644 --- a/bdgm/bdgm-play/src/fs.rs +++ b/bdgm/bdgm-play/src/fs.rs @@ -6,7 +6,7 @@ use std::{ pub fn acquire_lock(path: &PathBuf) -> io::Result { let file = get_file(&path.with_added_extension("lock"))?; - file.lock()?; + file.try_lock()?; Ok(file) } From b0240cbc4b5b31d0a35dc550781fdd3a0c6c145b Mon Sep 17 00:00:00 2001 From: yesseruser Date: Sat, 3 Oct 2026 00:31:12 +0200 Subject: [PATCH 14/19] Fix lock acquire fail because parent directory wasn't created --- bdgm/bdgm-play/src/install.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index 40fe5f0..fb9b352 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -12,18 +12,19 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: let install_dir = game_dir.join("app").join(game.version()); let install_part_dir = install_dir.with_added_extension("part"); if !install_dir.try_exists()? { + if install_part_dir.try_exists()? { + println!("Installation part directory exists but lock is not held. Removing..."); + std::fs::remove_dir_all(&install_part_dir)?; + } + dir::create_all(&install_part_dir, false)?; let lock = acquire_lock(&install_dir).map_err(|_| { io::Error::new( io::ErrorKind::ResourceBusy, "An installation is in progress.", ) })?; - if install_part_dir.try_exists()? { - println!("Installation part directory exists but lock is not held. Removing..."); - std::fs::remove_dir_all(&install_part_dir)?; - } + println!("Copying files..."); - dir::create_all(&install_part_dir, false)?; dir::copy( &get_app_dir_path(args), &install_part_dir, From a2e31261af95c687c54f23c528d51d55b1f6a304 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Sat, 3 Oct 2026 01:28:51 +0200 Subject: [PATCH 15/19] Add block parameter to acquire_lock --- bdgm/bdgm-play/src/fs.rs | 8 ++++++-- bdgm/bdgm-play/src/install.rs | 2 +- bdgm/bdgm-play/src/server.rs | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bdgm/bdgm-play/src/fs.rs b/bdgm/bdgm-play/src/fs.rs index 86e8e47..b83475f 100644 --- a/bdgm/bdgm-play/src/fs.rs +++ b/bdgm/bdgm-play/src/fs.rs @@ -4,9 +4,13 @@ use std::{ path::PathBuf, }; -pub fn acquire_lock(path: &PathBuf) -> io::Result { +pub fn acquire_lock(path: &PathBuf, block: bool) -> io::Result { let file = get_file(&path.with_added_extension("lock"))?; - file.try_lock()?; + if block { + file.lock()?; + } else { + file.try_lock()?; + } Ok(file) } diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index fb9b352..78e6fa2 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -17,7 +17,7 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: std::fs::remove_dir_all(&install_part_dir)?; } dir::create_all(&install_part_dir, false)?; - let lock = acquire_lock(&install_dir).map_err(|_| { + let lock = acquire_lock(&install_dir, false).map_err(|_| { io::Error::new( io::ErrorKind::ResourceBusy, "An installation is in progress.", diff --git a/bdgm/bdgm-play/src/server.rs b/bdgm/bdgm-play/src/server.rs index e6eb06f..233a8bd 100644 --- a/bdgm/bdgm-play/src/server.rs +++ b/bdgm/bdgm-play/src/server.rs @@ -87,7 +87,7 @@ pub async fn play_html_game( app_dirs: &AppDirs, verbose: bool, ) -> anyhow::Result<()> { - let lock = acquire_lock(&get_portlist_file_path(&app_dirs.data_dir))?; + let lock = acquire_lock(&get_portlist_file_path(&app_dirs.data_dir), true)?; let mut file = get_file(&get_portlist_file_path(&app_dirs.data_dir))?; let mut ports = load_ports(&mut file)?; let port = ports.get_by_left(game.id()); From 56d1db7314e3e4f8db2c735320c776a9891caeed Mon Sep 17 00:00:00 2001 From: yesseruser Date: Sat, 3 Oct 2026 01:53:54 +0200 Subject: [PATCH 16/19] Add blocking parameter to acquire_lock and add IoError --- bdgm/bdgm-play/src/error.rs | 35 ++++++++++++++++++++++++++++++++++- bdgm/bdgm-play/src/fs.rs | 17 +++++++++++++++-- bdgm/bdgm-play/src/install.rs | 14 ++++++++------ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/bdgm/bdgm-play/src/error.rs b/bdgm/bdgm-play/src/error.rs index f38e696..617370b 100644 --- a/bdgm/bdgm-play/src/error.rs +++ b/bdgm/bdgm-play/src/error.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{fmt::Display, fs, io}; use bdgm::error::BDGMError; use thiserror::Error; @@ -21,3 +21,36 @@ impl Display for AppError { } } } + +#[derive(Debug, Error)] +pub enum IoError { + IoError(io::Error), + TryLockError(fs::TryLockError), + FsExtraError(fs_extra::error::Error), +} + +impl Display for IoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IoError::IoError(error) => error.fmt(f), + IoError::TryLockError(error) => error.fmt(f), + IoError::FsExtraError(error) => error.fmt(f), + } + } +} + +impl From for IoError { + fn from(value: io::Error) -> Self { + Self::IoError(value) + } +} +impl From for IoError { + fn from(value: fs::TryLockError) -> Self { + Self::TryLockError(value) + } +} +impl From for IoError { + fn from(value: fs_extra::error::Error) -> Self { + Self::FsExtraError(value) + } +} diff --git a/bdgm/bdgm-play/src/fs.rs b/bdgm/bdgm-play/src/fs.rs index b83475f..4558579 100644 --- a/bdgm/bdgm-play/src/fs.rs +++ b/bdgm/bdgm-play/src/fs.rs @@ -4,9 +4,22 @@ use std::{ path::PathBuf, }; -pub fn acquire_lock(path: &PathBuf, block: bool) -> io::Result { +use fs_extra::dir; + +use crate::error::IoError; + +pub fn acquire_lock(path: &PathBuf, blocking: bool) -> Result { + match path.parent() { + Some(dir_path) => { + if dir_path.is_dir() { + dir::create_all(dir_path, false)?; + } + } + None => todo!(), + } + let file = get_file(&path.with_added_extension("lock"))?; - if block { + if blocking { file.lock()?; } else { file.try_lock()?; diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index 78e6fa2..a490594 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -12,17 +12,19 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: let install_dir = game_dir.join("app").join(game.version()); let install_part_dir = install_dir.with_added_extension("part"); if !install_dir.try_exists()? { + let lock = acquire_lock(&install_dir, false).map_err(|e| match e { + crate::error::IoError::TryLockError(try_lock_error) => io::Error::new( + io::ErrorKind::ResourceBusy, + format!("An installation is in progress: {}", try_lock_error), + ) + .into(), + _ => e, + })?; if install_part_dir.try_exists()? { println!("Installation part directory exists but lock is not held. Removing..."); std::fs::remove_dir_all(&install_part_dir)?; } dir::create_all(&install_part_dir, false)?; - let lock = acquire_lock(&install_dir, false).map_err(|_| { - io::Error::new( - io::ErrorKind::ResourceBusy, - "An installation is in progress.", - ) - })?; println!("Copying files..."); dir::copy( From b21ac5f46053d82642af426992ece6b845ee606d Mon Sep 17 00:00:00 2001 From: yesseruser Date: Sat, 3 Oct 2026 12:17:12 +0200 Subject: [PATCH 17/19] Handle lock parent not existing errors properly --- bdgm/bdgm-play/src/fs.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bdgm/bdgm-play/src/fs.rs b/bdgm/bdgm-play/src/fs.rs index 4558579..e50c869 100644 --- a/bdgm/bdgm-play/src/fs.rs +++ b/bdgm/bdgm-play/src/fs.rs @@ -11,11 +11,18 @@ use crate::error::IoError; pub fn acquire_lock(path: &PathBuf, blocking: bool) -> Result { match path.parent() { Some(dir_path) => { - if dir_path.is_dir() { - dir::create_all(dir_path, false)?; + if dir_path.exists() && !dir_path.is_dir() { + return Err(IoError::IoError(io::Error::new( + io::ErrorKind::NotADirectory, + format!( + "Parent of {} (which was attempted to lock) exists but is not a directory", + path.display() + ), + ))); } + dir::create_all(dir_path, false)?; } - None => todo!(), + None => {} // Parent does not exist so locking will probably fail, but we'll still try } let file = get_file(&path.with_added_extension("lock"))?; From 00cea7422cc7ca3767f5f35e5946d599c2fbaf57 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Sat, 3 Oct 2026 12:18:43 +0200 Subject: [PATCH 18/19] Accept Path instead of PathBuf in fs functions --- bdgm/bdgm-play/src/fs.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bdgm/bdgm-play/src/fs.rs b/bdgm/bdgm-play/src/fs.rs index e50c869..b57891c 100644 --- a/bdgm/bdgm-play/src/fs.rs +++ b/bdgm/bdgm-play/src/fs.rs @@ -1,14 +1,14 @@ use std::{ fs::File, io::{self, Seek, SeekFrom}, - path::PathBuf, + path::{Path, PathBuf}, }; use fs_extra::dir; use crate::error::IoError; -pub fn acquire_lock(path: &PathBuf, blocking: bool) -> Result { +pub fn acquire_lock(path: &Path, blocking: bool) -> Result { match path.parent() { Some(dir_path) => { if dir_path.exists() && !dir_path.is_dir() { @@ -34,7 +34,7 @@ pub fn acquire_lock(path: &PathBuf, blocking: bool) -> Result { Ok(file) } -pub fn get_file(path: &PathBuf) -> io::Result { +pub fn get_file(path: &Path) -> io::Result { File::options() .write(true) .read(true) @@ -42,7 +42,7 @@ pub fn get_file(path: &PathBuf) -> io::Result { .open(path) } -pub fn get_part_file(path: &PathBuf) -> io::Result<(File, PathBuf)> { +pub fn get_part_file(path: &Path) -> io::Result<(File, PathBuf)> { let path = path.with_added_extension("part"); Ok((get_file(&path)?, path)) } From 5208e107bce6edd280741818fed5827cf992dbae Mon Sep 17 00:00:00 2001 From: yesseruser Date: Mon, 5 Oct 2026 00:48:28 +0200 Subject: [PATCH 19/19] Add verbose argument to acquire_lock --- bdgm/bdgm-play/src/fs.rs | 15 +++++++++++++-- bdgm/bdgm-play/src/install.rs | 2 +- bdgm/bdgm-play/src/server.rs | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bdgm/bdgm-play/src/fs.rs b/bdgm/bdgm-play/src/fs.rs index b57891c..c9b982c 100644 --- a/bdgm/bdgm-play/src/fs.rs +++ b/bdgm/bdgm-play/src/fs.rs @@ -8,7 +8,7 @@ use fs_extra::dir; use crate::error::IoError; -pub fn acquire_lock(path: &Path, blocking: bool) -> Result { +pub fn acquire_lock(path: &Path, blocking: bool, verbose: bool) -> Result { match path.parent() { Some(dir_path) => { if dir_path.exists() && !dir_path.is_dir() { @@ -26,7 +26,18 @@ pub fn acquire_lock(path: &Path, blocking: bool) -> Result { } let file = get_file(&path.with_added_extension("lock"))?; - if blocking { + if blocking && verbose { + match file.try_lock() { + Ok(()) => {} + Err(e) => match e { + std::fs::TryLockError::Error(error) => return Err(error.into()), + std::fs::TryLockError::WouldBlock => { + println!("Waiting for lock on {}...", path.display()); + file.lock()?; + } + }, + }; + } else if blocking && !verbose { file.lock()?; } else { file.try_lock()?; diff --git a/bdgm/bdgm-play/src/install.rs b/bdgm/bdgm-play/src/install.rs index a490594..f424698 100644 --- a/bdgm/bdgm-play/src/install.rs +++ b/bdgm/bdgm-play/src/install.rs @@ -12,7 +12,7 @@ pub fn install(game: &ValidatedGame, app_dirs: &AppDirs, args: &Args) -> anyhow: let install_dir = game_dir.join("app").join(game.version()); let install_part_dir = install_dir.with_added_extension("part"); if !install_dir.try_exists()? { - let lock = acquire_lock(&install_dir, false).map_err(|e| match e { + let lock = acquire_lock(&install_dir, false, false).map_err(|e| match e { crate::error::IoError::TryLockError(try_lock_error) => io::Error::new( io::ErrorKind::ResourceBusy, format!("An installation is in progress: {}", try_lock_error), diff --git a/bdgm/bdgm-play/src/server.rs b/bdgm/bdgm-play/src/server.rs index 233a8bd..9453ad0 100644 --- a/bdgm/bdgm-play/src/server.rs +++ b/bdgm/bdgm-play/src/server.rs @@ -87,7 +87,7 @@ pub async fn play_html_game( app_dirs: &AppDirs, verbose: bool, ) -> anyhow::Result<()> { - let lock = acquire_lock(&get_portlist_file_path(&app_dirs.data_dir), true)?; + let lock = acquire_lock(&get_portlist_file_path(&app_dirs.data_dir), true, verbose)?; let mut file = get_file(&get_portlist_file_path(&app_dirs.data_dir))?; let mut ports = load_ports(&mut file)?; let port = ports.get_by_left(game.id());