Skip to content

Commit

Permalink
resolves #13, offers to update bepinex when lacking `--doorstop_enabl…
Browse files Browse the repository at this point in the history
…ed` support
  • Loading branch information
toebeann committed Aug 23, 2024
1 parent 15e9328 commit 8db6b9b
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 54 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "gib",
"version": "0.3.5",
"version": "0.4.0",
"description": "A TUI application for automating the installation of BepInEx",
"license": "ISC",
"author": "Tobey Blaber",
Expand Down Expand Up @@ -72,6 +72,7 @@
"find-process": "^1.4.7",
"fs-extra": "^11.2.0",
"gradient-string": "^2.0.2",
"jszip": "^3.10.1",
"lodash.camelcase": "^4.3.0",
"lodash.mapkeys": "^4.6.0",
"open": "^10.1.0",
Expand Down
180 changes: 127 additions & 53 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ import { watch } from "chokidar";
import cliWidth from "cli-width";
import findProcess from "find-process";
import { ensureDir } from "fs-extra";
import JSZip from "jszip";
import open from "open";
import { build as buildPlist } from "plist";
import readlineSync from "readline-sync";
import { quote } from "shell-quote";
import terminalLink from "terminal-link";
import unquote from "unquote";
import wrapAnsi from "wrap-ansi";
import { z } from "zod";
import { renderLogo } from "./renderLogo.ts";
import { exec } from "../fs/exec.ts";
import { getAppById, getAppsByPath, launch } from "../launchers/steam/app.ts";
Expand Down Expand Up @@ -483,6 +485,45 @@ log(
]),
);

const configureBepInExScript = async (path: string, executableName: string) => {
const bepinexScriptContents = await readFile(path, "utf8");
let output = bepinexScriptContents;

// check if line endings are CRLF and fix them if needed
if (output.includes("\r\n")) {
output = output.replaceAll("\r\n", "\n");
}

// check if the run_bepinex.sh needs to be configured, and configure it
if (output.includes('\nexecutable_name=""')) {
output = output.replace(
'\nexecutable_name=""',
`\nexecutable_name="${executableName}"`,
);
}

// workaround for issue with BepInEx v5.4.23 run_bepinex.sh script not working
// for some games unless ran from the game folder for some reason
if (
output.includes("BASEDIR=") &&
!output.includes('cd "$BASEDIR"')
) {
const index = output.indexOf("\n", output.indexOf("BASEDIR="));
output = `${
output.slice(0, index)
}\n\n# GIB: workaround for some games only working if script is run from game dir\ncd "$BASEDIR"${
output.slice(index)
}`;
}

// write the changes, if any
if (output !== bepinexScriptContents) {
await writeFile(path, output, "utf8");
}

await chmod(path, 0o764);
};

const installBepInEx = async () => {
const i = bepinexPath.split(sep).length;
const glob = new Glob("**/*");
Expand All @@ -501,42 +542,7 @@ const installBepInEx = async () => {
await copyFile(path, destination);

if (basename(path) === "run_bepinex.sh" && dirname(path) === bepinexPath) {
const bepinexScriptContents = await readFile(destination, "utf8");
let output = bepinexScriptContents;

// check if line endings are CRLF and fix them if needed
if (output.includes("\r\n")) {
output = output.replaceAll("\r\n", "\n");
}

// check if the run_bepinex.sh needs to be configured, and configure it
if (output.includes('\nexecutable_name=""')) {
output = output.replace(
'\nexecutable_name=""',
`\nexecutable_name="${basename(gameAppPath)}"`,
);
}

// workaround for issue with BepInEx v5.4.23 run_bepinex.sh script not working
// for some games unless ran from the game folder for some reason
if (
output.includes("BASEDIR=") &&
!output.includes('cd "$BASEDIR"')
) {
const index = output.indexOf("\n", output.indexOf("BASEDIR="));
output = `${
output.slice(0, index)
}\n\n# GIB: workaround for some games only working if script is run from game dir\ncd "$BASEDIR"${
output.slice(index)
}`;
}

// write the changes, if any
if (output !== bepinexScriptContents) {
await writeFile(destination, output, "utf8");
}

await chmod(destination, 0o764);
await configureBepInExScript(destination, basename(gameAppPath));
}
}
};
Expand Down Expand Up @@ -624,36 +630,34 @@ if (steamApps.length === 1) {
: chalk.redBright.bold("not found"),
"in your BepInEx pack.",
].join(" "),
null,
switchSupported
? [
"gib will set this flag in the vanilla shortcut. Steam may prompt",
"you about this flag whenever you launch the vanilla shortcut.",
].join(" ")
: [
"As your BepInEx pack does not support this feature, we will not",
"add the vanilla shortcut. We recommend continuing this",
"installation so that so that any game-specific configuration or",
"features included with this pack are installed, and then running",
"gib a second time with the latest release of BepInEx 5,",
link(
"downloaded directly from its official source.",
"https://github.com/BepInEx/BepInEx/releases/latest",
),
`${EOL}This will allow gib to set up the vanilla shortcut while`,
"retaining any game-specific configuration or features of the pack.",
"As your BepInEx pack does not support this feature, gib can",
"attempt to add support by downloading the latest version of",
"BepInEx and updating the provided pack. This will allow gib to set",
"up the vanilla shortcut while retaining any game-specific",
"customisations from the pack.",
].join(" "),
null,
]),
);

shouldAddShortcut = switchSupported && confirmShim(
shouldAddShortcut = confirmShim(
wrap(
`${EOL}Add experimental Steam shortcut to launch ${game} without mods?`,
switchSupported
? `Add experimental Steam shortcut to launch ${game} without mods?`
: [
`Add experimental Steam shortcut to launch ${game} without mods by`,
"updating this pack to the latest BepInEx 5 release?",
].join(" "),
),
);

if (!switchSupported) pressHeartToContinue();
else log();

log(
wrap(
chalk.bold(`gib will now perform the following operations:${EOL}`),
Expand All @@ -668,6 +672,8 @@ if (steamApps.length === 1) {
`configure Steam for user ${username} to launch ${game} modded`,
"with BepInEx",
].join(" "),
shouldAddShortcut && !switchSupported &&
"download the latest BepInEx 5 release to update your BepInex pack",
shouldAddShortcut &&
[
`add a Steam shortcut for user ${username} to launch ${game}`,
Expand Down Expand Up @@ -695,7 +701,75 @@ if (steamApps.length === 1) {
log();

operations.push(
installBepInEx(),
(async () => {
const installing = installBepInEx();
if (shouldAddShortcut && !switchSupported) {
let response: Promise<Response>;
try {
const releases = z.object({
target_commitish: z.string(),
prerelease: z.boolean(),
assets: z.object({
name: z.string(),
browser_download_url: z.string(),
}).array(),
}).array()
.parse(
await (await fetch(
"https://api.github.com/repos/BepInEx/BepInEx/releases",
)).json(),
);
const { assets } = releases
.find(({ target_commitish, prerelease, assets }) =>
!prerelease &&
target_commitish === "v5-lts" &&
assets.find(({ name }) =>
name.toLowerCase().includes("macos_x64") &&
name.toLowerCase().endsWith(".zip")
)
) ?? {};
const { browser_download_url } = assets?.find(({ name }) =>
name.toLowerCase().includes("macos_x64") &&
name.toLowerCase().endsWith(".zip")
) ?? {};
if (!browser_download_url) {
throw "Couldn't get latest BepInEx 5 release asset";
}
response = fetch(browser_download_url);
} catch {
response = fetch(
"https://github.com/BepInEx/BepInEx/releases/download/v5.4.23.2/BepInEx_macos_x64_5.4.23.2.zip",
);
}

const [archive] = await Promise.all([
response
.then((response) => response.arrayBuffer())
.then(JSZip.loadAsync),
installing,
]);

const filenames = Object.keys(archive.files);
if (!filenames.includes("run_bepinex.sh")) {
throw "Downloded BepInEx pack appears invalid";
}
await Promise.all(filenames
.map((filename) =>
archive.file(filename)!
.async("uint8array")
.then((data) => writeFile(resolve(gamePath, filename), data))
.then((_) =>
filename === "run_bepinex.sh" &&
configureBepInExScript(
resolve(gamePath, filename),
basename(gameAppPath),
) || undefined
)
));
} else {
await installing;
}
})(),
isOpen()
.then(async (isOpen) => {
if (isOpen && !await quit()) {
Expand Down

0 comments on commit 8db6b9b

Please sign in to comment.