feat: add some funcs

This commit is contained in:
lucasdpt
2026-06-14 14:23:37 +02:00
parent 48fa508540
commit 4756420f8d
11 changed files with 240 additions and 44 deletions
+9 -2
View File
@@ -6,6 +6,7 @@ import {
type DeviceCodeInfo, type DeviceCodeInfo,
type UpdateStatus type UpdateStatus
} from '../shared/ipc' } from '../shared/ipc'
import * as logger from './logger'
/** Fenêtre principale, définie au démarrage (src/main/index.ts). */ /** Fenêtre principale, définie au démarrage (src/main/index.ts). */
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
@@ -21,8 +22,14 @@ function send(channel: string, payload: unknown): void {
} }
export const emit = { export const emit = {
progress: (e: ProgressEvent): void => send(IPC_EVENT.progress, e), progress: (e: ProgressEvent): void => {
gameLog: (l: GameLogLine): void => send(IPC_EVENT.gameLog, l), if (e.phase === 'error') logger.write(`[ERREUR] ${e.message}`)
send(IPC_EVENT.progress, e)
},
gameLog: (l: GameLogLine): void => {
logger.write(l.line)
send(IPC_EVENT.gameLog, l)
},
gameClosed: (code: number | null): void => send(IPC_EVENT.gameClosed, code), gameClosed: (code: number | null): void => send(IPC_EVENT.gameClosed, code),
authCode: (info: DeviceCodeInfo): void => send(IPC_EVENT.authCode, info), authCode: (info: DeviceCodeInfo): void => send(IPC_EVENT.authCode, info),
updateStatus: (s: UpdateStatus): void => send(IPC_EVENT.updateStatus, s) updateStatus: (s: UpdateStatus): void => send(IPC_EVENT.updateStatus, s)
+7 -3
View File
@@ -1,9 +1,10 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron' import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path' import { join } from 'path'
import { setMainWindow } from './events' import { setMainWindow } from './events'
import { IPC, type UserSettings } from '../shared/ipc' import { IPC, type UserSettings, type PlayOptions } from '../shared/ipc'
import { login, logout, restoreSession, getCurrent } from './auth' import { login, logout, restoreSession, getCurrent } from './auth'
import { play } from './play' import { play, stopGame } from './play'
import { getPackMetaCached } from './modpack'
import { getSettings, setSettings } from './settings' import { getSettings, setSettings } from './settings'
import { paths } from './paths' import { paths } from './paths'
import { initUpdater, quitAndInstallUpdate } from './updater' import { initUpdater, quitAndInstallUpdate } from './updater'
@@ -50,11 +51,14 @@ function registerIpc(): void {
ipcMain.handle(IPC.authGetProfile, async () => { ipcMain.handle(IPC.authGetProfile, async () => {
return getCurrent()?.profile ?? (await restoreSession()) return getCurrent()?.profile ?? (await restoreSession())
}) })
ipcMain.handle(IPC.play, () => play()) ipcMain.handle(IPC.play, (_e, opts: PlayOptions | undefined) => play(opts))
ipcMain.handle(IPC.playStop, () => stopGame())
ipcMain.handle(IPC.packGet, () => getPackMetaCached())
ipcMain.handle(IPC.settingsGet, () => getSettings()) ipcMain.handle(IPC.settingsGet, () => getSettings())
ipcMain.handle(IPC.settingsSet, (_e, s: UserSettings) => setSettings(s)) ipcMain.handle(IPC.settingsSet, (_e, s: UserSettings) => setSettings(s))
ipcMain.handle(IPC.appVersion, () => app.getVersion()) ipcMain.handle(IPC.appVersion, () => app.getVersion())
ipcMain.handle(IPC.openInstanceDir, () => shell.openPath(paths.instanceDir)) ipcMain.handle(IPC.openInstanceDir, () => shell.openPath(paths.instanceDir))
ipcMain.handle(IPC.openLogsDir, () => shell.openPath(paths.logsDir))
ipcMain.handle(IPC.updateInstall, () => quitAndInstallUpdate()) ipcMain.handle(IPC.updateInstall, () => quitAndInstallUpdate())
} }
+49 -19
View File
@@ -1,8 +1,10 @@
import { existsSync } from 'fs' import { existsSync } from 'fs'
import { join } from 'path' import { join } from 'path'
import { getVersionList, install, installNeoForged } from '@xmcl/installer' import { getVersionList, installTask, installNeoForgedTask } from '@xmcl/installer'
import type { Task } from '@xmcl/task'
import { paths } from './paths' import { paths } from './paths'
import { emit } from './events' import { emit } from './events'
import { type LaunchPhase } from '../shared/ipc'
import { downloadDispatcher, DOWNLOAD_CONCURRENCY, withRetries } from './net' import { downloadDispatcher, DOWNLOAD_CONCURRENCY, withRetries } from './net'
/** Options de téléchargement communes : dispatcher tolérant + concurrence bridée. */ /** Options de téléchargement communes : dispatcher tolérant + concurrence bridée. */
@@ -12,6 +14,25 @@ const downloadOptions = {
librariesDownloadConcurrency: 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 * Installe le runtime Minecraft (vanilla 1.21.1) puis NeoForge dans
* paths.gameRoot. Les deux étapes sont idempotentes : si la version est déjà * paths.gameRoot. Les deux étapes sont idempotentes : si la version est déjà
@@ -25,25 +46,27 @@ function versionInstalled(id: string): boolean {
return existsSync(join(paths.gameRoot, 'versions', id, `${id}.json`)) return existsSync(join(paths.gameRoot, 'versions', id, `${id}.json`))
} }
/** Installe Minecraft vanilla <version> (json + jar + assets + libraries). */ /**
export async function installMinecraft(version: string): Promise<void> { * Installe Minecraft vanilla <version> (json + jar + assets + libraries).
if (versionInstalled(version)) return * `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 }) emit.progress({ phase: 'minecraft', message: `Minecraft ${version} : métadonnées…`, progress: undefined })
const list = await getVersionList() const list = await getVersionList()
const meta = list.versions.find((v) => v.id === version) const meta = list.versions.find((v) => v.id === version)
if (!meta) throw new Error(`Version Minecraft introuvable : ${version}`) if (!meta) throw new Error(`Version Minecraft introuvable : ${version}`)
emit.progress({ const label = force
phase: 'minecraft', ? `Vérification de Minecraft ${version}`
message: `Installation de Minecraft ${version} (assets + libs)…`, : `Installation de Minecraft ${version} (assets + libs)…`
progress: undefined
})
// ~3700 assets : on réessaie l'install entière plusieurs fois. Chaque passe // ~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. // ignore les fichiers déjà valides et ne reprend que les manquants.
await withRetries( await withRetries(
() => install(meta, paths.gameRoot, downloadOptions), () => runWithProgress('minecraft', label, installTask(meta, paths.gameRoot, downloadOptions)),
5, 5,
(attempt) => (attempt) =>
emit.progress({ emit.progress({
@@ -61,26 +84,33 @@ export async function installMinecraft(version: string): Promise<void> {
export async function installNeoForge( export async function installNeoForge(
neoforge: string, neoforge: string,
minecraft: string, minecraft: string,
javaPath: string javaPath: string,
force = false
): Promise<string> { ): Promise<string> {
// @xmcl nomme la version installée "neoforge-<version>" (ex. neoforge-21.1.224). // @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). // Si elle est déjà présente, on saute l'install (idempotent, comme Minecraft).
const expectedId = `neoforge-${neoforge}` const expectedId = `neoforge-${neoforge}`
if (versionInstalled(expectedId)) { if (!force && versionInstalled(expectedId)) {
void minecraft void minecraft
return expectedId return expectedId
} }
emit.progress({ phase: 'neoforge', message: `Installation de NeoForge ${neoforge}`, progress: undefined }) const label = force
? `Vérification de NeoForge ${neoforge}`
: `Installation de NeoForge ${neoforge}`
// installNeoForged retourne l'id de version installée à lancer. // installNeoForgedTask retourne l'id de version installée à lancer.
const versionId = await withRetries( const versionId = await withRetries(
() => () =>
installNeoForged('neoforge', neoforge, paths.gameRoot, { runWithProgress(
java: javaPath, 'neoforge',
dispatcher: downloadDispatcher, label,
librariesDownloadConcurrency: DOWNLOAD_CONCURRENCY installNeoForgedTask('neoforge', neoforge, paths.gameRoot, {
}), java: javaPath,
dispatcher: downloadDispatcher,
librariesDownloadConcurrency: DOWNLOAD_CONCURRENCY
})
),
3, 3,
(attempt) => (attempt) =>
emit.progress({ emit.progress({
+30
View File
@@ -0,0 +1,30 @@
import { createWriteStream, type WriteStream } from 'fs'
import { paths } from './paths'
/**
* Journalisation sur disque du launcher (logs/launcher.log).
*
* Capture tout ce qui transite par `events.ts` (sortie jeu + packwiz + messages
* de phase + erreurs), pour pouvoir dépanner un joueur à distance. Le fichier
* est tronqué au début de chaque session "Jouer".
*/
let stream: WriteStream | null = null
function ts(): string {
return new Date().toISOString()
}
/** Ouvre (en tronquant) un nouveau fichier de log et écrit un en-tête. */
export function startSession(): void {
stream?.end()
stream = createWriteStream(paths.launcherLogFile, { flags: 'w' })
stream.write(`=== Session OFLauncher ${ts()} ===\n`)
}
/** Ajoute une ligne au log courant (no-op si aucune session ouverte). */
export function write(line: string): void {
if (!stream) return
const text = line.endsWith('\n') ? line : `${line}\n`
stream.write(text)
}
+16 -6
View File
@@ -4,6 +4,9 @@ import { paths } from './paths'
import { config } from '../shared/config' import { config } from '../shared/config'
import { fetchText } from './download' import { fetchText } from './download'
import { emit } from './events' import { emit } from './events'
import type { PackMeta } from '../shared/ipc'
export type { PackMeta }
/** /**
* Gestion du modpack côté launcher : * Gestion du modpack côté launcher :
@@ -15,11 +18,17 @@ import { emit } from './events'
* comportement "pas de re-download complet à chaque update" recherché. * comportement "pas de re-download complet à chaque update" recherché.
*/ */
export interface PackMeta { /** Dernière PackMeta lue avec succès (pour l'affichage UI, y compris hors flux Jouer). */
name: string let lastMeta: PackMeta | null = null
version: string
minecraft: string /** Renvoie la dernière PackMeta connue, ou la récupère si jamais lue. Tolérant au offline. */
neoforge: string 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. */ /** Télécharge et parse le pack.toml distant. */
@@ -47,12 +56,13 @@ export async function fetchPackMeta(): Promise<PackMeta> {
) )
} }
return { lastMeta = {
name: data.name ?? 'Modpack', name: data.name ?? 'Modpack',
version: data.version ?? '0', version: data.version ?? '0',
minecraft, minecraft,
neoforge neoforge
} }
return lastMeta
} }
/** /**
+10
View File
@@ -41,6 +41,16 @@ class LauncherPaths {
return join(this.root, 'settings.json') return join(this.root, 'settings.json')
} }
/** Dossier des logs du launcher (install/Java/packwiz/jeu). */
get logsDir(): string {
return this.ensure(join(this.root, 'logs'))
}
/** Fichier de log courant du launcher. */
get launcherLogFile(): string {
return join(this.logsDir, 'launcher.log')
}
/** jar packwiz-installer-bootstrap embarqué dans les resources. */ /** jar packwiz-installer-bootstrap embarqué dans les resources. */
get packwizBootstrapJar(): string { get packwizBootstrapJar(): string {
// En prod, electron-builder copie resources/ via extraResources. // En prod, electron-builder copie resources/ via extraResources.
+15 -3
View File
@@ -7,6 +7,8 @@ import { syncModpack } from './modpack'
import { launchGame } from './launch' import { launchGame } from './launch'
import { getSettings } from './settings' import { getSettings } from './settings'
import { emit } from './events' import { emit } from './events'
import * as logger from './logger'
import type { PlayOptions } from '../shared/ipc'
/** Process de jeu courant (un seul à la fois). */ /** Process de jeu courant (un seul à la fois). */
let gameProcess: ChildProcess | null = null let gameProcess: ChildProcess | null = null
@@ -20,7 +22,7 @@ let gameProcess: ChildProcess | null = null
* idempotentes, donc à partir du 2e lancement seules les nouveautés du modpack * idempotentes, donc à partir du 2e lancement seules les nouveautés du modpack
* sont téléchargées. * sont téléchargées.
*/ */
export async function play(): Promise<void> { export async function play(opts?: PlayOptions): Promise<void> {
if (gameProcess) { if (gameProcess) {
throw new Error('Le jeu est déjà en cours.') throw new Error('Le jeu est déjà en cours.')
} }
@@ -30,11 +32,14 @@ export async function play(): Promise<void> {
throw new Error('Non connecté. Connecte-toi avec ton compte Microsoft dabord.') throw new Error('Non connecté. Connecte-toi avec ton compte Microsoft dabord.')
} }
const repair = opts?.repair ?? false
logger.startSession()
try { try {
const meta = await fetchPackMeta() const meta = await fetchPackMeta()
const javaPath = await ensureJava() const javaPath = await ensureJava()
await installMinecraft(meta.minecraft) await installMinecraft(meta.minecraft, repair)
const versionId = await installNeoForge(meta.neoforge, meta.minecraft, javaPath) const versionId = await installNeoForge(meta.neoforge, meta.minecraft, javaPath, repair)
await syncModpack(javaPath) await syncModpack(javaPath)
const settings = await getSettings() const settings = await getSettings()
@@ -48,3 +53,10 @@ export async function play(): Promise<void> {
throw e throw e
} }
} }
/** Tue le process de jeu courant. Renvoie true si un process tournait. */
export function stopGame(): boolean {
if (!gameProcess) return false
gameProcess.kill()
return true
}
+9 -2
View File
@@ -7,7 +7,9 @@ import {
type GameLogLine, type GameLogLine,
type UserSettings, type UserSettings,
type DeviceCodeInfo, type DeviceCodeInfo,
type UpdateStatus type UpdateStatus,
type PlayOptions,
type PackMeta
} from '../shared/ipc' } from '../shared/ipc'
/** API typée exposée au renderer via window.api. */ /** API typée exposée au renderer via window.api. */
@@ -18,7 +20,9 @@ const api = {
getProfile: (): Promise<PlayerProfile | null> => ipcRenderer.invoke(IPC.authGetProfile), getProfile: (): Promise<PlayerProfile | null> => ipcRenderer.invoke(IPC.authGetProfile),
// --- Jouer (install + sync + launch) --- // --- Jouer (install + sync + launch) ---
play: (): Promise<void> => ipcRenderer.invoke(IPC.play), play: (opts?: PlayOptions): Promise<void> => ipcRenderer.invoke(IPC.play, opts),
stopGame: (): Promise<boolean> => ipcRenderer.invoke(IPC.playStop),
getPackMeta: (): Promise<PackMeta | null> => ipcRenderer.invoke(IPC.packGet),
// --- Réglages --- // --- Réglages ---
getSettings: (): Promise<UserSettings> => ipcRenderer.invoke(IPC.settingsGet), getSettings: (): Promise<UserSettings> => ipcRenderer.invoke(IPC.settingsGet),
@@ -29,6 +33,9 @@ const api = {
/** Ouvre le dossier d'instance (mods/config/saves) dans l'explorateur. */ /** Ouvre le dossier d'instance (mods/config/saves) dans l'explorateur. */
openInstanceDir: (): Promise<string> => ipcRenderer.invoke(IPC.openInstanceDir), openInstanceDir: (): Promise<string> => ipcRenderer.invoke(IPC.openInstanceDir),
/** Ouvre le dossier des logs du launcher. */
openLogsDir: (): Promise<string> => ipcRenderer.invoke(IPC.openLogsDir),
/** Quitte et installe la mise à jour téléchargée. */ /** Quitte et installe la mise à jour téléchargée. */
installUpdate: (): Promise<void> => ipcRenderer.invoke(IPC.updateInstall), installUpdate: (): Promise<void> => ipcRenderer.invoke(IPC.updateInstall),
+50 -9
View File
@@ -4,9 +4,15 @@ import type {
ProgressEvent, ProgressEvent,
GameLogLine, GameLogLine,
DeviceCodeInfo, DeviceCodeInfo,
UpdateStatus UpdateStatus,
PackMeta
} from '../../shared/ipc' } from '../../shared/ipc'
/** Découpe une chaîne d'args JVM en tableau (espaces, vides ignorés). */
function parseArgs(s: string): string[] {
return s.split(/\s+/).filter(Boolean)
}
type Status = 'loading' | 'logged-out' | 'logged-in' | 'working' | 'running' type Status = 'loading' | 'logged-out' | 'logged-in' | 'working' | 'running'
export default function App(): JSX.Element { export default function App(): JSX.Element {
@@ -17,8 +23,10 @@ export default function App(): JSX.Element {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [authCode, setAuthCode] = useState<DeviceCodeInfo | null>(null) const [authCode, setAuthCode] = useState<DeviceCodeInfo | null>(null)
const [maxMemoryMb, setMaxMemoryMb] = useState(8192) const [maxMemoryMb, setMaxMemoryMb] = useState(8192)
const [jvmArgs, setJvmArgs] = useState('')
const [appVersion, setAppVersion] = useState('') const [appVersion, setAppVersion] = useState('')
const [update, setUpdate] = useState<UpdateStatus | null>(null) const [update, setUpdate] = useState<UpdateStatus | null>(null)
const [pack, setPack] = useState<PackMeta | null>(null)
const consoleRef = useRef<HTMLDivElement>(null) const consoleRef = useRef<HTMLDivElement>(null)
// Restaure la session + réglages au démarrage. // Restaure la session + réglages au démarrage.
@@ -30,10 +38,13 @@ export default function App(): JSX.Element {
window.api.getAppVersion() window.api.getAppVersion()
]) ])
setMaxMemoryMb(s.maxMemoryMb) setMaxMemoryMb(s.maxMemoryMb)
setJvmArgs(s.extraJvmArgs.join(' '))
setAppVersion(v) setAppVersion(v)
setProfile(p) setProfile(p)
setStatus(p ? 'logged-in' : 'logged-out') setStatus(p ? 'logged-in' : 'logged-out')
})() })()
// Récupère le nom/version réels du pack (tolérant au offline).
void window.api.getPackMeta().then((m) => m && setPack(m))
}, []) }, [])
// Abonnements aux événements main -> renderer. // Abonnements aux événements main -> renderer.
@@ -97,13 +108,21 @@ export default function App(): JSX.Element {
void window.api.openInstanceDir() void window.api.openInstanceDir()
} }
async function handlePlay(): Promise<void> { function handleOpenLogs(): void {
void window.api.openLogsDir()
}
function handleStop(): void {
void window.api.stopGame()
}
async function handlePlay(opts?: { repair?: boolean }): Promise<void> {
setError(null) setError(null)
setLogs([]) setLogs([])
setStatus('working') setStatus('working')
await window.api.setSettings({ maxMemoryMb, extraJvmArgs: [] }) await window.api.setSettings({ maxMemoryMb, extraJvmArgs: parseArgs(jvmArgs) })
try { try {
await window.api.play() await window.api.play(opts)
} catch (e) { } catch (e) {
setError(`Échec du lancement : ${(e as Error).message}`) setError(`Échec du lancement : ${(e as Error).message}`)
setStatus('logged-in') setStatus('logged-in')
@@ -178,7 +197,9 @@ export default function App(): JSX.Element {
<div className="topbar"> <div className="topbar">
<div className="brand"> <div className="brand">
OFLauncher OFLauncher
<span className="pack">All The Mods 10 · 1.21.1</span> <span className="pack">
{pack ? `${pack.name} · MC ${pack.minecraft}` : 'All The Mods 10 · 1.21.1'}
</span>
</div> </div>
<div className="profile"> <div className="profile">
<img src={`https://mc-heads.net/avatar/${profile?.uuid}`} alt="" /> <img src={`https://mc-heads.net/avatar/${profile?.uuid}`} alt="" />
@@ -220,14 +241,34 @@ export default function App(): JSX.Element {
onChange={(e) => setMaxMemoryMb(Number(e.target.value))} onChange={(e) => setMaxMemoryMb(Number(e.target.value))}
/> />
Mo Mo
<input
className="jvm"
type="text"
placeholder="Args JVM (avancé)"
value={jvmArgs}
disabled={busy}
onChange={(e) => setJvmArgs(e.target.value)}
/>
<button className="linkbtn" onClick={() => void handlePlay({ repair: true })} disabled={busy}>
Vérifier les fichiers
</button>
<button className="linkbtn" onClick={handleOpenInstance}> <button className="linkbtn" onClick={handleOpenInstance}>
Ouvrir le dossier de l'instance Ouvrir l'instance
</button>
<button className="linkbtn" onClick={handleOpenLogs}>
Ouvrir les logs
</button> </button>
</div> </div>
<button className="play" onClick={handlePlay} disabled={busy}> {status === 'running' ? (
{status === 'running' ? 'En jeu' : busy ? 'Patiente' : 'Jouer'} <button className="play stop" onClick={handleStop}>
</button> Arrêter le jeu
</button>
) : (
<button className="play" onClick={() => void handlePlay()} disabled={busy}>
{busy ? 'Patiente' : 'Jouer'}
</button>
)}
</div> </div>
{error && <div className="error">{error}</div>} {error && <div className="error">{error}</div>}
</div> </div>
+28
View File
@@ -123,6 +123,20 @@ body {
.progress { .progress {
flex: 1; flex: 1;
min-width: 0;
}
/* Fenêtre réduite : la barre de progression passe sur sa propre ligne
(sinon les réglages la compriment jusqu'à la rendre invisible). */
@media (max-width: 1200px) {
.footer {
flex-wrap: wrap;
}
.progress {
order: -1;
flex-basis: 100%;
}
} }
.progress .label { .progress .label {
@@ -180,6 +194,15 @@ button.play:disabled {
cursor: not-allowed; cursor: not-allowed;
} }
button.play.stop {
background: var(--danger);
color: #2a0606;
}
button.play.stop:hover:not(:disabled) {
background: #ff6258;
}
.center { .center {
flex: 1; flex: 1;
display: flex; display: flex;
@@ -214,6 +237,7 @@ button.play:disabled {
.settings { .settings {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 8px; gap: 8px;
font-size: 13px; font-size: 13px;
color: var(--text-dim); color: var(--text-dim);
@@ -228,6 +252,10 @@ button.play:disabled {
padding: 4px 6px; padding: 4px 6px;
} }
.settings input.jvm {
width: 200px;
}
.error { .error {
color: var(--danger); color: var(--danger);
font-size: 13px; font-size: 13px;
+17
View File
@@ -67,6 +67,20 @@ export interface UpdateStatus {
message?: string message?: string
} }
/** Métadonnées du modpack lues depuis le pack.toml packwiz distant. */
export interface PackMeta {
name: string
version: string
minecraft: string
neoforge: string
}
/** Options de la séquence "Jouer". */
export interface PlayOptions {
/** Force la revérification/redownload des fichiers (réparation). */
repair?: boolean
}
/** Réglages utilisateur persistés localement. */ /** Réglages utilisateur persistés localement. */
export interface UserSettings { export interface UserSettings {
/** RAM max allouée à la JVM, en Mo. */ /** RAM max allouée à la JVM, en Mo. */
@@ -90,6 +104,9 @@ export const IPC = {
settingsSet: 'settings:set', settingsSet: 'settings:set',
appVersion: 'app:version', appVersion: 'app:version',
openInstanceDir: 'instance:open', openInstanceDir: 'instance:open',
openLogsDir: 'logs:open',
playStop: 'play:stop',
packGet: 'pack:get',
updateInstall: 'update:install' updateInstall: 'update:install'
} as const } as const