mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
commit
dfe70a5a95
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -50,6 +50,7 @@ jobs:
|
|||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
|
|
||||||
- name: Build Windows
|
- name: Build Windows
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@ -62,6 +63,7 @@ jobs:
|
|||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
|
|
||||||
- name: Create artifact
|
- name: Create artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -49,6 +49,7 @@ jobs:
|
|||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
- name: Build Windows
|
- name: Build Windows
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
run: yarn build:win
|
run: yarn build:win
|
||||||
@ -60,6 +61,7 @@ jobs:
|
|||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
- name: Create artifact
|
- name: Create artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
@ -1,53 +1,172 @@
|
|||||||
import { IsNull, Not } from "typeorm";
|
|
||||||
import { gameRepository } from "@main/repository";
|
import { gameRepository } from "@main/repository";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { createGame, updateGamePlaytime } from "./library-sync";
|
import { createGame, updateGamePlaytime } from "./library-sync";
|
||||||
import type { GameRunning } from "@types";
|
import type { GameRunning } from "@types";
|
||||||
import { PythonInstance } from "./download";
|
import { PythonInstance } from "./download";
|
||||||
import { Game } from "@main/entity";
|
import { Game } from "@main/entity";
|
||||||
|
import axios from "axios";
|
||||||
|
import { exec } from "child_process";
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
|
||||||
|
findWineExecutables: `lsof -c wine 2>/dev/null | grep '\\.exe$' | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
|
||||||
|
};
|
||||||
|
|
||||||
export const gamesPlaytime = new Map<
|
export const gamesPlaytime = new Map<
|
||||||
number,
|
number,
|
||||||
{ lastTick: number; firstTick: number; lastSyncTick: number }
|
{ lastTick: number; firstTick: number; lastSyncTick: number }
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
interface ExecutableInfo {
|
||||||
|
name: string;
|
||||||
|
os: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GameExecutables {
|
||||||
|
[key: string]: ExecutableInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
const TICKS_TO_UPDATE_API = 120;
|
const TICKS_TO_UPDATE_API = 120;
|
||||||
let currentTick = 1;
|
let currentTick = 1;
|
||||||
|
|
||||||
const getSystemProcessSet = async () => {
|
const gameExecutables = (
|
||||||
const processes = await PythonInstance.getProcessList();
|
await axios
|
||||||
|
.get(
|
||||||
|
import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL +
|
||||||
|
"/game-executables.json"
|
||||||
|
)
|
||||||
|
.catch(() => {
|
||||||
|
return { data: {} };
|
||||||
|
})
|
||||||
|
).data as GameExecutables;
|
||||||
|
|
||||||
if (process.platform === "linux")
|
const findGamePathByProcess = (
|
||||||
return new Set(processes.map((process) => process.name));
|
processMap: Map<string, Set<string>>,
|
||||||
return new Set(processes.map((process) => process.exe));
|
gameId: string
|
||||||
|
) => {
|
||||||
|
const executables = gameExecutables[gameId].filter((info) => {
|
||||||
|
if (process.platform === "linux" && info.os === "linux") return true;
|
||||||
|
return info.os === "win32";
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const executable of executables) {
|
||||||
|
const exe = executable.name.slice(executable.name.lastIndexOf("/") + 1);
|
||||||
|
|
||||||
|
if (!exe) continue;
|
||||||
|
|
||||||
|
const pathSet = processMap.get(exe);
|
||||||
|
|
||||||
|
if (pathSet) {
|
||||||
|
const executableName =
|
||||||
|
process.platform === "win32"
|
||||||
|
? executable.name.replace(/\//g, "\\")
|
||||||
|
: executable.name;
|
||||||
|
|
||||||
|
pathSet.forEach((path) => {
|
||||||
|
if (path.toLowerCase().endsWith(executableName)) {
|
||||||
|
gameRepository.update(
|
||||||
|
{ objectID: gameId, shop: "steam" },
|
||||||
|
{ executablePath: path }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
exec(commands.findWineDir, (err, out) => {
|
||||||
|
if (err) return;
|
||||||
|
|
||||||
|
gameRepository.update(
|
||||||
|
{ objectID: gameId, shop: "steam" },
|
||||||
|
{
|
||||||
|
winePrefixPath: out.trim().replace("/drive_c/windows", ""),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getExecutable = (game: Game) => {
|
const getSystemProcessMap = async () => {
|
||||||
if (process.platform === "linux")
|
const processes = await PythonInstance.getProcessList();
|
||||||
return game.executablePath?.split("/").at(-1);
|
|
||||||
return game.executablePath;
|
const map = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
processes.forEach((process) => {
|
||||||
|
const key = process.name.toLowerCase();
|
||||||
|
const value = process.exe;
|
||||||
|
|
||||||
|
if (!key || !value) return;
|
||||||
|
|
||||||
|
const currentSet = map.get(key) ?? new Set();
|
||||||
|
map.set(key, currentSet.add(value));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
await new Promise((res) => {
|
||||||
|
exec(commands.findWineExecutables, (err, out) => {
|
||||||
|
if (err) {
|
||||||
|
res(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathSet = new Set(
|
||||||
|
out
|
||||||
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.map((path) => path.trim())
|
||||||
|
);
|
||||||
|
|
||||||
|
pathSet.forEach((path) => {
|
||||||
|
if (path.startsWith("/usr")) return;
|
||||||
|
|
||||||
|
const key = path.slice(path.lastIndexOf("/") + 1).toLowerCase();
|
||||||
|
|
||||||
|
if (!key || !path) return;
|
||||||
|
|
||||||
|
const currentSet = map.get(key) ?? new Set();
|
||||||
|
map.set(key, currentSet.add(path));
|
||||||
|
});
|
||||||
|
|
||||||
|
res(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const watchProcesses = async () => {
|
export const watchProcesses = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
where: {
|
where: {
|
||||||
executablePath: Not(IsNull()),
|
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (games.length === 0) return;
|
if (!games.length) return;
|
||||||
|
|
||||||
const processSet = await getSystemProcessSet();
|
const processMap = await getSystemProcessMap();
|
||||||
|
|
||||||
for (const game of games) {
|
for (const game of games) {
|
||||||
const executable = getExecutable(game);
|
const executablePath = game.executablePath;
|
||||||
|
|
||||||
if (!executable) continue;
|
if (!executablePath) {
|
||||||
|
if (gameExecutables[game.objectID]) {
|
||||||
|
findGamePathByProcess(processMap, game.objectID);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const gameProcess = processSet.has(executable);
|
const executable = executablePath
|
||||||
|
.slice(
|
||||||
|
executablePath.lastIndexOf(process.platform === "win32" ? "\\" : "/") +
|
||||||
|
1
|
||||||
|
)
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
if (gameProcess) {
|
const hasProcess = processMap.get(executable)?.has(executablePath);
|
||||||
|
|
||||||
|
if (hasProcess) {
|
||||||
if (gamesPlaytime.has(game.id)) {
|
if (gamesPlaytime.has(game.id)) {
|
||||||
onTickGame(game);
|
onTickGame(game);
|
||||||
} else {
|
} else {
|
||||||
|
1
src/main/vite-env.d.ts
vendored
1
src/main/vite-env.d.ts
vendored
@ -6,6 +6,7 @@ interface ImportMetaEnv {
|
|||||||
readonly MAIN_VITE_ANALYTICS_API_URL: string;
|
readonly MAIN_VITE_ANALYTICS_API_URL: string;
|
||||||
readonly MAIN_VITE_AUTH_URL: string;
|
readonly MAIN_VITE_AUTH_URL: string;
|
||||||
readonly MAIN_VITE_CHECKOUT_URL: string;
|
readonly MAIN_VITE_CHECKOUT_URL: string;
|
||||||
|
readonly MAIN_VITE_EXTERNAL_RESOURCES_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
@ -126,7 +126,7 @@ export function App() {
|
|||||||
|
|
||||||
const $script = document.createElement("script");
|
const $script = document.createElement("script");
|
||||||
$script.id = "external-resources";
|
$script.id = "external-resources";
|
||||||
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}?t=${Date.now()}`;
|
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/bundle.js?t=${Date.now()}`;
|
||||||
document.head.appendChild($script);
|
document.head.appendChild($script);
|
||||||
});
|
});
|
||||||
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
|
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
|
||||||
|
Loading…
Reference in New Issue
Block a user