Skip to content

Commit

Permalink
Merge pull request #1299 from hydralauncher/feature/rpc
Browse files Browse the repository at this point in the history
Feature/rpc
  • Loading branch information
JackEnx authored Dec 18, 2024
2 parents ce2cf89 + bc98f7c commit dfe70a5
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}

- name: Build Windows
if: matrix.os == 'windows-latest'
Expand All @@ -62,6 +63,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}

- name: Create artifact
uses: actions/upload-artifact@v4
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
- name: Build Windows
if: matrix.os == 'windows-latest'
run: yarn build:win
Expand All @@ -60,6 +61,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
- name: Create artifact
uses: actions/upload-artifact@v4
with:
Expand Down
153 changes: 136 additions & 17 deletions src/main/services/process-watcher.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,172 @@
import { IsNull, Not } from "typeorm";
import { gameRepository } from "@main/repository";
import { WindowManager } from "./window-manager";
import { createGame, updateGamePlaytime } from "./library-sync";
import type { GameRunning } from "@types";
import { PythonInstance } from "./download";
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<
number,
{ lastTick: number; firstTick: number; lastSyncTick: number }
>();

interface ExecutableInfo {
name: string;
os: string;
}

interface GameExecutables {
[key: string]: ExecutableInfo[];
}

const TICKS_TO_UPDATE_API = 120;
let currentTick = 1;

const getSystemProcessSet = async () => {
const processes = await PythonInstance.getProcessList();
const gameExecutables = (
await axios
.get(
import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL +
"/game-executables.json"
)
.catch(() => {
return { data: {} };
})
).data as GameExecutables;

const findGamePathByProcess = (
processMap: Map<string, Set<string>>,
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 (process.platform === "linux")
return new Set(processes.map((process) => process.name));
return new Set(processes.map((process) => process.exe));
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) => {
if (process.platform === "linux")
return game.executablePath?.split("/").at(-1);
return game.executablePath;
const getSystemProcessMap = async () => {
const processes = await PythonInstance.getProcessList();

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 () => {
const games = await gameRepository.find({
where: {
executablePath: Not(IsNull()),
isDeleted: false,
},
});

if (games.length === 0) return;
if (!games.length) return;

const processSet = await getSystemProcessSet();
const processMap = await getSystemProcessMap();

for (const game of games) {
const executable = getExecutable(game);
const executablePath = game.executablePath;

if (!executablePath) {
if (gameExecutables[game.objectID]) {
findGamePathByProcess(processMap, game.objectID);
}
continue;
}

if (!executable) continue;
const executable = executablePath
.slice(
executablePath.lastIndexOf(process.platform === "win32" ? "\\" : "/") +
1
)
.toLowerCase();

const gameProcess = processSet.has(executable);
const hasProcess = processMap.get(executable)?.has(executablePath);

if (gameProcess) {
if (hasProcess) {
if (gamesPlaytime.has(game.id)) {
onTickGame(game);
} else {
Expand Down
1 change: 1 addition & 0 deletions src/main/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface ImportMetaEnv {
readonly MAIN_VITE_ANALYTICS_API_URL: string;
readonly MAIN_VITE_AUTH_URL: string;
readonly MAIN_VITE_CHECKOUT_URL: string;
readonly MAIN_VITE_EXTERNAL_RESOURCES_URL: string;
}

interface ImportMeta {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function App() {

const $script = document.createElement("script");
$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);
});
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
Expand Down

0 comments on commit dfe70a5

Please sign in to comment.