1 | # Installer Module
|
2 |
|
3 | [![npm version](https://img.shields.io/npm/v/@xmcl/installer.svg)](https://www.npmjs.com/package/@xmcl/installer)
|
4 | [![Downloads](https://img.shields.io/npm/dm/@xmcl/installer.svg)](https://npmjs.com/@xmcl/installer)
|
5 | [![Install size](https://packagephobia.now.sh/badge?p=@xmcl/installer)](https://packagephobia.now.sh/result?p=@xmcl/installer)
|
6 | [![npm](https://img.shields.io/npm/l/@xmcl/minecraft-launcher-core.svg)](https://github.com/voxelum/minecraft-launcher-core-node/blob/master/LICENSE)
|
7 | [![Build Status](https://github.com/voxelum/minecraft-launcher-core-node/workflows/Build/badge.svg)](https://github.com/Voxelum/minecraft-launcher-core-node/actions?query=workflow%3ABuild)
|
8 |
|
9 | Provide functions to install Minecraft client, libraries, and assets.
|
10 |
|
11 | ## Usage
|
12 |
|
13 | ### Install Minecraft
|
14 |
|
15 | Fully install vanilla minecraft client including assets and libs.
|
16 |
|
17 | ```ts
|
18 | import { getVersionList, MinecraftVersion, install } from "@xmcl/installer";
|
19 | import { MinecraftLocation } from "@xmcl/core";
|
20 |
|
21 | const minecraft: MinecraftLocation;
|
22 | const list: MinecraftVersion[] = (await getVersionList()).versions;
|
23 | const aVersion: MinecraftVersion = list[0]; // i just pick the first version in list here
|
24 | await install(aVersion, minecraft);
|
25 | ```
|
26 |
|
27 | Just install libraries:
|
28 |
|
29 | ```ts
|
30 | import { installLibraries } from "@xmcl/installer";
|
31 | import { ResolvedVersion, MinecraftLocation, Version } from "@xmcl/core";
|
32 |
|
33 | const minecraft: MinecraftLocation;
|
34 | const version: string; // version string like 1.13
|
35 | const resolvedVersion: ResolvedVersion = await Version.parse(minecraft, version);
|
36 | await installLibraries(resolvedVersion);
|
37 | ```
|
38 |
|
39 | Just install assets:
|
40 |
|
41 | ```ts
|
42 | import { installAssets } from "@xmcl/installer";
|
43 | import { MinecraftLocation, ResolvedVersion, Version } from "@xmcl/core";
|
44 |
|
45 | const minecraft: MinecraftLocation;
|
46 | const version: string; // version string like 1.13
|
47 | const resolvedVersion: ResolvedVersion = await Version.parse(minecraft, version);
|
48 | await installAssets(resolvedVersion);
|
49 | ```
|
50 |
|
51 | Just ensure all assets and libraries are installed:
|
52 |
|
53 | ```ts
|
54 | import { installDependencies } from "@xmcl/installer";
|
55 | import { MinecraftLocation, ResolvedVersion, Version } from "@xmcl/core";
|
56 |
|
57 | const minecraft: MinecraftLocation;
|
58 | const version: string; // version string like 1.13
|
59 | const resolvedVersion: ResolvedVersion = await Version.parse(minecraft, version);
|
60 | await installDependencies(resolvedVersion);
|
61 | ```
|
62 | ### Progress Moniting on Installation
|
63 |
|
64 | 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.
|
65 |
|
66 | Here is the example of just moniting the install task overall progress:
|
67 |
|
68 | ```ts
|
69 | // suppose you have define such functions to update UI
|
70 | declare function updateTaskProgress(task: Task<any>, progress: number, total: number): void;
|
71 | declare function setTaskToFail(task: Task<any>): void;
|
72 | declare function setTaskToSuccess(task: Task<any>): void;
|
73 | declare function trackTask(task: Task<any>): void;
|
74 |
|
75 | const installAllTask: Task<ResolvedVersion> = installTask(versionMetadata, mcLocation);
|
76 | await installAllTask.startAndWait({
|
77 | onStart(task: Task<any>) {
|
78 | // a task start
|
79 | // task.path show the path
|
80 | // task.name is the name
|
81 | trackTask(task)
|
82 | },
|
83 | onUpdate(task: Task<any>, chunkSize: number) {
|
84 | // a task update
|
85 | // the chunk size usually the buffer size
|
86 | // you can use this to track download speed
|
87 |
|
88 | // you can track this specific task progress
|
89 | updateTaskProgress(task, task.progress, task.total);
|
90 |
|
91 | // or you can update the root task by
|
92 | updateTaskProgress(task, installAllTask.progress, installAllTask.total);
|
93 | },
|
94 | onFailed(task: Task<any>, error: any) {
|
95 | // on a task fail
|
96 | setTaskToFail(task);
|
97 | },
|
98 | onSuccessed(task: Task<any>, result: any) {
|
99 | // on task success
|
100 | setTaskToSuccess(task);
|
101 | },
|
102 | // on task is paused/resumed/cancelled
|
103 | onPaused(task: Task<any>) {
|
104 | },
|
105 | onResumed(task: Task<any>) {
|
106 | },
|
107 | onCancelled(task: Task<any>) {
|
108 | },
|
109 | });
|
110 |
|
111 | ```
|
112 |
|
113 | The task is designed to organize the all the works in a tree like structure.
|
114 |
|
115 | The `installTask` has such parent/child structure
|
116 |
|
117 | - install
|
118 | - version
|
119 | - json
|
120 | - jar
|
121 | - dependencies
|
122 | - assets
|
123 | - assetsJson
|
124 | - asset
|
125 | - libraries
|
126 | - library
|
127 |
|
128 | To generally display this tree in UI. You can identify the task by its `path`.
|
129 |
|
130 | ```ts
|
131 | function updateTaskUI(task: Task<any>, progress: number, total: number) {
|
132 | // you can use task.path as identifier
|
133 | // and update the task on UI
|
134 | const path = task.path;
|
135 | // the path can be something like `install.version.json`
|
136 | }
|
137 | ```
|
138 |
|
139 | Or you can use your own identifier like uuid:
|
140 |
|
141 | ```ts
|
142 | // you customize function to make task to a user reacable string to display in UI
|
143 | declare function getTaskName(task: Task<any>): string;
|
144 |
|
145 | function runTask(rootTask: Task<any>) {
|
146 | // your own id for this root task
|
147 | const uid = uuid();
|
148 | await rootTask.startAndWait({
|
149 | onStart(task: Task<any>) {
|
150 | // tell ui that a task with such name started
|
151 | // the task id is a number id from 0
|
152 | trackTask(`${uid}.${task.id}`, getTaskName(task));
|
153 | },
|
154 | onUpdate(task: Task<any>, chunkSize: number) {
|
155 | // update the total progress
|
156 | updateTaskProgress(`${uid}.${task.id}`, installAllTask.progress, installAllTask.total);
|
157 | },
|
158 | onStart(task: Task<any>) {
|
159 | // tell ui this task ended
|
160 | endTask(`${uid}.${task.id}`);
|
161 | },
|
162 | });
|
163 | }
|
164 |
|
165 | ```
|
166 |
|
167 | ### Install Library/Assets with Customized Host
|
168 |
|
169 | To swap the library to your self-host or other customized host, you can assign the `libraryHost` field in options.
|
170 |
|
171 | For example, if you want to download the library `commons-io:commons-io:2.5` from your self hosted server, you can have
|
172 |
|
173 | ```ts
|
174 | // the example for call `installLibraries`
|
175 | // this option will also work for other functions involving libraries like `install`, `installDependencies`.
|
176 | await installLibraries(resolvedVersion, {
|
177 | libraryHost(library: ResolvedLibrary) {
|
178 | if (library.name === "commons-io:commons-io:2.5") {
|
179 | // the downloader will first try the first url in the array
|
180 | // if this failed, it will try the 2nd.
|
181 | // if it's still failed, it will try original url
|
182 | return ["https://your-host.org/the/path/to/the/jar", "your-sencodary-url"];
|
183 | // if you just have one url
|
184 | // just return a string here...
|
185 | }
|
186 | // return undefined if you don't want to change lib url
|
187 | return undefined;
|
188 | },
|
189 | mavenHost: ['https://www.your-other-maven.org'], // you still can use this to add other maven
|
190 | });
|
191 |
|
192 | // it will first try you libraryHost url and then try mavenHost url.
|
193 | ```
|
194 |
|
195 | To swap the assets host, you can just assign the assets host url to the options
|
196 |
|
197 | ```ts
|
198 | await installAssets(resolvedVersion, {
|
199 | assetsHost: "https://www.your-url/assets"
|
200 | });
|
201 | ```
|
202 |
|
203 | 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.
|
204 |
|
205 | ### Install Forge
|
206 |
|
207 | Get the forge version info and install forge from it.
|
208 |
|
209 | ```ts
|
210 | import { installForge, getForgeVersionList, ForgeVersionList, ForgeVersion } from "@xmcl/installer";
|
211 | import { MinecraftLocation } from "@xmcl/core";
|
212 |
|
213 | const list: ForgeVersionList = await getForgeVersionList();
|
214 | const minecraftLocation: MinecraftLocation;
|
215 | const mcversion = page.mcversion; // mc version
|
216 | const firstVersionOnPage: ForgeVersion = page.versions[0];
|
217 | await installForge(firstVersionOnPage, minecraftLocation);
|
218 | ```
|
219 |
|
220 | If you know forge version and minecraft version. You can directly do such:
|
221 |
|
222 | ```ts
|
223 | import { installForge } from "@xmcl/installer";
|
224 |
|
225 | const forgeVersion = 'a-forge-version'; // like 31.1.27
|
226 | await installForge({ version: forgeVersion, mcversion: '1.15.2' }, minecraftLocation);
|
227 | ```
|
228 |
|
229 | Notice that this installation doesn't ensure full libraries installation.
|
230 | Please run `installDependencies` afther that.
|
231 |
|
232 | The new 1.13 forge installation process requires java to run.
|
233 | Either you have `java` executable in your environment variable PATH,
|
234 | or you can assign java location by `installForge(forgeVersionMeta, minecraftLocation, { java: yourJavaExecutablePath });`.
|
235 |
|
236 | If you use this auto installation process to install forge, please checkout [Lex's Patreon](https://www.patreon.com/LexManos).
|
237 | Consider support him to maintains forge.
|
238 |
|
239 | ### Install Fabric
|
240 |
|
241 | Fetch the new fabric version list.
|
242 |
|
243 | ```ts
|
244 | import { installFabric, FabricArtifactVersion } from "@xmcl/installer";
|
245 |
|
246 | const versionList: FabricArtifactVersion[] = await getFabricArtifactList();
|
247 | ```
|
248 |
|
249 | Install fabric to the client. This installation process doesn't ensure the minecraft libraries.
|
250 |
|
251 | ```ts
|
252 | const minecraftLocation: MinecraftLocation;
|
253 | await installFabric(versionList[0], minecraftLocation);
|
254 | ```
|
255 |
|
256 | Please run `Installer.installDependencies` after that to install fully.
|
257 |
|
258 | ## New Forge Installing process
|
259 |
|
260 | The module have three stage for installing new forge *(mcversion >= 1.13)*
|
261 |
|
262 | 1. Deploy forge installer jar
|
263 | 1. Download installer jar
|
264 | 2. Extract forge universal jar files in installer jar into `.minecraft/libraries`
|
265 | 3. Extract `version.json` into target version folder, `.minecraft/versions/<ver>/<ver>.json`
|
266 | 4. Extract `installer_profile.json` into target version folder, `.minecraft/versions/<ver>/installer_profile.json`
|
267 | 2. Download Dependencies
|
268 | 1. Merge libraires in `installer_profile.json` and `<ver>.json`
|
269 | 2. Download them
|
270 | 3. Post processing forge jar
|
271 | 1. Parse `installer_profile.json`
|
272 | 2. Get the processors info and execute all of them.
|
273 |
|
274 | The `installForge` will do all of them.
|
275 |
|
276 | The `installByProfile` will do 2 and 3.
|
277 |
|
278 | ### Install Java 8 From Mojang Source
|
279 |
|
280 | Scan java installation path from the disk. (Require a lzma unpacker, like [7zip-bin](https://www.npmjs.com/package/7zip-bin) or [lzma-native](https://www.npmjs.com/package/lzma-native))
|
281 |
|
282 | ```ts
|
283 | import { installJreFromMojang } from "@xmcl/installer";
|
284 |
|
285 | // this require a unpackLZMA util to work
|
286 | // you can use `7zip-bin`
|
287 | // or `lzma-native` for this
|
288 | const unpackLZMA: (src: string, dest: string) => Promise<void>;
|
289 |
|
290 | await installJreFromMojang({
|
291 | destination: "your/java/home",
|
292 | unpackLZMA,
|
293 | });
|
294 | ```
|