mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-24 03:11:03 +00:00
feat: adding wine prefix
This commit is contained in:
@@ -39,6 +39,9 @@ export class Game {
|
||||
@Column("text", { nullable: true })
|
||||
executablePath: string | null;
|
||||
|
||||
@Column("text", { nullable: true })
|
||||
winePrefixPath: string | null;
|
||||
|
||||
@Column("int", { default: 0 })
|
||||
playTimeInMilliseconds: number;
|
||||
|
||||
|
||||
@@ -10,8 +10,13 @@ import os from "node:os";
|
||||
import { backupsPath } from "@main/constants";
|
||||
import { app } from "electron";
|
||||
import { normalizePath } from "@main/helpers";
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
const bundleBackup = async (shop: GameShop, objectId: string) => {
|
||||
const bundleBackup = async (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
winePrefix: string | null
|
||||
) => {
|
||||
const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
|
||||
|
||||
// Remove existing backup
|
||||
@@ -19,7 +24,7 @@ const bundleBackup = async (shop: GameShop, objectId: string) => {
|
||||
fs.rmSync(backupPath, { recursive: true });
|
||||
}
|
||||
|
||||
await Ludusavi.backupGame(shop, objectId, backupPath);
|
||||
await Ludusavi.backupGame(shop, objectId, backupPath, winePrefix);
|
||||
|
||||
const tarLocation = path.join(backupsPath, `${crypto.randomUUID()}.tar`);
|
||||
|
||||
@@ -38,9 +43,21 @@ const bundleBackup = async (shop: GameShop, objectId: string) => {
|
||||
const uploadSaveGame = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
objectId: string,
|
||||
shop: GameShop
|
||||
shop: GameShop,
|
||||
downloadOptionTitle: string | null
|
||||
) => {
|
||||
const bundleLocation = await bundleBackup(shop, objectId);
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
objectID: objectId,
|
||||
shop,
|
||||
},
|
||||
});
|
||||
|
||||
const bundleLocation = await bundleBackup(
|
||||
shop,
|
||||
objectId,
|
||||
game?.winePrefixPath ?? null
|
||||
);
|
||||
|
||||
fs.stat(bundleLocation, async (err, stat) => {
|
||||
if (err) {
|
||||
@@ -57,6 +74,7 @@ const uploadSaveGame = async (
|
||||
objectId,
|
||||
hostname: os.hostname(),
|
||||
homeDir: normalizePath(app.getPath("home")),
|
||||
downloadOptionTitle,
|
||||
platform: os.platform(),
|
||||
});
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import "./library/update-executable-path";
|
||||
import "./library/verify-executable-path";
|
||||
import "./library/remove-game";
|
||||
import "./library/remove-game-from-library";
|
||||
import "./library/select-game-wine-prefix";
|
||||
import "./misc/open-external";
|
||||
import "./misc/show-open-dialog";
|
||||
import "./torrenting/cancel-game-download";
|
||||
|
||||
13
src/main/events/library/select-game-wine-prefix.ts
Normal file
13
src/main/events/library/select-game-wine-prefix.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const selectGameWinePrefix = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number,
|
||||
winePrefixPath: string
|
||||
) => {
|
||||
return gameRepository.update({ id }, { winePrefixPath });
|
||||
};
|
||||
|
||||
registerEvent("selectGameWinePrefix", selectGameWinePrefix);
|
||||
@@ -10,6 +10,7 @@ import { CreateGameAchievement } from "./migrations/20240919030940_create_game_a
|
||||
import { AddAchievementNotificationPreference } from "./migrations/20241013012900_add_achievement_notification_preference";
|
||||
import { CreateUserSubscription } from "./migrations/20241015235142_create_user_subscription";
|
||||
import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_background_image_url";
|
||||
import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game";
|
||||
|
||||
export type HydraMigration = Knex.Migration & { name: string };
|
||||
|
||||
@@ -25,6 +26,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
|
||||
AddAchievementNotificationPreference,
|
||||
CreateUserSubscription,
|
||||
AddBackgroundImageUrl,
|
||||
AddWinePrefixToGame,
|
||||
]);
|
||||
}
|
||||
getMigrationName(migration: HydraMigration): string {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { DownloadManager, PythonInstance, startMainLoop } from "./services";
|
||||
import {
|
||||
DownloadManager,
|
||||
Ludusavi,
|
||||
PythonInstance,
|
||||
startMainLoop,
|
||||
} from "./services";
|
||||
import {
|
||||
downloadQueueRepository,
|
||||
userPreferencesRepository,
|
||||
@@ -15,6 +20,8 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
||||
RealDebridClient.authorize(userPreferences?.realDebridApiToken);
|
||||
}
|
||||
|
||||
Ludusavi.addManifestToLudusaviConfig();
|
||||
|
||||
HydraApi.setupApi().then(() => {
|
||||
uploadGamesBatch();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { HydraMigration } from "@main/knex-client";
|
||||
import type { Knex } from "knex";
|
||||
|
||||
export const AddWinePrefixToGame: HydraMigration = {
|
||||
name: "AddWinePrefixToGame",
|
||||
up: (knex: Knex) => {
|
||||
return knex.schema.alterTable("game", (table) => {
|
||||
return table.text("winePrefixPath").nullable();
|
||||
});
|
||||
},
|
||||
|
||||
down: async (knex: Knex) => {
|
||||
return knex.schema.alterTable("game", (table) => {
|
||||
return table.dropColumn("winePrefixPath");
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -50,7 +50,7 @@ export const searchHowLongToBeat = async (gameName: string) => {
|
||||
|
||||
const response = await axios
|
||||
.post(
|
||||
"https://howlongtobeat.com/api/search/8fbd64723a8204dd",
|
||||
`https://howlongtobeat.com/api/search/${state.apiKey}`,
|
||||
{
|
||||
searchType: "games",
|
||||
searchTerms: formatName(gameName).split(" "),
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import { GameShop, LudusaviBackup } from "@types";
|
||||
import type { GameShop, LudusaviBackup, LudusaviConfig } from "@types";
|
||||
import Piscina from "piscina";
|
||||
|
||||
import { app } from "electron";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import YAML from "yaml";
|
||||
|
||||
import ludusaviWorkerPath from "../workers/ludusavi.worker?modulePath";
|
||||
|
||||
const binaryPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, "ludusavi", "ludusavi")
|
||||
: path.join(__dirname, "..", "..", "ludusavi", "ludusavi");
|
||||
|
||||
export class Ludusavi {
|
||||
private static ludusaviPath = path.join(app.getPath("appData"), "ludusavi");
|
||||
private static ludusaviConfigPath = path.join(
|
||||
this.ludusaviPath,
|
||||
"config.yaml"
|
||||
);
|
||||
private static binaryPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, "ludusavi", "ludusavi")
|
||||
: path.join(__dirname, "..", "..", "ludusavi", "ludusavi");
|
||||
|
||||
private static worker = new Piscina({
|
||||
filename: ludusaviWorkerPath,
|
||||
workerData: {
|
||||
binaryPath,
|
||||
binaryPath: this.binaryPath,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,16 +34,29 @@ export class Ludusavi {
|
||||
return games;
|
||||
}
|
||||
|
||||
static async getConfig() {
|
||||
if (!fs.existsSync(this.ludusaviConfigPath)) {
|
||||
await this.worker.run(undefined, { name: "generateConfig" });
|
||||
}
|
||||
|
||||
const config = YAML.parse(
|
||||
fs.readFileSync(this.ludusaviConfigPath, "utf-8")
|
||||
) as LudusaviConfig;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
static async backupGame(
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
backupPath: string
|
||||
backupPath: string,
|
||||
winePrefix?: string | null
|
||||
): Promise<LudusaviBackup> {
|
||||
const games = await this.findGames(shop, objectId);
|
||||
if (!games.length) throw new Error("Game not found");
|
||||
|
||||
return this.worker.run(
|
||||
{ title: games[0], backupPath },
|
||||
{ title: games[0], backupPath, winePrefix },
|
||||
{ name: "backupGame" }
|
||||
);
|
||||
}
|
||||
@@ -60,4 +80,31 @@ export class Ludusavi {
|
||||
static async restoreBackup(backupPath: string) {
|
||||
return this.worker.run(backupPath, { name: "restoreBackup" });
|
||||
}
|
||||
|
||||
static async addManifestToLudusaviConfig() {
|
||||
const config = await this.getConfig();
|
||||
|
||||
config.manifest.enable = false;
|
||||
config.manifest.secondary = [
|
||||
{ url: "https://cdn.losbroxas.org/manifest.yaml", enable: true },
|
||||
];
|
||||
|
||||
fs.writeFileSync(this.ludusaviConfigPath, YAML.stringify(config));
|
||||
}
|
||||
|
||||
static async addCustomGame(title: string, savePath: string) {
|
||||
const config = await this.getConfig();
|
||||
const filteredGames = config.customGames.filter(
|
||||
(game) => game.name !== title
|
||||
);
|
||||
|
||||
filteredGames.push({
|
||||
name: title,
|
||||
files: [savePath],
|
||||
registry: [],
|
||||
});
|
||||
|
||||
config.customGames = filteredGames;
|
||||
fs.writeFileSync(this.ludusaviConfigPath, YAML.stringify(config));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,4 +58,8 @@ export const restoreBackup = (backupPath: string) => {
|
||||
return JSON.parse(result.toString("utf-8")) as LudusaviBackup;
|
||||
};
|
||||
|
||||
// --wine-prefix
|
||||
export const generateConfig = () => {
|
||||
const result = cp.execFileSync(binaryPath, ["schema", "config"]);
|
||||
|
||||
return JSON.parse(result.toString("utf-8")) as LudusaviBackup;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user