129 lines
4.5 KiB
TypeScript
129 lines
4.5 KiB
TypeScript
import { existsSync } from 'fs'
|
|
import { join } from 'path'
|
|
import { getVersionList, installTask, installNeoForgedTask } from '@xmcl/installer'
|
|
import type { Task } from '@xmcl/task'
|
|
import { paths } from './paths'
|
|
import { emit } from './events'
|
|
import { type LaunchPhase } from '../shared/ipc'
|
|
import { downloadDispatcher, DOWNLOAD_CONCURRENCY, withRetries } from './net'
|
|
|
|
/** Options de téléchargement communes : dispatcher tolérant + concurrence bridée. */
|
|
const downloadOptions = {
|
|
dispatcher: downloadDispatcher,
|
|
assetsDownloadConcurrency: DOWNLOAD_CONCURRENCY,
|
|
librariesDownloadConcurrency: DOWNLOAD_CONCURRENCY
|
|
}
|
|
|
|
/**
|
|
* Exécute une task @xmcl en émettant sa progression réelle (0..1) vers le
|
|
* renderer. On lit la progression cumulée de la task racine ; on n'émet qu'au
|
|
* changement de pourcentage entier pour ne pas inonder l'IPC.
|
|
*/
|
|
async function runWithProgress<T>(phase: LaunchPhase, message: string, task: Task<T>): Promise<T> {
|
|
let lastPct = -1
|
|
return task.startAndWait({
|
|
onUpdate() {
|
|
if (task.total <= 0) return
|
|
const ratio = task.progress / task.total
|
|
const pct = Math.floor(ratio * 100)
|
|
if (pct === lastPct) return
|
|
lastPct = pct
|
|
emit.progress({ phase, message, progress: Math.min(1, ratio) })
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Installe le runtime Minecraft (vanilla 1.21.1) puis NeoForge dans
|
|
* paths.gameRoot. Les deux étapes sont idempotentes : si la version est déjà
|
|
* présente sur le disque, on ne refait pas le travail.
|
|
*
|
|
* Retourne l'id de version NeoForge à lancer (ex. "neoforge-21.1.73").
|
|
*/
|
|
|
|
/** true si un dossier de version <id> avec son JSON existe déjà. */
|
|
function versionInstalled(id: string): boolean {
|
|
return existsSync(join(paths.gameRoot, 'versions', id, `${id}.json`))
|
|
}
|
|
|
|
/**
|
|
* Installe Minecraft vanilla <version> (json + jar + assets + libraries).
|
|
* `force` saute le court-circuit "déjà installé" pour revalider/réparer les
|
|
* fichiers (l'install @xmcl ignore les fichiers déjà valides par checksum).
|
|
*/
|
|
export async function installMinecraft(version: string, force = false): Promise<void> {
|
|
if (!force && versionInstalled(version)) return
|
|
|
|
emit.progress({ phase: 'minecraft', message: `Minecraft ${version} : métadonnées…`, progress: undefined })
|
|
const list = await getVersionList()
|
|
const meta = list.versions.find((v) => v.id === version)
|
|
if (!meta) throw new Error(`Version Minecraft introuvable : ${version}`)
|
|
|
|
const label = force
|
|
? `Vérification de Minecraft ${version}…`
|
|
: `Installation de Minecraft ${version} (assets + libs)…`
|
|
|
|
// ~3700 assets : on réessaie l'install entière plusieurs fois. Chaque passe
|
|
// ignore les fichiers déjà valides et ne reprend que les manquants.
|
|
await withRetries(
|
|
() => runWithProgress('minecraft', label, installTask(meta, paths.gameRoot, downloadOptions)),
|
|
5,
|
|
(attempt) =>
|
|
emit.progress({
|
|
phase: 'minecraft',
|
|
message: `Reprise des téléchargements (tentative ${attempt + 1})…`,
|
|
progress: undefined
|
|
})
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Installe NeoForge <neoforge> par-dessus Minecraft <minecraft>.
|
|
* Nécessite un Java (les "processors" de l'installeur tournent en Java).
|
|
*/
|
|
export async function installNeoForge(
|
|
neoforge: string,
|
|
minecraft: string,
|
|
javaPath: string,
|
|
force = false
|
|
): Promise<string> {
|
|
// @xmcl nomme la version installée "neoforge-<version>" (ex. neoforge-21.1.224).
|
|
// Si elle est déjà présente, on saute l'install (idempotent, comme Minecraft).
|
|
const expectedId = `neoforge-${neoforge}`
|
|
if (!force && versionInstalled(expectedId)) {
|
|
void minecraft
|
|
return expectedId
|
|
}
|
|
|
|
const label = force
|
|
? `Vérification de NeoForge ${neoforge}…`
|
|
: `Installation de NeoForge ${neoforge}…`
|
|
|
|
// installNeoForgedTask retourne l'id de version installée à lancer.
|
|
const versionId = await withRetries(
|
|
() =>
|
|
runWithProgress(
|
|
'neoforge',
|
|
label,
|
|
installNeoForgedTask('neoforge', neoforge, paths.gameRoot, {
|
|
java: javaPath,
|
|
dispatcher: downloadDispatcher,
|
|
librariesDownloadConcurrency: DOWNLOAD_CONCURRENCY
|
|
})
|
|
),
|
|
3,
|
|
(attempt) =>
|
|
emit.progress({
|
|
phase: 'neoforge',
|
|
message: `Reprise de l'installation NeoForge (tentative ${attempt + 1})…`,
|
|
progress: undefined
|
|
})
|
|
)
|
|
|
|
if (!versionInstalled(versionId)) {
|
|
throw new Error(`NeoForge installé mais version "${versionId}" introuvable sur le disque.`)
|
|
}
|
|
void minecraft // (info de contexte ; la version NeoForge hérite déjà de Minecraft)
|
|
return versionId
|
|
}
|