* Style Fix. * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
120 lines
4.1 KiB
TypeScript
120 lines
4.1 KiB
TypeScript
/*
|
|
* Vencord, a Discord client mod
|
|
* Copyright (c) 2026 Vendicated and contributors
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "original-fs";
|
|
import { basename, dirname, join } from "path";
|
|
|
|
const STUB_PACKAGE = JSON.stringify({ name: "discord", main: "index.js" });
|
|
const VERSION_PREFIX = "app-";
|
|
|
|
const makeStubIndex = (patcherPath: string) =>
|
|
`require(${JSON.stringify(patcherPath)});`;
|
|
|
|
/** `_app.asar` next to `app.asar` marks any patched install. */
|
|
export const isAlreadyPatched = (resources: string) =>
|
|
existsSync(join(resources, "_app.asar"));
|
|
|
|
/**
|
|
* apply the folder-shim patch to a discord `resources/` directory.
|
|
*
|
|
* renames vanilla `app.asar` to `_app.asar`, creates an `app/` folder
|
|
* whose `index.js` requires the given patcher script. electron prefers
|
|
* the folder over the asar of the same name and loads it first.
|
|
*
|
|
* idempotent. returns `false` if the directory is already patched or
|
|
* has no vanilla `app.asar`. throws on partial failure after rolling
|
|
* back any disk changes already made.
|
|
*/
|
|
export const patchResourcesDir = (resources: string, patcherJsPath: string): boolean => {
|
|
const app = join(resources, "app.asar");
|
|
const _app = join(resources, "_app.asar");
|
|
|
|
if (isAlreadyPatched(resources)) return false;
|
|
if (!existsSync(app)) return false;
|
|
try {
|
|
if (lstatSync(app).isDirectory()) return false;
|
|
} catch {
|
|
return false;
|
|
}
|
|
|
|
const undo: Array<() => void> = [];
|
|
try {
|
|
renameSync(app, _app);
|
|
undo.push(() => renameSync(_app, app));
|
|
|
|
mkdirSync(app);
|
|
undo.push(() => {
|
|
const indexPath = join(app, "index.js");
|
|
const pkgPath = join(app, "package.json");
|
|
if (existsSync(indexPath)) unlinkSync(indexPath);
|
|
if (existsSync(pkgPath)) unlinkSync(pkgPath);
|
|
rmdirSync(app);
|
|
});
|
|
|
|
writeFileSync(join(app, "package.json"), STUB_PACKAGE);
|
|
writeFileSync(join(app, "index.js"), makeStubIndex(patcherJsPath));
|
|
return true;
|
|
} catch (err) {
|
|
for (let i = undo.length - 1; i >= 0; i--) {
|
|
try {
|
|
undo[i]();
|
|
} catch (cleanupErr) {
|
|
console.error("[Equicord] Rollback step failed", cleanupErr);
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
const parsePart = (s: string) => parseInt(s, 10) || 0;
|
|
|
|
const isNewer = ($new: string, old: string): boolean => {
|
|
const newParts = $new.slice(VERSION_PREFIX.length).split(".").map(parsePart);
|
|
const oldParts = old.slice(VERSION_PREFIX.length).split(".").map(parsePart);
|
|
const len = Math.max(newParts.length, oldParts.length);
|
|
for (let i = 0; i < len; i++) {
|
|
const n = newParts[i] ?? 0;
|
|
const o = oldParts[i] ?? 0;
|
|
if (n > o) return true;
|
|
if (n < o) return false;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* find the newest sibling `app-VERSION` directory's `resources/` path.
|
|
*
|
|
* squirrel-only layout. returns `null` on non-win32 platforms, or when
|
|
* the running process is already in the newest sibling.
|
|
*/
|
|
export const findStaleSibling = (currentExeDir: string): string | null => {
|
|
if (process.platform !== "win32") return null;
|
|
|
|
const discordPath = dirname(currentExeDir);
|
|
const currentVersion = basename(currentExeDir);
|
|
|
|
let latest = currentVersion;
|
|
try {
|
|
for (const name of readdirSync(discordPath)) {
|
|
if (!name.startsWith(VERSION_PREFIX)) continue;
|
|
let isDir = false;
|
|
try {
|
|
isDir = statSync(join(discordPath, name)).isDirectory();
|
|
} catch (statErr) {
|
|
console.error("[Equicord] Skipping unreadable sibling", name, statErr);
|
|
continue;
|
|
}
|
|
if (!isDir) continue;
|
|
if (isNewer(name, latest)) latest = name;
|
|
}
|
|
} catch (err) {
|
|
console.error("[Equicord] Failed to scan for sibling versions", err);
|
|
return null;
|
|
}
|
|
|
|
if (latest === currentVersion) return null;
|
|
return join(discordPath, latest, "resources");
|
|
};
|