Skip to main content

loadsmith_loader/loaders/
return_of_modding.rs

1use std::{path::PathBuf, sync::LazyLock};
2
3use loadsmith_install::{InstallRule, InstallRuleset, OwnedInstallRuleset, RouteRule};
4
5use crate::{LaunchArgs, LaunchContext, Loader, Result, glob_rule};
6
7/// Loader implementation for Return of Modding (ROM), a mod loader for the
8/// game Hellsmith.
9///
10/// Routes packages into `ReturnOfModding/plugins`, `ReturnOfModding/plugins_data`,
11/// and `ReturnOfModding/config` (mutable). The loader passes
12/// `--rom_modding_root_folder` as the launch argument.
13///
14/// # Examples
15///
16/// ```rust
17/// use loadsmith_loader::{ReturnOfModding, Loader};
18///
19/// let loader = ReturnOfModding::with_default_rules();
20/// assert_eq!(loader.id(), "Shimloader");
21/// ```
22#[derive(Debug, Clone)]
23pub struct ReturnOfModding {
24    package_install_ruleset: OwnedInstallRuleset,
25}
26
27impl ReturnOfModding {
28    /// Creates a `ReturnOfModding` loader with a custom install ruleset.
29    pub fn with_rules(package_install_ruleset: OwnedInstallRuleset) -> Self {
30        Self {
31            package_install_ruleset,
32        }
33    }
34
35    /// Creates a `ReturnOfModding` loader with the default rules.
36    ///
37    /// Default routes:
38    ///
39    /// * `ReturnOfModding/plugins` — default rule
40    /// * `ReturnOfModding/plugins_data`
41    /// * `ReturnOfModding/config` (mutable)
42    pub fn with_default_rules() -> Self {
43        OwnedInstallRuleset::from_rule_iter(
44            [
45                RouteRule::new_static("ReturnOfModding/plugins").with_flatten(false),
46                RouteRule::new_static("ReturnOfModding/plugins_data").with_flatten(false),
47                RouteRule::new_static("ReturnOfModding/config")
48                    .with_flatten(false)
49                    .with_mutable(true),
50            ],
51            Some(0),
52        )
53        .map(Self::with_rules)
54        .expect("rules are not empty so there should always be a valid default rule index")
55    }
56}
57
58impl Loader for ReturnOfModding {
59    fn id(&self) -> &'static str {
60        "Shimloader"
61    }
62
63    fn loader_install_rules(&self) -> InstallRuleset<'_> {
64        static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
65            vec![
66                // extract all non-top-level files into the package root
67                glob_rule!("*/**" => ".").strip_top_level(true).into(),
68            ]
69        });
70
71        InstallRuleset::new(&RULES)
72    }
73
74    fn package_install_rules(&self) -> InstallRuleset<'_> {
75        self.package_install_ruleset.as_ref()
76    }
77
78    fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
79        let path = ctx.format_proton_path(ctx.profile_path());
80
81        let args = LaunchArgs::new()
82            .arg("--rom_modding_root_folder")
83            .arg(&*path);
84
85        Ok(args)
86    }
87
88    fn package_config_dirs(&self) -> Vec<PathBuf> {
89        vec!["ReturnOfModding/config".into()]
90    }
91
92    fn log_file(&self) -> Option<PathBuf> {
93        None
94    }
95
96    fn proxy_dll(&self) -> Option<PathBuf> {
97        None
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use loadsmith_core::{PackageRef, Version};
104
105    use crate::{assert_map, assert_maps, test_util::MapFileTester};
106
107    use super::*;
108
109    #[test]
110    fn map_loader_files() {
111        assert_maps!(MapFileTester::new(
112            ReturnOfModding::with_default_rules(),
113            PackageRef::new("Hell2Modding-Hell2Modding".to_string(), Version::new(1, 0, 105)),
114            true,
115        ), [
116            "icon.png" => None,
117            "README.md" => None,
118            "ReturnOfModdingPack/d3d12.dll" => "./d3d12.dll",
119            "ReturnOfModdingPack/version.dll" => "./version.dll",
120            "Other/file.txt" => "./file.txt",
121        ]);
122    }
123
124    #[test]
125    fn map_package_files() {
126        assert_maps!(
127            MapFileTester::new(
128                ReturnOfModding::with_default_rules(),
129                PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
130                false,
131            ),
132            [
133                "README.md" => "ReturnOfModding/plugins/Author-Name/README.md",
134                "nested/file.txt" => "ReturnOfModding/plugins/Author-Name/nested/file.txt",
135                "plugins/nested/file.txt" => "ReturnOfModding/plugins/Author-Name/nested/file.txt",
136                "config/settings.json" => "ReturnOfModding/config/Author-Name/settings.json",
137                "plugins_data/file.txt" => "ReturnOfModding/plugins_data/Author-Name/file.txt",
138                "plugins_data/nested/file.txt" => "ReturnOfModding/plugins_data/Author-Name/nested/file.txt",
139                "nested/plugins_data/file.txt" => "ReturnOfModding/plugins_data/Author-Name/nested/file.txt",
140            ]
141        );
142    }
143
144    #[test]
145    fn package_dir_works() {
146        let loader = ReturnOfModding::with_default_rules();
147        let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
148
149        let package_dir = loader.package_dir(&package);
150
151        assert_eq!(
152            package_dir,
153            Some(PathBuf::from("ReturnOfModding/plugins/Author-Name"))
154        );
155    }
156}