1use std::{fmt::Debug, path::PathBuf, sync::LazyLock};
18
19use camino::Utf8PathBuf;
20use globset::{Glob, GlobBuilder, GlobSet};
21use loadsmith_core::PackageRef;
22use loadsmith_install::InstallRuleset;
23
24mod args;
25mod context;
26pub mod doorstop;
27mod error;
28mod loaders;
29
30pub use args::LaunchArgs;
31pub use context::LaunchContext;
32pub use error::{Error, Result};
33pub use loaders::*;
34
35pub trait Loader: Debug + Send + Sync {
57 fn id(&self) -> &'static str;
59
60 fn package_install_rules(&self) -> InstallRuleset<'_>;
62 fn loader_install_rules(&self) -> InstallRuleset<'_>;
64
65 fn prepare_launch(&self, ctx: &LaunchContext) -> Result<()> {
69 static GLOB_SET: LazyLock<GlobSet> = LazyLock::new(|| {
70 GlobSet::builder()
71 .add(top_level_dll_glob())
72 .build()
73 .expect("constant globs should be valid")
74 });
75
76 ctx.copy_glob_to_game(&GLOB_SET)
77 }
78
79 fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs>;
82
83 fn package_dir(&self, package: &PackageRef) -> Option<PathBuf> {
85 self.package_install_rules()
86 .default_rule()
87 .and_then(|default_rule| default_rule.map_file("", package))
88 .map(Utf8PathBuf::into_std_path_buf)
89 }
90
91 fn package_config_dirs(&self) -> Vec<PathBuf> {
93 Vec::new()
94 }
95 fn log_file(&self) -> Option<PathBuf> {
97 None
98 }
99 fn proxy_dll(&self) -> Option<PathBuf> {
101 None
102 }
103}
104
105fn top_level_dll_glob() -> Glob {
106 GlobBuilder::new("*.dll")
107 .literal_separator(true)
108 .build()
109 .expect("constant glob should be valid")
110}
111
112#[cfg(test)]
113mod test_util {
114 use camino::{Utf8Path, Utf8PathBuf};
115 use loadsmith_core::PackageRef;
116 use loadsmith_install::InstallRuleset;
117
118 use crate::Loader;
119
120 #[macro_export]
121 macro_rules! assert_map {
122 ($tester:expr, $file:literal, $expected:literal) => {
123 assert_eq!(
124 $tester.map($file),
125 Some(camino::Utf8PathBuf::from($expected))
126 );
127 };
128 ($tester:expr, $file:literal, None) => {
129 assert_eq!($tester.map($file), None);
130 };
131 }
132
133 #[macro_export]
134 macro_rules! assert_maps {
135 ($tester:expr, [$($file:literal => $expected:tt),* $(,)?]) => {
136 {
137 let _tester = $tester;
138 $(
139 assert_map!(_tester, $file, $expected);
140 )*
141 }
142 };
143 }
144
145 pub struct MapFileTester<T> {
146 loader: T,
147 package: PackageRef,
148 loader_package: bool,
149 }
150
151 impl<T: Loader> MapFileTester<T> {
152 pub fn new(loader: T, package: PackageRef, loader_package: bool) -> Self {
153 Self {
154 package,
155 loader_package,
156 loader,
157 }
158 }
159
160 pub fn ruleset(&self) -> InstallRuleset<'_> {
161 if self.loader_package {
162 self.loader.loader_install_rules()
163 } else {
164 self.loader.package_install_rules()
165 }
166 }
167
168 pub fn map(&self, file: impl AsRef<Utf8Path>) -> Option<Utf8PathBuf> {
169 self.ruleset().map_file(file, &self.package)
170 }
171 }
172}