114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
import { spawn } from 'child_process'
|
|
import { parse as parseToml } from 'smol-toml'
|
|
import { paths } from './paths'
|
|
import { config } from '../shared/config'
|
|
import { fetchText } from './download'
|
|
import { emit } from './events'
|
|
import type { PackMeta } from '../shared/ipc'
|
|
|
|
export type { PackMeta }
|
|
|
|
/**
|
|
* Gestion du modpack côté launcher :
|
|
* - lecture du pack.toml packwiz distant (versions MC/NeoForge = source de vérité)
|
|
* - sync incrémentale des mods/configs via packwiz-installer-bootstrap.
|
|
*
|
|
* packwiz-installer garde un manifeste local (packwiz.json dans l'instance) et
|
|
* ne re-télécharge QUE les fichiers dont le hash a changé — c'est exactement le
|
|
* comportement "pas de re-download complet à chaque update" recherché.
|
|
*/
|
|
|
|
/** Dernière PackMeta lue avec succès (pour l'affichage UI, y compris hors flux Jouer). */
|
|
let lastMeta: PackMeta | null = null
|
|
|
|
/** Renvoie la dernière PackMeta connue, ou la récupère si jamais lue. Tolérant au offline. */
|
|
export async function getPackMetaCached(): Promise<PackMeta | null> {
|
|
if (lastMeta) return lastMeta
|
|
try {
|
|
return await fetchPackMeta()
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** Télécharge et parse le pack.toml distant. */
|
|
export async function fetchPackMeta(): Promise<PackMeta> {
|
|
emit.progress({ phase: 'pack-meta', message: 'Lecture du modpack…', progress: undefined })
|
|
|
|
if (!config.packTomlUrl || config.packTomlUrl.includes('CHANGE_ME')) {
|
|
throw new Error(
|
|
'URL du pack.toml non configurée. Renseigne packTomlUrl dans src/shared/config.ts.'
|
|
)
|
|
}
|
|
|
|
const raw = await fetchText(config.packTomlUrl)
|
|
const data = parseToml(raw) as {
|
|
name?: string
|
|
version?: string
|
|
versions?: { minecraft?: string; neoforge?: string }
|
|
}
|
|
|
|
const minecraft = data.versions?.minecraft
|
|
const neoforge = data.versions?.neoforge
|
|
if (!minecraft || !neoforge) {
|
|
throw new Error(
|
|
'pack.toml invalide : [versions] doit contenir "minecraft" et "neoforge".'
|
|
)
|
|
}
|
|
|
|
lastMeta = {
|
|
name: data.name ?? 'Modpack',
|
|
version: data.version ?? '0',
|
|
minecraft,
|
|
neoforge
|
|
}
|
|
return lastMeta
|
|
}
|
|
|
|
/**
|
|
* Lance packwiz-installer-bootstrap pour synchroniser l'instance.
|
|
* `java` = chemin du binaire Java 21 géré (réutilisé pour faire tourner le jar).
|
|
*/
|
|
export async function syncModpack(javaPath: string): Promise<void> {
|
|
emit.progress({ phase: 'modpack', message: 'Synchronisation du modpack…', progress: undefined })
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
const child = spawn(
|
|
javaPath,
|
|
['-jar', paths.packwizBootstrapJar, '-g', '-s', 'client', config.packTomlUrl],
|
|
{ cwd: paths.instanceDir }
|
|
)
|
|
|
|
// On garde les dernières lignes de sortie pour pouvoir afficher la VRAIE
|
|
// erreur de packwiz si le process échoue.
|
|
const recent: string[] = []
|
|
const onLine = (buf: Buffer): void => {
|
|
for (const line of buf.toString().split(/\r?\n/)) {
|
|
const text = line.trim()
|
|
if (!text) continue
|
|
recent.push(text)
|
|
if (recent.length > 25) recent.shift()
|
|
emit.progress({ phase: 'modpack', message: text, progress: undefined })
|
|
// Visible aussi dans la console du launcher.
|
|
emit.gameLog({ stream: 'stdout', line: `[packwiz] ${text}` })
|
|
}
|
|
}
|
|
child.stdout.on('data', onLine)
|
|
child.stderr.on('data', onLine)
|
|
|
|
child.on('error', reject)
|
|
child.on('close', (code) => {
|
|
if (code === 0) return resolve()
|
|
const tail = recent.slice(-12).join('\n')
|
|
reject(
|
|
new Error(
|
|
`packwiz-installer a échoué (code ${code}).` +
|
|
(tail ? `\nDernières lignes :\n${tail}` : '')
|
|
)
|
|
)
|
|
})
|
|
})
|
|
|
|
emit.progress({ phase: 'modpack', message: 'Modpack à jour.', progress: 1 })
|
|
}
|