chore: update dependencies and refactor game launch logic

This commit is contained in:
Moyasee
2026-01-26 10:42:43 +02:00
parent a54dacb2da
commit edd94ba7c4
7 changed files with 92 additions and 62 deletions

View File

@@ -74,6 +74,7 @@
"lucide-react": "^0.544.0",
"node-7z": "^3.0.0",
"parse-torrent": "^11.0.18",
"png-to-ico": "^3.0.1",
"rc-virtual-list": "^3.18.3",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import fs from "node:fs";
import { app } from "electron";
import axios from "axios";
import pngToIco from "png-to-ico";
import { removeSymbolsFromName } from "@shared";
import { GameShop, ShortcutLocation } from "@types";
import { gamesSublevel, levelKeys } from "@main/level";
@@ -17,25 +18,29 @@ const downloadIcon = async (
objectId: string,
iconUrl?: string | null
): Promise<string | null> => {
const iconPath = path.join(ASSETS_PATH, `${shop}-${objectId}`, "icon.ico");
if (!iconUrl) {
return null;
}
const iconDir = path.join(ASSETS_PATH, `${shop}-${objectId}`);
const iconPath = path.join(iconDir, "icon.ico");
try {
if (fs.existsSync(iconPath)) {
return iconPath;
}
if (!iconUrl) {
return null;
}
fs.mkdirSync(path.dirname(iconPath), { recursive: true });
fs.mkdirSync(iconDir, { recursive: true });
const response = await axios.get(iconUrl, { responseType: "arraybuffer" });
fs.writeFileSync(iconPath, response.data);
const imageBuffer = Buffer.from(response.data);
const icoBuffer = await pngToIco(imageBuffer);
fs.writeFileSync(iconPath, icoBuffer);
return iconPath;
} catch (error) {
logger.error("Failed to download game icon", error);
logger.error("Failed to download/convert game icon", error);
return null;
}
};

View File

@@ -1,11 +1,6 @@
import { registerEvent } from "../register-event";
import { shell } from "electron";
import { spawn } from "node:child_process";
import { parseExecutablePath } from "../helpers/parse-executable-path";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
import { parseLaunchOptions } from "../helpers/parse-launch-options";
import { WindowManager } from "@main/services";
import { launchGame } from "@main/helpers";
const openGame = async (
_event: Electron.IpcMainInvokeEvent,
@@ -14,32 +9,7 @@ const openGame = async (
executablePath: string,
launchOptions?: string | null
) => {
const parsedPath = parseExecutablePath(executablePath);
const parsedParams = parseLaunchOptions(launchOptions);
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (!game) return;
await gamesSublevel.put(gameKey, {
...game,
executablePath: parsedPath,
launchOptions,
});
// Always show the launcher window when launching a game
await WindowManager.createGameLauncherWindow(shop, objectId);
await new Promise((resolve) => setTimeout(resolve, 2000));
if (parsedParams.length === 0) {
shell.openPath(parsedPath);
return;
}
spawn(parsedPath, parsedParams, { shell: false, detached: true });
await launchGame({ shop, objectId, executablePath, launchOptions });
};
registerEvent("openGame", openGame);

View File

@@ -94,3 +94,4 @@ export const getThemeSoundPath = (
};
export * from "./reg-parser";
export * from "./launch-game";

View File

@@ -0,0 +1,47 @@
import { shell } from "electron";
import { spawn } from "node:child_process";
import { GameShop } from "@types";
import { gamesSublevel, levelKeys } from "@main/level";
import { WindowManager } from "@main/services";
import { parseExecutablePath } from "../events/helpers/parse-executable-path";
import { parseLaunchOptions } from "../events/helpers/parse-launch-options";
export interface LaunchGameOptions {
shop: GameShop;
objectId: string;
executablePath: string;
launchOptions?: string | null;
}
/**
* Shows the launcher window and launches the game executable
* Shared between deep link handler and openGame event
*/
export const launchGame = async (options: LaunchGameOptions): Promise<void> => {
const { shop, objectId, executablePath, launchOptions } = options;
const parsedPath = parseExecutablePath(executablePath);
const parsedParams = parseLaunchOptions(launchOptions);
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (game) {
await gamesSublevel.put(gameKey, {
...game,
executablePath: parsedPath,
launchOptions,
});
}
await WindowManager.createGameLauncherWindow(shop, objectId);
await new Promise((resolve) => setTimeout(resolve, 2000));
if (parsedParams.length === 0) {
shell.openPath(parsedPath);
return;
}
spawn(parsedPath, parsedParams, { shell: false, detached: true });
};

View File

@@ -1,9 +1,8 @@
import { app, BrowserWindow, net, protocol, shell } from "electron";
import { app, BrowserWindow, net, protocol } from "electron";
import updater from "electron-updater";
import i18n from "i18next";
import path from "node:path";
import url from "node:url";
import { spawn } from "node:child_process";
import { electronApp, optimizer } from "@electron-toolkit/utils";
import {
logger,
@@ -16,8 +15,7 @@ import resources from "@locales";
import { PythonRPC } from "./services/python-rpc";
import { db, gamesSublevel, levelKeys } from "./level";
import { GameShop, UserPreferences } from "@types";
import { parseExecutablePath } from "./events/helpers/parse-executable-path";
import { parseLaunchOptions } from "./events/helpers/parse-launch-options";
import { launchGame } from "./helpers";
import { loadState } from "./main";
const { autoUpdater } = updater;
@@ -180,30 +178,17 @@ const handleRunGame = async (shop: GameShop, objectId: string) => {
{ valueEncoding: "json" }
);
// Always show the launcher window
await WindowManager.createGameLauncherWindow(shop, objectId);
// Only open main window if setting is disabled
if (!userPreferences?.hideToTrayOnGameStart) {
WindowManager.createMainWindow();
}
await new Promise((resolve) => setTimeout(resolve, 2000));
const parsedPath = parseExecutablePath(game.executablePath);
const parsedParams = parseLaunchOptions(game.launchOptions);
await gamesSublevel.put(gameKey, {
...game,
executablePath: parsedPath,
await launchGame({
shop,
objectId,
executablePath: game.executablePath,
launchOptions: game.launchOptions,
});
if (parsedParams.length === 0) {
shell.openPath(parsedPath);
return;
}
spawn(parsedPath, parsedParams, { shell: false, detached: true });
};
const handleDeepLinkPath = (uri?: string) => {

View File

@@ -3383,6 +3383,13 @@
dependencies:
undici-types "~6.21.0"
"@types/node@^22.10.3":
version "22.19.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.7.tgz#434094ee1731ae76c16083008590a5835a8c39c1"
integrity sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==
dependencies:
undici-types "~6.21.0"
"@types/node@^22.7.7":
version "22.18.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.12.tgz#e165d87bc25d7bf6d3657035c914db7485de84fb"
@@ -7367,6 +7374,20 @@ plist@3.1.0, plist@^3.0.4, plist@^3.0.5, plist@^3.1.0:
base64-js "^1.5.1"
xmlbuilder "^15.1.1"
png-to-ico@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/png-to-ico/-/png-to-ico-3.0.1.tgz#6ad50bec9ffa40aa74265deadc5128fa4097dfbe"
integrity sha512-S8BOAoaGd9gT5uaemQ62arIY3Jzco7Uc7LwUTqRyqJDTsKqOAiyfyN4dSdT0D+Zf8XvgztgpRbM5wnQd7EgYwg==
dependencies:
"@types/node" "^22.10.3"
minimist "^1.2.8"
pngjs "^7.0.0"
pngjs@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26"
integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==
possible-typed-array-names@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"