From 2140a770b468c36b5f0f5e69c318636914cae344 Mon Sep 17 00:00:00 2001 From: skjsjhb Date: Thu, 27 Feb 2025 14:44:22 +0800 Subject: [PATCH] fix(install): add ModLoader for legacy Forge versions --- src/main/api/install.ts | 26 ++++++++++++++-- src/main/install/forge-compat.ts | 49 +++++++++++++++++++++++++++++++ src/main/install/forge.ts | 5 ++++ src/main/install/smelt-legacy.ts | 4 +-- src/refs/legacy-forge-compat.json | 33 +++++++++++++++++++++ 5 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 src/main/install/forge-compat.ts create mode 100644 src/refs/legacy-forge-compat.json diff --git a/src/main/api/install.ts b/src/main/api/install.ts index fa2094c0..3301d801 100644 --- a/src/main/api/install.ts +++ b/src/main/api/install.ts @@ -2,6 +2,7 @@ import { containers } from "@/main/container/manage"; import { games } from "@/main/game/manage"; import { fabricInstaller } from "@/main/install/fabric"; import { forgeInstaller } from "@/main/install/forge"; +import { forgeCompat } from "@/main/install/forge-compat"; import { neoforgedInstaller } from "@/main/install/neoforged"; import { quiltInstaller } from "@/main/install/quilt"; import { smelt, type SmeltInstallInit } from "@/main/install/smelt"; @@ -54,6 +55,7 @@ ipcMain.on("installGame", async (e, gameId) => { let p = vanillaProfile; let forgeInstallerPath: string | null = null; + let forgeModLoaderPath: string | null = null; let forgeInstallerInit: SmeltInstallInit | null = null; let forgeInstallAction: ForgeInstallActionType = "none"; @@ -103,6 +105,9 @@ ipcMain.on("installGame", async (e, gameId) => { const installerType = forgeInstaller.getInstallType(gameVersion); forgeInstallerPath = await forgeInstaller.downloadInstaller(loaderVersion, installerType, { onProgress }); + const modLoaderUrl = await forgeCompat.getModLoaderUrl(gameVersion); + forgeModLoaderPath = modLoaderUrl && await forgeCompat.downloadModLoader(modLoaderUrl); + if (installerType === "installer") { const legacyProfileId = await smeltLegacy.dumpContent(forgeInstallerPath, c); if (legacyProfileId) { @@ -116,7 +121,16 @@ ipcMain.on("installGame", async (e, gameId) => { } } else { forgeInstallAction = "merge"; - p = vanillaProfile; + if (modLoaderUrl) { + // There exists a bug with ModLoader which makes it incompatible with the directory structure + // This cannot be fixed even with VENV + // We're using a patch named DAMT: https://www.minecraftforum.net/forums/mapping-and-modding-java-edition/minecraft-mods/mods-discussion/1291855-a-custom-tweaker-for-using-older-modloaders-in-the + await forgeCompat.patchProfile(c, gameVersion); + p = await profileLoader.fromContainer(gameVersion, c); + } else { + p = vanillaProfile; + } + } } @@ -136,10 +150,16 @@ ipcMain.on("installGame", async (e, gameId) => { c ); - await smeltLegacy.mergeClient(forgeInstallerPath!, c.client(p.version || p.id)); - game.launchHint.venv = true; + const clientPath = c.client(p.version || p.id); + + if (forgeModLoaderPath) { + await smeltLegacy.mergeClient(forgeModLoaderPath, clientPath); + } + + await smeltLegacy.mergeClient(forgeInstallerPath!, clientPath); } + game.launchHint.venv = await forgeCompat.shouldUseVenv(gameVersion); await fs.remove(forgeInstallerPath!); } diff --git a/src/main/install/forge-compat.ts b/src/main/install/forge-compat.ts new file mode 100644 index 00000000..2af0df58 --- /dev/null +++ b/src/main/install/forge-compat.ts @@ -0,0 +1,49 @@ +import type { Container } from "@/main/container/spec"; +import { paths } from "@/main/fs/paths"; +import { dlx } from "@/main/net/dlx"; +import type { Library } from "@/main/profile/version-profile"; +import { unwrapESM } from "@/main/util/module"; +import fs from "fs-extra"; +import { nanoid } from "nanoid"; + +async function loadCompat() { + return await unwrapESM(import("@/refs/legacy-forge-compat.json")); +} + +async function shouldUseVenv(v: string): Promise { + return (await loadCompat()).venv.includes(v); +} + +async function getModLoaderUrl(v: string): Promise { + return (await loadCompat() as any)["mod-loader"][v] ?? ""; +} + +async function downloadModLoader(url: string): Promise { + console.debug(`Fetching ModLoader from ${url}`); + const fp = paths.temp.to(`mod-loader-${nanoid()}.jar`); + await dlx.getAll([{ url, path: fp }]); + return fp; +} + +async function getModLoaderDamtLibrary(): Promise { + return (await loadCompat())["damt"]; +} + +async function getModLoaderDamtArg(): Promise { + return (await loadCompat())["damt-arg"]; +} + +async function patchProfile(container: Container, id: string) { + console.debug(`Patching ${id} with DAMT...`); + const prof = await fs.readJSON(container.profile(id)); + prof.libraries.push(await getModLoaderDamtLibrary()); + prof.minecraftArguments += (" " + await getModLoaderDamtArg()); + await fs.writeJSON(container.profile(id), prof); +} + +export const forgeCompat = { + shouldUseVenv, + getModLoaderUrl, + downloadModLoader, + patchProfile +}; diff --git a/src/main/install/forge.ts b/src/main/install/forge.ts index 2e8a49bf..1cbb7fdf 100644 --- a/src/main/install/forge.ts +++ b/src/main/install/forge.ts @@ -30,6 +30,11 @@ async function syncVersions(): Promise { async function queryLoaderVersions(gameVersion: string, control?: ProgressController): Promise { control?.onProgress?.(progress.indefinite("forge.download")); + // This is the only except in versioning + if (gameVersion === "1.4") { + gameVersion = "1.4.0"; + } + const versions = await syncVersions(); // Versions of Forge are named in the format `-` diff --git a/src/main/install/smelt-legacy.ts b/src/main/install/smelt-legacy.ts index 8004b126..f75bc828 100644 --- a/src/main/install/smelt-legacy.ts +++ b/src/main/install/smelt-legacy.ts @@ -10,7 +10,7 @@ import StreamZip from "node-stream-zip"; import child_process from "node:child_process"; import path from "node:path"; import { pEvent } from "p-event"; -import { COMPRESSION_LEVEL, zip } from "zip-a-folder"; +import { zip } from "zip-a-folder"; async function dumpContent(installer: string, container: Container): Promise { let zip: StreamZip.StreamZipAsync | null = null; @@ -143,7 +143,7 @@ async function mergeClient(src: string, fp: string): Promise { await fs.remove(path.join(workDir, "META-INF")); // Drop signatures await fs.remove(fp); - await zip(workDir, fp, { compression: COMPRESSION_LEVEL.uncompressed }); + await zip(workDir, fp); await fs.remove(workDir); } diff --git a/src/refs/legacy-forge-compat.json b/src/refs/legacy-forge-compat.json new file mode 100644 index 00000000..00c0e795 --- /dev/null +++ b/src/refs/legacy-forge-compat.json @@ -0,0 +1,33 @@ +{ + "mod-loader": { + "1.2.4": "https://github.com/skjsjhb/ModLoader-Binaries/releases/download/1.0/ModLoader.1.2.4.zip", + "1.2.3": "https://github.com/skjsjhb/ModLoader-Binaries/releases/download/1.0/ModLoader.1.2.3.zip", + "1.1": "https://github.com/skjsjhb/ModLoader-Binaries/releases/download/1.0/ModLoader.1.1.zip" + }, + "damt": { + "name": "damt:damt:0.1", + "downloads": { + "artifact": { + "url": "https://github.com/skjsjhb/ModLoader-Binaries/releases/download/1.0/damt-0.1.jar" + } + } + }, + "damt-arg": "--tweakClass damt.ModLoaderTweaker", + "venv": [ + "1.5.1", + "1.5", + "1.4.7", + "1.4.6", + "1.4.5", + "1.4.4", + "1.4.3", + "1.4.2", + "1.4.1", + "1.4", + "1.3.2", + "1.2.5", + "1.2.4", + "1.2.3", + "1.1" + ] +}