Install a pre-built collection of mods + config overrides described by
a Modrinth .mrpack or a CurseForge .zip. Compiled in as soon as the
matching provider feature (modrinth or curseforge) is enabled — no
separate modpack feature.
.with_mod()
.with_modrinth_modpack("https://cdn.modrinth.com/.../pack.mrpack")
// or, pinned by (project, version_id):
.with_modrinth_modpack(ModpackSource::ModrinthPinned {
project: "simply-optimized".into(),
version: Some("AbCdEfGh".into()),
})
// or CurseForge:
.with_curseforge_modpack(project_id, file_id)
.done()One modpack per instance — calling .with_*_modpack(...) twice replaces
the previous source. Modpack + per-mod methods can be combined; modpack
files install first, user mods override on filename conflict.
ModpackSource lives in the flat module file
crates/modsloader/src/modpack.rs (it's just the enum + From<&str> /
From<String> impls — nothing else). The actual parsers live next to
their provider clients (cf. Where the code lives below).
ZIP with at its root:
| Entry | Role |
|---|---|
modrinth.index.json |
Manifest (cf. below) |
overrides/ |
Files to copy into runtime_dir/ (configs, scripts, resourcepacks…) |
client-overrides/ (opt) |
Client-only surcouche |
server-overrides/ (opt) |
Ignored by the launcher |
modrinth.index.json (excerpt):
{
"formatVersion": 1,
"game": "minecraft",
"versionId": "1.0.0",
"name": "My Pack",
"files": [
{
"path": "mods/sodium-fabric-mc1.21-0.5.8.jar",
"hashes": { "sha1": "abc...", "sha512": "..." },
"env": { "client": "required", "server": "required" },
"downloads": ["https://cdn.modrinth.com/.../sodium.jar"],
"fileSize": 854321
}
],
"dependencies": {
"minecraft": "1.21.1",
"fabric-loader": "0.16.9"
}
}Only files with env.client == "required" (or no env block) are
installed. Each entry's path is relative to runtime_dir/ — the
pack author controls where the file lands (mods/foo.jar,
resourcepacks/bar.zip, …). The installer no longer prefixes mods/
itself, so a manifest entry like resourcepacks/foo.zip now correctly
lands at <runtime>/resourcepacks/foo.zip (previously it was
mis-routed to <runtime>/mods/resourcepacks/foo.zip).
ZIP with at its root:
| Entry | Role |
|---|---|
manifest.json |
Manifest (cf. below) |
overrides/ |
Surcouche directory (name configurable via manifest.overrides) |
modlist.html |
Ignored |
manifest.json (excerpt):
{
"minecraft": {
"version": "1.21.1",
"modLoaders": [{ "id": "fabric-0.16.9", "primary": true }]
},
"manifestType": "minecraftModpack",
"manifestVersion": 1,
"name": "My Pack",
"version": "1.0.0",
"files": [
{ "projectID": 238222, "fileID": 5234567, "required": true }
],
"overrides": "overrides"
}The launcher resolves each (projectID, fileID) through the CurseForge
API (so set_api_key must have been called) and rejects packs that
contain files where the project has disabled third-party distribution
(ModDistributionForbidden). Each resolved file is routed through
curseforge::client::install_subdir_for so resourcepacks / shaderpacks
declared inside a CF modpack land in their proper sub-folder instead of
being shoved into mods/.
The id in modLoaders is parsed as <loader>-<version> and matched
to Loader::{Fabric, Forge, NeoForge, Quilt}. Other loaders surface
UnsupportedLoader.
| Concern | Path |
|---|---|
ModpackSource enum + From impls |
crates/modsloader/src/modpack.rs |
.mrpack URL resolver + manifest parser |
crates/modsloader/src/modrinth/modpack.rs |
.mrpack wire types (MrpackManifest, …) |
crates/modsloader/src/modrinth/modpack_metadata.rs |
.zip (CF) URL resolver + manifest parser |
crates/modsloader/src/curseforge/modpack.rs |
.zip (CF) wire types (CfModpackManifest, …) |
crates/modsloader/src/curseforge/modpack_metadata.rs |
| Download / extract / overrides / cache | crates/launch/src/installer/ressources/modpack/ |
The old crates/modsloader/src/modpack/ directory no longer exists.
Parsers are now co-located with their provider client.
When the modpack extracts overrides/ into runtime_dir/:
- Existing files are kept. The override is skipped and
trace_warn!("[Modpack] Skipping override of existing file: …")is emitted. - Directories are merged recursively.
Rationale: users who tweaked their options.txt, keybinds, or
NBT-backed configs don't want them silently wiped on relaunch. To
force-reset, delete runtime_dir/ (or the conflicting files) manually
and rerun.
If the pack manifest declares an MC + loader different from the
VersionBuilder's values, the launcher logs a trace_warn! line and
uses the builder's values. This is the current behaviour — a future
patch will likely flip the precedence (modpack authoritative). Either
way, the warn line gives you an audit trail.
Archives are cached at <cache_dir>/modpacks/<url_sha1>.archive with a
<url_sha1>.installed marker. On relaunch the marker is checked — if
it matches the SHA1 of the resolved URL, the whole pipeline short-
circuits.
Force a re-install:
rm "$XDG_CACHE_HOME"/LightyLauncher/modpacks/*.installed
# or wipe the whole cache:
rm -rf "$XDG_CACHE_HOME"/LightyLauncher/modpacksModloaderEvent::ModpackResolveStart { source: String }
ModloaderEvent::ModpackArchiveDownloaded { sha1: String, bytes: u64 }
ModloaderEvent::ModpackOverridesExtracted { count: usize } // reserved
ModloaderEvent::ModpackInstalled { name: String, mods_count: usize }See events.md for the full list (including the new
ResourcePacksInstalled / ShaderPacksInstalled / DatapacksInstalled
bucket summaries).
| Variant | Cause |
|---|---|
QueryError::Conversion { message: "Unsupported .mrpack formatVersion: …" } |
Format bump newer than 1 — upgrade the library |
QueryError::Conversion { message: "Modpack archive contains neither modrinth.index.json nor manifest.json" } |
Archive doesn't look like a known modpack |
QueryError::ModDistributionForbidden { id } |
A CurseForge file in the pack has download_url: null |
QueryError::UnsupportedFormat { what, expected, found } |
A CF modpack file references a classId outside {6, 12, 6552} |
QueryError::UnsupportedLoader(...) |
Pack's loader id doesn't map to Fabric / Forge / NeoForge / Quilt |