Provide functions to install Minecraft client, libraries, and assets.
Fully install vanilla minecraft client including assets and libs.
import { getVersionList, MinecraftVersion, install } from "@xmcl/installer";
import { MinecraftLocation } from "@xmcl/core";
const minecraft: MinecraftLocation;
const list: MinecraftVersion[] = (await getVersionList()).versions;
const aVersion: MinecraftVersion = list[0]; // i just pick the first version in list here
await install(aVersion, minecraft);
Just install libraries:
import { installLibraries } from "@xmcl/installer";
import { ResolvedVersion, MinecraftLocation, Version } from "@xmcl/core";
const minecraft: MinecraftLocation;
const version: string; // version string like 1.13
const resolvedVersion: ResolvedVersion = await Version.parse(minecraft, version);
await installLibraries(resolvedVersion);
Just install assets:
import { installAssets } from "@xmcl/installer";
import { MinecraftLocation, ResolvedVersion, Version } from "@xmcl/core";
const minecraft: MinecraftLocation;
const version: string; // version string like 1.13
const resolvedVersion: ResolvedVersion = await Version.parse(minecraft, version);
await installAssets(resolvedVersion);
Just ensure all assets and libraries are installed:
import { installDependencies } from "@xmcl/installer";
import { MinecraftLocation, ResolvedVersion, Version } from "@xmcl/core";
const minecraft: MinecraftLocation;
const version: string; // version string like 1.13
const resolvedVersion: ResolvedVersion = await Version.parse(minecraft, version);
await installDependencies(resolvedVersion);
The library is using undici as the backbone of http request. It's a very fast http client. But it's also very aggressive. It will create a lot of connections to the server. If you want to limit the concurrency of the installation, you want to create your own undici Dispatcher
to handle the request.
import { Dispatcher, Agent } from "undici";
const agent = new Agent({
connection: 16 // only 16 connection (socket) we should create at most
// you can have other control here.
});
await installAssets(resolvedVersion, {
agent: { // notice this is the DownloadAgent from `@xmcl/file-transfer`
dispatcher: agent // this is the undici Dispatcher option
}
});
There are other type of Dispatcher
, like Pool
, Client
, ProxyAgent
. You can read undici document for more information.
Most install function has a corresponding task function. For example, install
function has the function name installTask
which is the task version monitor the progress of install.
Here is the example of just moniting the install task overall progress:
// suppose you have define such functions to update UI
declare function updateTaskProgress(task: Task<any>, progress: number, total: number): void;
declare function setTaskToFail(task: Task<any>): void;
declare function setTaskToSuccess(task: Task<any>): void;
declare function trackTask(task: Task<any>): void;
const installAllTask: Task<ResolvedVersion> = installTask(versionMetadata, mcLocation);
await installAllTask.startAndWait({
onStart(task: Task<any>) {
// a task start
// task.path show the path
// task.name is the name
trackTask(task)
},
onUpdate(task: Task<any>, chunkSize: number) {
// a task update
// the chunk size usually the buffer size
// you can use this to track download speed
// you can track this specific task progress
updateTaskProgress(task, task.progress, task.total);
// or you can update the root task by
updateTaskProgress(task, installAllTask.progress, installAllTask.total);
},
onFailed(task: Task<any>, error: any) {
// on a task fail
setTaskToFail(task);
},
onSucceed(task: Task<any>, result: any) {
// on task success
setTaskToSuccess(task);
},
// on task is paused/resumed/cancelled
onPaused(task: Task<any>) {
},
onResumed(task: Task<any>) {
},
onCancelled(task: Task<any>) {
},
});
The task is designed to organize the all the works in a tree like structure.
The installTask
has such parent/child structure
- install
- version
- json
- jar
- dependencies
- assets
- assetsJson
- asset
- libraries
- library
- assets
- version
To generally display this tree in UI. You can identify the task by its path
.
function updateTaskUI(task: Task<any>, progress: number, total: number) {
// you can use task.path as identifier
// and update the task on UI
const path = task.path;
// the path can be something like `install.version.json`
}
Or you can use your own identifier like uuid:
// you customize function to make task to a user reacable string to display in UI
declare function getTaskName(task: Task<any>): string;
function runTask(rootTask: Task<any>) {
// your own id for this root task
const uid = uuid();
await rootTask.startAndWait({
onStart(task: Task<any>) {
// tell ui that a task with such name started
// the task id is a number id from 0
trackTask(`${uid}.${task.id}`, getTaskName(task));
},
onUpdate(task: Task<any>, chunkSize: number) {
// update the total progress
updateTaskProgress(`${uid}.${task.id}`, installAllTask.progress, installAllTask.total);
},
onStart(task: Task<any>) {
// tell ui this task ended
endTask(`${uid}.${task.id}`);
},
});
}
To swap the library to your self-host or other customized host, you can assign the libraryHost
field in options.
For example, if you want to download the library commons-io:commons-io:2.5
from your self hosted server, you can have
// the example for call `installLibraries`
// this option will also work for other functions involving libraries like `install`, `installDependencies`.
await installLibraries(resolvedVersion, {
libraryHost(library: ResolvedLibrary) {
if (library.name === "commons-io:commons-io:2.5") {
// the downloader will first try the first url in the array
// if this failed, it will try the 2nd.
// if it's still failed, it will try original url
return ["https://your-host.org/the/path/to/the/jar", "your-sencodary-url"];
// if you just have one url
// just return a string here...
}
// return undefined if you don't want to change lib url
return undefined;
},
mavenHost: ['https://www.your-other-maven.org'], // you still can use this to add other maven
});
// it will first try you libraryHost url and then try mavenHost url.
To swap the assets host, you can just assign the assets host url to the options
await installAssets(resolvedVersion, {
assetsHost: "https://www.your-url/assets"
});
The assets host should accept the get asset request like GET https://www.your-url/assets/<hash-head>/<hash>
, where hash-head
is the first two char in <hash>
. The <hash>
is the sha1 of the asset.
Get the forge version info and install forge from it.
import { installForge, getForgeVersionList, ForgeVersionList, ForgeVersion } from "@xmcl/installer";
import { MinecraftLocation } from "@xmcl/core";
const list: ForgeVersionList = await getForgeVersionList();
const minecraftLocation: MinecraftLocation;
const mcversion = page.mcversion; // mc version
const firstVersionOnPage: ForgeVersion = page.versions[0];
await installForge(firstVersionOnPage, minecraftLocation);
If you know forge version and minecraft version. You can directly do such:
import { installForge } from "@xmcl/installer";
const forgeVersion = 'a-forge-version'; // like 31.1.27
await installForge({ version: forgeVersion, mcversion: '1.15.2' }, minecraftLocation);
Notice that this installation doesn't ensure full libraries installation.
Please run installDependencies
afther that.
The new 1.13 forge installation process requires java to run.
Either you have java
executable in your environment variable PATH,
or you can assign java location by installForge(forgeVersionMeta, minecraftLocation, { java: yourJavaExecutablePath });
.
If you use this auto installation process to install forge, please checkout Lex's Patreon. Consider support him to maintains forge.
Fetch the new fabric version list.
import { installFabric, FabricArtifactVersion } from "@xmcl/installer";
const versionList: FabricArtifactVersion[] = await getFabricArtifactList();
Install fabric to the client. This installation process doesn't ensure the minecraft libraries.
const minecraftLocation: MinecraftLocation;
await installFabric(versionList[0], minecraftLocation);
Please run Installer.installDependencies
after that to install fully.
The module have three stage for installing new forge (mcversion >= 1.13)
- Deploy forge installer jar
- Download installer jar
- Extract forge universal jar files in installer jar into
.minecraft/libraries
- Extract
version.json
into target version folder,.minecraft/versions/<ver>/<ver>.json
- Extract
installer_profile.json
into target version folder,.minecraft/versions/<ver>/installer_profile.json
- Download Dependencies
- Merge libraires in
installer_profile.json
and<ver>.json
- Download them
- Merge libraires in
- Post processing forge jar
- Parse
installer_profile.json
- Get the processors info and execute all of them.
- Parse
The installForge
will do all of them.
The installByProfile
will do 2 and 3.
Scan java installation path from the disk. (Require a lzma unpacker, like 7zip-bin or lzma-native)
import { installJreFromMojang } from "@xmcl/installer";
// this require a unpackLZMA util to work
// you can use `7zip-bin`
// or `lzma-native` for this
const unpackLZMA: (src: string, dest: string) => Promise<void>;
await installJreFromMojang({
destination: "your/java/home",
unpackLZMA,
});