Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

PC Game Pass support #856

Merged
merged 6 commits into from
Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/components/settings-components/SettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,14 @@ import moment from 'moment';
return 'Please set manually';
},
'fa-folder-open',
() => this.emitInvoke('ChangeGameDirectory')
() => {
if (StorePlatform.XBOX_GAME_PASS == this.activeGame.activePlatform.storePlatform) {
this.emitInvoke('ChangeGameDirectoryGamePass');
}
else {
this.emitInvoke('ChangeGameDirectory');
}
}
),
new SettingsRow(
'Locations',
Expand Down
36 changes: 25 additions & 11 deletions src/model/game/GameManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,25 @@ export default class GameManager {
new Game('Dyson Sphere Program', 'DysonSphereProgram', 'DysonSphereProgram',
'Dyson Sphere Program', ['DSPGAME.exe'], 'DSPGAME_Data',
'https://dsp.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
[new StorePlatformMetadata(StorePlatform.STEAM, "1366540")], "DysonSphereProgram.jpg",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, ["DSP"]),
[
new StorePlatformMetadata(StorePlatform.STEAM, "1366540"),
new StorePlatformMetadata(StorePlatform.XBOX_GAME_PASS, "GameraGame.DysonSphereProgram")
], "DysonSphereProgram.jpg", GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, ["DSP"]),

new Game('Valheim', 'Valheim', 'Valheim',
'Valheim', ['valheim.exe', 'valheim.x86_64'], 'valheim_Data',
'https://valheim.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
[new StorePlatformMetadata(StorePlatform.STEAM, "892970")], "Valheim.jpg",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX),
[
new StorePlatformMetadata(StorePlatform.STEAM, "892970"),
new StorePlatformMetadata(StorePlatform.XBOX_GAME_PASS, "CoffeeStainStudios.Valheim")
], "Valheim.jpg", GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX),

new Game('Valheim Dedicated Server', 'Valheim', 'ValheimServer',
'Valheim dedicated server', ['valheim_server.exe', 'valheim_server.x86_64'], 'valheim_server_Data',
'https://valheim.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
[new StorePlatformMetadata(StorePlatform.STEAM, "896660")], "Valheim.jpg",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.SERVER, PackageLoader.BEPINEX),
[
new StorePlatformMetadata(StorePlatform.STEAM, "896660")
], "Valheim.jpg", GameSelectionDisplayMode.VISIBLE, GameInstanceType.SERVER, PackageLoader.BEPINEX),

new Game('GTFO', 'GTFO', 'GTFO',
'GTFO', ['GTFO.exe'], 'GTFO_Data',
Expand Down Expand Up @@ -125,7 +130,11 @@ export default class GameManager {
new Game("TABS", "TABS", "TotallyAccurateBattleSimulator",
"Totally Accurate Battle Simulator", ["TotallyAccurateBattleSimulator.exe"], "TotallyAccurateBattleSimulator_Data",
"https://totally-accurate-battle-simulator.thunderstore.io/api/v1/package/", "https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md",
[new StorePlatformMetadata(StorePlatform.STEAM, "508440"), new StorePlatformMetadata(StorePlatform.EPIC_GAMES_STORE, "Driftfish")], "TotallyAccurateBattleSimulator.jpg",
[
new StorePlatformMetadata(StorePlatform.STEAM, "508440"),
new StorePlatformMetadata(StorePlatform.EPIC_GAMES_STORE, "Driftfish"),
new StorePlatformMetadata(StorePlatform.XBOX_GAME_PASS, "LandfallGames.TotallyAccurateBattleSimulator")
], "TotallyAccurateBattleSimulator.jpg",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, ["Totally Accurate Battle Simulator"]),

new Game("Nickelodeon All‑Star Brawl", "NASB", "NASB",
Expand Down Expand Up @@ -197,14 +206,19 @@ export default class GameManager {
new Game('Subnautica', 'Subnautica', 'Subnautica',
'Subnautica', ['Subnautica.exe'], 'Subnautica_Data',
'https://subnautica.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
[new StorePlatformMetadata(StorePlatform.STEAM, "264710"), new StorePlatformMetadata(StorePlatform.EPIC_GAMES_STORE, "Jaguar")], "Subnautica.png",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, []),
[
new StorePlatformMetadata(StorePlatform.STEAM, "264710"),
new StorePlatformMetadata(StorePlatform.EPIC_GAMES_STORE, "Jaguar"),
new StorePlatformMetadata(StorePlatform.XBOX_GAME_PASS, "UnknownWorldsEntertainmen.GAMEPREVIEWSubnautica"),
], "Subnautica.png", GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, []),

new Game('Subnautica: Below Zero', 'SubnauticaBZ', 'SubnauticaBZ',
'SubnauticaZero', ['SubnauticaZero.exe'], 'SubnauticaZero_Data',
'https://belowzero.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
[new StorePlatformMetadata(StorePlatform.STEAM, '848450')], 'SubnauticaBelowZero.png',
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, ["bz", "sbz", "s:bz"]),
[
new StorePlatformMetadata(StorePlatform.STEAM, '848450'),
new StorePlatformMetadata(StorePlatform.XBOX_GAME_PASS, "UnknownWorldsEntertainmen.SubnauticaBelowZero"),
], 'SubnauticaBelowZero.png', GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, ["bz", "sbz", "s:bz"]),

new Game("Core Keeper", "CoreKeeper", "CoreKeeper",
"Core Keeper", ["CoreKeeper.exe"], "CoreKeeper_Data",
Expand Down
1 change: 1 addition & 0 deletions src/model/game/StorePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export enum StorePlatform {
EPIC_GAMES_STORE = "Epic Games Store",
OCULUS_STORE = "Oculus Store",
ORIGIN = "Origin / EA Desktop",
XBOX_GAME_PASS = "Xbox Game Pass",
OTHER = "Other",
}
31 changes: 31 additions & 0 deletions src/pages/Manager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,34 @@ import GameInstructions from '../r2mm/launching/instructions/GameInstructions';
});
}

changeGameInstallDirectoryGamePass() {
const ror2Directory: string = this.settings.getContext().gameSpecific.gameDirectory || this.computeDefaultInstallDirectory();
InteractionProvider.instance.selectFile({
title: `Locate gamelaunchhelper Executable`,
filters: [{ name: "gamelaunchhelper", extensions: ["exe"] }],
defaultPath: ror2Directory,
buttonLabel: 'Select Executable'
}).then(async files => {
if (files.length === 1) {
try {
const containsGameExecutable = (path.basename(files[0]).toLowerCase() === "gamelaunchhelper.exe");
if (containsGameExecutable) {
await this.settings.setGameDirectory(path.dirname(await FsProvider.instance.realpath(files[0])));
} else {
throw new Error("The selected executable is not gamelaunchhelper.exe");
}
} catch (e) {
const err: Error = e as Error;
this.showError(new R2Error(
"Failed to change the game directory",
err.message,
null
));
}
}
});
}

computeDefaultSteamDirectory() : string {
switch(process.platform){
case 'win32':
Expand Down Expand Up @@ -936,6 +964,9 @@ import GameInstructions from '../r2mm/launching/instructions/GameInstructions';
break;
case "ChangeGameDirectory":
this.changeGameInstallDirectory();
break;
case "ChangeGameDirectoryGamePass":
this.changeGameInstallDirectoryGamePass();
break;
case "ChangeSteamDirectory":
this.changeSteamDirectory();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import GameDirectoryResolverProvider from '../../../../ror2/game/GameDirectoryResolverProvider';
import Game from '../../../../../model/game/Game';
import R2Error from '../../../../../model/errors/R2Error';
import ManagerSettings from '../../../../../r2mm/manager/ManagerSettings';
import FsProvider from '../../../../../providers/generic/file/FsProvider';
import child from 'child_process';

export default class XboxGamePassDirectoryResolver extends GameDirectoryResolverProvider {

public async getDirectory(game: Game): Promise<string | R2Error> {
const settings = await ManagerSettings.getSingleton(game);
if (settings.getContext().gameSpecific.gameDirectory !== null) {
return settings.getContext().gameSpecific.gameDirectory!;
}

try {
const installDirectoryQuery = `get-appxpackage -Name ${game.activePlatform.storeIdentifier} | select -expand InstallLocation`;
const queryResult: string = child.execSync(`powershell.exe "${installDirectoryQuery}"`).toString().trim();
const realInstallLocation = await FsProvider.instance.realpath(queryResult);
if (FsProvider.instance.exists(realInstallLocation)) {
return realInstallLocation;
}
else {
throw new Error(realInstallLocation);
}
} catch (err) {
return new R2Error(
`Unable to resolve the ${game.displayName} install directory`,
err.message,
`Try manually locating the ${game.displayName} install directory through the settings`
);
}
}

public async getSteamDirectory(): Promise<string | R2Error> {
return new R2Error("Directory shouldn't be retrieved for a non-steam game", "", null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import GameDirectoryResolverImpl_Steam_Win from '../../../../r2mm/manager/win32/
import GameDirectoryResolverImpl_Steam_Linux from '../../../../r2mm/manager/linux/GameDirectoryResolver';
import PlatformInterceptorProvider from '../platform_interceptor/PlatformInterceptorProvider';
import EGSDirectoryResolver from '../directory_resolver/win/EGSDirectoryResolver';
import XboxGamePassDirectoryResolver from '../directory_resolver/win/XboxGamePassDirectoryResolver';
import DRMFreeDirectoryResolver from '../directory_resolver/win/DRMFreeDirectoryResolver';
import { PackageLoader } from '../../../../model/installing/PackageLoader';
import DarwinGameDirectoryResolver from '../../../../r2mm/manager/darwin/DarwinGameDirectoryResolver';
Expand All @@ -13,6 +14,7 @@ import DirectGameRunner from '../../../../r2mm/launching/runners/multiplatform/D
import SteamGameRunner_Linux from '../../../../r2mm/launching/runners/linux/SteamGameRunner_Linux';
import SteamGameRunner_Darwin from '../../../../r2mm/launching/runners/darwin/SteamGameRunner_Darwin';
import EgsGameRunner from '../../../../r2mm/launching/runners/multiplatform/EgsGameRunner';
import XboxGamePassGameRunner from '../../../../r2mm/launching/runners/windows/XboxGamePassGameRunner';

type RunnerType = {
[platkey in StorePlatform]: {
Expand Down Expand Up @@ -79,6 +81,11 @@ const RUNNERS: RunnerType = {
"darwin": new DirectGameRunner(),
}
},
[StorePlatform.XBOX_GAME_PASS]: {
[PackageLoader.BEPINEX]: {
"win32": new XboxGamePassGameRunner()
}
},
[StorePlatform.OTHER]: {
[PackageLoader.BEPINEX]: {
"win32": new DirectGameRunner(),
Expand Down Expand Up @@ -114,6 +121,9 @@ const RESOLVERS: ResolverType = {
"linux": new DRMFreeDirectoryResolver(),
"darwin": new DRMFreeDirectoryResolver()
},
[StorePlatform.XBOX_GAME_PASS]: {
"win32": new XboxGamePassDirectoryResolver()
},
[StorePlatform.OTHER]: {
"win32": new DRMFreeDirectoryResolver(),
"linux": new DRMFreeDirectoryResolver(),
Expand Down
61 changes: 61 additions & 0 deletions src/r2mm/launching/runners/windows/XboxGamePassGameRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import GameRunnerProvider from '../../../../providers/generic/game/GameRunnerProvider';
import Game from '../../../../model/game/Game';
import R2Error from '../../../../model/errors/R2Error';
import Profile from '../../../../model/Profile';
import ManagerSettings from '../../../manager/ManagerSettings';
import LoggerProvider, { LogSeverity } from '../../../../providers/ror2/logging/LoggerProvider';
import { exec } from 'child_process';
import GameInstructions from '../../instructions/GameInstructions';
import GameInstructionParser from '../../instructions/GameInstructionParser';
import GameDirectoryResolverProvider from '../../../../providers/ror2/game/GameDirectoryResolverProvider';
import FsProvider from '../../../../providers/generic/file/FsProvider';

export default class XboxGamePassGameRunner extends GameRunnerProvider {

public async getGameArguments(game: Game, profile: Profile): Promise<string | R2Error> {
const instructions = await GameInstructions.getInstructionsForGame(game, profile);
return await GameInstructionParser.parse(instructions.moddedParameters, game, profile);
}

public async startModded(game: Game, profile: Profile): Promise<void | R2Error> {
const args = await this.getGameArguments(game, profile);
if (args instanceof R2Error) {
return args
}
return this.start(game, args);
}

public async startVanilla(game: Game, profile: Profile): Promise<void | R2Error> {
const instructions = await GameInstructions.getInstructionsForGame(game, profile);
return this.start(game, instructions.vanillaParameters);
}

async start(game: Game, args: string): Promise<void | R2Error> {
return new Promise(async (resolve, reject) => {
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);
const gameExecutable = (await FsProvider.instance.readdir(gameDir))
.find((x: string) => "gamelaunchhelper.exe" === x);

LoggerProvider.instance.Log(LogSeverity.INFO, `Running command: ${gameDir}/${gameExecutable} ${args} ${settings.getContext().gameSpecific.launchParameters}`);

exec(`"${gameExecutable}" ${args} ${settings.getContext().gameSpecific.launchParameters}`, {
cwd: gameDir,
windowsHide: false,
}, (err => {
if (err !== null) {
LoggerProvider.instance.Log(LogSeverity.ACTION_STOPPED, 'Error was thrown whilst starting modded');
LoggerProvider.instance.Log(LogSeverity.ERROR, err.message);
const r2err = new R2Error('Error starting the game', err.message, 'Ensure that the game directory has been set correctly in the settings');
return reject(r2err);
}
return resolve();
}));
});
}
}