Skip to main content

loadsmith_loader/loaders/
gd_weave.rs

1use std::{path::PathBuf, sync::LazyLock};
2
3use camino::Utf8Path;
4use globset::GlobBuilder;
5use loadsmith_install::{GlobRule, InstallRule, InstallRuleset};
6
7use crate::{LaunchArgs, LaunchContext, Loader, Result, glob_rule};
8
9/// Loader implementation for [GDWeave](https://github.com/NotNet-GDWeave/GDWeave),
10/// a mod loader for the game WEBFISHING.
11///
12/// GDWeave uses a `GDWeave/mods` directory for packages and a `winmm.dll`
13/// proxy DLL for injection.
14///
15/// # Examples
16///
17/// ```rust
18/// use loadsmith_loader::{GDWeave, Loader};
19///
20/// let loader = GDWeave::new();
21/// assert_eq!(loader.id(), "GDWeave");
22/// ```
23#[derive(Debug, Clone)]
24pub struct GDWeave;
25
26impl GDWeave {
27    /// Creates a new `GDWeave` loader.
28    pub fn new() -> Self {
29        Self
30    }
31}
32
33impl Default for GDWeave {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl Loader for GDWeave {
40    fn id(&self) -> &'static str {
41        "GDWeave"
42    }
43
44    fn loader_install_rules(&self) -> InstallRuleset<'_> {
45        static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
46            vec![
47                glob_rule!("GDWeave/*" => ".").use_links(true).into(),
48                glob_rule!("*.dll" => ".").use_links(true).into(),
49            ]
50        });
51
52        InstallRuleset::new(&RULES)
53    }
54
55    fn package_install_rules(&self) -> InstallRuleset<'_> {
56        static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
57            vec![
58                GlobRule::new(
59                    GlobBuilder::new("GDWeave/mods/*/**")
60                        .case_insensitive(true)
61                        .literal_separator(true)
62                        .build()
63                        .expect("constant glob should be valid"),
64                    Utf8Path::new("GDWeave/mods"),
65                )
66                .strip_levels(3)
67                .with_subdir(true)
68                .use_links(true)
69                .into(),
70            ]
71        });
72
73        InstallRuleset::new(&RULES)
74    }
75
76    fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
77        let gd_weave = ctx.profile_path().join("GDWeave");
78        let path = ctx.format_proton_path(&gd_weave);
79
80        let args = LaunchArgs::new().arg(format!("--gdweave-folder-override={path}"));
81
82        Ok(args)
83    }
84
85    fn package_dir(&self, package: &loadsmith_core::PackageRef) -> Option<PathBuf> {
86        Some(PathBuf::from("GDWeave/mods").join(package.id().as_str()))
87    }
88
89    fn package_config_dirs(&self) -> Vec<PathBuf> {
90        vec!["GDWeave/configs".into()]
91    }
92
93    fn log_file(&self) -> Option<PathBuf> {
94        Some("GDWeave/GDWeave.log".into())
95    }
96
97    fn proxy_dll(&self) -> Option<PathBuf> {
98        Some("winmm.dll".into())
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use loadsmith_core::{PackageRef, Version};
105
106    use crate::{assert_map, assert_maps, test_util::MapFileTester};
107
108    use super::*;
109
110    #[test]
111    fn map_loader_files() {
112        assert_maps!(MapFileTester::new(
113            GDWeave::new(),
114            PackageRef::new("NotNet-GDWeave".to_string(), Version::new(2, 0, 14)),
115            true,
116        ), [
117            "README.md" => None,
118            "winmm.dll" => "./winmm.dll",
119            "GDWeave/core/GDWeave.dll" => "./GDWeave/core/GDWeave.dll",
120            "GDWeave/core/GDWeave.pub" => "./GDWeave/core/GDWeave.pub",
121        ]);
122    }
123
124    #[test]
125    fn map_package_files() {
126        assert_maps!(
127            MapFileTester::new(
128                GDWeave::new(),
129                PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
130                false,
131            ),
132            [
133                "manifest.json" => None,
134                "other/file.txt" => None,
135                "mod/other/file.txt" => None,
136                "mod/other/nested/file.txt" => None,
137                "GDWeave/mods/Author.Name/manifest.json" => "GDWeave/mods/Author-Name/manifest.json",
138                "GDWeave/mods/Author.Name/nested/file.txt" => "GDWeave/mods/Author-Name/nested/file.txt",
139                "GDWeave/mods/Author.Name/1/2/file.txt" => "GDWeave/mods/Author-Name/1/2/file.txt",
140                "gdweave/mods/author.name/file.txt" => "GDWeave/mods/Author-Name/file.txt",
141                "GDWEAVE/MODS/AUTHOR.NAME/FILE.txt" => "GDWeave/mods/Author-Name/FILE.txt",
142            ]
143        );
144    }
145
146    #[test]
147    fn package_dir_works() {
148        let loader = GDWeave::new();
149        let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
150
151        let package_dir = loader.package_dir(&package);
152
153        assert_eq!(package_dir, Some(PathBuf::from("GDWeave/mods/Author-Name")));
154    }
155}