Skip to main content

loadsmith_loader/loaders/
lovely.rs

1use std::{path::PathBuf, sync::LazyLock};
2
3use loadsmith_core::PackageRef;
4use loadsmith_install::{InstallRule, InstallRuleset};
5
6use crate::{LaunchArgs, LaunchContext, Loader, Result, glob_rule};
7
8/// Loader implementation for [Lovely](https://github.com/ethangreen-dev/lovely),
9/// a mod loader for the visual-novel game Doki Doki Literature Club!.
10///
11/// Lovely uses a `mods` directory for packages and a `version.dll` proxy DLL.
12///
13/// # Examples
14///
15/// ```rust
16/// use loadsmith_loader::{Lovely, Loader};
17///
18/// let loader = Lovely::new();
19/// assert_eq!(loader.id(), "Lovely");
20/// ```
21#[derive(Debug, Clone)]
22pub struct Lovely;
23
24impl Lovely {
25    /// Creates a new `Lovely` loader.
26    pub fn new() -> Self {
27        Self
28    }
29}
30
31impl Default for Lovely {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl Loader for Lovely {
38    fn id(&self) -> &'static str {
39        "Lovely"
40    }
41
42    fn loader_install_rules(&self) -> InstallRuleset<'_> {
43        static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
44            vec![
45                glob_rule!("*.dll" => ".").use_links(true).into(),
46                glob_rule!("lovely/*" => "mods/lovely")
47                    .strip_top_level(true)
48                    .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                glob_rule!("*" => "mods")
59                    .with_subdir(true)
60                    .use_links(true)
61                    .into(),
62            ]
63        });
64
65        InstallRuleset::new(&RULES)
66    }
67
68    fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
69        let mods_path = ctx.profile_path().join("mods");
70        let path = ctx.format_proton_path(&mods_path);
71
72        let args = LaunchArgs::new().arg("--mod-dir").arg(&*path);
73
74        Ok(args)
75    }
76
77    fn package_dir(&self, package: &PackageRef) -> Option<PathBuf> {
78        Some(PathBuf::from("mods").join(package.id().as_str()))
79    }
80
81    fn package_config_dirs(&self) -> Vec<PathBuf> {
82        Vec::new()
83    }
84
85    fn log_file(&self) -> Option<PathBuf> {
86        Some("mods/lovely/log".into())
87    }
88
89    fn proxy_dll(&self) -> Option<PathBuf> {
90        Some("version.dll".into())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use loadsmith_core::{PackageRef, Version};
97
98    use crate::{assert_map, assert_maps, test_util::MapFileTester};
99
100    use super::*;
101
102    #[test]
103    fn map_loader_files() {
104        assert_maps!(MapFileTester::new(
105            Lovely::new(),
106            PackageRef::new("Thunderstore-lovely".to_string(), Version::new(0, 9, 0)),
107            true,
108        ), [
109            "README.md" => None,
110            "version.dll" => "./version.dll",
111            "lovely/config.toml" => "mods/lovely/config.toml",
112        ]);
113    }
114
115    #[test]
116    fn map_package_files() {
117        assert_maps!(
118            MapFileTester::new(
119                Lovely::new(),
120                PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
121                false,
122            ),
123            [
124                "manifest.json" => "mods/Author-Name/manifest.json",
125                "nested/file.txt" => "mods/Author-Name/nested/file.txt",
126                "1/2/file.txt" => "mods/Author-Name/1/2/file.txt",
127            ]
128        );
129    }
130
131    #[test]
132    fn package_dir_works() {
133        let loader = Lovely::new();
134        let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
135
136        let package_dir = loader.package_dir(&package);
137
138        assert_eq!(package_dir, Some(PathBuf::from("mods/Author-Name")));
139    }
140}