feat: add way to select game dir

This commit is contained in:
lucasdpt
2026-06-17 20:43:27 +02:00
parent b8204c80bd
commit 161ea50234
7 changed files with 212 additions and 18 deletions
+35 -1
View File
@@ -1,4 +1,4 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { app, shell, BrowserWindow, ipcMain, dialog } from 'electron'
import { join } from 'path'
import { setMainWindow } from './events'
import { IPC, type UserSettings, type PlayOptions } from '../shared/ipc'
@@ -8,6 +8,12 @@ import { getPackMetaCached } from './modpack'
import { getSettings, setSettings } from './settings'
import { paths } from './paths'
import { initUpdater, quitAndInstallUpdate } from './updater'
import {
isFirstLaunch,
writeLauncherConfig,
defaultDataDir,
readLauncherConfigSync
} from './launcher-config'
function createWindow(): BrowserWindow {
const win = new BrowserWindow({
@@ -60,6 +66,34 @@ function registerIpc(): void {
ipcMain.handle(IPC.openInstanceDir, () => shell.openPath(paths.instanceDir))
ipcMain.handle(IPC.openLogsDir, () => shell.openPath(paths.logsDir))
ipcMain.handle(IPC.updateInstall, () => quitAndInstallUpdate())
ipcMain.handle(IPC.isFirstLaunch, () => isFirstLaunch())
ipcMain.handle(IPC.dataDirGet, () => {
const { dataDir } = readLauncherConfigSync()
const defaultSuggestion = defaultDataDir()
return { current: dataDir ?? defaultSuggestion, defaultSuggestion }
})
ipcMain.handle(IPC.dataDirBrowse, async () => {
const { dataDir } = readLauncherConfigSync()
const { canceled, filePaths } = await dialog.showOpenDialog({
title: 'Choisir le dossier de données',
defaultPath: dataDir ?? defaultDataDir(),
properties: ['openDirectory', 'createDirectory']
})
return canceled ? null : filePaths[0]
})
ipcMain.handle(IPC.dataDirSet, async (_e, dir: string, relaunch: boolean) => {
await writeLauncherConfig({ dataDir: dir })
paths.invalidate()
if (relaunch) {
app.relaunch()
app.exit(0)
}
return dir
})
}
app.whenReady().then(() => {
+33
View File
@@ -0,0 +1,33 @@
import { app } from 'electron'
import { join } from 'path'
import { existsSync, readFileSync } from 'fs'
import { writeFile } from 'fs/promises'
interface LauncherConfig {
dataDir?: string
}
function configPath(): string {
return join(app.getPath('userData'), 'launcher.json')
}
export function readLauncherConfigSync(): LauncherConfig {
try {
return JSON.parse(readFileSync(configPath(), 'utf-8')) as LauncherConfig
} catch {
return {}
}
}
export function isFirstLaunch(): boolean {
return !existsSync(configPath())
}
export async function writeLauncherConfig(cfg: LauncherConfig): Promise<void> {
const current = readLauncherConfigSync()
await writeFile(configPath(), JSON.stringify({ ...current, ...cfg }, null, 2))
}
export function defaultDataDir(): string {
return join(app.getPath('home'), 'Games', 'OFLauncher')
}
+22 -9
View File
@@ -1,19 +1,34 @@
import { app } from 'electron'
import { join } from 'path'
import { mkdirSync } from 'fs'
import { readLauncherConfigSync } from './launcher-config'
/**
* Arborescence des données du launcher, rangée sous le userData d'Electron :
* Windows : %APPDATA%/OFLauncher
* Linux : ~/.config/OFLauncher
* Arborescence des données du launcher.
*
* On garde le runtime (MC/NeoForge/assets/libs/java) séparé de l'instance de
* jeu (mods/config/saves) pour que la sync packwiz ne touche jamais au runtime.
* La racine est configurable via launcher.json (userData/launcher.json) :
* { "dataDir": "/chemin/choisi/par/l/utilisateur" }
* Par défaut : userData d'Electron (%APPDATA%/OFLauncher sur Windows,
* ~/.config/OFLauncher sur Linux).
*
* La config launcher.json elle-même reste toujours dans userData
* (point d'ancrage fixe, indépendant du dataDir choisi).
*/
class LauncherPaths {
/** Racine : userData d'Electron. */
private _root: string | null = null
/** Racine des données du jeu (configurable). */
get root(): string {
return app.getPath('userData')
if (!this._root) {
const { dataDir } = readLauncherConfigSync()
this._root = dataDir ?? app.getPath('userData')
}
return this._root
}
/** Invalide le cache de root (à appeler après écriture d'un nouveau dataDir). */
invalidate(): void {
this._root = null
}
/** Dossier "Minecraft" géré par @xmcl : versions/, libraries/, assets/. */
@@ -53,8 +68,6 @@ class LauncherPaths {
/** jar packwiz-installer-bootstrap embarqué dans les resources. */
get packwizBootstrapJar(): string {
// En prod, electron-builder copie resources/ via extraResources.
// En dev, on lit directement le dossier resources/ du repo.
const base = app.isPackaged
? join(process.resourcesPath, 'resources')
: join(app.getAppPath(), 'resources')