loadsmith_loader/loaders/
northstar.rs1use std::{path::PathBuf, sync::LazyLock};
2
3use globset::{GlobBuilder, GlobSet};
4use loadsmith_install::{InstallRule, InstallRuleset, OwnedInstallRuleset, RouteRule};
5
6use crate::{LaunchArgs, LaunchContext, Loader, Result, glob, glob_rule};
7
8#[derive(Debug, Clone)]
24pub struct Northstar {
25 package_install_ruleset: OwnedInstallRuleset,
26}
27
28impl Northstar {
29 pub fn with_rules(package_install_ruleset: OwnedInstallRuleset) -> Self {
31 Self {
32 package_install_ruleset,
33 }
34 }
35
36 pub fn with_default_rules() -> Self {
41 let exclude = GlobSet::builder()
42 .add(glob!("manifest.json"))
43 .add(glob!("README.md"))
44 .add(glob!("icon.png"))
45 .add(glob!("LICENSE"))
46 .build()
47 .expect("constant globs should be valid");
48
49 let ruleset = OwnedInstallRuleset::from_rule_iter(
50 vec![
51 RouteRule::new_static("R2Northstar/mods")
52 .with_subdir(false)
53 .with_flatten(false),
54 ],
55 None,
56 )
57 .expect("rules are not empty so there should always be a valid default rule index")
58 .with_exclude(exclude);
59
60 Self::with_rules(ruleset)
61 }
62
63 pub fn add_install_rule(&mut self, rule: impl Into<InstallRule>) {
65 self.package_install_ruleset.add_rule(rule.into());
66 }
67
68 pub fn insert_install_rule(&mut self, index: usize, rule: impl Into<InstallRule>) {
70 self.package_install_ruleset.insert_rule(index, rule.into());
71 }
72}
73
74impl Default for Northstar {
75 fn default() -> Self {
76 Self::with_default_rules()
77 }
78}
79
80impl Loader for Northstar {
81 fn id(&self) -> &'static str {
82 "Northstar"
83 }
84
85 fn loader_install_rules(&self) -> InstallRuleset<'_> {
86 static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
87 vec![
88 glob_rule!("Northstar/*" => ".")
89 .strip_top_level(true)
90 .use_links(true)
91 .into(),
92 ]
93 });
94
95 InstallRuleset::new(&RULES)
96 }
97
98 fn package_install_rules(&self) -> InstallRuleset<'_> {
99 self.package_install_ruleset.as_ref()
100 }
101
102 fn prepare_launch(&self, ctx: &LaunchContext) -> Result<()> {
103 static GLOB_SET: LazyLock<GlobSet> = LazyLock::new(|| {
104 GlobSet::builder()
105 .add(
106 GlobBuilder::new("*.{dll,exe,bat}")
107 .literal_separator(true)
108 .build()
109 .expect("constant glob should be valid"),
110 )
111 .build()
112 .expect("constant globs should be valid")
113 });
114
115 ctx.copy_glob_to_game(&GLOB_SET)
116 }
117
118 fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
119 let r2northstar_path = ctx.profile_path().join("R2Northstar");
120 let path = ctx.format_proton_path(&r2northstar_path);
121
122 let args = LaunchArgs::new()
123 .arg("-northstar")
124 .arg(format!("-profile={path}"));
125
126 Ok(args)
127 }
128
129 fn package_config_dirs(&self) -> Vec<PathBuf> {
130 Vec::new()
131 }
132
133 fn log_file(&self) -> Option<PathBuf> {
134 None
135 }
136
137 fn proxy_dll(&self) -> Option<PathBuf> {
138 None
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use loadsmith_core::{PackageRef, Version};
145
146 use crate::{assert_map, assert_maps, test_util::MapFileTester};
147
148 use super::*;
149
150 #[test]
151 fn map_loader_files() {
152 assert_maps!(MapFileTester::new(
153 Northstar::with_default_rules(),
154 PackageRef::new("northstar-Northstar".to_string(), Version::new(1, 31, 10)),
155 true,
156 ), [
157 "README.md" => None,
158 "Northstar/r2ds.bat" => "./r2ds.bat",
159 "Northstar/R2Northstar/file.txt" => "./R2Northstar/file.txt",
160 "Northstar/R2Northstar/plugins/DiscordRPC.dll" => "./R2Northstar/plugins/DiscordRPC.dll",
161 ])
162 }
163
164 #[test]
165 fn map_package_files() {
166 assert_maps!(MapFileTester::new(
167 Northstar::with_default_rules(),
168 PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
169 false,
170 ), [
171 "manifest.json" => None,
172 "README.md" => None,
173 "icon.png" => None,
174 "LICENSE" => None,
175 "file.txt" => None,
176 "mods/file.txt" => "R2Northstar/mods/file.txt",
177 "mods/nested/file.txt" => "R2Northstar/mods/nested/file.txt",
178 "nested/mods/file.txt" => "R2Northstar/mods/nested/file.txt",
179 ]);
180 }
181
182 #[test]
183 fn package_dir_works() {
184 let loader = Northstar::with_default_rules();
185 let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
186
187 assert!(loader.package_dir(&package).is_none());
188 }
189}