From f209c7ad64ba95f19468ec3ce1f9798a71da2234 Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Tue, 29 Sep 2026 08:04:27 +0530 Subject: [PATCH] feat(bootstrap): implement non-destructive mod overlay manager --- crates/molotov-bootstrap/src/lib.rs | 1 + crates/molotov-bootstrap/src/mods.rs | 65 +++++++++++++++++++++ crates/molotov-bootstrap/tests/mods_test.rs | 31 ++++++++++ 3 files changed, 97 insertions(+) create mode 100644 crates/molotov-bootstrap/src/mods.rs create mode 100644 crates/molotov-bootstrap/tests/mods_test.rs diff --git a/crates/molotov-bootstrap/src/lib.rs b/crates/molotov-bootstrap/src/lib.rs index dec9401..0f8afac 100644 --- a/crates/molotov-bootstrap/src/lib.rs +++ b/crates/molotov-bootstrap/src/lib.rs @@ -3,3 +3,4 @@ pub mod apk; pub mod cdn; pub mod error; pub mod fflags; +pub mod mods; diff --git a/crates/molotov-bootstrap/src/mods.rs b/crates/molotov-bootstrap/src/mods.rs new file mode 100644 index 0000000..6c314d9 --- /dev/null +++ b/crates/molotov-bootstrap/src/mods.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use crate::error::MolotovError; +use std::collections::HashMap; +use std::path::PathBuf; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ModType { + DeathSound, + Cursor, + Font, +} + +pub struct ModManager { + mods_root: PathBuf, + active_overlays: HashMap, +} + +impl ModManager { + pub fn new(mods_root: PathBuf) -> Self { + Self { + mods_root, + active_overlays: HashMap::new(), + } + } + + pub fn scan_installed_mods(&mut self) -> Result<(), MolotovError> { + if !self.mods_root.exists() { + std::fs::create_dir_all(&self.mods_root)?; + return Ok(()); + } + + // Custom death sound check + let death_sound = self.mods_root.join("ouch.ogg"); + if death_sound.exists() { + self.active_overlays + .insert("content/sounds/ouch.ogg".to_string(), death_sound); + } + + // Custom cursor check + let cursor = self.mods_root.join("ArrowCursor.png"); + if cursor.exists() { + self.active_overlays.insert( + "content/textures/Cursors/KeyboardMouse/ArrowCursor.png".to_string(), + cursor, + ); + } + + // Custom font check + let font = self.mods_root.join("custom_font.ttf"); + if font.exists() { + self.active_overlays + .insert("content/fonts/arial.ttf".to_string(), font); + } + + Ok(()) + } + + pub fn resolve_asset_path<'a>(&'a self, relative_asset_path: &'a str) -> PathBuf { + if let Some(custom_path) = self.active_overlays.get(relative_asset_path) { + custom_path.clone() + } else { + PathBuf::from(relative_asset_path) + } + } +} diff --git a/crates/molotov-bootstrap/tests/mods_test.rs b/crates/molotov-bootstrap/tests/mods_test.rs new file mode 100644 index 0000000..5948143 --- /dev/null +++ b/crates/molotov-bootstrap/tests/mods_test.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_bootstrap::mods::ModManager; +use tempfile::tempdir; + +#[test] +fn test_mod_redirection_for_custom_death_sound() { + let temp = tempdir().unwrap(); + let mod_dir = temp.path().join("mods"); + std::fs::create_dir_all(&mod_dir).unwrap(); + + let custom_sound = mod_dir.join("ouch.ogg"); + std::fs::write(&custom_sound, b"OGG_CUSTOM_DATA").unwrap(); + + let mut manager = ModManager::new(mod_dir); + manager.scan_installed_mods().unwrap(); + + let default_asset = "content/sounds/ouch.ogg"; + let resolved = manager.resolve_asset_path(default_asset); + + assert_eq!(resolved, custom_sound, "Death sound must be redirected to user mod"); +} + +#[test] +fn test_unmodded_asset_passes_through() { + let temp = tempdir().unwrap(); + let manager = ModManager::new(temp.path().to_path_buf()); + let default_asset = "content/textures/unmodified_texture.png"; + let resolved = manager.resolve_asset_path(default_asset); + + assert_eq!(resolved.to_str().unwrap(), default_asset); +}