Merge pull request #9563 from xxaxio/feat/9547-sources-dawn-client

🤖 Agent: Dawn Client is only one more place to find the player's own 26.3 jar and assets (cache/minecraft under .dawn). The tests check that nothing is fetched from Mojang.
This commit is contained in:
Ludovic Marie authored and GitHub committed 2026-11-05 02:48:37 +01:00
commit 32e5a325a7
2 files changed
+53 -9

No files matched your search

+20 -7
View File
@@ -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<PathBuf> {
.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<PathBuf> {
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<Found, String> {
if !dir.is_dir() {
return Err("the folder doesn't exist".into());
@@ -273,7 +286,7 @@ pub fn find(dir: &Path, pins: &Pins) -> Result<Found, String> {
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<PathBuf> = 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);
}
+33 -2
View File
@@ -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()));
}