From 1e7234d12d2e918d9106d580d8b152855bf5f5da Mon Sep 17 00:00:00 2001
From: Jydet
Date: Sat, 11 Jul 2026 10:20:20 +0200
Subject: [PATCH 1/2] feat: edit the settings of the game in the dev tab
---
arguments_example.json | 57 +++++
package-lock.json | 4 +-
packages/main/src/modules/GameClient.ts | 59 +++--
packages/main/src/modules/GameUpdater.ts | 1 +
packages/main/src/services/SettingsManager.ts | 1 +
packages/preload/src/index.ts | 2 +
.../renderer/src/components/SettingsMenu.tsx | 238 +++++++++++++++++-
packages/renderer/src/types/index.ts | 1 +
packages/renderer/src/vite-env.d.ts | 1 +
9 files changed, 339 insertions(+), 25 deletions(-)
create mode 100644 arguments_example.json
diff --git a/arguments_example.json b/arguments_example.json
new file mode 100644
index 0000000..e68bc54
--- /dev/null
+++ b/arguments_example.json
@@ -0,0 +1,57 @@
+[
+ {
+ "key": "DEV_MODE",
+ "description": "Activates the developer mode, enabling additional debug tools and shortcuts.",
+ "type": "boolean"
+ },
+ {
+ "key": "CONFIGURATION_FILE",
+ "description": "Path to the configuration file to use.",
+ "type": "string"
+ },
+ {
+ "key": "NO_CAMERA_MIN_ZOOM",
+ "description": "Disables the minimum zoom restriction on the camera.",
+ "type": "boolean"
+ },
+ {
+ "key": "ONLY_ALLOWED_TEAM_TAB",
+ "description": "The tab number of the only tab allowed in the Fighter Management UI.",
+ "type": "number"
+ },
+ {
+ "key": "ONLY_ALLOWED_LADDER_TAB",
+ "description": "The tab number of the only tab allowed in the Ladder UI.",
+ "type": "number"
+ },
+ {
+ "key": "SKIP_TURN_NO_DELAY",
+ "description": "Bypass the delay before ending a turn.",
+ "type": "boolean"
+ },
+ {
+ "key": "REPLAY_FILE_PATH",
+ "description": "Path to a replay file to load and play.",
+ "type": "string"
+ },
+ {
+ "key": "WORLD_FADE",
+ "description": "Activates the world fade effect during transitions.",
+ "type": "boolean"
+ },
+ {
+ "key": "HOT_RELOAD_EFFECT",
+ "description": "Enables hot reloading of visual effects.",
+ "type": "boolean"
+ },
+ {
+ "key": "NO_CLASS_RESTRICTION",
+ "description": "Disables class restrictions for character creation or editing.",
+ "type": "boolean"
+ },
+ {
+ "key": "RESTORE_CONSOLE_PATH",
+ "description": "Restores the last used console path on startup instead of defaulting to admin.",
+ "type": "boolean"
+ }
+]
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index e0cc8d0..1565856 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "arena-returns-launcher",
- "version": "3.4.2",
+ "version": "3.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "arena-returns-launcher",
- "version": "3.4.2",
+ "version": "3.5.1",
"workspaces": [
"packages/*"
],
diff --git a/packages/main/src/modules/GameClient.ts b/packages/main/src/modules/GameClient.ts
index 35a86fe..c5a9519 100644
--- a/packages/main/src/modules/GameClient.ts
+++ b/packages/main/src/modules/GameClient.ts
@@ -1,13 +1,12 @@
-import { ipcMain, app } from "electron";
-import { join } from "path";
-import { existsSync, mkdirSync } from "fs";
-import { chmodSync } from "fs";
-import { stat, chmod, readdir, readFile, appendFile } from "fs/promises";
-import { exec } from "child_process";
+import {app, ipcMain} from "electron";
+import {join} from "path";
+import {chmodSync, existsSync, mkdirSync} from "fs";
+import {appendFile, chmod, readdir, readFile, stat} from "fs/promises";
+import {exec} from "child_process";
import log from "electron-log";
-import { GameUpdater, GameSettings, ReplayFile } from "./GameUpdater.js";
-import type { AppModule } from "../AppModule.js";
-import type { ModuleContext } from "../ModuleContext.js";
+import {GameSettings, GameUpdater, ReplayFile} from "./GameUpdater.js";
+import type {AppModule} from "../AppModule.js";
+import type {ModuleContext} from "../ModuleContext.js";
export class GameClient implements AppModule {
private gameUpdater: GameUpdater | null = null;
@@ -37,6 +36,9 @@ export class GameClient implements AppModule {
ipcMain.handle("gameClient:launchReplayOffline", (_e, path) =>
this.launchReplayOffline(path)
);
+ ipcMain.handle("gameClient:getGameArgumentsDescriptor", () =>
+ this.getGameArgumentsDescriptor()
+ );
}
onSettingsUpdate(settings: GameSettings): void {
@@ -75,18 +77,15 @@ export class GameClient implements AppModule {
await this.startJavaProcess({
mainClass: "com.ankamagames.dofusarena.client.DofusArenaClient",
settings: this.currentSettings || undefined,
- extraArgs: [
- "-ONLY_ALLOWED_TEAM_TAB=1",
- "-ONLY_ALLOWED_LADDER_TAB=ONE_VS_ONE",
- ],
+ extraArgs: this.currentSettings?.devExtraJavaArgs.split(" ").map(arg => `-${arg}`) ?? [],
});
}
async openReplaysFolder(): Promise {
- const { shell } = await import("electron");
+ const {shell} = await import("electron");
const replaysPath = join(this.gameClientPath, "game", "replays");
- mkdirSync(replaysPath, { recursive: true });
+ mkdirSync(replaysPath, {recursive: true});
try {
await shell.openPath(replaysPath);
@@ -101,8 +100,8 @@ export class GameClient implements AppModule {
async listReplays(): Promise {
const replaysPath = join(this.gameClientPath, "game", "replays");
- const { readdir } = await import("fs/promises");
- mkdirSync(replaysPath, { recursive: true });
+ const {readdir} = await import("fs/promises");
+ mkdirSync(replaysPath, {recursive: true});
try {
const files = await readdir(replaysPath);
@@ -191,7 +190,7 @@ export class GameClient implements AppModule {
settings?: GameSettings;
extraArgs?: string[];
}): Promise {
- const { mainClass, settings, extraArgs = [] } = options;
+ const {mainClass, settings, extraArgs = []} = options;
const gameDir = join(this.gameClientPath, "game");
const libDir = join(this.gameClientPath, "lib");
const jreDir = join(this.gameClientPath, "jre");
@@ -349,7 +348,7 @@ export class GameClient implements AppModule {
return new Promise((resolve, reject) => {
const child = exec(
`"${javaExecutable}" ${args.join(" ")}`,
- { cwd },
+ {cwd},
(error) => {
if (error && !error.killed) {
log.error("Java process error:", error);
@@ -380,7 +379,7 @@ export class GameClient implements AppModule {
}
const child = exec(
`"${javaExecutable}" ${args.join(" ")}`,
- { cwd },
+ {cwd},
(error) => {
if (error && !error.killed) {
log.error("Java process error:", error);
@@ -411,7 +410,7 @@ export class GameClient implements AppModule {
}
// Ensure we have the full system PATH for finding wine
- const env = { ...process.env };
+ const env = {...process.env};
if (!env.PATH?.includes("/opt/homebrew/bin")) {
env.PATH = `${
env.PATH || ""
@@ -420,7 +419,7 @@ export class GameClient implements AppModule {
const child = exec(
`wine "${javaExecutable}" ${args.join(" ")}`,
- { cwd, env },
+ {cwd, env},
(error) => {
if (error && !error.killed) {
log.error("Java process error:", error);
@@ -435,6 +434,22 @@ export class GameClient implements AppModule {
});
}
+ async getGameArgumentsDescriptor(): Promise {
+ try {
+ let schemaPath = join(this.gameClientPath, "game", "arguments.json");
+ if (existsSync(schemaPath)) {
+ log.info("Loading arguments descriptor from ", schemaPath)
+ const content = await readFile(schemaPath, "utf-8");
+ return JSON.parse(content);
+ }
+ log.info("Arguments descriptor not found")
+ return null;
+ } catch (error) {
+ log.error("Failed to load arguments.json:", error);
+ return null;
+ }
+ }
+
private parseReplayFilename(filename: string, fullPath: string): ReplayFile {
const replayFile: ReplayFile = {
filename,
diff --git a/packages/main/src/modules/GameUpdater.ts b/packages/main/src/modules/GameUpdater.ts
index 7042480..cd601ec 100644
--- a/packages/main/src/modules/GameUpdater.ts
+++ b/packages/main/src/modules/GameUpdater.ts
@@ -62,6 +62,7 @@ export interface GameSettings {
gameRamAllocation: number;
devModeEnabled: boolean;
devExtraJavaArgs: string;
+ devGameArgs: string;
devForceVersion: string;
devCdnEnvironment: "production" | "staging";
}
diff --git a/packages/main/src/services/SettingsManager.ts b/packages/main/src/services/SettingsManager.ts
index 1bd6191..d89bd1a 100644
--- a/packages/main/src/services/SettingsManager.ts
+++ b/packages/main/src/services/SettingsManager.ts
@@ -21,6 +21,7 @@ export class SettingsManager {
return {
gameRamAllocation: 2,
devModeEnabled: false,
+ devGameArgs: "ONLY_ALLOWED_TEAM_TAB=1 ONLY_ALLOWED_LADDER_TAB=ONE_VS_ONE",
devExtraJavaArgs: "",
devForceVersion: "",
devCdnEnvironment: "production",
diff --git a/packages/preload/src/index.ts b/packages/preload/src/index.ts
index 8777d36..9d9af93 100644
--- a/packages/preload/src/index.ts
+++ b/packages/preload/src/index.ts
@@ -58,6 +58,8 @@ const gameClient = {
listReplays: () => ipcRenderer.invoke("gameClient:listReplays"),
launchReplayOffline: (replayPath: string) =>
ipcRenderer.invoke("gameClient:launchReplayOffline", replayPath),
+ getGameArgumentsDescriptor: () =>
+ ipcRenderer.invoke("gameClient:getGameArgumentsDescriptor"),
};
// News functions
diff --git a/packages/renderer/src/components/SettingsMenu.tsx b/packages/renderer/src/components/SettingsMenu.tsx
index 255ccbb..c53c33b 100644
--- a/packages/renderer/src/components/SettingsMenu.tsx
+++ b/packages/renderer/src/components/SettingsMenu.tsx
@@ -16,12 +16,120 @@ import {
Code,
AlertTriangle,
} from "lucide-react";
-import { SimpleSlider, SimpleSelect } from "./common/FormControls";
+import { SimpleSlider, SimpleSelect, SimpleSwitch } from "./common/FormControls";
import type { SettingsState } from "@/types";
import { gameClient, gameUpdater, system } from "@app/preload";
import { useGameStateContext } from "@/contexts/GameStateContext";
import log from "@/utils/logger";
+interface ArgumentDescriptorItem {
+ key: string;
+ description: string;
+ type: "boolean" | "string" | "number";
+}
+
+const parseGameArgs = (argsString: string): Record => {
+ const parsed: Record = {};
+ if (!argsString) return parsed;
+ argsString.split(/\s+/).forEach((part) => {
+ if (!part) return;
+ const [key, ...valParts] = part.split("=");
+ if (valParts.length > 0) {
+ parsed[key] = valParts.join("=");
+ } else {
+ parsed[key] = true;
+ }
+ });
+ return parsed;
+};
+
+const updateDescriptorArg = (
+ currentArgsStr: string,
+ descriptor: ArgumentDescriptorItem[],
+ keyToUpdate: string,
+ newValue: string | boolean
+): string => {
+ const parsed = parseGameArgs(currentArgsStr);
+ const descriptorKeys = new Set(descriptor.map((s) => s.key));
+
+ if (newValue === false || newValue === "") {
+ delete parsed[keyToUpdate];
+ } else {
+ parsed[keyToUpdate] = newValue;
+ }
+
+ const descriptorParts: string[] = [];
+ const customParts: string[] = [];
+
+ Object.entries(parsed).forEach(([key, val]) => {
+ if (descriptorKeys.has(key)) {
+ if (val === true) {
+ descriptorParts.push(key);
+ } else if (val !== false && val !== "" && val !== undefined) {
+ descriptorParts.push(`${key}=${val}`);
+ }
+ } else {
+ if (val === true) {
+ customParts.push(key);
+ } else {
+ customParts.push(`${key}=${val}`);
+ }
+ }
+ });
+
+ const finalParts = [...descriptorParts];
+ if (customParts.length > 0) {
+ finalParts.push(customParts.join(" "));
+ }
+
+ return finalParts.join(" ");
+};
+
+const updateCustomArgs = (
+ currentArgsStr: string,
+ descriptor: ArgumentDescriptorItem[],
+ newCustomStr: string
+): string => {
+ const parsed = parseGameArgs(currentArgsStr);
+ const descriptorKeys = new Set(descriptor.map((s) => s.key));
+
+ const descriptorParts: string[] = [];
+ Object.entries(parsed).forEach(([key, val]) => {
+ if (descriptorKeys.has(key)) {
+ if (val === true) {
+ descriptorParts.push(key);
+ } else if (val !== false && val !== "" && val !== undefined) {
+ descriptorParts.push(`${key}=${val}`);
+ }
+ }
+ });
+
+ const finalParts = [...descriptorParts];
+ if (newCustomStr.trim()) {
+ finalParts.push(newCustomStr.trim());
+ }
+
+ return finalParts.join(" ");
+};
+
+const getCustomArgsString = (currentArgsStr: string, descriptor: ArgumentDescriptorItem[]): string => {
+ const parsed = parseGameArgs(currentArgsStr);
+ const descriptorKeys = new Set(descriptor.map((s) => s.key));
+ const customParts: string[] = [];
+
+ Object.entries(parsed).forEach(([key, val]) => {
+ if (!descriptorKeys.has(key)) {
+ if (val === true) {
+ customParts.push(key);
+ } else {
+ customParts.push(`${key}=${val}`);
+ }
+ }
+ });
+
+ return customParts.join(" ");
+};
+
interface SettingsMenuProps {
isOpen: boolean;
onClose: () => void;
@@ -44,6 +152,36 @@ export const SettingsMenu: React.FC = ({
// Local settings state for editing (doesn't affect main UI)
const [localSettings, setLocalSettings] = useState(settings);
+ const [ArgumentsDescriptor, setArgumentsDescriptor] = useState(null);
+ const [descriptorLoading, setDescriptorLoading] = useState(false);
+
+ // Fetch arguments.json descriptor when developer mode is enabled and settings menu is open
+ useEffect(() => {
+ if (!isOpen || !localSettings.devModeEnabled) {
+ setArgumentsDescriptor(null);
+ return;
+ }
+
+ const fetchDescriptor = async () => {
+ setDescriptorLoading(true);
+ try {
+ const descriptor = await gameClient.getGameArgumentsDescriptor();
+ if (Array.isArray(descriptor) && descriptor.every((item: any) => item && typeof item.key === "string" && typeof item.type === "string")) {
+ setArgumentsDescriptor(descriptor as ArgumentDescriptorItem[]);
+ } else {
+ setArgumentsDescriptor(null);
+ }
+ } catch (error) {
+ log.error("Failed to fetch arguments descriptor:", error);
+ setArgumentsDescriptor(null);
+ } finally {
+ setDescriptorLoading(false);
+ }
+ };
+
+ fetchDescriptor();
+ }, [isOpen, localSettings.devModeEnabled]);
+
// Update local settings when menu opens or settings prop changes
useEffect(() => {
if (isOpen) {
@@ -309,6 +447,104 @@ export const SettingsMenu: React.FC = ({
jeu.
+
+
+
+ {descriptorLoading ? (
+
+ Chargement des arguments...
+
+ ) : ArgumentsDescriptor ? (
+
+
+ {ArgumentsDescriptor.map((item) => {
+ const parsedArgs = parseGameArgs(localSettings.devGameArgs);
+ const currentValue = parsedArgs[item.key];
+
+ return (
+
+
+ {item.key}
+ {item.type === "boolean" ? (
+ {
+ const updated = updateDescriptorArg(
+ localSettings.devGameArgs,
+ ArgumentsDescriptor,
+ item.key,
+ checked
+ );
+ updateSetting("devGameArgs", updated);
+ }}
+ />
+ ) : (
+ {
+ const updated = updateDescriptorArg(
+ localSettings.devGameArgs,
+ ArgumentsDescriptor,
+ item.key,
+ e.target.value
+ );
+ updateSetting("devGameArgs", updated);
+ }}
+ className="w-1/2 px-2 py-1 bg-black/40 border border-white/20 rounded text-white text-sm"
+ />
+ )}
+
+
{item.description}
+
+ );
+ })}
+
+
+
+
+
+
+ ) : (
+ <>
+
)}
diff --git a/packages/renderer/src/types/index.ts b/packages/renderer/src/types/index.ts
index 1e9cae3..5c7045a 100644
--- a/packages/renderer/src/types/index.ts
+++ b/packages/renderer/src/types/index.ts
@@ -35,6 +35,7 @@ export interface SettingsState {
// Developer mode
devModeEnabled: boolean;
devExtraJavaArgs: string;
+ devGameArgs: string;
devForceVersion: string;
devCdnEnvironment: "production" | "staging";
}
diff --git a/packages/renderer/src/vite-env.d.ts b/packages/renderer/src/vite-env.d.ts
index 75a2d5a..10b3c17 100644
--- a/packages/renderer/src/vite-env.d.ts
+++ b/packages/renderer/src/vite-env.d.ts
@@ -21,6 +21,7 @@ interface GameSettings {
gameRamAllocation: number;
devModeEnabled: boolean;
devExtraJavaArgs: string;
+ devGameArgs: string;
devForceVersion: string;
devCdnEnvironment: "production" | "staging";
}
From 0dee71622353c0d45a76e895f8af3d6b48f2622d Mon Sep 17 00:00:00 2001
From: Jordan Rey
Date: Thu, 27 Aug 2026 12:21:06 +0200
Subject: [PATCH 2/2] chore: lint and allow git in npmrc
---
.npmrc | 1 +
packages/main/src/index.ts | 14 ++--
packages/main/src/modules/GameClient.ts | 77 +++++++++---------
packages/main/src/modules/GameUpdater.ts | 32 ++++----
packages/main/src/services/SettingsManager.ts | 6 +-
.../renderer/src/components/SettingsMenu.tsx | 78 ++++++++++++++-----
6 files changed, 126 insertions(+), 82 deletions(-)
create mode 100644 .npmrc
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..7b1e8b3
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+allow-git=all
\ No newline at end of file
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts
index 9e309a2..e06f0dd 100644
--- a/packages/main/src/index.ts
+++ b/packages/main/src/index.ts
@@ -44,7 +44,7 @@ export async function initApp(initConfig: AppInitConfig) {
createWindowManagerModule({
initConfig,
openDevTools: import.meta.env.DEV,
- })
+ }),
)
.init(disallowMultipleAppInstance())
.init(terminateAppOnLastWindowClose())
@@ -59,16 +59,18 @@ export async function initApp(initConfig: AppInitConfig) {
.init(
allowInternalOrigins(
new Set(
- initConfig.renderer instanceof URL ? [initConfig.renderer.origin] : []
- )
- )
+ initConfig.renderer instanceof URL
+ ? [initConfig.renderer.origin]
+ : [],
+ ),
+ ),
)
.init(
allowExternalUrls(
initConfig.renderer instanceof URL
? ALLOWED_EXTERNAL_ORIGINS
- : new Set()
- )
+ : new Set(),
+ ),
);
await moduleRunner;
diff --git a/packages/main/src/modules/GameClient.ts b/packages/main/src/modules/GameClient.ts
index c5a9519..c17eb1a 100644
--- a/packages/main/src/modules/GameClient.ts
+++ b/packages/main/src/modules/GameClient.ts
@@ -1,12 +1,12 @@
-import {app, ipcMain} from "electron";
-import {join} from "path";
-import {chmodSync, existsSync, mkdirSync} from "fs";
-import {appendFile, chmod, readdir, readFile, stat} from "fs/promises";
-import {exec} from "child_process";
+import { app, ipcMain } from "electron";
+import { join } from "path";
+import { chmodSync, existsSync, mkdirSync } from "fs";
+import { appendFile, chmod, readdir, readFile, stat } from "fs/promises";
+import { exec } from "child_process";
import log from "electron-log";
-import {GameSettings, GameUpdater, ReplayFile} from "./GameUpdater.js";
-import type {AppModule} from "../AppModule.js";
-import type {ModuleContext} from "../ModuleContext.js";
+import { GameSettings, GameUpdater, ReplayFile } from "./GameUpdater.js";
+import type { AppModule } from "../AppModule.js";
+import type { ModuleContext } from "../ModuleContext.js";
export class GameClient implements AppModule {
private gameUpdater: GameUpdater | null = null;
@@ -30,14 +30,14 @@ export class GameClient implements AppModule {
// Register GameClient-specific IPC handlers
ipcMain.handle("gameClient:launchGame", () => this.launchGame());
ipcMain.handle("gameClient:openReplaysFolder", () =>
- this.openReplaysFolder()
+ this.openReplaysFolder(),
);
ipcMain.handle("gameClient:listReplays", () => this.listReplays());
ipcMain.handle("gameClient:launchReplayOffline", (_e, path) =>
- this.launchReplayOffline(path)
+ this.launchReplayOffline(path),
);
ipcMain.handle("gameClient:getGameArgumentsDescriptor", () =>
- this.getGameArgumentsDescriptor()
+ this.getGameArgumentsDescriptor(),
);
}
@@ -77,15 +77,18 @@ export class GameClient implements AppModule {
await this.startJavaProcess({
mainClass: "com.ankamagames.dofusarena.client.DofusArenaClient",
settings: this.currentSettings || undefined,
- extraArgs: this.currentSettings?.devExtraJavaArgs.split(" ").map(arg => `-${arg}`) ?? [],
+ extraArgs:
+ this.currentSettings?.devExtraJavaArgs
+ .split(" ")
+ .map((arg) => `-${arg}`) ?? [],
});
}
async openReplaysFolder(): Promise {
- const {shell} = await import("electron");
+ const { shell } = await import("electron");
const replaysPath = join(this.gameClientPath, "game", "replays");
- mkdirSync(replaysPath, {recursive: true});
+ mkdirSync(replaysPath, { recursive: true });
try {
await shell.openPath(replaysPath);
@@ -93,15 +96,15 @@ export class GameClient implements AppModule {
throw new Error(
`Failed to open replays folder: ${
error instanceof Error ? error.message : "Unknown error"
- }`
+ }`,
);
}
}
async listReplays(): Promise {
const replaysPath = join(this.gameClientPath, "game", "replays");
- const {readdir} = await import("fs/promises");
- mkdirSync(replaysPath, {recursive: true});
+ const { readdir } = await import("fs/promises");
+ mkdirSync(replaysPath, { recursive: true });
try {
const files = await readdir(replaysPath);
@@ -152,7 +155,7 @@ export class GameClient implements AppModule {
const gameConfigPath = join(
this.gameClientPath,
"game",
- "config.properties"
+ "config.properties",
);
try {
@@ -173,11 +176,11 @@ export class GameClient implements AppModule {
log.info("Adding dev mode proxy settings to config.properties");
await appendFile(
gameConfigPath,
- "\nproxyGroup_2=Localhost\nproxyAddresses_2=localhost:5555\n"
+ "\nproxyGroup_2=Localhost\nproxyAddresses_2=localhost:5555\n",
);
await appendFile(
gameConfigPath,
- "\nproxyGroup_3=Staging\nproxyAddresses_3=minuit-staging.arenareturns.com:6666\n"
+ "\nproxyGroup_3=Staging\nproxyAddresses_3=minuit-staging.arenareturns.com:6666\n",
);
} catch (error) {
log.error("Failed to update config.properties for dev mode:", error);
@@ -190,7 +193,7 @@ export class GameClient implements AppModule {
settings?: GameSettings;
extraArgs?: string[];
}): Promise {
- const {mainClass, settings, extraArgs = []} = options;
+ const { mainClass, settings, extraArgs = [] } = options;
const gameDir = join(this.gameClientPath, "game");
const libDir = join(this.gameClientPath, "lib");
const jreDir = join(this.gameClientPath, "jre");
@@ -208,7 +211,7 @@ export class GameClient implements AppModule {
.join(
process.platform === "win32" || process.platform === "darwin"
? ";"
- : ":"
+ : ":",
);
const coreJarPath = join(gameDir, "core.jar");
// FIXME: Gigahack since darwin relies on wine
@@ -286,7 +289,7 @@ export class GameClient implements AppModule {
...settings.devExtraJavaArgs
.split(" ")
.map((arg) => arg.trim())
- .filter((arg) => arg.length > 0)
+ .filter((arg) => arg.length > 0),
);
}
@@ -343,17 +346,17 @@ export class GameClient implements AppModule {
private async launchJavaProcessWindows(
javaExecutable: string,
args: string[],
- cwd: string
+ cwd: string,
): Promise {
return new Promise((resolve, reject) => {
const child = exec(
`"${javaExecutable}" ${args.join(" ")}`,
- {cwd},
+ { cwd },
(error) => {
if (error && !error.killed) {
log.error("Java process error:", error);
}
- }
+ },
);
if (child.pid) {
resolve();
@@ -366,7 +369,7 @@ export class GameClient implements AppModule {
private async launchJavaProcessLinux(
javaExecutable: string,
args: string[],
- cwd: string
+ cwd: string,
): Promise {
return new Promise((resolve, reject) => {
try {
@@ -374,17 +377,17 @@ export class GameClient implements AppModule {
} catch (error) {
log.warn(
`Failed to set permissions on Java executable ${javaExecutable}:`,
- error
+ error,
);
}
const child = exec(
`"${javaExecutable}" ${args.join(" ")}`,
- {cwd},
+ { cwd },
(error) => {
if (error && !error.killed) {
log.error("Java process error:", error);
}
- }
+ },
);
if (child.pid) {
resolve();
@@ -397,7 +400,7 @@ export class GameClient implements AppModule {
private async launchJavaProcessDarwin(
javaExecutable: string,
args: string[],
- cwd: string
+ cwd: string,
): Promise {
return new Promise((resolve, reject) => {
try {
@@ -405,12 +408,12 @@ export class GameClient implements AppModule {
} catch (error) {
log.warn(
`Failed to set permissions on Java executable ${javaExecutable}:`,
- error
+ error,
);
}
// Ensure we have the full system PATH for finding wine
- const env = {...process.env};
+ const env = { ...process.env };
if (!env.PATH?.includes("/opt/homebrew/bin")) {
env.PATH = `${
env.PATH || ""
@@ -419,12 +422,12 @@ export class GameClient implements AppModule {
const child = exec(
`wine "${javaExecutable}" ${args.join(" ")}`,
- {cwd, env},
+ { cwd, env },
(error) => {
if (error && !error.killed) {
log.error("Java process error:", error);
}
- }
+ },
);
if (child.pid) {
resolve();
@@ -438,11 +441,11 @@ export class GameClient implements AppModule {
try {
let schemaPath = join(this.gameClientPath, "game", "arguments.json");
if (existsSync(schemaPath)) {
- log.info("Loading arguments descriptor from ", schemaPath)
+ log.info("Loading arguments descriptor from ", schemaPath);
const content = await readFile(schemaPath, "utf-8");
return JSON.parse(content);
}
- log.info("Arguments descriptor not found")
+ log.info("Arguments descriptor not found");
return null;
} catch (error) {
log.error("Failed to load arguments.json:", error);
diff --git a/packages/main/src/modules/GameUpdater.ts b/packages/main/src/modules/GameUpdater.ts
index cd601ec..d2c4b7f 100644
--- a/packages/main/src/modules/GameUpdater.ts
+++ b/packages/main/src/modules/GameUpdater.ts
@@ -119,12 +119,12 @@ export class GameUpdater implements AppModule {
ipcMain.handle("gameUpdater:checkForUpdates", () => this.checkForUpdates());
ipcMain.handle("gameUpdater:startDownload", () => this.startDownload());
ipcMain.handle("gameUpdater:getDownloadProgress", () =>
- this.getDownloadProgress()
+ this.getDownloadProgress(),
);
ipcMain.handle("gameUpdater:cancelDownload", () => this.cancelDownload());
ipcMain.handle("gameUpdater:repairClient", () => this.repairClient());
ipcMain.handle("gameUpdater:openGameDirectory", () =>
- this.openGameDirectory()
+ this.openGameDirectory(),
);
}
@@ -173,7 +173,7 @@ export class GameUpdater implements AppModule {
if (previousSettings) {
if (previousSettings.devCdnEnvironment !== settings.devCdnEnvironment) {
log.debug(
- `CDN environment changed from ${previousSettings.devCdnEnvironment} to ${settings.devCdnEnvironment}, notifying UI to refresh status`
+ `CDN environment changed from ${previousSettings.devCdnEnvironment} to ${settings.devCdnEnvironment}, notifying UI to refresh status`,
);
this.notifyRenderer("status-changed", {});
@@ -226,7 +226,7 @@ export class GameUpdater implements AppModule {
if (!response.ok) {
throw new Error(
- `Failed to fetch version info: ${response.status} ${response.statusText}`
+ `Failed to fetch version info: ${response.status} ${response.statusText}`,
);
}
@@ -251,7 +251,7 @@ export class GameUpdater implements AppModule {
throw new Error(
`Failed to update local version: ${
error instanceof Error ? error.message : "Unknown error"
- }`
+ }`,
);
}
}
@@ -277,12 +277,12 @@ export class GameUpdater implements AppModule {
const remoteVersion = await this.getRemoteVersion();
const manifestResponse = await fetch(
- `${this.cdnUrl}/versions/${remoteVersion}.json`
+ `${this.cdnUrl}/versions/${remoteVersion}.json`,
);
if (!manifestResponse.ok) {
throw new Error(
- `Failed to fetch version manifest: ${manifestResponse.status}`
+ `Failed to fetch version manifest: ${manifestResponse.status}`,
);
}
@@ -335,12 +335,12 @@ export class GameUpdater implements AppModule {
const remoteVersion = await this.getRemoteVersion();
const manifestResponse = await fetch(
- `${this.cdnUrl}/versions/${remoteVersion}.json`
+ `${this.cdnUrl}/versions/${remoteVersion}.json`,
);
if (!manifestResponse.ok) {
throw new Error(
- `Failed to fetch version manifest: ${manifestResponse.status}`
+ `Failed to fetch version manifest: ${manifestResponse.status}`,
);
}
@@ -373,7 +373,7 @@ export class GameUpdater implements AppModule {
throw new Error(
`Failed to open game directory: ${
error instanceof Error ? error.message : "Unknown error"
- }`
+ }`,
);
}
}
@@ -406,7 +406,7 @@ export class GameUpdater implements AppModule {
private async checkFiles(
files: FileManifest[],
- forceCheck = false
+ forceCheck = false,
): Promise {
if (!existsSync(this.gameClientPath)) {
mkdirSync(this.gameClientPath, { recursive: true });
@@ -492,7 +492,7 @@ export class GameUpdater implements AppModule {
const downloadPromises = files.map((file) =>
queue.add(() => this.downloadAndSaveFile(file), {
priority: 1,
- })
+ }),
);
try {
@@ -536,7 +536,7 @@ export class GameUpdater implements AppModule {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(
- `Failed to download file: ${response.status} ${response.statusText}`
+ `Failed to download file: ${response.status} ${response.statusText}`,
);
}
@@ -549,7 +549,7 @@ export class GameUpdater implements AppModule {
const dir = join(
this.gameClientPath,
- file.path.split("/").slice(0, -1).join("/")
+ file.path.split("/").slice(0, -1).join("/"),
);
if (dir && !existsSync(dir)) {
mkdirSync(dir, { recursive: true });
@@ -570,7 +570,7 @@ export class GameUpdater implements AppModule {
throw new Error(
`Failed to download file ${file.path}: ${
error instanceof Error ? error.message : "Unknown error"
- }`
+ }`,
);
}
}
@@ -653,7 +653,7 @@ export class GameUpdater implements AppModule {
private async getAllLocalFiles(
dirPath: string = this.gameClientPath,
relativeTo: string = this.gameClientPath,
- files: string[] = []
+ files: string[] = [],
): Promise {
try {
const entries = await readdir(dirPath);
diff --git a/packages/main/src/services/SettingsManager.ts b/packages/main/src/services/SettingsManager.ts
index d89bd1a..f91036b 100644
--- a/packages/main/src/services/SettingsManager.ts
+++ b/packages/main/src/services/SettingsManager.ts
@@ -67,7 +67,7 @@ export class SettingsManager {
await writeFile(
this.settingsPath,
JSON.stringify(settings, null, 2),
- "utf-8"
+ "utf-8",
);
log.info("Settings saved successfully:", settings);
@@ -82,7 +82,7 @@ export class SettingsManager {
throw new Error(
`Failed to save settings: ${
error instanceof Error ? error.message : "Unknown error"
- }`
+ }`,
);
}
}
@@ -120,7 +120,7 @@ export class SettingsManager {
*/
private notifySettingsChange(settings: GameSettings): void {
log.info(
- `Notifying ${this.changeCallbacks.size} modules of settings change`
+ `Notifying ${this.changeCallbacks.size} modules of settings change`,
);
for (const callback of this.changeCallbacks) {
diff --git a/packages/renderer/src/components/SettingsMenu.tsx b/packages/renderer/src/components/SettingsMenu.tsx
index c53c33b..482ad48 100644
--- a/packages/renderer/src/components/SettingsMenu.tsx
+++ b/packages/renderer/src/components/SettingsMenu.tsx
@@ -16,7 +16,11 @@ import {
Code,
AlertTriangle,
} from "lucide-react";
-import { SimpleSlider, SimpleSelect, SimpleSwitch } from "./common/FormControls";
+import {
+ SimpleSlider,
+ SimpleSelect,
+ SimpleSwitch,
+} from "./common/FormControls";
import type { SettingsState } from "@/types";
import { gameClient, gameUpdater, system } from "@app/preload";
import { useGameStateContext } from "@/contexts/GameStateContext";
@@ -28,7 +32,9 @@ interface ArgumentDescriptorItem {
type: "boolean" | "string" | "number";
}
-const parseGameArgs = (argsString: string): Record => {
+const parseGameArgs = (
+ argsString: string,
+): Record => {
const parsed: Record = {};
if (!argsString) return parsed;
argsString.split(/\s+/).forEach((part) => {
@@ -47,7 +53,7 @@ const updateDescriptorArg = (
currentArgsStr: string,
descriptor: ArgumentDescriptorItem[],
keyToUpdate: string,
- newValue: string | boolean
+ newValue: string | boolean,
): string => {
const parsed = parseGameArgs(currentArgsStr);
const descriptorKeys = new Set(descriptor.map((s) => s.key));
@@ -88,7 +94,7 @@ const updateDescriptorArg = (
const updateCustomArgs = (
currentArgsStr: string,
descriptor: ArgumentDescriptorItem[],
- newCustomStr: string
+ newCustomStr: string,
): string => {
const parsed = parseGameArgs(currentArgsStr);
const descriptorKeys = new Set(descriptor.map((s) => s.key));
@@ -112,7 +118,10 @@ const updateCustomArgs = (
return finalParts.join(" ");
};
-const getCustomArgsString = (currentArgsStr: string, descriptor: ArgumentDescriptorItem[]): string => {
+const getCustomArgsString = (
+ currentArgsStr: string,
+ descriptor: ArgumentDescriptorItem[],
+): string => {
const parsed = parseGameArgs(currentArgsStr);
const descriptorKeys = new Set(descriptor.map((s) => s.key));
const customParts: string[] = [];
@@ -152,7 +161,9 @@ export const SettingsMenu: React.FC = ({
// Local settings state for editing (doesn't affect main UI)
const [localSettings, setLocalSettings] = useState(settings);
- const [ArgumentsDescriptor, setArgumentsDescriptor] = useState(null);
+ const [ArgumentsDescriptor, setArgumentsDescriptor] = useState<
+ ArgumentDescriptorItem[] | null
+ >(null);
const [descriptorLoading, setDescriptorLoading] = useState(false);
// Fetch arguments.json descriptor when developer mode is enabled and settings menu is open
@@ -166,7 +177,15 @@ export const SettingsMenu: React.FC = ({
setDescriptorLoading(true);
try {
const descriptor = await gameClient.getGameArgumentsDescriptor();
- if (Array.isArray(descriptor) && descriptor.every((item: any) => item && typeof item.key === "string" && typeof item.type === "string")) {
+ if (
+ Array.isArray(descriptor) &&
+ descriptor.every(
+ (item: any) =>
+ item &&
+ typeof item.key === "string" &&
+ typeof item.type === "string",
+ )
+ ) {
setArgumentsDescriptor(descriptor as ArgumentDescriptorItem[]);
} else {
setArgumentsDescriptor(null);
@@ -405,7 +424,7 @@ export const SettingsMenu: React.FC = ({
onChange={(value) =>
updateSetting(
"devCdnEnvironment",
- value as "production" | "staging"
+ value as "production" | "staging",
)
}
options={[
@@ -460,31 +479,44 @@ export const SettingsMenu: React.FC = ({
{ArgumentsDescriptor.map((item) => {
- const parsedArgs = parseGameArgs(localSettings.devGameArgs);
+ const parsedArgs = parseGameArgs(
+ localSettings.devGameArgs,
+ );
const currentValue = parsedArgs[item.key];
return (
-
+
- {item.key}
+
+ {item.key}
+
{item.type === "boolean" ? (
{
const updated = updateDescriptorArg(
localSettings.devGameArgs,
ArgumentsDescriptor,
item.key,
- checked
+ checked,
);
updateSetting("devGameArgs", updated);
}}
/>
) : (
= ({
localSettings.devGameArgs,
ArgumentsDescriptor,
item.key,
- e.target.value
+ e.target.value,
);
updateSetting("devGameArgs", updated);
}}
@@ -501,7 +533,9 @@ export const SettingsMenu: React.FC = ({
/>
)}
-
{item.description}
+
+ {item.description}
+
);
})}
@@ -512,12 +546,15 @@ export const SettingsMenu: React.FC
= ({
Autres arguments du jeu