Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[bugfix-733] manually set WINEPREFIX when executing IPA.exe #734

Merged
merged 4 commits into from
Jan 11, 2025
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ module.exports = {
"react/button-has-type": "off",
"max-classes-per-file": "off",
"jest/no-standalone-expect": "off",
"no-bitwise": "off",
},
parserOptions: {
ecmaVersion: 2020,
Expand Down
10 changes: 6 additions & 4 deletions src/__tests__/unit/os.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BsmShellLog,
bsmSpawn,
// bsmExec,
isProcessRunning,
Expand All @@ -23,6 +24,7 @@ jest.mock("electron-log", () => ({
info: jest.fn(),
error: jest.fn(),
}));

jest.mock("ps-list", () => (): unknown[] => []);

const IS_WINDOWS = process.platform === "win32";
Expand Down Expand Up @@ -76,7 +78,7 @@ describe("Test os.helpers bsmSpawn", () => {
it("Simple spawn command with logging", () => {
bsmSpawn("mkdir", {
args: ["new_folder"],
log: true,
log: BsmShellLog.Command,
});
expect(spawnSpy).toHaveBeenCalledTimes(1);
expect(spawnSpy).toHaveBeenCalledWith("mkdir new_folder", expect.anything());
Expand All @@ -86,7 +88,7 @@ describe("Test os.helpers bsmSpawn", () => {

it("Complex spawn command call (Mods install)", () => {
bsmSpawn(`"./BSIPA.exe" "./Beat Saber.exe" -n`, {
log: true,
log: BsmShellLog.Command,
linux: { prefix: `"./wine64"` },
});

Expand All @@ -109,7 +111,7 @@ describe("Test os.helpers bsmSpawn", () => {
detached: true,
env: BS_ENV,
},
log: true,
log: BsmShellLog.Command,
linux: { prefix: `"./proton" run` },
});

Expand Down Expand Up @@ -152,7 +154,7 @@ describe("Test os.helpers bsmSpawn", () => {
detached: true,
env: newEnv,
},
log: true,
log: BsmShellLog.Command,
linux: { prefix: `"./proton" run` },
flatpak: {
host: true,
Expand Down
47 changes: 27 additions & 20 deletions src/main/helpers/os.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,23 @@ type FlatpakOptions = {
env?: string[];
};

export type BsmSpawnOptions = {
args?: string[];
options?: cp.SpawnOptions;
log?: boolean;
linux?: LinuxOptions;
flatpak?: FlatpakOptions;
export enum BsmShellLog {
Command = 0x0000_0001,
EnvVariables = 0x0000_0002,
};

export type BsmExecOptions = {
interface BsmShellOptions<OptionsType> {
args?: string[];
options?: cp.ExecOptions;
log?: boolean;
options?: OptionsType;
// Look into BsmShellLog values
log?: number;
linux?: LinuxOptions;
flatpak?: FlatpakOptions;
};

export type BsmSpawnOptions = BsmShellOptions<cp.SpawnOptions>;
export type BsmExecOptions = BsmShellOptions<cp.ExecOptions>;

function updateCommand(command: string, options: BsmSpawnOptions) {
if (options?.args) {
command += ` ${options.args.join(" ")}`;
Expand Down Expand Up @@ -63,14 +64,25 @@ function updateCommand(command: string, options: BsmSpawnOptions) {
return command;
}

function logValues(shell: "spawn" | "exec", command: string, options?: BsmShellOptions<cp.SpawnOptions | cp.ExecOptions>) {
const platform = process.platform === "win32" ? "Windows" : "Linux";
const optionsLog = options?.log || 0;

if ((optionsLog & BsmShellLog.EnvVariables) > 0) {
log.info(platform, shell, "env", options?.options?.env);
}

if ((optionsLog & BsmShellLog.Command) > 0) {
log.info(platform, shell, "command\n>", command);
}
}

export function bsmSpawn(command: string, options?: BsmSpawnOptions) {
options = options || {};
options.options = options.options || {};
command = updateCommand(command, options);

if (options?.log) {
log.info(process.platform === "win32" ? "Windows" : "Linux", "spawn command\n>", command);
}
logValues("spawn", command, options);

return cp.spawn(command, options.options);
}
Expand All @@ -83,12 +95,7 @@ export function bsmExec(command: string, options?: BsmExecOptions): Promise<{
options.options = options.options || {};
command = updateCommand(command, options);

if (options?.log) {
log.info(
process.platform === "win32" ? "Windows" : "Linux",
"exec command\n>", command
);
}
logValues("exec", command, options);

return new Promise((resolve, reject) => {
cp.exec(command, options?.options || {}, (error: Error, stdout: string, stderr: string) => {
Expand All @@ -111,7 +118,7 @@ async function isProcessRunningLinux(name: string): Promise<boolean> {
try {
const processName = transformProcessNameForPS(name);
const { stdout: count } = await bsmExec(`ps awwxo args | grep -c "${processName}"`, {
log: true,
log: BsmShellLog.Command,
flatpak: { host: IS_FLATPAK },
});

Expand Down Expand Up @@ -157,7 +164,7 @@ async function getProcessIdLinux(name: string): Promise<number | null> {
try {
const processName = transformProcessNameForPS(name);
const { stdout } = await bsmExec(`ps awwxo pid,args | grep "${processName}"`, {
log: true,
log: BsmShellLog.Command,
flatpak: { host: IS_FLATPAK },
});

Expand Down
4 changes: 2 additions & 2 deletions src/main/services/bs-launcher/abstract-launcher.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from "path";
import log from "electron-log";
import { sToMs } from "../../../shared/helpers/time.helpers";
import { LinuxService } from "../linux.service";
import { bsmSpawn } from "main/helpers/os.helpers";
import { BsmShellLog, bsmSpawn } from "main/helpers/os.helpers";
import { IS_FLATPAK } from "main/constants";
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";

Expand Down Expand Up @@ -56,7 +56,7 @@ export abstract class AbstractLauncherService {

spawnOptions.shell = true; // For windows to spawn properly
return bsmSpawn(`"${bsExePath}"`, {
args, options: spawnOptions, log: true,
args, options: spawnOptions, log: BsmShellLog.Command,
linux: { prefix: this.linux.getProtonPrefix() },
flatpak: {
host: IS_FLATPAK,
Expand Down
35 changes: 24 additions & 11 deletions src/main/services/linux.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import log from "electron-log";
import path from "path";
import { BS_APP_ID, IS_FLATPAK, PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
import { StaticConfigurationService } from "./static-configuration.service";
import { SteamService } from "./steam.service";
import { CustomError } from "shared/models/exceptions/custom-error.class";
import { BSLaunchError, LaunchOption } from "shared/models/bs-launch";
import { bsmExec } from "main/helpers/os.helpers";
import { BsmShellLog, bsmExec } from "main/helpers/os.helpers";
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";

export class LinuxService {
Expand All @@ -19,16 +20,32 @@ export class LinuxService {
}

private readonly staticConfig: StaticConfigurationService;
private readonly steamService: SteamService;
private protonPrefix = "";

private nixOS: boolean | undefined;

private constructor() {
this.staticConfig = StaticConfigurationService.getInstance();
this.steamService = SteamService.getInstance();
}

// === Launching === //

private async getCompatDataPath() {
// Create the compat data path if it doesn't exist.
// If the user never ran Beat Saber through steam before
// using bsmanager, it won't exist, and proton will fail
// to launch the game.
const commonFolder = await this.steamService.getGameFolder(BS_APP_ID);
const compatDataPath = path.resolve(commonFolder, "..", "compatdata", BS_APP_ID);
if (!fs.existsSync(compatDataPath)) {
log.info(`Proton compat data path not found at '${compatDataPath}', creating directory`);
fs.mkdirSync(compatDataPath);
}
return compatDataPath;
}

public async setupLaunch(
launchOptions: LaunchOption,
steamPath: string,
Expand All @@ -40,15 +57,7 @@ export class LinuxService {
launchOptions.admin = false;
}

// Create the compat data path if it doesn't exist.
// If the user never ran Beat Saber through steam before
// using bsmanager, it won't exist, and proton will fail
// to launch the game.
const compatDataPath = `${steamPath}/steamapps/compatdata/${BS_APP_ID}`;
if (!fs.existsSync(compatDataPath)) {
log.info(`Proton compat data path not found at '${compatDataPath}', creating directory`);
fs.mkdirSync(compatDataPath);
}
const compatDataPath = this.getCompatDataPath();

if (!this.staticConfig.has("proton-folder")) {
throw CustomError.fromError(
Expand Down Expand Up @@ -120,6 +129,10 @@ export class LinuxService {
return winePath;
}

public async getWinePrefixPath(): Promise<string> {
return path.join(await this.getCompatDataPath(), "pfx");
}

public getProtonPrefix(): string {
// Set in setupLaunch
return this.protonPrefix;
Expand All @@ -134,7 +147,7 @@ export class LinuxService {

try {
await bsmExec("nixos-version", {
log: true,
log: BsmShellLog.Command,
flatpak: { host: IS_FLATPAK },
});
this.nixOS = true;
Expand Down
26 changes: 18 additions & 8 deletions src/main/services/mods/bs-mods-manager.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { LinuxService } from "../linux.service";
import { tryit } from "shared/helpers/error.helpers";
import crypto from "crypto";
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
import { bsmSpawn } from "main/helpers/os.helpers";
import { BsmShellLog, bsmSpawn } from "main/helpers/os.helpers";
import { BbmFullMod, BbmModVersion, ExternalMod } from "../../../shared/models/mods/mod.interface";

export class BsModsManagerService {
Expand Down Expand Up @@ -139,25 +139,35 @@ export class BsModsManagerService {
return false;
}

const env: Record<string, string> = {};
const cmd = `"${ipaPath}" "${bsExePath}" ${args.join(" ")}`;
let winePath: string = "";
if (process.platform === "linux") {
const { error, result } = tryit(() => this.linuxService.getWinePath());
if (error) {
log.error(error);
const { error: winePathError, result: winePathResult } =
tryit(() => this.linuxService.getWinePath());
if (winePathError) {
log.error(winePathError);
return false;
}
winePath = `"${result}"`;
winePath = `"${winePathResult}"`;

const { error: winePrefixError, result: winePrefixResult } =
await tryit(async () => this.linuxService.getWinePrefixPath());
if (winePrefixError) {
log.warn("Could not get WINEPREFIX value", winePrefixError);
} else {
env.WINEPREFIX = winePrefixResult;
}
}

return new Promise<boolean>(resolve => {
log.info("START IPA PROCESS", cmd);
const processIPA = bsmSpawn(cmd, {
log: true,
log: BsmShellLog.Command | BsmShellLog.EnvVariables,
options: {
cwd: versionPath,
detached: true,
shell: true
shell: true,
env
},
linux: { prefix: winePath },
});
Expand Down
3 changes: 2 additions & 1 deletion src/main/services/steam.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export class SteamService {
}

if (libraryFolders[libKey].apps[gameId] != null) {
return path.join(libraryFolders[libKey].path, "steamapps", "common", gameFolder);
const commonFolder = path.join(libraryFolders[libKey].path, "steamapps", "common");
return gameFolder ? path.join(commonFolder, gameFolder) : commonFolder;
}
}

Expand Down
Loading