Compare commits

...

9 Commits

Author SHA1 Message Date
Moyasee
c9801644ac fix: prevent processing downloads without a folder name 2026-01-19 04:22:44 +02:00
Moyasee
98cfe7be98 feat: add automatic executable path binding upon download finish 2026-01-19 04:01:21 +02:00
Zamitto
7293afb618 Merge branch 'release/v3.8.0' 2026-01-15 08:43:06 -03:00
Zamitto
194e7918ca feat: dont setup ww feedback widget if user has no token 2026-01-15 08:42:33 -03:00
Zamitto
979958aca6 feat: update ww webRequest interceptor 2026-01-14 19:37:17 -03:00
Zamitto
6e92e0f79f fix: getLibrary throwing error 2026-01-14 00:37:22 -03:00
Zamitto
aef069d4c7 Merge branch 'release/v3.8.1' 2026-01-14 00:07:53 -03:00
Zamitto
5d2dc3616c Merge pull request #1938 from hydralauncher/release/v3.8.1
sync main
2026-01-13 23:43:48 -03:00
Zamitto
96140e614c Merge pull request #1917 from hydralauncher/fix/friends-box-display
hotfix: add empty state for friends box and new translation key
2026-01-04 02:59:53 -03:00
9 changed files with 141 additions and 5 deletions

View File

@@ -25,7 +25,7 @@ const getLibrary = async (): Promise<LibraryGame[]> => {
const achievements = await gameAchievementsSublevel.get(key);
unlockedAchievementCount =
achievements?.unlockedAchievements.length ?? 0;
achievements?.unlockedAchievements?.length ?? 0;
}
return {

View File

@@ -362,6 +362,11 @@ export class DownloadManager {
if (download.automaticallyExtract) {
this.handleExtraction(download, game);
} else {
// For downloads without extraction (e.g., torrents with ready-to-play files),
// search for executable in the download folder
const gameFilesManager = new GameFilesManager(game.shop, game.objectId);
gameFilesManager.searchAndBindExecutable();
}
await this.processNextQueuedDownload();

View File

@@ -0,0 +1,13 @@
import { gameExecutables } from "./process-watcher";
export class GameExecutables {
static getExecutablesForGame(objectId: string): string[] | null {
const executables = gameExecutables[objectId];
if (!executables || executables.length === 0) {
return null;
}
return executables.map((exe) => exe.exe);
}
}

View File

@@ -7,6 +7,7 @@ import { SevenZip, ExtractionProgress } from "./7zip";
import { WindowManager } from "./window-manager";
import { publishExtractionCompleteNotification } from "./notifications";
import { logger } from "./logger";
import { GameExecutables } from "./game-executables";
const PROGRESS_THROTTLE_MS = 1000;
@@ -151,6 +152,100 @@ export class GameFilesManager {
if (publishNotification && game) {
publishExtractionCompleteNotification(game);
}
await this.searchAndBindExecutable();
}
async searchAndBindExecutable(): Promise<void> {
try {
const [download, game] = await Promise.all([
downloadsSublevel.get(this.gameKey),
gamesSublevel.get(this.gameKey),
]);
if (!download || !game || game.executablePath) {
return;
}
const executableNames = GameExecutables.getExecutablesForGame(
this.objectId
);
if (!executableNames || executableNames.length === 0) {
return;
}
if (!download.folderName) {
return;
}
const gameFolderPath = path.join(
download.downloadPath,
download.folderName
);
if (!fs.existsSync(gameFolderPath)) {
return;
}
const foundExePath = await this.findExecutableInFolder(
gameFolderPath,
executableNames
);
if (foundExePath) {
logger.info(
`[GameFilesManager] Auto-detected executable for ${this.objectId}: ${foundExePath}`
);
await gamesSublevel.put(this.gameKey, {
...game,
executablePath: foundExePath,
});
WindowManager.mainWindow?.webContents.send("on-library-batch-complete");
}
} catch (err) {
logger.error(
`[GameFilesManager] Error searching for executable: ${this.objectId}`,
err
);
}
}
private async findExecutableInFolder(
folderPath: string,
executableNames: string[]
): Promise<string | null> {
const normalizedNames = new Set(
executableNames.map((name) => name.toLowerCase())
);
try {
const entries = await fs.promises.readdir(folderPath, {
withFileTypes: true,
recursive: true,
});
for (const entry of entries) {
if (!entry.isFile()) continue;
const fileName = entry.name.toLowerCase();
if (normalizedNames.has(fileName)) {
const parentPath =
"parentPath" in entry
? entry.parentPath
: (entry as unknown as { path?: string }).path || folderPath;
return path.join(parentPath, entry.name);
}
}
} catch {
// Silently fail if folder cannot be read
}
return null;
}
async extractDownloadedFile() {

View File

@@ -10,6 +10,7 @@ export * from "./ludusavi";
export * from "./cloud-sync";
export * from "./7zip";
export * from "./game-files-manager";
export * from "./game-executables";
export * from "./common-redist-manager";
export * from "./aria2";
export * from "./ws";

View File

@@ -69,7 +69,7 @@ const getGameExecutables = async () => {
return gameExecutables;
};
const gameExecutables = await getGameExecutables();
export const gameExecutables = await getGameExecutables();
const findGamePathByProcess = async (
processMap: Map<string, Set<string>>,

View File

@@ -138,12 +138,21 @@ export class WindowManager {
(details, callback) => {
if (
details.webContentsId !== this.mainWindow?.webContents.id ||
details.url.includes("chatwoot") ||
details.url.includes("workwonders")
details.url.includes("chatwoot")
) {
return callback(details);
}
if (details.url.includes("workwonders")) {
return callback({
...details,
requestHeaders: {
Origin: "https://workwonders.app",
...details.requestHeaders,
},
});
}
const userAgent = new UserAgent();
callback({

View File

@@ -134,7 +134,10 @@ export function App() {
await workwondersRef.current.initChangelogWidget();
workwondersRef.current.initChangelogWidgetMini();
workwondersRef.current.initFeedbackWidget();
if (token) {
workwondersRef.current.initFeedbackWidget();
}
},
[workwondersRef]
);

View File

@@ -225,6 +225,16 @@ export function GameDetailsContextProvider({
};
}, [game?.id, isGameRunning, updateGame]);
useEffect(() => {
const unsubscribe = window.electron.onLibraryBatchComplete(() => {
updateGame();
});
return () => {
unsubscribe();
};
}, [updateGame]);
useEffect(() => {
const handler = (ev: Event) => {
try {