From db1e20d954c34bc89fd7f4ed4abe4073cc3f3102 Mon Sep 17 00:00:00 2001 From: Ethan Green Date: Fri, 15 Mar 2024 12:55:52 -0400 Subject: [PATCH] add subdir support to direct game runner Simple change, this introduces nested subdir support to the direct game runner. This means that games defined within GameManager.ts can refer to executable path(s) as relative paths, rooted by the game directory itself. This change is primarily intended to support the Nickel modloader, whose executable is nested within `Nickel/Nickel.exe`. --- .../runners/multiplatform/DirectGameRunner.ts | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts b/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts index 875c15d2d..c26d0ca90 100644 --- a/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts +++ b/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts @@ -9,6 +9,7 @@ import GameDirectoryResolverProvider from '../../../../providers/ror2/game/GameD import FsProvider from '../../../../providers/generic/file/FsProvider'; import LoggerProvider, { LogSeverity } from '../../../../providers/ror2/logging/LoggerProvider'; import { exec } from 'child_process'; +import path from 'path' export default class DirectGameRunner extends GameRunnerProvider { @@ -32,16 +33,39 @@ export default class DirectGameRunner extends GameRunnerProvider { async start(game: Game, args: string): Promise { return new Promise(async (resolve, reject) => { + const fs = FsProvider.instance; const settings = await ManagerSettings.getSingleton(game); let gameDir = await GameDirectoryResolverProvider.instance.getDirectory(game); if (gameDir instanceof R2Error) { return resolve(gameDir); } - gameDir = await FsProvider.instance.realpath(gameDir); + // Search through the registered game executable *relative* paths until a valid match has been found. + // Note that this doesn't do any validation to assert that the file is an actual executable, but that's ok. + let gameExecutable = undefined; + for (const exeItem of game.exeName) { + const absExePath = path.join(gameDir, exeItem); + if (!(await fs.exists(absExePath))) { + continue; + } + + const stat = await fs.lstat(absExePath); + if (!stat.isFile()) { + continue; + } - const gameExecutable = (await FsProvider.instance.readdir(gameDir)) - .filter((x: string) => game.exeName.includes(x))[0]; + gameExecutable = absExePath; + break; + } + + if (gameExecutable == undefined) { + const err = new R2Error( + "Error finding game executable", + "Failed to find a valid game executable within the game directory", + `Ensure that one of the following executables exists within ${gameDir}: ${game.exeName}`, + ); + return reject(err); + } LoggerProvider.instance.Log(LogSeverity.INFO, `Running command: ${gameDir}/${gameExecutable} ${args} ${settings.getContext().gameSpecific.launchParameters}`);