loadsmith_loader/loaders/
shimloader.rs1use std::{path::PathBuf, sync::LazyLock, vec};
2
3use loadsmith_install::{InstallRule, InstallRuleset, OwnedInstallRuleset, RouteRule};
4
5use crate::{LaunchArgs, LaunchContext, Loader, Result, glob_rule};
6
7#[derive(Debug, Clone)]
23pub struct Shimloader {
24 package_install_ruleset: OwnedInstallRuleset,
25}
26
27impl Shimloader {
28 pub fn with_rules(package_install_ruleset: OwnedInstallRuleset) -> Self {
30 Self {
31 package_install_ruleset,
32 }
33 }
34
35 pub fn with_default_rules() -> Self {
44 OwnedInstallRuleset::from_rule_iter(
45 [
46 RouteRule::new_static("shimloader/mod"),
47 RouteRule::new_static("shimloader/pak").with_file_extension("pak"),
48 RouteRule::new_static("shimloader/cfg")
49 .with_subdir(false)
50 .with_mutable(true),
51 RouteRule::new_static("shimloader/overlay"),
52 ],
53 Some(0),
54 )
55 .map(Self::with_rules)
56 .expect("rules are not empty so there should always be a valid default rule index")
57 }
58}
59
60impl Default for Shimloader {
61 fn default() -> Self {
62 Self::with_default_rules()
63 }
64}
65
66impl Loader for Shimloader {
67 fn id(&self) -> &'static str {
68 "Shimloader"
69 }
70
71 fn loader_install_rules(&self) -> InstallRuleset<'_> {
72 static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
73 vec![
74 glob_rule!("UE4SS/Mods/*" => "shimloader/mod")
75 .use_links(true)
76 .strip_levels(2)
77 .into(),
78 glob_rule!("UE4SS/UE4SS.dll" => ".")
79 .use_links(true)
80 .strip_levels(1)
81 .into(),
82 glob_rule!("UE4SS/UE4SS-settings.ini" => ".")
83 .use_links(true)
84 .strip_levels(1)
85 .into(),
86 glob_rule!("dwmapi.dll" => ".").use_links(true).into(),
87 ]
88 });
89
90 InstallRuleset::new(&RULES)
91 }
92
93 fn package_install_rules(&self) -> InstallRuleset<'_> {
94 self.package_install_ruleset.as_ref()
95 }
96
97 fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
98 let path = ctx.profile_path().join("shimloader");
99
100 let mod_path = path.join("mod");
101 let pak_path = path.join("pak");
102 let cfg_path = path.join("cfg");
103
104 let mod_path = ctx.format_proton_path(&mod_path);
105 let pak_path = ctx.format_proton_path(&pak_path);
106 let cfg_path = ctx.format_proton_path(&cfg_path);
107
108 let args = LaunchArgs::new()
109 .arg("--mod-dir")
110 .arg(&*mod_path)
111 .arg("--pak-dir")
112 .arg(&*pak_path)
113 .arg("--cfg-dir")
114 .arg(&*cfg_path);
115
116 Ok(args)
117 }
118
119 fn package_config_dirs(&self) -> Vec<PathBuf> {
120 vec!["shimloader/cfg".into()]
121 }
122
123 fn log_file(&self) -> Option<PathBuf> {
124 None
125 }
126
127 fn proxy_dll(&self) -> Option<PathBuf> {
128 Some("dwmapi.dll".into())
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use loadsmith_core::{PackageRef, Version};
135
136 use crate::{assert_map, assert_maps, test_util::MapFileTester};
137
138 use super::*;
139
140 #[test]
141 fn map_loader_files() {
142 assert_maps!(MapFileTester::new(
143 Shimloader::with_default_rules(),
144 PackageRef::new("Thunderstore-unreal_shimloader".to_string(), Version::new(1, 1, 7)),
145 true,
146 ), [
147 "README.md" => None,
148 "dwmapi.dll" => "./dwmapi.dll",
149 "UE4SS/dwmapi.dll" => None,
150 "UE4SS/UE4SS.dll" => "./UE4SS.dll",
151 "UE4SS/UE4SS-settings.ini" => "./UE4SS-settings.ini",
152 "UE4SS/Mods/mods.json" => "shimloader/mod/mods.json",
153 "UE4SS/Mods/mods.txt" => "shimloader/mod/mods.txt",
154 "UE4SS/Mods/shared/scripts/script.lua" => "shimloader/mod/shared/scripts/script.lua",
155 ]);
156 }
157
158 #[test]
159 fn map_package_files() {
160 assert_maps!(
161 MapFileTester::new(
162 Shimloader::with_default_rules(),
163 PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
164 false,
165 ),
166 [
167 "README.md" => "shimloader/mod/Author-Name/README.md",
168 "pak/file" => "shimloader/pak/Author-Name/file",
169 "cfg/settings.json" => "shimloader/cfg/settings.json",
170 "nested/file.txt" => "shimloader/mod/Author-Name/file.txt",
171 "mypak.pak" => "shimloader/pak/Author-Name/mypak.pak",
172 ]
173 );
174 }
175
176 #[test]
177 fn package_dir_works() {
178 let loader = Shimloader::with_default_rules();
179 let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
180
181 let package_dir = loader.package_dir(&package);
182
183 assert_eq!(
184 package_dir,
185 Some(PathBuf::from("shimloader/mod/Author-Name"))
186 );
187 }
188}