Skip to main content

loadsmith_loader/loaders/
bepis_loader.rs

1use std::path::PathBuf;
2
3use camino::Utf8Path;
4use loadsmith_install::{InstallRuleset, OwnedInstallRuleset, RouteRule};
5
6use crate::{BepInEx, LaunchArgs, LaunchContext, Loader, Result};
7
8/// Loader implementation for BepisLoader, a mod loader for Honey Select /
9/// Koikatsu that wraps BepInEx with a renderer-specific plugin directory.
10///
11/// BepisLoader adds a `renderer` route pointing at `Renderer/BepInEx/plugins`
12/// on top of the standard BepInEx rules. It also forces Doorstop version 4.
13///
14/// # Examples
15///
16/// ```rust
17/// use loadsmith_loader::{BepisLoader, Loader};
18///
19/// let loader = BepisLoader::with_default_rules();
20/// assert_eq!(loader.id(), "BepisLoader");
21/// ```
22#[derive(Debug, Clone)]
23pub struct BepisLoader {
24    inner: BepInEx,
25}
26
27impl BepisLoader {
28    fn new(inner: BepInEx) -> Self {
29        Self { inner }
30    }
31
32    /// Creates a `BepisLoader` with a custom install ruleset.
33    pub fn with_rules(package_install_ruleset: OwnedInstallRuleset) -> Self {
34        Self::new(BepInEx::with_rules(package_install_ruleset))
35    }
36
37    /// Creates a `BepisLoader` with the default rules (BepInEx defaults plus a
38    /// `renderer` route).
39    pub fn with_default_rules() -> Self {
40        let mut inner = BepInEx::with_default_rules();
41        inner.insert_install_rule(
42            0,
43            RouteRule::new_with_target("renderer", Utf8Path::new("Renderer/BepInEx/plugins")),
44        );
45        Self::new(inner)
46    }
47}
48
49impl Loader for BepisLoader {
50    fn id(&self) -> &'static str {
51        "BepisLoader"
52    }
53
54    fn loader_install_rules(&self) -> InstallRuleset<'_> {
55        self.inner.loader_install_rules()
56    }
57
58    fn package_install_rules(&self) -> InstallRuleset<'_> {
59        self.inner.package_install_rules()
60    }
61
62    fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
63        // Don't use format_path as BepisLoader escapes Proton and therefore
64        // does not recognize the Z: prefix, see https://github.com/Kesomannen/gale/issues/627
65        let bepinex_target = ctx.profile_path().join("BepInEx");
66
67        let mut args = LaunchArgs::new()
68            .arg("--hookfxr-enable")
69            .arg("--bepinex-target")
70            .arg(bepinex_target);
71
72        let preloader = super::bep_in_ex::bepinex_preloader_path(Some("Renderer"), ctx)?;
73        let preloader = ctx.format_proton_path(&preloader);
74
75        let (enable_prefix, target_prefix) = crate::doorstop::args(Some(4), ctx)?;
76        args = args
77            .arg(enable_prefix)
78            .arg("true")
79            .arg(target_prefix)
80            .arg(&*preloader);
81
82        Ok(args)
83    }
84
85    fn package_config_dirs(&self) -> Vec<PathBuf> {
86        vec!["BepInEx/config".into(), "Renderer/BepInEx/config".into()]
87    }
88
89    fn log_file(&self) -> Option<PathBuf> {
90        self.inner.log_file()
91    }
92
93    fn proxy_dll(&self) -> Option<PathBuf> {
94        self.inner.proxy_dll()
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            BepisLoader::with_default_rules(),
110            PackageRef::new("BepInEx-BepInExPack".to_string(), Version::new(5, 4, 2100)),
111            true,
112        ), [
113            "README.md" => None,
114            "BepInExPack/doorstop_config.ini" => "./doorstop_config.ini",
115            "BepInExPack/BepInEx/core/BepInEx.Preloader.dll" => "./BepInEx/core/BepInEx.Preloader.dll"
116        ])
117    }
118
119    #[test]
120    fn map_package_files() {
121        assert_maps!(MapFileTester::new(
122            BepisLoader::with_default_rules(),
123            PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
124            false,
125        ), [
126            "README.md" => "BepInEx/plugins/Author-Name/README.md",
127            "nested/file.txt" => "BepInEx/plugins/Author-Name/file.txt",
128            "plugins/nested/file.txt" => "BepInEx/plugins/Author-Name/nested/file.txt",
129            "config/settings.json" => "BepInEx/config/settings.json",
130            "patchers/patcher.dll" => "BepInEx/patchers/Author-Name/patcher.dll",
131            "core/core.dll" => "BepInEx/core/Author-Name/core.dll",
132            "patch.mm.dll" => "BepInEx/monomod/Author-Name/patch.mm.dll",
133            "nested/patch.mm.dll" => "BepInEx/monomod/Author-Name/patch.mm.dll",
134            "monomod/patch.dll" => "BepInEx/monomod/Author-Name/patch.dll",
135            "monomod/nested/patch.dll" => "BepInEx/monomod/Author-Name/nested/patch.dll",
136            "renderer/patch.dll" => "Renderer/BepInEx/plugins/Author-Name/patch.dll",
137        ]);
138    }
139
140    #[test]
141    fn config_files_should_not_link() {
142        let loader = BepisLoader::with_default_rules();
143        let rules = loader.package_install_rules();
144
145        assert!(
146            !rules
147                .find_rule_for_path("config/settings.json")
148                .unwrap()
149                .use_links()
150        );
151    }
152
153    #[test]
154    fn package_dir_works() {
155        let loader = BepisLoader::with_default_rules();
156        let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
157
158        let package_dir = loader.package_dir(&package).unwrap();
159
160        assert_eq!(package_dir, PathBuf::from("BepInEx/plugins/Author-Name"));
161    }
162}