Skip to main content

loadsmith_loader/loaders/
rivet.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 [Rivet](https://thunderstore.io/c/lethal-company/p/ReDoIngMods/Rivet/),
9/// a mod loader for Lethal Company.
10///
11/// Rivet places mods into `Rivet/Mods` and uses a `version.dll` proxy DLL.
12/// Launch arguments include `-rivetEnable`, `-rivetTarget`, and `-rivetDirectory`.
13///
14/// # Examples
15///
16/// ```rust
17/// use loadsmith_loader::{Rivet, Loader};
18///
19/// let loader = Rivet::new();
20/// assert_eq!(loader.id(), "Rivet");
21/// ```
22#[derive(Debug, Clone)]
23pub struct Rivet;
24
25impl Rivet {
26    /// Creates a new `Rivet` loader.
27    pub fn new() -> Self {
28        Self
29    }
30}
31
32impl Default for Rivet {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl Loader for Rivet {
39    fn id(&self) -> &'static str {
40        "Rivet"
41    }
42
43    fn loader_install_rules(&self) -> InstallRuleset<'_> {
44        static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
45            // extract all non-top-level files into the package root
46            vec![glob_rule!("*/*" => ".").strip_top_level(true).into()]
47        });
48
49        InstallRuleset::new(&RULES)
50    }
51
52    fn package_install_rules(&self) -> InstallRuleset<'_> {
53        static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
54            vec![
55                glob_rule!("*" => "Rivet/Mods")
56                    .with_subdir(true)
57                    .use_links(true)
58                    .into(),
59            ]
60        });
61
62        InstallRuleset::new(&RULES)
63    }
64
65    fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
66        let rivet_directory = ctx.format_proton_path(ctx.profile_path().join("Rivet"));
67        let rivet_target =
68            ctx.format_proton_path(ctx.profile_path().join("Rivet").join("Loader.dll"));
69
70        let args = LaunchArgs::new()
71            .arg("-rivetEnable")
72            .arg("true")
73            .arg("-rivetTarget")
74            .arg(rivet_target)
75            .arg("-rivetDirectory")
76            .arg(rivet_directory);
77
78        Ok(args)
79    }
80
81    fn package_dir(&self, package: &PackageRef) -> Option<PathBuf> {
82        Some(PathBuf::from("Rivet/Mods").join(package.id().as_str()))
83    }
84
85    fn package_config_dirs(&self) -> Vec<PathBuf> {
86        Vec::new()
87    }
88
89    fn log_file(&self) -> Option<PathBuf> {
90        Some("Rivet/Rivet.log".into())
91    }
92
93    fn proxy_dll(&self) -> Option<PathBuf> {
94        Some("version.dll".into())
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use loadsmith_core::{PackageRef, Version};
101
102    use crate::{assert_map, assert_maps, test_util::MapFileTester};
103
104    use super::*;
105
106    #[test]
107    fn map_loader_files() {
108        assert_maps!(MapFileTester::new(
109            Rivet::new(),
110            PackageRef::new("ReDoIngMods-Rivet".to_string(), Version::new(0, 1, 9)),
111            true,
112        ), [
113            "README.md" => None,
114            "RivetPack/version.dll" => "./version.dll",
115            "RivetPack/Rivet.ini" => "./Rivet.ini",
116            "OtherFolder/file.txt" => "./file.txt",
117            "RivetPack/Rivet/Loader.dll" => "./Rivet/Loader.dll",
118        ]);
119    }
120
121    #[test]
122    fn map_package_files() {
123        assert_maps!(
124            MapFileTester::new(
125                Rivet::new(),
126                PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
127                false,
128            ),
129            [
130                "manifest.json" => "Rivet/Mods/Author-Name/manifest.json",
131                "nested/file.txt" => "Rivet/Mods/Author-Name/nested/file.txt",
132                "1/2/file.txt" => "Rivet/Mods/Author-Name/1/2/file.txt",
133            ]
134        );
135    }
136
137    #[test]
138    fn package_dir_works() {
139        let loader = Rivet::new();
140        let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
141
142        let package_dir = loader.package_dir(&package);
143
144        assert_eq!(package_dir, Some(PathBuf::from("Rivet/Mods/Author-Name")));
145    }
146}