diff --git a/launcher/src/sources.rs b/launcher/src/sources.rs index 5c2d31b0cd..7d2c9a6898 100644 --- a/launcher/src/sources.rs +++ b/launcher/src/sources.rs @@ -1,5 +1,6 @@ //! Where the player's own Minecraft files are: the folder they chose, the official launcher's, -//! or another launcher's (Prism Launcher, MultiMC, Modrinth App, ATLauncher, GDLauncher), #434. +//! or another launcher's (Prism Launcher, MultiMC, Modrinth App, ATLauncher, GDLauncher, Dawn +//! Client), #434. //! horizon-prepare reads the official launcher's layout and pins the version JSON's own sha1, so //! files found elsewhere are linked into that layout in the launcher's data folder, with the //! pinned version JSON from Mojang when the other launcher keeps its own rewrite of it. @@ -132,6 +133,7 @@ pub fn known_folders(os: Os, f: &Folders) -> Vec<(&'static str, PathBuf)> { "ATLauncher", flatpak("com.atlauncher.ATLauncher", "data/atlauncher"), ), + ("Dawn Client", f.home.join(".dawn")), ], Os::MacOs => vec![ (OFFICIAL, f.data.join("minecraft")), @@ -139,12 +141,14 @@ pub fn known_folders(os: Os, f: &Folders) -> Vec<(&'static str, PathBuf)> { ("MultiMC", f.data.join("MultiMC")), ("Modrinth App", f.data.join("ModrinthApp")), ("ATLauncher", f.data.join("ATLauncher")), + ("Dawn Client", f.data.join(".dawn")), ], Os::Windows => vec![ (OFFICIAL, f.data.join(".minecraft")), ("Prism Launcher", f.data.join("PrismLauncher")), ("Modrinth App", f.data.join("ModrinthApp")), ("ATLauncher", f.data.join("ATLauncher")), + ("Dawn Client", f.data.join(".dawn")), ], }; // Names every system shares: the Modrinth App before its rename, GDLauncher and its @@ -246,6 +250,14 @@ fn instance_root(dir: &Path) -> Option { .map(Path::to_path_buf) } +/// The Dawn Client root above `dir`: Dawn stores its shared official cache in `cache/minecraft/` +/// directly below `.dawn/`. +fn dawn_root(dir: &Path) -> Option { + dir.ancestors() + .find(|a| a.file_name() == Some(std::ffi::OsStr::new(".dawn"))) + .map(Path::to_path_buf) +} + /// The launcher whose tree `path` is inside, when its folder name is one Horizon knows, so the /// not-found message can name it (#693). fn launcher_of(path: &Path) -> Option<&'static str> { @@ -258,13 +270,14 @@ fn launcher_of(path: &Path) -> Option<&'static str> { "ModrinthApp" | "com.modrinth.theseus" => Some("the Modrinth App"), "atlauncher" | "ATLauncher" => Some("ATLauncher"), "gdlauncher_carbon" | "gdlauncher_next" => Some("GDLauncher"), + ".dawn" | "dawn" => Some("Dawn Client"), _ => None, }) } /// The pinned version in one launcher folder, or what the folder lacks. The folder holding /// `libraries` is the folder itself or its `meta` (Modrinth App), `data` (GDLauncher) or -/// `datastore` (GDLauncher Next). +/// `datastore` (GDLauncher Next) or `cache/minecraft` (Dawn Client). pub fn find(dir: &Path, pins: &Pins) -> Result { if !dir.is_dir() { return Err("the folder doesn't exist".into()); @@ -273,7 +286,7 @@ pub fn find(dir: &Path, pins: &Pins) -> Result { let mut other = None; let bases = [dir.to_path_buf()] .into_iter() - .chain(["meta", "data", "datastore"].map(|sub| dir.join(sub))); + .chain(["meta", "data", "datastore", "cache/minecraft"].map(|sub| dir.join(sub))); for base in bases { for jar in client_jars(&base, v) { let client = base.join(&jar); @@ -321,7 +334,7 @@ pub fn not_found(version: &str, checked: &[(PathBuf, String)], chosen: &Path) -> ), None => format!( "\n • Play {version} once in the official Minecraft launcher, or in Prism Launcher, \ - MultiMC, the Modrinth App, ATLauncher or GDLauncher, then prepare again.\n \ + MultiMC, the Modrinth App, ATLauncher, GDLauncher or Dawn Client, then prepare again.\n \ • Or choose the folder where your launcher keeps Minecraft {version} (Minecraft \ folder › Choose… in the launcher, --minecraft on the command line)." ), @@ -356,10 +369,10 @@ pub fn resolve( let mut checked = Vec::new(); let mut found = None; // The chosen folder, the folders above it (an instance's own folder may be chosen), and, - // when it is inside a Prism or MultiMC instance tree, the launcher root above `instances` - // that holds their shared `libraries` (#693). + // when it is inside a Prism or MultiMC instance tree or a Dawn Client folder, the launcher + // root that holds their shared `libraries` (#693). let mut dirs: Vec = chosen.ancestors().take(4).map(Path::to_path_buf).collect(); - if let Some(root) = instance_root(chosen) { + if let Some(root) = instance_root(chosen).or_else(|| dawn_root(chosen)) { if !dirs.contains(&root) { dirs.push(root); } diff --git a/launcher/src/sources_tests.rs b/launcher/src/sources_tests.rs index 396f50881b..fbf4b06086 100644 --- a/launcher/src/sources_tests.rs +++ b/launcher/src/sources_tests.rs @@ -289,6 +289,30 @@ fn the_modrinth_app_atlauncher_and_gdlauncher_are_found() { } } +#[test] +fn dawn_client_is_found_in_cache() { + let s = Scratch::new("dawn"); + let dir = s.launcher(".dawn", "cache/minecraft", "versions/26.3/26.3.jar"); + s.write(".dawn/cache/minecraft/versions/26.3/26.3.json", MANIFEST); + let linked = s.0.join("linked"); + let mojang = Mojang::new(b""); + let got = resolve_with(&dir, &[], &linked, &mojang).unwrap(); + assert_eq!(got, dir.join("cache/minecraft")); + assert!(mojang.urls.lock().unwrap().is_empty()); +} + +#[test] +fn a_dawn_sync_folder_finds_the_launcher_root() { + let s = Scratch::new("dawn-tree"); + let dir = s.launcher(".dawn", "cache/minecraft", "versions/26.3/26.3.jar"); + s.write(".dawn/cache/minecraft/versions/26.3/26.3.json", MANIFEST); + let chosen = dir.join("auto-sync/groups/default"); + fs::create_dir_all(&chosen).unwrap(); + let linked = s.0.join("linked"); + let got = resolve_with(&chosen, &[], &linked, &Mojang::new(b"")).unwrap(); + assert_eq!(got, dir.join("cache/minecraft")); +} + /// A Modrinth App that only ever played a Fabric profile of 26.3 keeps the vanilla client jar /// under the loader's name (#1783); a profile of another version beside it is not taken. #[test] @@ -389,7 +413,9 @@ fn the_not_found_message_names_the_launcher_that_was_chosen() { fs::create_dir_all(&other).unwrap(); let error = resolve_with(&other, &[], &s.0.join("linked"), &Mojang::new(MANIFEST)).unwrap_err(); assert!( - error.contains("Prism Launcher, MultiMC, the Modrinth App, ATLauncher or GDLauncher"), + error.contains( + "Prism Launcher, MultiMC, the Modrinth App, ATLauncher, GDLauncher or Dawn Client" + ), "{error}" ); } @@ -480,7 +506,9 @@ fn nothing_found_lists_every_folder_and_what_it_lacks() { empty.display() ); assert!(error.starts_with(&expected), "{error}"); - assert!(error.contains("Prism Launcher, MultiMC, the Modrinth App, ATLauncher or GDLauncher")); + assert!(error.contains( + "Prism Launcher, MultiMC, the Modrinth App, ATLauncher, GDLauncher or Dawn Client" + )); // A launcher that isn't installed isn't listed. assert!(!error.contains("atlauncher")); assert!(mojang.urls.lock().unwrap().is_empty()); @@ -521,6 +549,7 @@ fn each_system_has_its_launcher_folders() { "ATLauncher: /home/r/.local/share/atlauncher", "GDLauncher: /home/r/.local/share/gdlauncher_carbon", "GDLauncher: /home/r/.config/gdlauncher_next", + "Dawn Client: /home/r/.dawn", ] { assert!(got.iter().any(|g| g == expected), "{expected} in {got:#?}"); } @@ -538,6 +567,7 @@ fn each_system_has_its_launcher_folders() { assert!( got.contains(&"Prism Launcher: /Users/r/Library/Application Support/PrismLauncher".into()) ); + assert!(got.contains(&"Dawn Client: /Users/r/Library/Application Support/.dawn".into())); let windows = Folders { home: "C:/Users/r".into(), data: "C:/Users/r/AppData/Roaming".into(), @@ -549,4 +579,5 @@ fn each_system_has_its_launcher_folders() { "the official launcher: C:/Users/r/AppData/Roaming/.minecraft" ); assert!(got.contains(&"ATLauncher: C:/Users/r/AppData/Roaming/ATLauncher".into())); + assert!(got.contains(&"Dawn Client: C:/Users/r/AppData/Roaming/.dawn".into())); }