feat(bootstrap): implement non-destructive mod overlay manager

This commit is contained in:
katR 2026-09-29 08:04:27 +05:30
parent 72dcee25a8
commit f209c7ad64
3 changed files with 97 additions and 0 deletions

View file

@ -3,3 +3,4 @@ pub mod apk;
pub mod cdn;
pub mod error;
pub mod fflags;
pub mod mods;

View file

@ -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<String, PathBuf>,
}
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)
}
}
}

View file

@ -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);
}