mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
horizon-prepare: name the game files one by one (#3545)
--version-json, --client-jar, --libraries-dir and --assets-dir name the files a launcher keeps in a layout of its own (instance folders, shared libraries and assets), each replacing only what --minecraft-dir would give; every file is still checked against research-lock.json's pins. GameFiles gains the libraries and asset store folders, and Options the Inputs (Default in the launcher, so it is unchanged). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
82cf8916e2
commit
7f8c190b29
6 files changed
+188
-22
No files matched your search
@@ -15,7 +15,8 @@ const LAUNCHER_HINT: &str =
|
||||
Minecraft Java Edition with so it downloads the game's files, then rerun. Horizon reads the \
|
||||
official launcher's folder, ATLauncher's and the CurseForge app's; for Prism Launcher, MultiMC, \
|
||||
the Modrinth App or GDLauncher, prepare from the Horizon launcher, which finds their files. If your launcher keeps \
|
||||
them in another folder, pass --minecraft-dir or set HORIZON_MINECRAFT_DIR.";
|
||||
them in another folder, pass --minecraft-dir or set HORIZON_MINECRAFT_DIR, or name the files with \
|
||||
--version-json, --client-jar, --libraries-dir and --assets-dir.";
|
||||
const SERVER_HINT: &str = "Horizon does not download the game: download the {version} server jar yourself (minecraft.net/download/server \
|
||||
while {version} is the current release, otherwise the downloads.server.url in your launcher's \
|
||||
versions/{version}/{version}.json), then pass --server-jar PATH or set HORIZON_SERVER_JAR.";
|
||||
@@ -137,13 +138,40 @@ pub struct GameFiles {
|
||||
pub folder: PathBuf,
|
||||
pub manifest: PathBuf,
|
||||
pub client: PathBuf,
|
||||
/// Where the client's libraries are (`folder`'s `libraries`, or `--libraries-dir`).
|
||||
pub libraries: PathBuf,
|
||||
/// The asset store: `indexes/` and `objects/` (`folder`'s `assets`, or `--assets-dir`).
|
||||
pub assets: PathBuf,
|
||||
/// Mojang's Java runtimes the launcher downloaded (java::minecraft_runtimes).
|
||||
pub runtimes: PathBuf,
|
||||
/// The folders looked in, each with its launcher, when none held the version.
|
||||
searched: Vec<(&'static str, PathBuf)>,
|
||||
}
|
||||
|
||||
/// Files named one by one (`--version-json`, `--client-jar`, `--libraries-dir`, `--assets-dir`),
|
||||
/// for launchers whose layout is not one `game_files` knows: each replaces what it names.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Inputs {
|
||||
pub version_json: Option<PathBuf>,
|
||||
pub client_jar: Option<PathBuf>,
|
||||
pub libraries_dir: Option<PathBuf>,
|
||||
pub assets_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl GameFiles {
|
||||
/// These files with what `inputs` names instead.
|
||||
pub fn with(mut self, inputs: &Inputs) -> Self {
|
||||
let pick = |given: &Option<PathBuf>, found: &mut PathBuf| {
|
||||
if let Some(given) = given.as_ref().filter(|p| !p.as_os_str().is_empty()) {
|
||||
*found = expand_user(given);
|
||||
}
|
||||
};
|
||||
pick(&inputs.version_json, &mut self.manifest);
|
||||
pick(&inputs.client_jar, &mut self.client);
|
||||
pick(&inputs.libraries_dir, &mut self.libraries);
|
||||
pick(&inputs.assets_dir, &mut self.assets);
|
||||
self
|
||||
}
|
||||
/// What to do when these files are missing, with the folders looked in.
|
||||
pub(crate) fn hint(&self, version: &str) -> String {
|
||||
let mut text = hint(LAUNCHER_HINT, version);
|
||||
@@ -165,6 +193,8 @@ fn layouts(folder: &Path, version: &str) -> [GameFiles; 2] {
|
||||
folder: folder.to_path_buf(),
|
||||
manifest: folder.join(manifest),
|
||||
client: folder.join(client),
|
||||
libraries: folder.join("libraries"),
|
||||
assets: folder.join("assets"),
|
||||
runtimes: folder.join(runtimes),
|
||||
searched: Vec::new(),
|
||||
};
|
||||
@@ -527,7 +557,11 @@ pub(crate) fn safe_relative(path: &str) -> Option<PathBuf> {
|
||||
/// `com/mojang/jtracy-natives-windows/1.14.38/jtracy-natives-windows-1.14.38.jar`. The path the
|
||||
/// version JSON gives is returned when neither exists, for the error to name.
|
||||
pub fn launcher_library(minecraft: &Path, name: &str, relative: &Path) -> PathBuf {
|
||||
let libraries = minecraft.join("libraries");
|
||||
library_in(&minecraft.join("libraries"), name, relative)
|
||||
}
|
||||
|
||||
/// `launcher_library` in a libraries folder of its own.
|
||||
fn library_in(libraries: &Path, name: &str, relative: &Path) -> PathBuf {
|
||||
let official = libraries.join(relative);
|
||||
if official.is_file() {
|
||||
return official;
|
||||
@@ -640,10 +674,11 @@ pub fn import(
|
||||
root: &Path,
|
||||
lock: &Lock,
|
||||
minecraft: Option<&Path>,
|
||||
inputs: &Inputs,
|
||||
server_jar: Option<&Path>,
|
||||
log: Log,
|
||||
) -> Result<(), String> {
|
||||
let files = game_files(minecraft, &lock.version);
|
||||
let files = game_files(minecraft, &lock.version).with(inputs);
|
||||
let minecraft = &files.folder;
|
||||
let manifest = import_manifest(lock, &files)?;
|
||||
let client = import_copy(
|
||||
@@ -676,12 +711,12 @@ pub fn import(
|
||||
for (name, artifact) in client_library_artifacts(&manifest)? {
|
||||
let (path, sha1) = artifact_fields(artifact)?;
|
||||
let relative = safe_relative(path).ok_or("Unsafe library path")?;
|
||||
let source = launcher_library(minecraft, name, &relative);
|
||||
let source = library_in(&files.libraries, name, &relative);
|
||||
let destination = lock.directory.join("libraries/client").join(&relative);
|
||||
if runs_without(name) && !source.is_file() && !destination.exists() {
|
||||
log(&format!(
|
||||
"No {name} in {}: the game runs without it",
|
||||
minecraft.display()
|
||||
files.libraries.display()
|
||||
));
|
||||
continue;
|
||||
}
|
||||
@@ -1339,7 +1374,16 @@ mod tests {
|
||||
.unwrap();
|
||||
let lock = Lock::read(&fixture.target).unwrap();
|
||||
let server = old.join("jars/server.jar");
|
||||
let prepare = || import(&fixture.target, &lock, Some(&prism), Some(&server), &|_| {});
|
||||
let prepare = || {
|
||||
import(
|
||||
&fixture.target,
|
||||
&lock,
|
||||
Some(&prism),
|
||||
&Inputs::default(),
|
||||
Some(&server),
|
||||
&|_| {},
|
||||
)
|
||||
};
|
||||
let imported = || {
|
||||
let manifest = verify(&lock).unwrap().manifest;
|
||||
client_libraries(&lock, &manifest).unwrap().len()
|
||||
@@ -1405,7 +1449,14 @@ mod tests {
|
||||
let server = old.join("jars/server.jar");
|
||||
let prepare = |dir: &Path| {
|
||||
let _ = fs::remove_dir_all(&lock.directory);
|
||||
import(&fixture.target, &lock, Some(dir), Some(&server), &|_| {})
|
||||
import(
|
||||
&fixture.target,
|
||||
&lock,
|
||||
Some(dir),
|
||||
&Inputs::default(),
|
||||
Some(&server),
|
||||
&|_| {},
|
||||
)
|
||||
};
|
||||
|
||||
for chosen in [atlauncher.clone(), atlauncher.join("instances/Minecraft99")] {
|
||||
@@ -1438,6 +1489,68 @@ mod tests {
|
||||
.contains("may have rewritten it"));
|
||||
}
|
||||
|
||||
/// A launcher with a layout of its own names each file (`--version-json`, `--client-jar`,
|
||||
/// `--libraries-dir`): they prepare with the same pins, from no Minecraft folder at all.
|
||||
#[test]
|
||||
fn named_inputs_prepare_without_a_minecraft_folder() {
|
||||
let fixture = Imports::new();
|
||||
let old = fixture.imported(&fixture.source);
|
||||
let own = fixture.directory.join("OwnLauncher");
|
||||
let put = |path: &str, data: &[u8]| {
|
||||
let path = own.join(path);
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(path, data).unwrap();
|
||||
};
|
||||
let client = fs::read(old.join("jars/client.jar")).unwrap();
|
||||
put("instance/minecraft.jar", &client);
|
||||
put("shared/libs/client.jar", b"client library");
|
||||
let manifest = fs::read_to_string(old.join("manifest.json")).unwrap();
|
||||
let manifest = manifest.replacen('{', r#"{"javaVersion":{"majorVersion":25},"#, 1);
|
||||
put("meta/9.9.json", manifest.as_bytes());
|
||||
let pinned = digest(&own.join("meta/9.9.json"), "sha1").unwrap();
|
||||
let lock_path = fixture.target.join("research-lock.json");
|
||||
let text = fs::read_to_string(&lock_path).unwrap();
|
||||
let old_sha1 = digest(&old.join("manifest.json"), "sha1").unwrap();
|
||||
fs::write(&lock_path, text.replace(&old_sha1, &pinned)).unwrap();
|
||||
let inputs = Inputs {
|
||||
version_json: Some(own.join("meta/9.9.json")),
|
||||
client_jar: Some(own.join("instance/minecraft.jar")),
|
||||
libraries_dir: Some(own.join("shared/libs")),
|
||||
assets_dir: None,
|
||||
};
|
||||
let lock = Lock::read(&fixture.target).unwrap();
|
||||
let server = old.join("jars/server.jar");
|
||||
let empty = fixture.directory.join("no-minecraft");
|
||||
fs::create_dir_all(&empty).unwrap();
|
||||
let prepare = |inputs: &Inputs| {
|
||||
let _ = fs::remove_dir_all(&lock.directory);
|
||||
import(
|
||||
&fixture.target,
|
||||
&lock,
|
||||
Some(&empty),
|
||||
inputs,
|
||||
Some(&server),
|
||||
&|_| {},
|
||||
)
|
||||
};
|
||||
|
||||
prepare(&inputs).unwrap();
|
||||
let artifacts = verify(&lock).unwrap();
|
||||
assert_eq!(fs::read(&artifacts.jars["client"]).unwrap(), client);
|
||||
assert_eq!(
|
||||
client_libraries(&lock, &artifacts.manifest).unwrap().len(),
|
||||
1
|
||||
);
|
||||
// Without them the empty folder has nothing; a wrong jar fails its pin.
|
||||
assert!(prepare(&Inputs::default())
|
||||
.unwrap_err()
|
||||
.starts_with("Missing "));
|
||||
put("instance/minecraft.jar", b"other");
|
||||
assert!(prepare(&inputs)
|
||||
.unwrap_err()
|
||||
.contains("is not the pinned file"));
|
||||
}
|
||||
|
||||
/// With no launcher's files in the chosen folder, the error names where Horizon looked and
|
||||
/// the launchers it reads.
|
||||
#[test]
|
||||
@@ -1446,7 +1559,15 @@ mod tests {
|
||||
let empty = fixture.directory.join("instances/empty");
|
||||
fs::create_dir_all(&empty).unwrap();
|
||||
let lock = Lock::read(&fixture.target).unwrap();
|
||||
let error = import(&fixture.target, &lock, Some(&empty), None, &|_| {}).unwrap_err();
|
||||
let error = import(
|
||||
&fixture.target,
|
||||
&lock,
|
||||
Some(&empty),
|
||||
&Inputs::default(),
|
||||
None,
|
||||
&|_| {},
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.starts_with(&format!(
|
||||
"Missing {}",
|
||||
empty.join("versions/9.9/9.9.json").display()
|
||||
|
||||
@@ -98,6 +98,9 @@ pub struct Options {
|
||||
/// The Java runtime to try after JAVA_HOME and the PATH (the launcher's pick, e.g. Minecraft's
|
||||
/// own Java it downloaded from Mojang).
|
||||
pub java_home: Option<PathBuf>,
|
||||
/// Files named one by one instead of found in `minecraft_dir` (`--version-json`,
|
||||
/// `--client-jar`, `--libraries-dir`, `--assets-dir`).
|
||||
pub inputs: artifacts::Inputs,
|
||||
/// Where the resources go (`--output`); None for `root`'s `.local/horizon-client/<version>`.
|
||||
/// The exporters' scratch files stay in `root`'s `.local/` either way.
|
||||
pub output: Option<PathBuf>,
|
||||
@@ -331,6 +334,7 @@ struct Run<'a> {
|
||||
origin: Value,
|
||||
log: Log<'a>,
|
||||
progress: Progress<'a>,
|
||||
inputs: artifacts::Inputs,
|
||||
/// The stages of `steps` begun so far.
|
||||
stages: std::cell::Cell<u32>,
|
||||
}
|
||||
@@ -493,6 +497,7 @@ fn prepare_impl(
|
||||
root,
|
||||
&lock,
|
||||
options.minecraft_dir.as_deref(),
|
||||
&options.inputs,
|
||||
options.server_jar.as_deref(),
|
||||
log,
|
||||
)?;
|
||||
@@ -548,6 +553,7 @@ fn prepare_impl(
|
||||
origin,
|
||||
log,
|
||||
progress,
|
||||
inputs: options.inputs.clone(),
|
||||
stages: std::cell::Cell::new(0),
|
||||
};
|
||||
if compile_only {
|
||||
@@ -782,7 +788,7 @@ fn steps(
|
||||
// the vanilla pack's icon (`pack.png`) for the pack screen.
|
||||
run.stage();
|
||||
(run.log)("Copying the client's assets and data");
|
||||
let mut indexed = sounds::Assets::load(repo, Some(root), minecraft, run.log);
|
||||
let mut indexed = sounds::Assets::load(repo, Some(root), minecraft, &run.inputs, run.log);
|
||||
let (version, count) = extract_client_resources(&run.client, root, indexed.as_mut().ok())?;
|
||||
sounds::optional_imports(indexed.as_mut().map_err(|e| e.as_str()), run.log);
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@ Usage: horizon-prepare [--prepare] [--server-jar PATH] [--minecraft-dir PATH] [-
|
||||
(default: HORIZON_SERVER_JAR, or the copy imported before)
|
||||
--minecraft-dir PATH The official launcher's game directory with your installed copy
|
||||
(default: HORIZON_MINECRAFT_DIR, or the launcher's own)
|
||||
--version-json PATH The pinned version's JSON, where --minecraft-dir has none
|
||||
--client-jar PATH The pinned client jar, where --minecraft-dir has none
|
||||
--libraries-dir PATH The folder of the client's libraries (Maven layout)
|
||||
--assets-dir PATH The asset store, with indexes/ and objects/
|
||||
(each of these four replaces what --minecraft-dir would give)
|
||||
--root PATH The Horizon folder to prepare (default: the folder this program is
|
||||
in, else the nearest folder above the current one with research-lock.json)
|
||||
--output PATH Where the resources go (default: <root>/.local/horizon-client/<version>);
|
||||
@@ -81,6 +86,7 @@ fn main() -> ExitCode {
|
||||
minecraft_dir: None,
|
||||
server_jar: None,
|
||||
java_home: None,
|
||||
inputs: Default::default(),
|
||||
output: None,
|
||||
};
|
||||
let mut root = None;
|
||||
@@ -121,6 +127,14 @@ fn main() -> ExitCode {
|
||||
}
|
||||
"--server-jar" => value("--server-jar").map(|v| options.server_jar = Some(v)),
|
||||
"--minecraft-dir" => value("--minecraft-dir").map(|v| options.minecraft_dir = Some(v)),
|
||||
"--version-json" => {
|
||||
value("--version-json").map(|v| options.inputs.version_json = Some(v))
|
||||
}
|
||||
"--client-jar" => value("--client-jar").map(|v| options.inputs.client_jar = Some(v)),
|
||||
"--libraries-dir" => {
|
||||
value("--libraries-dir").map(|v| options.inputs.libraries_dir = Some(v))
|
||||
}
|
||||
"--assets-dir" => value("--assets-dir").map(|v| options.inputs.assets_dir = Some(v)),
|
||||
"--root" => value("--root").map(|v| root = Some(v)),
|
||||
"--output" => value("--output").map(|v| options.output = Some(v)),
|
||||
"--check" if inline.is_none() => {
|
||||
@@ -193,6 +207,7 @@ fn main() -> ExitCode {
|
||||
&options.root,
|
||||
options.output.as_deref(),
|
||||
options.minecraft_dir.as_deref(),
|
||||
&options.inputs,
|
||||
&log,
|
||||
)
|
||||
}
|
||||
@@ -232,6 +247,7 @@ fn complete_resources(
|
||||
&options.root,
|
||||
Some(&resources),
|
||||
options.minecraft_dir.as_deref(),
|
||||
&options.inputs,
|
||||
log,
|
||||
)?;
|
||||
let lacking = horizon_prepare::check(&options.root, &resources)?;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! `.local/horizon-client/<version>/assets` where the game reads them (tools/fetch_sounds.py and
|
||||
//! the tools/lab.py functions it called). Nothing is downloaded.
|
||||
|
||||
use crate::artifacts::{self, Lock};
|
||||
use crate::artifacts::{self, Inputs, Lock};
|
||||
use crate::pyjson::Value;
|
||||
use crate::{digest, Log};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -22,7 +22,7 @@ fn launcher_assets(
|
||||
cached_index: &Path,
|
||||
log: Log,
|
||||
) -> Result<(PathBuf, PathBuf, Vec<u8>), String> {
|
||||
let assets = files.folder.join("assets");
|
||||
let assets = files.assets.clone();
|
||||
let info = manifest
|
||||
.get("assetIndex")
|
||||
.ok_or("The version JSON has no assetIndex")?;
|
||||
@@ -77,20 +77,20 @@ fn is_sound(name: &str) -> bool {
|
||||
/// `.local/horizon-client/<version>`; returns that folder. Files already there with the right
|
||||
/// SHA-1 are kept.
|
||||
pub fn sounds(root: &Path, minecraft: Option<&Path>, log: Log) -> Result<PathBuf, String> {
|
||||
add_assets("sounds", root, None, minecraft, log)
|
||||
add_assets("sounds", root, None, minecraft, &Inputs::default(), log)
|
||||
}
|
||||
|
||||
/// Replaces the client JAR's 1x1 title placeholders with the full panorama from the
|
||||
/// launcher's asset store. Also usable on already-prepared resources without Java.
|
||||
pub fn panorama(root: &Path, minecraft: Option<&Path>, log: Log) -> Result<PathBuf, String> {
|
||||
add_assets("panorama", root, None, minecraft, log)
|
||||
add_assets("panorama", root, None, minecraft, &Inputs::default(), log)
|
||||
}
|
||||
|
||||
/// Replaces the client JAR's empty `font/include/unifont.json` with the asset index's, and
|
||||
/// adds the GNU Unifont glyphs it names (`font/unifont*.zip`): the default font's fallback for
|
||||
/// every character the bitmap fonts lack (#1076). Also usable on prepared resources without Java.
|
||||
pub fn fonts(root: &Path, minecraft: Option<&Path>, log: Log) -> Result<PathBuf, String> {
|
||||
add_assets("fonts", root, None, minecraft, log)
|
||||
add_assets("fonts", root, None, minecraft, &Inputs::default(), log)
|
||||
}
|
||||
|
||||
/// Every bundled language and the language list, both of which only the asset index holds
|
||||
@@ -98,7 +98,7 @@ pub fn fonts(root: &Path, minecraft: Option<&Path>, log: Log) -> Result<PathBuf,
|
||||
/// language screen offers, `minecraft/lang/<code>.json` the strings each one draws (#1272).
|
||||
/// Also usable on already-prepared resources without Java.
|
||||
pub fn languages(root: &Path, minecraft: Option<&Path>, log: Log) -> Result<PathBuf, String> {
|
||||
add_assets("languages", root, None, minecraft, log)
|
||||
add_assets("languages", root, None, minecraft, &Inputs::default(), log)
|
||||
}
|
||||
|
||||
const LANGUAGES: [&str; 1] = ["pack.mcmeta"];
|
||||
@@ -111,7 +111,7 @@ fn is_language(name: &str) -> bool {
|
||||
#[cfg(test)]
|
||||
fn optional_assets(root: &Path, minecraft: Option<&Path>, log: Log) {
|
||||
optional_imports(
|
||||
Assets::load(root, None, minecraft, log)
|
||||
Assets::load(root, None, minecraft, &Inputs::default(), log)
|
||||
.as_mut()
|
||||
.map_err(|e| e.as_str()),
|
||||
log,
|
||||
@@ -185,7 +185,7 @@ const UNIFONT: [&str; 2] = [
|
||||
/// Packs made to sit on Programmer Art, like Golden Days, show the default textures without it.
|
||||
/// Also usable on prepared resources without Java.
|
||||
pub fn packs(root: &Path, minecraft: Option<&Path>, log: Log) -> Result<PathBuf, String> {
|
||||
add_assets("packs", root, None, minecraft, log)
|
||||
add_assets("packs", root, None, minecraft, &Inputs::default(), log)
|
||||
}
|
||||
|
||||
/// The built-in packs `packs` copies, under the prepared resources' `assets`.
|
||||
@@ -220,19 +220,21 @@ const KINDS: [Kind; 5] = [
|
||||
];
|
||||
|
||||
/// `horizon-prepare <kind>`: copies what `kind` names (`sounds`, `panorama`, `fonts`, `packs` or
|
||||
/// `languages`) into the resources at `output` (default: `root`'s `.local/horizon-client/<version>`).
|
||||
/// `languages`) into the resources at `output` (default: `root`'s `.local/horizon-client/<version>`),
|
||||
/// from the asset store of `minecraft` or the one `inputs` names.
|
||||
pub fn add_assets(
|
||||
kind: &str,
|
||||
root: &Path,
|
||||
output: Option<&Path>,
|
||||
minecraft: Option<&Path>,
|
||||
inputs: &Inputs,
|
||||
log: Log,
|
||||
) -> Result<PathBuf, String> {
|
||||
let (_, wanted, label, required) = KINDS
|
||||
.iter()
|
||||
.find(|(name, ..)| *name == kind)
|
||||
.ok_or_else(|| format!("no assets called {kind}"))?;
|
||||
Assets::load(root, output, minecraft, log)?.copy(*wanted, label, required, log)
|
||||
Assets::load(root, output, minecraft, inputs, log)?.copy(*wanted, label, required, log)
|
||||
}
|
||||
|
||||
/// A preparation's index and verified destinations. Never persisted: a later invocation
|
||||
@@ -249,10 +251,11 @@ impl Assets {
|
||||
root: &Path,
|
||||
output: Option<&Path>,
|
||||
minecraft: Option<&Path>,
|
||||
inputs: &Inputs,
|
||||
log: Log,
|
||||
) -> Result<Self, String> {
|
||||
let lock = Lock::read(root)?;
|
||||
let files = artifacts::game_files(minecraft, &lock.version);
|
||||
let files = artifacts::game_files(minecraft, &lock.version).with(inputs);
|
||||
let manifest = artifacts::import_manifest(&lock, &files)?;
|
||||
let target = crate::resources_dir(root, &lock.version, output);
|
||||
let (store, index, bytes) = launcher_assets(
|
||||
@@ -840,7 +843,14 @@ mod tests {
|
||||
#[test]
|
||||
fn one_index_snapshot_serves_every_import_and_reruns_repair_corruption() {
|
||||
let f = Fixture::with_extras("snapshot", &PANORAMA, &[("pack.mcmeta", b"{}")]);
|
||||
let mut assets = Assets::load(&f.root, None, Some(&f.minecraft), &|_| {}).unwrap();
|
||||
let mut assets = Assets::load(
|
||||
&f.root,
|
||||
None,
|
||||
Some(&f.minecraft),
|
||||
&Inputs::default(),
|
||||
&|_| {},
|
||||
)
|
||||
.unwrap();
|
||||
fs::remove_file(f.minecraft.join("assets/indexes/7.json")).unwrap();
|
||||
let lines = std::cell::RefCell::new(Vec::new());
|
||||
optional_imports(Ok(&mut assets), &|line| {
|
||||
@@ -904,7 +914,14 @@ mod tests {
|
||||
// Preparing from cached JARs must also preserve full imports without the launcher.
|
||||
fs::remove_dir_all(f.minecraft.join("assets")).unwrap();
|
||||
for _ in 0..2 {
|
||||
let mut assets = Assets::load(&f.root, None, Some(&f.minecraft), &|_| {}).unwrap();
|
||||
let mut assets = Assets::load(
|
||||
&f.root,
|
||||
None,
|
||||
Some(&f.minecraft),
|
||||
&Inputs::default(),
|
||||
&|_| {},
|
||||
)
|
||||
.unwrap();
|
||||
let (_, count) =
|
||||
crate::extract_client_resources(&jar, &target, Some(&mut assets)).unwrap();
|
||||
assert_eq!(count, 5);
|
||||
|
||||
@@ -45,6 +45,11 @@ version until they are prepared again. `--complete` prepares, copies the sounds,
|
||||
unless `--check` would then pass: one command for resources with nothing missing.
|
||||
`--progress json` prints one JSON object a line instead of plain text: `{"message": …}` for
|
||||
each log line, `{"fraction": F}` (0 to 1) as each stage begins, `{"error": …}` on failure.
|
||||
A launcher whose files are laid out its own way names them one by one instead of
|
||||
`--minecraft-dir`: `--version-json` (the pinned version's JSON), `--client-jar`,
|
||||
`--libraries-dir` (the client's libraries, Maven layout) and `--assets-dir` (the asset store,
|
||||
with `indexes/` and `objects/`); each replaces only what it names, and every file is still
|
||||
checked against `research-lock.json`'s pins.
|
||||
Exit codes: 0 done, 1 failed or gaps found, 2 a refused command line.
|
||||
|
||||
```sh
|
||||
|
||||
@@ -1587,6 +1587,7 @@ pub fn preparer(
|
||||
minecraft_dir: Some(minecraft.clone()),
|
||||
server_jar: (!reused).then_some(jar),
|
||||
java_home: Some(java_home.clone()),
|
||||
inputs: Default::default(),
|
||||
output: None,
|
||||
};
|
||||
return Ok((Preparer::InProcess(options), minecraft));
|
||||
|
||||
Reference in new issue
Block a user