From b4ad542023ab8a75eecc561a53856d1a63b436ff Mon Sep 17 00:00:00 2001 From: skjsjhb Date: Sat, 1 Mar 2025 11:38:45 +0800 Subject: [PATCH] chore(install): cleanup install code --- src/main/api/game.ts | 5 +- src/main/api/install.ts | 144 +----------- src/main/game/spec.ts | 19 +- src/main/install/installers.ts | 219 ++++++++++++++++++ src/main/install/smelt.ts | 2 +- src/main/profile/profile-adaptor.ts | 2 + src/main/profile/version-profile.ts | 6 +- .../pages/create-game/CreateGameView.tsx | 4 +- 8 files changed, 240 insertions(+), 161 deletions(-) create mode 100644 src/main/install/installers.ts diff --git a/src/main/api/game.ts b/src/main/api/game.ts index e5cec96b..e3b3b342 100644 --- a/src/main/api/game.ts +++ b/src/main/api/game.ts @@ -5,7 +5,8 @@ import { containers } from "@/main/container/manage"; import type { ContainerProps } from "@/main/container/spec"; import { paths } from "@/main/fs/paths"; import { games } from "@/main/game/manage"; -import type { GameCoreType, GameInstallProps, GameProfile } from "@/main/game/spec"; +import type { GameCoreType, GameProfile } from "@/main/game/spec"; +import type { InstallerProps } from "@/main/install/installers"; import { vanillaInstaller } from "@/main/install/vanilla"; import { ipcMain } from "@/main/ipc/typed"; import { venv } from "@/main/launch/venv"; @@ -37,7 +38,7 @@ export interface CreateGameInit { name: string; gameVersion: string; authType: "new-vanilla" | "manual" | "reuse"; - installProps: GameInstallProps; + installProps: InstallerProps; playerName: string; accountId: string | null; assetsLevel: "full" | "video-only"; diff --git a/src/main/api/install.ts b/src/main/api/install.ts index 9edf92b9..6e2fdc5b 100644 --- a/src/main/api/install.ts +++ b/src/main/api/install.ts @@ -1,20 +1,13 @@ 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 { installers } from "@/main/install/installers"; import { neoforgedInstaller } from "@/main/install/neoforged"; import { quiltInstaller } from "@/main/install/quilt"; -import { smelt, type SmeltInstallInit } from "@/main/install/smelt"; -import { smeltLegacy } from "@/main/install/smelt-legacy"; -import { vanillaInstaller } from "@/main/install/vanilla"; import { ipcMain } from "@/main/ipc/typed"; -import { jrt } from "@/main/jrt/install"; -import { profileLoader } from "@/main/profile/loader"; import { reg } from "@/main/registry/registry"; import { exceptions } from "@/main/util/exception"; import type { Progress, ProgressController } from "@/main/util/progress"; -import fs from "fs-extra"; export type VanillaInstallEvent = { @@ -44,7 +37,7 @@ ipcMain.on("installGame", async (e, gameId) => { console.debug(`Starting installation of ${gameId}`); const game = structuredClone(reg.games.get(gameId)); - const c = containers.get(game.launchHint.containerId); + const container = containers.get(game.launchHint.containerId); function send(e: VanillaInstallEvent) { port.postMessage(e); @@ -57,134 +50,13 @@ ipcMain.on("installGame", async (e, gameId) => { const abortController = new AbortController(); installControllers.set(gameId, abortController); + const control: ProgressController = { + signal: abortController.signal, + onProgress + }; + try { - const installType = game.installProps.type; - const { gameVersion } = game.installProps; - - const control: ProgressController = { - signal: abortController.signal, - onProgress - }; - - const vanillaProfile = await vanillaInstaller.installProfile(gameVersion, c, control); - let p = vanillaProfile; - - let forgeInstallerPath: string | null = null; - let forgeModLoaderPath: string | null = null; - let forgeInstallerInit: SmeltInstallInit | null = null; - let forgeInstallAction: ForgeInstallActionType = "none"; - - // Preprocess mod loaders, retrieve profile - if (installType === "fabric") { - const fid = await fabricInstaller.retrieveProfile( - gameVersion, - game.installProps.loaderVersion, - c, - control - ); - p = await profileLoader.fromContainer(fid, c); - } - - if (installType === "quilt") { - const qid = await quiltInstaller.retrieveProfile( - gameVersion, - game.installProps.loaderVersion, - c, - control - ); - p = await profileLoader.fromContainer(qid, c); - } - - if (installType === "neoforged") { - let loaderVersion = game.installProps.loaderVersion; - - if (!loaderVersion) { - loaderVersion = await neoforgedInstaller.pickLoaderVersion(gameVersion, control); - } - - forgeInstallerPath = await neoforgedInstaller.downloadInstaller(loaderVersion, control); - forgeInstallAction = "smelt"; - - forgeInstallerInit = await smelt.readInstallProfile(forgeInstallerPath); - const fid = await smelt.deployVersionProfile(forgeInstallerInit, c); - p = await profileLoader.fromContainer(fid, c); - } - - if (installType === "forge") { - let loaderVersion = game.installProps.loaderVersion; - - if (!loaderVersion) { - loaderVersion = await forgeInstaller.pickLoaderVersion(gameVersion, control); - } - - const installerType = forgeInstaller.getInstallType(gameVersion); - forgeInstallerPath = await forgeInstaller.downloadInstaller(loaderVersion, installerType, control); - - const modLoaderUrl = await forgeCompat.getModLoaderUrl(gameVersion); - forgeModLoaderPath = modLoaderUrl && await forgeCompat.downloadModLoader(modLoaderUrl); - - if (installerType === "installer") { - const legacyProfileId = await smeltLegacy.dumpContent(forgeInstallerPath, c); - if (legacyProfileId) { - forgeInstallAction = "smelt-legacy"; - p = await profileLoader.fromContainer(legacyProfileId, c); - } else { - forgeInstallAction = "smelt"; - forgeInstallerInit = await smelt.readInstallProfile(forgeInstallerPath); - const fid = await smelt.deployVersionProfile(forgeInstallerInit, c); - p = await profileLoader.fromContainer(fid, c); - } - } else { - forgeInstallAction = "merge"; - 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; - } - - } - } - - // Ensure libraries - await jrt.installRuntime(p.javaVersion?.component ?? "jre-legacy", control); - - await vanillaInstaller.installLibraries(p, c, new Set(), control); - - // Finalize Forge - if (installType === "neoforged" || installType === "forge") { - if (forgeInstallAction === "smelt") { - await smelt.runPostInstall(forgeInstallerInit!, forgeInstallerPath!, p, c, control); - } else if (forgeInstallAction === "merge") { - await smeltLegacy.patchLegacyLibraries( - jrt.executable(p.javaVersion?.component ?? "jre-legacy"), - forgeInstallerPath!, - c - ); - - 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!); - } - - // Game-level post install - await vanillaInstaller.installAssets(p, c, game.assetsLevel, control); - await vanillaInstaller.emitOptions(c); - - game.launchHint.profileId = p.id; - game.installed = true; - games.add(game); + await installers.runInstall(gameId, control); console.debug(`Completed installation of ${gameId}`); send({ type: "finish" }); diff --git a/src/main/game/spec.ts b/src/main/game/spec.ts index 206611fb..668143fe 100644 --- a/src/main/game/spec.ts +++ b/src/main/game/spec.ts @@ -1,3 +1,4 @@ +import type { InstallerProps } from "@/main/install/installers"; import type { LaunchHint } from "@/main/launch/types"; import type { RegistryTransformer } from "@/main/registry/registry"; @@ -25,7 +26,7 @@ export interface GameProfile { /** * Props for installing the game. */ - installProps: GameInstallProps; + installProps: InstallerProps; /** * Game related versions. @@ -59,22 +60,6 @@ export type GameCoreType = "neoforged" | "unknown" -export type GameInstallProps = - { - type: "vanilla"; - gameVersion: string; - } | - { - type: "fabric" | "quilt"; - gameVersion: string; - loaderVersion: string; - } | - { - type: "neoforged" | "forge"; - gameVersion: string; - loaderVersion: string; - } - export const GAME_REG_VERSION = 2; export const GAME_REG_TRANS: RegistryTransformer[] = [ // v1: patch the `installerProps` key diff --git a/src/main/install/installers.ts b/src/main/install/installers.ts new file mode 100644 index 00000000..9c20e04b --- /dev/null +++ b/src/main/install/installers.ts @@ -0,0 +1,219 @@ +import { containers } from "@/main/container/manage"; +import type { Container } from "@/main/container/spec"; +import { games } from "@/main/game/manage"; +import type { GameProfile } from "@/main/game/spec"; +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"; +import { smeltLegacy } from "@/main/install/smelt-legacy"; +import { vanillaInstaller } from "@/main/install/vanilla"; +import { jrt } from "@/main/jrt/install"; +import { profileLoader } from "@/main/profile/loader"; +import type { VersionProfile } from "@/main/profile/version-profile"; +import { reg } from "@/main/registry/registry"; +import type { ProgressController } from "@/main/util/progress"; +import fs from "fs-extra"; + +interface VanillaInstallerProps { + type: "vanilla"; + gameVersion: string; +} + +interface ModLoaderInstallerProps { + gameVersion: string; + loaderVersion: string; +} + +interface FabricInstallerProps extends ModLoaderInstallerProps { + type: "fabric"; +} + +interface QuiltInstallerProps extends ModLoaderInstallerProps { + type: "quilt"; +} + +interface NeoForgedInstallerProps extends ModLoaderInstallerProps { + type: "neoforged"; +} + +interface ForgeInstallerProps extends ModLoaderInstallerProps { + type: "forge"; +} + +export type InstallerProps = + VanillaInstallerProps + | FabricInstallerProps + | QuiltInstallerProps + | NeoForgedInstallerProps + | ForgeInstallerProps; + +export interface DetailedInstallerContext { + game: GameProfile; + container: Container; + control?: ProgressController; +} + +async function installVanilla(props: VanillaInstallerProps, context: DetailedInstallerContext) { + const { game, container, control } = context; + const { gameVersion } = props; + + const p = await vanillaInstaller.installProfile(gameVersion, container, control); + + await jrt.installRuntime(p.javaVersion.component, control); + await vanillaInstaller.installLibraries(p, container, new Set(), control); + + await vanillaInstaller.installAssets(p, container, game.assetsLevel, control); + await vanillaInstaller.emitOptions(container); + + game.launchHint.profileId = p.id; +} + +async function installFabricOrQuilt(props: FabricInstallerProps | QuiltInstallerProps, context: DetailedInstallerContext) { + const { game, container, control } = context; + const { gameVersion, loaderVersion } = props; + + await vanillaInstaller.installProfile(gameVersion, container, control); + + const installer = props.type === "fabric" ? fabricInstaller : quiltInstaller; + const fid = await installer.retrieveProfile(gameVersion, loaderVersion, container, control); + const p = await profileLoader.fromContainer(fid, container); + + await jrt.installRuntime(p.javaVersion.component, control); + await vanillaInstaller.installLibraries(p, container, new Set(), control); + + await vanillaInstaller.installAssets(p, container, game.assetsLevel, control); + await vanillaInstaller.emitOptions(container); + + game.launchHint.profileId = p.id; +} + +async function installNeoForged(props: NeoForgedInstallerProps, context: DetailedInstallerContext) { + const { game, container, control } = context; + let { gameVersion, loaderVersion } = props; + + await vanillaInstaller.installProfile(gameVersion, container, control); + + if (!loaderVersion) { + loaderVersion = await neoforgedInstaller.pickLoaderVersion(gameVersion, control); + } + + const installerPath = await neoforgedInstaller.downloadInstaller(loaderVersion, control); + const installInit = await smelt.readInstallProfile(installerPath); + + const fid = await smelt.deployVersionProfile(installInit, container); + const p = await profileLoader.fromContainer(fid, container); + + await jrt.installRuntime(p.javaVersion.component, control); + await vanillaInstaller.installLibraries(p, container, new Set(), control); + + await smelt.runPostInstall(installInit!, installerPath!, p, container, control); + await fs.remove(installerPath!); + + await vanillaInstaller.installAssets(p, container, game.assetsLevel, control); + await vanillaInstaller.emitOptions(container); + + game.launchHint.profileId = p.id; +} + +type ForgeInstallActionType = "smelt" | "smelt-legacy" | "merge"; + +async function installForge(props: ForgeInstallerProps, context: DetailedInstallerContext) { + const { game, container, control } = context; + let { gameVersion, loaderVersion } = props; + + const vanillaProfile = await vanillaInstaller.installProfile(gameVersion, container, control); + let p: VersionProfile; + + let forgeInstallerInit: SmeltInstallInit | null = null; + let forgeInstallAction: ForgeInstallActionType; + + if (!loaderVersion) { + loaderVersion = await forgeInstaller.pickLoaderVersion(gameVersion, control); + } + + const installerType = forgeInstaller.getInstallType(gameVersion); + const forgeInstallerPath = await forgeInstaller.downloadInstaller(loaderVersion, installerType, control); + + const modLoaderUrl = await forgeCompat.getModLoaderUrl(gameVersion); + const forgeModLoaderPath = modLoaderUrl && await forgeCompat.downloadModLoader(modLoaderUrl); + + if (installerType === "installer") { + const legacyProfileId = await smeltLegacy.dumpContent(forgeInstallerPath, container); + if (legacyProfileId) { + forgeInstallAction = "smelt-legacy"; + p = await profileLoader.fromContainer(legacyProfileId, container); + } else { + forgeInstallAction = "smelt"; + forgeInstallerInit = await smelt.readInstallProfile(forgeInstallerPath); + const fid = await smelt.deployVersionProfile(forgeInstallerInit, container); + p = await profileLoader.fromContainer(fid, container); + } + } else { + forgeInstallAction = "merge"; + 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(container, gameVersion); + p = await profileLoader.fromContainer(gameVersion, container); + } else { + p = vanillaProfile; + } + + } + + await jrt.installRuntime(p.javaVersion.component, control); + await vanillaInstaller.installLibraries(p, container, new Set(), control); + + if (forgeInstallAction === "smelt") { + await smelt.runPostInstall(forgeInstallerInit!, forgeInstallerPath!, p, container, control); + } else if (forgeInstallAction === "merge") { + await smeltLegacy.patchLegacyLibraries( + jrt.executable(p.javaVersion.component), + forgeInstallerPath!, + container + ); + + const clientPath = container.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!); + + await vanillaInstaller.installAssets(p, container, game.assetsLevel, control); + await vanillaInstaller.emitOptions(container); + + game.launchHint.profileId = p.id; +} + +const internalInstallers = { + vanilla: installVanilla, + fabric: installFabricOrQuilt, + quilt: installFabricOrQuilt, + neoforged: installNeoForged, + forge: installForge +} as const; + +async function runInstall(gameId: string, control?: ProgressController) { + const game = reg.games.get(gameId); + const props = game.installProps; + const container = containers.get(game.launchHint.containerId); + + const context: DetailedInstallerContext = { game, container, control }; + + await internalInstallers[props.type](props as any, context); + + game.installed = true; + games.add(game); +} + +export const installers = { runInstall }; diff --git a/src/main/install/smelt.ts b/src/main/install/smelt.ts index d89b8da9..ab6815e2 100644 --- a/src/main/install/smelt.ts +++ b/src/main/install/smelt.ts @@ -321,7 +321,7 @@ async function runPostInstall( await extractLibraries(init, installer, container); await downloadLibraries(init, container, control); - const jrtExec = jrt.executable(profile.javaVersion?.component ?? "jre-legacy"); + const jrtExec = jrt.executable(profile.javaVersion.component); signal?.throwIfAborted(); diff --git a/src/main/profile/profile-adaptor.ts b/src/main/profile/profile-adaptor.ts index eafce5b3..9035c0e0 100644 --- a/src/main/profile/profile-adaptor.ts +++ b/src/main/profile/profile-adaptor.ts @@ -52,6 +52,8 @@ async function patchJRTVersion(src: Record) { } } } + + src["javaVersion"] = { component: "jre-legacy" }; } /** diff --git a/src/main/profile/version-profile.ts b/src/main/profile/version-profile.ts index 15952d9a..6be5f6a0 100644 --- a/src/main/profile/version-profile.ts +++ b/src/main/profile/version-profile.ts @@ -48,7 +48,7 @@ export interface VersionProfile { * * This key is missing in some early versions. */ - javaVersion?: JRTVersionPrompt; + javaVersion: JRTVersionPrompt; libraries: Library[]; @@ -108,8 +108,8 @@ export interface DownloadsArtifact { } export interface JRTVersionPrompt { - component: string; - majorVersion: number; + component: string; // Will be patched when linking + majorVersion?: number; } export interface Library { diff --git a/src/renderer/pages/create-game/CreateGameView.tsx b/src/renderer/pages/create-game/CreateGameView.tsx index 4d9668d6..0d911472 100644 --- a/src/renderer/pages/create-game/CreateGameView.tsx +++ b/src/renderer/pages/create-game/CreateGameView.tsx @@ -1,4 +1,4 @@ -import type { GameInstallProps } from "@/main/game/spec"; +import type { InstallerProps } from "@/main/install/installers"; import { useAccounts } from "@/renderer/services/auth"; import { useNav } from "@/renderer/util/nav"; import { Alert } from "@components/Alert"; @@ -68,7 +68,7 @@ export function CreateGameView() { !(authType === "manual" && !playerName) && !(authType === "reuse" && !accountId); - function buildInstallProps(): GameInstallProps { + function buildInstallProps(): InstallerProps { if (!valid) throw "Cannot create game with incomplete install props"; switch (installType) {