mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-27 12:51:03 +00:00
chore: update dependencies and refactor game launch logic
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -94,3 +94,4 @@ export const getThemeSoundPath = (
|
||||
};
|
||||
|
||||
export * from "./reg-parser";
|
||||
export * from "./launch-game";
|
||||
|
||||
47
src/main/helpers/launch-game.ts
Normal file
47
src/main/helpers/launch-game.ts
Normal 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 });
|
||||
};
|
||||
@@ -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) => {
|
||||
|
||||
21
yarn.lock
21
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user