loadsmith_loader/loaders/
melon_loader.rs1use std::{path::PathBuf, sync::LazyLock};
2
3use camino::Utf8Path;
4use globset::GlobSet;
5use loadsmith_install::{InstallRule, InstallRuleset, OwnedInstallRuleset, RouteRule};
6
7use crate::{LaunchArgs, LaunchContext, Loader, Result, glob_rule};
8
9#[derive(Debug, Clone)]
28pub struct MelonLoader {
29 package_install_ruleset: OwnedInstallRuleset,
30}
31
32impl MelonLoader {
33 pub fn with_rules(package_install_ruleset: OwnedInstallRuleset) -> Self {
35 Self {
36 package_install_ruleset,
37 }
38 }
39
40 pub fn with_default_legacy_rules() -> Self {
43 OwnedInstallRuleset::from_rule_iter(
44 [
45 RouteRule::new_static("UserLibs")
46 .with_file_extension("lib.dll")
47 .with_subdir(false),
48 RouteRule::new_static("Plugins")
49 .with_file_extension("plugin.dll")
50 .with_subdir(false),
51 RouteRule::new_static("MelonLoader/Managed")
52 .with_file_extension("managed.dll")
53 .with_subdir(false),
54 RouteRule::new_static("MelonLoader/Libs").with_subdir(false),
55 RouteRule::new_static("MelonLoader").with_subdir(false),
56 RouteRule::new_static("Mods")
57 .with_file_extension("dll")
58 .with_subdir(false),
59 RouteRule::new_static("UserData/ModManager")
60 .with_flatten(false)
61 .with_mutable(true),
62 ],
63 Some(6),
64 )
65 .map(Self::with_rules)
66 .expect("rules are not empty so there should always be a valid default rule index")
67 }
68
69 pub fn with_default_recursive_rules() -> Self {
72 OwnedInstallRuleset::from_rule_iter(
73 [
74 RouteRule::new_with_target("", Utf8Path::new("Mods")).with_flatten(false),
75 RouteRule::new_static("UserData").with_mutable(true),
76 ],
77 Some(0),
78 )
79 .map(Self::with_rules)
80 .expect("rules are not empty so there should always be a valid default rule index")
81 }
82
83 pub fn set_exclude(&mut self, exclude: GlobSet) {
85 self.package_install_ruleset.set_exclude(exclude);
86 }
87
88 pub fn add_install_rule(&mut self, rule: InstallRule) {
90 self.package_install_ruleset.add_rule(rule);
91 }
92}
93
94impl Default for MelonLoader {
95 fn default() -> Self {
96 Self::with_default_legacy_rules()
97 }
98}
99
100impl Loader for MelonLoader {
101 fn id(&self) -> &'static str {
102 "MelonLoader"
103 }
104
105 fn loader_install_rules(&self) -> InstallRuleset<'_> {
106 static RULES: LazyLock<Vec<InstallRule>> = LazyLock::new(|| {
107 vec![
108 glob_rule!("MelonLoader/{Dependencies,Documentation,net*}/*" => ".")
109 .use_links(true)
110 .into(),
111 glob_rule!("{version,dobby}.dll" => ".").into(),
112 ]
113 });
114
115 InstallRuleset::new(&RULES)
116 }
117
118 fn package_install_rules(&self) -> InstallRuleset<'_> {
119 self.package_install_ruleset.as_ref()
120 }
121
122 fn generate_launch_args(&self, ctx: &LaunchContext) -> Result<LaunchArgs> {
123 let mut args = LaunchArgs::new()
124 .arg("--melonloader.basedir")
125 .arg(ctx.profile_path());
126
127 let mono_assembly_exists = ctx
128 .profile_path()
129 .join("MelonLoader/Managed/Assembly-CSharp.dll")
130 .exists();
131 let il2cpp_assembly_exists = ctx
132 .profile_path()
133 .join("MelonLoader/Il2CppAssemblies/Assembly-CSharp.dll")
134 .exists();
135
136 if !mono_assembly_exists && !il2cpp_assembly_exists {
137 args = args.arg("--melonloader.agfregenerate");
138 }
139
140 Ok(args)
141 }
142
143 fn package_config_dirs(&self) -> Vec<PathBuf> {
144 Vec::new()
145 }
146
147 fn log_file(&self) -> Option<PathBuf> {
148 Some("MelonLoader/Latest.log".into())
149 }
150
151 fn proxy_dll(&self) -> Option<PathBuf> {
152 None
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use loadsmith_core::{PackageRef, Version};
159
160 use crate::{assert_map, assert_maps, test_util::MapFileTester};
161
162 use super::*;
163
164 #[test]
165 fn map_loader_files() {
166 assert_maps!(MapFileTester::new(
167 MelonLoader::with_default_legacy_rules(),
168 PackageRef::new("LavaGang-MelonLoader".to_string(), Version::new(1, 0, 0)),
169 true,
170 ), [
171 "README.md" => None,
172 "version.dll" => "./version.dll",
173 "MelonLoader/Dependencies/Name/file.dll" => "./MelonLoader/Dependencies/Name/file.dll",
174 "MelonLoader/Documentation/file.txt" => "./MelonLoader/Documentation/file.txt",
175 "MelonLoader/net6/file.dll" => "./MelonLoader/net6/file.dll",
176 "MelonLoader/net35/file.dll" => "./MelonLoader/net35/file.dll",
177 "MelonLoader/net472/file.dll" => "./MelonLoader/net472/file.dll",
178 ])
179 }
180
181 #[test]
182 fn map_package_files() {
183 assert_maps!(MapFileTester::new(
184 MelonLoader::with_default_legacy_rules(),
185 PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0)),
186 false,
187 ), [
188 "UserLibs/file" => "UserLibs/file",
189 "UserLibs/nested/file" => "UserLibs/nested/file",
190 "userlibrary.lib.dll" => "UserLibs/userlibrary.lib.dll",
191 "Managed/file" => "MelonLoader/Managed/file",
192 "Managed/nested/file" => "MelonLoader/Managed/nested/file",
193 "managed.managed.dll" => "MelonLoader/Managed/managed.managed.dll",
194 "Mods/file" => "Mods/file",
195 "Mods/nested/file" => "Mods/nested/file",
196 "mod.dll" => "Mods/mod.dll",
197 "manifest.json" => "UserData/ModManager/Author-Name/manifest.json",
198 "ModManager/file" => "UserData/ModManager/Author-Name/file",
199 "ModManager/nested/file" => "UserData/ModManager/Author-Name/nested/file",
200 "MelonLoader/file" => "MelonLoader/file",
201 "MelonLoader/nested/file" => "MelonLoader/nested/file",
202 "MelonLoader/Libs/file" => "MelonLoader/Libs/file",
203 "MelonLoader/Libs/nested/file" => "MelonLoader/Libs/nested/file",
204 "MelonLoader/Libs/lib.dll" => "MelonLoader/Libs/lib.dll",
205 ]);
206 }
207
208 #[test]
209 fn package_dir_works() {
210 let loader = MelonLoader::with_default_legacy_rules();
211 let package = PackageRef::new("Author-Name".to_string(), Version::new(1, 0, 0));
212
213 let package_dir = loader.package_dir(&package);
214
215 assert_eq!(
216 package_dir,
217 Some(PathBuf::from("UserData/ModManager/Author-Name"))
218 );
219 }
220
221 #[test]
222 fn loader_files_should_link() {
223 let loader = MelonLoader::with_default_legacy_rules();
224 let rules = loader.loader_install_rules();
225
226 assert!(
227 rules
228 .find_rule_for_mapped_path("./MelonLoader/Dependencies/Name/file.dll")
229 .unwrap()
230 .use_links()
231 );
232 }
233}