UNPKG

21 kBJavaScriptView Raw
1import { Installer } from "@xmcl/installer";
2import { downloadFileIfAbsentWork, downloadFileWork } from "@xmcl/net";
3import Task from "@xmcl/task";
4import Unzip from "@xmcl/unzip";
5import { MinecraftFolder, JavaExecutor, vfs } from "@xmcl/util";
6import { Version } from "@xmcl/version";
7import { delimiter, join } from "path";
8async function findMainClass(lib) {
9 const zip = await Unzip.open(lib, { lazyEntries: true });
10 const [manifest] = await zip.filterEntries(["META-INF/MANIFEST.MF"]);
11 let mainClass;
12 if (manifest) {
13 const content = await zip.readEntry(manifest).then((b) => b.toString());
14 const mainClassPair = content.split("\n").map((l) => l.split(": ")).filter((arr) => arr[0] === "Main-Class")[0];
15 if (mainClassPair) {
16 mainClass = mainClassPair[1].trim();
17 }
18 }
19 zip.close();
20 return mainClass;
21}
22/**
23 * The forge installer Module to install forge to the game
24 */
25export var ForgeInstaller;
26(function (ForgeInstaller) {
27 ForgeInstaller.DEFAULT_FORGE_MAVEN = "http://files.minecraftforge.net";
28 /**
29 * Diagnose for specific forge version. Majorly for the current installer forge. (mcversion >= 1.13)
30 *
31 * Don't use this with the version less than 1.13
32 * @param versionOrProfile If the version string present, it will try to find the installer profile under version folder. Otherwise it will use presented installer profile to diagnose
33 * @param minecraft The minecraft location.
34 */
35 async function diagnoseForgeVersion(versionOrProfile, minecraft) {
36 const version = typeof versionOrProfile === "string" ? versionOrProfile : versionOrProfile.version;
37 const mc = MinecraftFolder.from(minecraft);
38 const verRoot = mc.getVersionRoot(version);
39 const versionJsonPath = mc.getVersionJson(version);
40 const diag = {
41 badProcessedFiles: [],
42 missingInstallDependencies: [],
43 badVersionJson: false,
44 missingBinpatch: false,
45 badInstall: false,
46 missingSrgJar: false,
47 missingMinecraftExtraJar: false,
48 missingForgePatchesJar: false,
49 };
50 let prof;
51 if (typeof versionOrProfile === "string") {
52 const installProfPath = join(verRoot, "install_profile.json");
53 if (await vfs.exists(installProfPath)) {
54 prof = JSON.parse(await vfs.readFile(installProfPath).then((b) => b.toString()));
55 }
56 }
57 else {
58 prof = versionOrProfile;
59 }
60 if (prof) {
61 const processedProfile = postProcessInstallProfile(mc, prof);
62 for (const proc of processedProfile.processors) {
63 if (proc.outputs) {
64 let bad = false;
65 for (const file in proc.outputs) {
66 if (!await vfs.validateSha1(file, proc.outputs[file].replace(/'/g, ""))) {
67 bad = true;
68 break;
69 }
70 }
71 if (bad) {
72 diag.badProcessedFiles.push(proc);
73 }
74 }
75 }
76 // if we have to process file, we have to check if the forge deps are ready
77 if (diag.badProcessedFiles.length !== 0) {
78 const libValidMask = await Promise.all(processedProfile.libraries.map(async (lib) => {
79 const artifact = lib.downloads.artifact;
80 const libPath = mc.getLibraryByPath(artifact.path);
81 if (await vfs.exists(libPath)) {
82 return artifact.sha1 ? vfs.validateSha1(libPath, artifact.sha1) : true;
83 }
84 return false;
85 }));
86 const missingLibraries = processedProfile.libraries.filter((_, i) => !libValidMask[i]);
87 diag.missingInstallDependencies.push(...missingLibraries);
88 const validClient = await vfs.stat(processedProfile.data.BINPATCH.client).then((s) => s.size !== 0).catch((_) => false);
89 if (!validClient) {
90 diag.missingBinpatch = true;
91 diag.badInstall = true;
92 }
93 }
94 }
95 if (await vfs.exists(versionJsonPath)) {
96 const versionJSON = JSON.parse(await vfs.readFile(versionJsonPath).then((b) => b.toString()));
97 if (versionJSON.arguments && versionJSON.arguments.game) {
98 const args = versionJSON.arguments.game;
99 const forgeVersion = args.indexOf("--fml.forgeVersion") + 1;
100 const mcVersion = args.indexOf("--fml.mcVersion") + 1;
101 const mcpVersion = args.indexOf("--fml.mcpVersion") + 1;
102 if (!forgeVersion || !mcVersion || !mcpVersion) {
103 diag.badVersionJson = true;
104 diag.badInstall = true;
105 }
106 else {
107 const srgPath = mc.getLibraryByPath(`net/minecraft/client/${mcVersion}-${mcpVersion}/client-${mcVersion}-${mcpVersion}-srg.jar`);
108 const extraPath = mc.getLibraryByPath(`net/minecraft/client/${mcVersion}/client-${mcVersion}-extra.jar`);
109 const forgePatchPath = mc.getLibraryByPath(`net/minecraftforge/forge/${mcVersion}-${forgeVersion}/forge-${mcVersion}-${forgeVersion}-client.jar`);
110 diag.missingSrgJar = await vfs.missing(srgPath);
111 diag.missingMinecraftExtraJar = await vfs.missing(extraPath);
112 diag.missingForgePatchesJar = await vfs.missing(forgePatchPath);
113 }
114 }
115 else {
116 diag.badVersionJson = true;
117 diag.badInstall = true;
118 }
119 }
120 else {
121 diag.badVersionJson = true;
122 diag.badInstall = true;
123 }
124 return diag;
125 }
126 ForgeInstaller.diagnoseForgeVersion = diagnoseForgeVersion;
127 /**
128 * Post processing function for new forge installer (mcversion >= 1.13). You can use this with `ForgeInstaller.diagnose`.
129 *
130 * @param mc The minecraft location
131 * @param proc The processor
132 * @param java The java executor
133 */
134 async function postProcess(mc, proc, java) {
135 const jarRealPath = mc.getLibraryByPath(Version.getLibraryInfo(proc.jar).path);
136 const mainClass = await findMainClass(jarRealPath);
137 if (!mainClass) {
138 throw new Error(`Cannot find main class for processor ${proc.jar}.`);
139 }
140 const cp = [...proc.classpath, proc.jar].map(Version.getLibraryInfo).map((p) => mc.getLibraryByPath(p.path)).join(delimiter);
141 const cmd = ["-cp", cp, mainClass, ...proc.args];
142 await java(cmd);
143 let failed = false;
144 if (proc.outputs) {
145 for (const file in proc.outputs) {
146 if (!await vfs.validateSha1(file, proc.outputs[file].replace(/'/g, ""))) {
147 console.error(`Fail to process ${proc.jar} @ ${file} since its validation failed.`);
148 failed = true;
149 }
150 }
151 }
152 if (failed) {
153 console.error(`Java arguments: ${JSON.stringify(cmd)}`);
154 throw new Error("Fail to process post processing since its validation failed.");
155 }
156 }
157 ForgeInstaller.postProcess = postProcess;
158 function postProcessInstallProfile(mc, installProfile) {
159 function processValue(v) {
160 if (v.match(/^\[.+\]$/g)) {
161 const targetId = v.substring(1, v.length - 1);
162 return mc.getLibraryByPath(Version.getLibraryInfo(targetId).path);
163 }
164 return v;
165 }
166 function processMapping(data, m) {
167 m = processValue(m);
168 if (m.match(/^{.+}$/g)) {
169 const key = m.substring(1, m.length - 1);
170 m = data[key].client;
171 }
172 return m;
173 }
174 const profile = JSON.parse(JSON.stringify(installProfile));
175 profile.data.MINECRAFT_JAR = {
176 client: mc.getVersionJar(profile.minecraft),
177 server: "",
178 };
179 for (const key in profile.data) {
180 const value = profile.data[key];
181 value.client = processValue(value.client);
182 value.server = processValue(value.server);
183 if (key === "BINPATCH") {
184 const verRoot = mc.getVersionRoot(profile.version);
185 value.client = join(verRoot, value.client);
186 value.server = join(verRoot, value.server);
187 }
188 }
189 for (const proc of profile.processors) {
190 proc.args = proc.args.map((a) => processMapping(profile.data, a));
191 if (proc.outputs) {
192 const replacedOutput = {};
193 for (const key in proc.outputs) {
194 replacedOutput[processMapping(profile.data, key)] = processMapping(profile.data, proc.outputs[key]);
195 }
196 proc.outputs = replacedOutput;
197 }
198 }
199 return profile;
200 }
201 /**
202 * Install for forge installer step 2 and 3.
203 * @param version The version string or installer profile
204 * @param minecraft The minecraft location
205 */
206 function installByInstallerPartialTask(version, minecraft, option = {}) {
207 return async function installForge(context) {
208 const mc = MinecraftFolder.from(minecraft);
209 let prof;
210 let ver;
211 if (typeof version === "string") {
212 const versionRoot = mc.getVersionRoot(version);
213 prof = await vfs.readFile(join(versionRoot, "install_profile.json")).then((b) => b.toString()).then(JSON.parse);
214 }
215 else {
216 prof = version;
217 }
218 ver = await vfs.readFile(mc.getVersionJson(prof.version)).then((b) => b.toString()).then(JSON.parse);
219 await installByInstallerPartialWork(mc, prof, ver, option.java || JavaExecutor.createSimple("java"), option)(context);
220 };
221 }
222 ForgeInstaller.installByInstallerPartialTask = installByInstallerPartialTask;
223 /**
224 * Install for forge installer step 2 and 3.
225 * @param version The version string or installer profile
226 * @param minecraft The minecraft location
227 */
228 async function installByInstallerPartial(version, minecraft, option = {}) {
229 return Task.execute(installByInstallerPartialTask(version, minecraft, option));
230 }
231 ForgeInstaller.installByInstallerPartial = installByInstallerPartial;
232 function installByInstallerPartialWork(mc, profile, versionJson, java, installLibOption) {
233 return async (context) => {
234 profile = postProcessInstallProfile(mc, profile);
235 const parsedLibs = Version.resolveLibraries([...profile.libraries, ...versionJson.libraries]);
236 await context.execute(Installer.installLibrariesDirectTask(parsedLibs, mc, {
237 ...installLibOption,
238 libraryHost: installLibOption.libraryHost ? (l) => {
239 if (l.artifactId === "forge" && l.groupId === "net.minecraftforge") {
240 return `file://${mc.getLibraryByPath(l.path)}`;
241 }
242 return installLibOption.libraryHost(l);
243 } : undefined,
244 }));
245 await context.execute(async function postProcessing(ctx) {
246 ctx.update(0, profile.processors.length);
247 let i = 0;
248 const errs = [];
249 for (const proc of profile.processors) {
250 try {
251 await postProcess(mc, proc, java);
252 }
253 catch (e) {
254 errs.push(e);
255 }
256 ctx.update(i += 1, profile.processors.length);
257 }
258 i += 1;
259 ctx.update(i, profile.processors.length);
260 if (errs.length !== 0) {
261 throw new Error("Fail to post processing");
262 }
263 });
264 };
265 }
266 function installByInstallerTask(version, minecraft, maven, installLibOption, java) {
267 return async function installForge(context) {
268 const mc = MinecraftFolder.from(minecraft);
269 const forgeVersion = `${version.mcversion}-${version.version}`;
270 const installerURL = `${maven}${version.installer.path}`;
271 const installerURLFallback = `${maven}/maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}-installer.jar`;
272 const installJar = mc.getLibraryByPath(version.installer.path.substring(version.installer.path.substring(1).indexOf("/") + 1));
273 let versionId;
274 let profile;
275 let versionJson;
276 function downloadInstallerTask(installer, dest) {
277 return async function downloadInstaller(ctx) {
278 await downloadFileIfAbsentWork({
279 url: installer,
280 destination: dest,
281 checksum: {
282 hash: version.installer.sha1,
283 algorithm: "sha1",
284 },
285 })(ctx);
286 return vfs.createReadStream(dest).pipe(Unzip.createParseStream({ lazyEntries: true })).wait();
287 };
288 }
289 async function processVersion(zip, installProfileEntry, versionEntry, clientDataEntry) {
290 profile = await zip.readEntry(installProfileEntry).then((b) => b.toString()).then(JSON.parse);
291 versionJson = await zip.readEntry(versionEntry).then((b) => b.toString()).then(JSON.parse);
292 versionId = versionJson.id;
293 const rootPath = mc.getVersionRoot(versionJson.id);
294 const jsonPath = join(rootPath, `${versionJson.id}.json`);
295 const installJsonPath = join(rootPath, "install_profile.json");
296 const clientDataPath = join(rootPath, profile.data.BINPATCH.client);
297 await vfs.ensureFile(jsonPath);
298 await vfs.writeFile(installJsonPath, JSON.stringify(profile));
299 await vfs.writeFile(jsonPath, JSON.stringify(versionJson));
300 await vfs.ensureFile(clientDataPath);
301 const stream = await zip.openEntry(clientDataEntry);
302 await vfs.waitStream(stream.pipe(vfs.createWriteStream(clientDataPath)));
303 }
304 async function processExtractLibrary(stream, p) {
305 const file = mc.getLibraryByPath(p.substring(p.indexOf("/") + 1));
306 await vfs.ensureFile(file);
307 await vfs.waitStream(stream.pipe(vfs.createWriteStream(file)));
308 }
309 try {
310 let zip;
311 try {
312 zip = await context.execute(downloadInstallerTask(installerURL, installJar));
313 }
314 catch (_a) {
315 zip = await context.execute(downloadInstallerTask(installerURLFallback, installJar));
316 }
317 const [forgeEntry, forgeUniversalEntry, clientDataEntry, installProfileEntry, versionEntry] = await zip.filterEntries([
318 `maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}.jar`,
319 `maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}-universal.jar`,
320 "data/client.lzma",
321 "install_profile.json",
322 "version.json"
323 ]);
324 if (!forgeEntry) {
325 throw new Error("Missing forge jar entry");
326 }
327 if (!forgeUniversalEntry) {
328 throw new Error("Missing forge universal entry");
329 }
330 if (!installProfileEntry) {
331 throw new Error("Missing install profile");
332 }
333 if (!versionEntry) {
334 throw new Error("Missing version entry");
335 }
336 await processExtractLibrary(await zip.openEntry(forgeEntry), forgeEntry.fileName);
337 await processExtractLibrary(await zip.openEntry(forgeUniversalEntry), forgeUniversalEntry.fileName);
338 await processVersion(zip, installProfileEntry, versionEntry, clientDataEntry);
339 await installByInstallerPartialWork(mc, profile, versionJson, java, installLibOption)(context);
340 return versionId;
341 }
342 catch (e) {
343 console.error(`Cannot install forge by installer ${version.version}`);
344 throw e;
345 }
346 };
347 }
348 function installByUniversalTask(version, minecraft, maven) {
349 return async function installForge(context) {
350 const mc = MinecraftFolder.from(minecraft);
351 const forgeVersion = `${version.mcversion}-${version.version}`;
352 const paths = version.universal.path.split("/");
353 const realForgeVersion = paths[paths.length - 2];
354 const jarPath = mc.getLibraryByPath(`net/minecraftforge/forge/${realForgeVersion}/forge-${realForgeVersion}.jar`);
355 let fullVersion;
356 let realJarPath;
357 const universalURLFallback = `${maven}/maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}-universal.jar`;
358 const universalURL = `${maven}${version.universal.path}`;
359 await context.execute(async function installForgeJar() {
360 if (await vfs.exists(jarPath)) {
361 const valid = await vfs.validate(jarPath, { algorithm: "md5", hash: version.universal.md5 }, { algorithm: "sha1", hash: version.universal.sha1 });
362 if (valid) {
363 return;
364 }
365 }
366 function downloadJar(ctx) {
367 return downloadFileWork({ url: universalURL, destination: jarPath })(ctx);
368 }
369 await context.execute(downloadJar);
370 });
371 await context.execute(async function installForgeJson() {
372 const zip = await Unzip.open(jarPath, { lazyEntries: true });
373 const [versionEntry] = await zip.filterEntries(["version.json"]);
374 if (versionEntry) {
375 const buf = await zip.readEntry(versionEntry);
376 const raw = JSON.parse(buf.toString());
377 const id = raw.id;
378 fullVersion = id;
379 const rootPath = mc.getVersionRoot(fullVersion);
380 realJarPath = mc.getLibraryByPath(Version.getLibraryInfo(raw.libraries.find((l) => l.name.startsWith("net.minecraftforge:forge"))).path);
381 await vfs.ensureDir(rootPath);
382 const jsonPath = join(rootPath, `${id}.json`);
383 if (await vfs.missing(jsonPath)) {
384 await vfs.writeFile(jsonPath, buf);
385 }
386 }
387 else {
388 throw new Error(`Cannot install forge json for ${version.version} since the version json is missing!`);
389 }
390 });
391 if (realJarPath !== jarPath) {
392 await vfs.ensureFile(realJarPath);
393 await vfs.copyFile(jarPath, realJarPath);
394 await vfs.unlink(jarPath);
395 }
396 return fullVersion;
397 };
398 }
399 /**
400 * Install forge to target location.
401 * Installation task for forge with mcversion >= 1.13 requires java installed on your pc.
402 * @param version The forge version meta
403 */
404 function install(version, minecraft, option) {
405 return Task.execute(installTask(version, minecraft, option));
406 }
407 ForgeInstaller.install = install;
408 /**
409 * Install forge to target location.
410 * Installation task for forge with mcversion >= 1.13 requires java installed on your pc.
411 * @param version The forge version meta
412 */
413 function installTask(version, minecraft, option = {}) {
414 let byInstaller = true;
415 try {
416 const minorVersion = Number.parseInt(version.mcversion.split(".")[1], 10);
417 byInstaller = minorVersion >= 13;
418 }
419 catch (_a) { }
420 const work = byInstaller
421 ? installByInstallerTask(version, minecraft, option.maven || ForgeInstaller.DEFAULT_FORGE_MAVEN, option, option.java || JavaExecutor.createSimple("java"))
422 : installByUniversalTask(version, minecraft, option.maven || ForgeInstaller.DEFAULT_FORGE_MAVEN);
423 return work;
424 }
425 ForgeInstaller.installTask = installTask;
426})(ForgeInstaller || (ForgeInstaller = {}));
427export * from "./forgeweb";
428export default ForgeInstaller;
429//# sourceMappingURL=index.js.map
\No newline at end of file