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

Asar handling fixed #594

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
11 changes: 8 additions & 3 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import electron from "electron";
import { CONFIG_PATHS } from "src/util.mjs";
import type { RepluggedWebContents } from "../types";
import { getSetting } from "./ipc/settings";

const electronPath = require.resolve("electron");
const discordPath = join(dirname(require.main!.filename), "..", "app.orig.asar");
// require.main!.filename = discordMain;
Expand Down Expand Up @@ -170,10 +169,16 @@ electron.app.once("ready", () => {
filePath = join(CONFIG_PATHS.quickcss, reqUrl.pathname);
break;
case "theme":
filePath = join(CONFIG_PATHS.themes, reqUrl.pathname);
filePath = join(
reqUrl.pathname.includes(".asar") ? CONFIG_PATHS.temp_themes : CONFIG_PATHS.themes,
reqUrl.pathname.replace(".asar", ""),
);
break;
case "plugin":
filePath = join(CONFIG_PATHS.plugins, reqUrl.pathname);
filePath = join(
reqUrl.pathname.includes(".asar") ? CONFIG_PATHS.temp_plugins : CONFIG_PATHS.plugins,
reqUrl.pathname.replace(".asar", ""),
);
break;
}
cb({ path: filePath });
Expand Down
43 changes: 31 additions & 12 deletions src/main/ipc/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,31 @@ IPC events:
- REPLUGGED_UNINSTALL_PLUGIN: returns whether a plugin by the provided name was successfully uninstalled
*/

import { readFile, readdir, readlink, rm, stat } from "fs/promises";
import { readFile, readdir, readlink, stat } from "fs/promises";
import { extname, join, sep } from "path";
import { ipcMain, shell } from "electron";
import { RepluggedIpcChannels, type RepluggedPlugin } from "../../types";
import { plugin } from "../../types/addon";
import type { Dirent, Stats } from "fs";
import { CONFIG_PATHS } from "src/util.mjs";
import { type Dirent, type Stats, rmdirSync, unlinkSync } from "fs";
import { CONFIG_PATHS, extractAddon } from "src/util.mjs";

const PLUGINS_DIR = CONFIG_PATHS.plugins;
const TEMP_PLUGINS_DIR = CONFIG_PATHS.temp_plugins;

export const isFileAPlugin = (f: Dirent | Stats, name: string): boolean => {
return f.isDirectory() || (f.isFile() && extname(name) === ".asar");
};

async function getPlugin(pluginName: string): Promise<RepluggedPlugin> {
const manifestPath = join(PLUGINS_DIR, pluginName, "manifest.json");
if (!manifestPath.startsWith(`${PLUGINS_DIR}${sep}`)) {
const isAsar = pluginName.includes(".asar");
const pluginPath = join(PLUGINS_DIR, pluginName);
const realPluginPath = isAsar
? join(TEMP_PLUGINS_DIR, pluginName.replace(/\.asar$/, ""))
: pluginPath; // Remove ".asar" from the directory name
if (isAsar) await extractAddon(pluginPath, realPluginPath);

const manifestPath = join(realPluginPath, "manifest.json");
if (!manifestPath.startsWith(`${realPluginPath}${sep}`)) {
// Ensure file changes are restricted to the base path
throw new Error("Invalid plugin name");
}
Expand All @@ -40,7 +48,7 @@ async function getPlugin(pluginName: string): Promise<RepluggedPlugin> {
const cssPath = data.manifest.renderer?.replace(/\.js$/, ".css");
const hasCSS =
cssPath &&
(await stat(join(PLUGINS_DIR, pluginName, cssPath))
(await stat(join(realPluginPath, cssPath))
.then(() => true)
.catch(() => false));

Expand Down Expand Up @@ -90,17 +98,28 @@ ipcMain.handle(RepluggedIpcChannels.LIST_PLUGINS, async (): Promise<RepluggedPlu
return plugins;
});

ipcMain.handle(RepluggedIpcChannels.UNINSTALL_PLUGIN, async (_, pluginName: string) => {
ipcMain.handle(RepluggedIpcChannels.UNINSTALL_PLUGIN, (_, pluginName: string) => {
const isAsar = pluginName.includes(".asar");
const pluginPath = join(PLUGINS_DIR, pluginName);
if (!pluginPath.startsWith(`${PLUGINS_DIR}${sep}`)) {
const realPluginPath = isAsar
? join(TEMP_PLUGINS_DIR, pluginName.replace(".asar", ""))
: pluginPath; // Remove ".asar" from the directory name

if (!realPluginPath.startsWith(`${isAsar ? TEMP_PLUGINS_DIR : PLUGINS_DIR}${sep}`)) {
// Ensure file changes are restricted to the base path
throw new Error("Invalid plugin name");
}

await rm(pluginPath, {
recursive: true,
force: true,
});
if (isAsar) {
unlinkSync(pluginPath);
rmdirSync(realPluginPath, { recursive: true });
} else rmdirSync(pluginPath, { recursive: true });
});

ipcMain.on(RepluggedIpcChannels.OPEN_PLUGINS_FOLDER, () => shell.openPath(PLUGINS_DIR));

ipcMain.on(RepluggedIpcChannels.CLEAR_TEMP_THEME, () => {
try {
rmdirSync(TEMP_PLUGINS_DIR, { recursive: true });
} catch {}
});
46 changes: 30 additions & 16 deletions src/main/ipc/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,33 @@ IPC events:
- REPLUGGED_LIST_THEMES: returns an array of all valid themes available
- REPLUGGED_UNINSTALL_THEME: uninstalls a theme by name
*/

import { readFile, readdir, readlink, rm, stat } from "fs/promises";
import { type Dirent, type Stats, rmdirSync, unlinkSync } from "fs";
import { readFile, readdir, readlink, stat } from "fs/promises";
import { extname, join, sep } from "path";
import { ipcMain, shell } from "electron";
import { RepluggedIpcChannels, type RepluggedTheme } from "../../types";
import { theme } from "../../types/addon";
import { CONFIG_PATHS } from "src/util.mjs";
import type { Dirent, Stats } from "fs";
import { CONFIG_PATHS, extractAddon } from "src/util.mjs";

const THEMES_DIR = CONFIG_PATHS.themes;
const TEMP_THEMES_DIR = CONFIG_PATHS.temp_themes;

export const isFileATheme = (f: Dirent | Stats, name: string): boolean => {
return f.isDirectory() || (f.isFile() && extname(name) === ".asar");
};

async function getTheme(path: string): Promise<RepluggedTheme> {
const manifestPath = join(THEMES_DIR, path, "manifest.json");
if (!manifestPath.startsWith(`${THEMES_DIR}${sep}`)) {
const isAsar = path.includes(".asar");
const themePath = join(THEMES_DIR, path);
const realThemePath = isAsar ? join(TEMP_THEMES_DIR, path.replace(/\.asar$/, "")) : themePath; // Remove ".asar" from the directory name
if (isAsar) await extractAddon(themePath, realThemePath);

const manifestPath = join(realThemePath, "manifest.json");

if (!manifestPath.startsWith(`${realThemePath}${sep}`)) {
// Ensure file changes are restricted to the base path
throw new Error("Invalid theme name");
throw new Error("Invalid plugin name");
yofukashino marked this conversation as resolved.
Show resolved Hide resolved
}

const manifest: unknown = JSON.parse(
await readFile(manifestPath, {
encoding: "utf-8",
Expand Down Expand Up @@ -77,17 +82,26 @@ ipcMain.handle(RepluggedIpcChannels.LIST_THEMES, async (): Promise<RepluggedThem
return themes;
});

ipcMain.handle(RepluggedIpcChannels.UNINSTALL_THEME, async (_, themeName: string) => {
ipcMain.handle(RepluggedIpcChannels.UNINSTALL_THEME, (_, themeName: string) => {
const isAsar = themeName.includes(".asar");
const themePath = join(THEMES_DIR, themeName);
if (!themePath.startsWith(`${THEMES_DIR}${sep}`)) {
// Ensure file changes are restricted to the base path
const realThemePath = isAsar
? join(TEMP_THEMES_DIR, themeName.replace(/\.asar$/, ""))
: themePath; // Remove ".asar" from the directory name

if (!realThemePath.startsWith(`${isAsar ? TEMP_THEMES_DIR : THEMES_DIR}${sep}`)) {
throw new Error("Invalid theme name");
}

await rm(themePath, {
recursive: true,
force: true,
});
if (isAsar) {
unlinkSync(themePath);
rmdirSync(realThemePath, { recursive: true });
} else rmdirSync(themePath, { recursive: true });
});

ipcMain.on(RepluggedIpcChannels.OPEN_THEMES_FOLDER, () => shell.openPath(THEMES_DIR));

ipcMain.on(RepluggedIpcChannels.CLEAR_TEMP_THEME, () => {
try {
rmdirSync(TEMP_THEMES_DIR, { recursive: true });
} catch {}
});
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export enum RepluggedIpcChannels {
INSTALL_ADDON = "REPLUGGED_INSTALL_ADDON",
OPEN_PLUGINS_FOLDER = "REPLUGGED_OPEN_PLUGINS_FOLDER",
OPEN_THEMES_FOLDER = "REPLUGGED_OPEN_THEMES_FOLDER",
CLEAR_TEMP_PLUGIN = "REPLUGGED_CLEAR_PLUGIN_TEMP",
CLEAR_TEMP_THEME = "REPLUGGED_CLEAR_THEME_TEMP",
OPEN_SETTINGS_FOLDER = "REPLUGGED_OPEN_SETTINGS_FOLDER",
OPEN_QUICKCSS_FOLDER = "REPLUGGED_OPEN_QUICKCSS_FOLDER",
GET_REPLUGGED_VERSION = "REPLUGGED_GET_REPLUGGED_VERSION",
Expand Down
35 changes: 30 additions & 5 deletions src/util.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import esbuild from "esbuild";
import { execSync } from "child_process";
import { chownSync, existsSync, mkdirSync, statSync, writeFileSync } from "fs";
import { extractAll } from "@electron/asar";
import { tmpdir } from "os";
import { chownSync, existsSync, mkdirSync, mkdtempSync, statSync, writeFileSync } from "fs";
import path, { join } from "path";
import chalk from "chalk";

Expand Down Expand Up @@ -55,15 +57,27 @@ const CONFIG_FOLDER_NAMES = [
"settings",
"quickcss",
"react-devtools",
"temp_themes",
"temp_plugins",
] as const;

export const CONFIG_PATHS = Object.fromEntries(
CONFIG_FOLDER_NAMES.map((name) => {
const path = join(CONFIG_PATH, name);
if (!existsSync(path)) {
mkdirSync(path);
switch (name) {
case "temp_themes": {
return [name, mkdtempSync(join(tmpdir(), "replugged-theme-"))];
}
case "temp_plugins": {
return [name, mkdtempSync(join(tmpdir(), "replugged-plugin-"))];
}
default: {
const path = join(CONFIG_PATH, name);
if (!existsSync(path)) {
mkdirSync(path);
}
return [name, path];
}
}
return [name, path];
}),
) as Record<(typeof CONFIG_FOLDER_NAMES)[number], string>;

Expand Down Expand Up @@ -145,3 +159,14 @@ export const logBuildPlugin: esbuild.Plugin = {
});
},
};

export const extractAddon = async (srcPath: string, destPath: string): Promise<void> => {
return new Promise<void>((res) => {
// Ensure the destination directory exists
mkdirSync(destPath, { recursive: true });

// Extract the contents of the asar archive directly into the destination directory
extractAll(srcPath, destPath);
res();
});
};
Loading