From eef5dba950ef20989486789d7f7aac9b93371c70 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Wed, 26 Oct 2022 22:13:38 -0400 Subject: [PATCH 01/12] Switch to discord_desktop_core injection --- src/renderer/actions/install.js | 56 ++++++++++++++++++++++++--------- src/renderer/actions/paths.js | 35 ++++++--------------- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/renderer/actions/install.js b/src/renderer/actions/install.js index e917da2b..d0a1b050 100644 --- a/src/renderer/actions/install.js +++ b/src/renderer/actions/install.js @@ -4,6 +4,7 @@ import {remote, shell} from "electron"; import {promises as fs} from "fs"; import path from "path"; import phin from "phin"; +import rimraf from "rimraf"; import {log, lognewline} from "./utils/log"; import succeed from "./utils/succeed"; @@ -18,7 +19,7 @@ const MAKE_DIR_PROGRESS = 30; const CHECK_OLD_INSTALL = 40; const TRANSFER_OLD_ADDONS = 50; const DOWNLOAD_PACKAGE_PROGRESS = 60; -const INJECT_SHIM_PROGRESS = 90; +const INJECT_SHIM_PROGRESS = 80; const RESTART_DISCORD_PROGRESS = 100; const oldBDFolder = path.join(remote.app.getPath("home"), "Library", "Preferences", "betterdiscord"); // Old MacOS @@ -79,18 +80,33 @@ const downloadFile = phin.defaults({method: "GET", followRedirects: true, header const asarPath = path.join(bdDataFolder, "betterdiscord.asar"); async function downloadAsar() { let downloadUrl = "https://api.github.com/repos/BetterDiscord/BetterDiscord/releases"; + let response; try { - const response = await getJSON(downloadUrl); + response = await getJSON(downloadUrl); const releases = response.body; - const asset = releases && releases.length ? releases[0].assets.find(a => a.name === "betterdiscord.asar") : "https://api.github.com/repos/BetterDiscord/BetterDiscord/releases/assets/39982244"; // temporary workaround - downloadUrl = asset.url; + const asset = releases && releases.length && releases[0].assets && releases[0].assets.find(a => a.name.toLowerCase() === "betterdiscord.asar"); + downloadUrl = asset && asset.url; + if (!downloadUrl) { + let errMessage = "Could not get the asset url"; + if (!asset) errMessage = "Could not get asset object"; + if (!releases) errMessage = "Could not get response body"; + if (!response) errMessage = "Could not get any response"; + throw new Error(errMessage); + } - const resp = await downloadFile(downloadUrl); - const originalFs = require("original-fs").promises; // because electron doesn't like when I write asar files - await originalFs.writeFile(asarPath, resp.body); + try { + const resp = await downloadFile(downloadUrl); + const originalFs = require("original-fs").promises; // because electron doesn't like when I write asar files + await originalFs.writeFile(asarPath, resp.body); + } + catch (error) { + log(`❌ Failed to download package ${downloadUrl}`); + log(`❌ ${error.message}`); + return error; + } } catch (err) { - log(`❌ Failed to download package ${downloadUrl}`); + log(`❌ Failed to get asset url ${downloadUrl}`); log(`❌ ${err.message}`); return err; } @@ -100,18 +116,28 @@ async function injectShims(paths) { const progressPerLoop = (INJECT_SHIM_PROGRESS - progress.value) / paths.length; for (const discordPath of paths) { log("Injecting into: " + discordPath); + const appAsar = path.join(discordPath, "app.asar"); + const discordAsar = path.join(discordPath, "discord.asar"); const appPath = path.join(discordPath, "app"); const pkgFile = path.join(appPath, "package.json"); const indexFile = path.join(appPath, "index.js"); try { - if (process.platform === "win32" || process.platform === "darwin") { - if (!(await exists(appPath))) await fs.mkdir(appPath); - await fs.writeFile(pkgFile, JSON.stringify({name: "betterdiscord", main: "index.js"})); - await fs.writeFile(indexFile, `require("${asarPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}");`); - } - else { + // if (process.platform === "win32" || process.platform === "darwin") { + // const originalFs = require("original-fs").promises; + // if (await exists(appAsar)) { + // await originalFs.rename(appAsar, discordAsar); + // // await originalFs.copyFile(appAsar, discordAsar); + // // await originalFs.unlink(appAsar); + // // const error = await new Promise(resolve => rimraf(appAsar, originalFs, resolve)); + // // if (error) throw error; + // } + // if (!(await exists(appPath))) await fs.mkdir(appPath); + // await fs.writeFile(pkgFile, JSON.stringify({name: "betterdiscord", main: "index.js"})); + // await fs.writeFile(indexFile, `require("${asarPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}");`); + // } + // else { await fs.writeFile(path.join(discordPath, "index.js"), `require("${asarPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}");\nmodule.exports = require("./core.asar");`); - } + // } log("✅ Injection successful"); progress.set(progress.value + progressPerLoop); } diff --git a/src/renderer/actions/paths.js b/src/renderer/actions/paths.js index 5e07e570..199a838f 100644 --- a/src/renderer/actions/paths.js +++ b/src/renderer/actions/paths.js @@ -22,10 +22,7 @@ const getDiscordPath = function(releaseChannel) { if (!fs.existsSync(basedir)) return ""; const version = fs.readdirSync(basedir).filter(f => safeIsDir(path.join(basedir, f)) && f.split(".").length > 1).sort().reverse()[0]; if (!version) return ""; - resourcePath = path.join(basedir, version, "resources"); - } - else if (process.platform === "darwin") { - resourcePath = path.join("/Applications", `${releaseChannel}.app`, "Contents", "Resources"); + resourcePath = path.join(basedir, version, "modules", "discord_desktop_core-1", "discord_desktop_core"); } else { const basedir = path.join(remote.app.getPath("userData"), "..", releaseChannel.toLowerCase().replace(" ", "")); @@ -45,14 +42,12 @@ for (const channel in platforms) { export const getBrowsePath = function(channel) { if (process.platform === "win32") return path.join(process.env.LOCALAPPDATA, platforms[channel].replace(" ", "")); - else if (process.platform === "darwin") return path.join("/Applications", `${platforms[channel]}.app`); return path.join(remote.app.getPath("userData"), "..", platforms[channel].toLowerCase().replace(" ", "")); }; export const validatePath = function(channel, proposedPath) { if (process.platform === "win32") return validateWindows(channel, proposedPath); - else if (process.platform === "darwin") return validateMac(channel, proposedPath); - return validateLinux(channel, proposedPath); + return validateLinuxMac(channel, proposedPath); }; const validateWindows = function(channel, proposedPath) { @@ -61,36 +56,24 @@ const validateWindows = function(channel, proposedPath) { const isParentDir = fs.existsSync(path.join(proposedPath, channelName)); if (isParentDir) proposedPath = path.join(proposedPath, channelName); - let resourcePath = ""; + let corePath = ""; const selected = path.basename(proposedPath); const isBaseDir = selected === channelName; if (isBaseDir) { const version = fs.readdirSync(proposedPath).filter(f => safeIsDir(path.join(proposedPath, f)) && f.split(".").length > 1).sort().reverse()[0]; if (!version) return ""; - resourcePath = path.join(proposedPath, version, "resources"); + corePath = path.join(proposedPath, version, "modules", "discord_desktop_core-1", "discord_desktop_core"); } - if (selected.startsWith("app-") && selected.split(".").length > 2) resourcePath = path.join(proposedPath, "resources"); - if (selected === "resources") resourcePath = proposedPath; - - const executablePath = path.join(resourcePath, "..", `${channelName}.exe`); - if (fs.existsSync(executablePath)) return resourcePath; - return ""; -}; - -const validateMac = function(channel, proposedPath) { - let resourcePath = ""; - const selected = path.basename(proposedPath); - if (selected === `${platforms[channel]}.app`) resourcePath = path.join(proposedPath, "Contents", "Resources"); - if (selected === "Contents") resourcePath = path.join(proposedPath, "Resources"); - if (selected === "Resources") resourcePath = proposedPath; + if (selected.split(".").length > 2) corePath = path.join(proposedPath, "modules", "discord_desktop_core-1", "discord_desktop_core"); + if (selected === "discord_desktop_core") corePath = proposedPath; - const executablePath = path.join(resourcePath, "..", "MacOS", platforms[channel]); - if (fs.existsSync(executablePath)) return resourcePath; + const coreAsar = path.join(corePath, `core.asar`); + if (fs.existsSync(coreAsar)) return corePath; return ""; }; -const validateLinux = function(channel, proposedPath) { +const validateLinuxMac = function(channel, proposedPath) { if (proposedPath.includes("/snap/")) { remote.dialog.showErrorBox("BetterDiscord Incompatible", "BetterDiscord is currently incompatible with Snap installs of Discord. Support for snap installs is coming soon!"); return ""; From 4ab2b8fd5a32c04e6cdd90657c984f371ef6e9b6 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Thu, 27 Oct 2022 20:19:20 -0400 Subject: [PATCH 02/12] Separate process kill and restart - Makes killing and restarting different steps - Kill now happens before any other action - Will now rename the app.asar to discord.asar - Improve certain log messages for end users - Use betterdiscord.app/invite --- src/renderer/actions/install.js | 21 +++++++++++-- src/renderer/actions/repair.js | 37 ++++++++++++++++++++--- src/renderer/actions/uninstall.js | 42 +++++++++++++++++++++++--- src/renderer/actions/utils/fail.js | 2 +- src/renderer/actions/utils/kill.js | 22 +++++++++----- src/renderer/actions/utils/reset.js | 3 +- src/renderer/actions/utils/restart.js | 20 ++++++++++++ src/renderer/common/SocialLinks.svelte | 2 +- src/renderer/stores/installation.js | 3 +- 9 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 src/renderer/actions/utils/restart.js diff --git a/src/renderer/actions/install.js b/src/renderer/actions/install.js index e917da2b..8e882452 100644 --- a/src/renderer/actions/install.js +++ b/src/renderer/actions/install.js @@ -11,9 +11,11 @@ import fail from "./utils/fail"; import exists from "./utils/exists"; import reset from "./utils/reset"; import kill from "./utils/kill"; -import {showRestartNotice} from "./utils/notices"; +import restart from "./utils/restart"; +import {showRestartNotice, showKillNotice} from "./utils/notices"; import doSanityCheck from "./utils/sanity"; +const KILL_DISCORD_PROGRESS = 20; const MAKE_DIR_PROGRESS = 30; const CHECK_OLD_INSTALL = 40; const TRANSFER_OLD_ADDONS = 50; @@ -100,11 +102,15 @@ async function injectShims(paths) { const progressPerLoop = (INJECT_SHIM_PROGRESS - progress.value) / paths.length; for (const discordPath of paths) { log("Injecting into: " + discordPath); + const appAsar = path.join(discordPath, "app.asar"); + const discordAsar = path.join(discordPath, "discord.asar"); const appPath = path.join(discordPath, "app"); const pkgFile = path.join(appPath, "package.json"); const indexFile = path.join(appPath, "index.js"); try { if (process.platform === "win32" || process.platform === "darwin") { + const originalFs = require("original-fs"); + if (originalFs.existsSync(appAsar)) await fs.rename(appAsar, discordAsar); if (!(await exists(appPath))) await fs.mkdir(appPath); await fs.writeFile(pkgFile, JSON.stringify({name: "betterdiscord", main: "index.js"})); await fs.writeFile(indexFile, `require("${asarPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}");`); @@ -133,6 +139,15 @@ export default async function(config) { const channels = Object.keys(config); const paths = Object.values(config); + lognewline("Stopping Discord..."); + const killErr = await kill(channels, (KILL_DISCORD_PROGRESS - progress.value) / channels.length); + if (killErr) { + showKillNotice(); + return fail(); + } + log("✅ Discord stopped"); + progress.set(KILL_DISCORD_PROGRESS); + lognewline("Creating required directories..."); const makeDirErr = await makeDirectories(bdFolder, bdDataFolder, bdThemesFolder, bdPluginsFolder); @@ -179,8 +194,8 @@ export default async function(config) { lognewline("Restarting Discord..."); - const killErr = await kill(channels, (RESTART_DISCORD_PROGRESS - progress.value) / channels.length); - if (killErr) showRestartNotice(); // No need to bail out and show failed + const restartErr = await restart(channels, (RESTART_DISCORD_PROGRESS - progress.value) / channels.length); + if (restartErr) showRestartNotice(); // No need to bail out and show failed else log("✅ Discord restarted"); progress.set(RESTART_DISCORD_PROGRESS); diff --git a/src/renderer/actions/repair.js b/src/renderer/actions/repair.js index 1f687139..80f8cdc6 100644 --- a/src/renderer/actions/repair.js +++ b/src/renderer/actions/repair.js @@ -17,6 +17,7 @@ import doSanityCheck from "./utils/sanity"; const KILL_DISCORD_PROGRESS = 20; const DELETE_APP_DIRS_PROGRESS = 50; +const RENAME_ASAR_PROGRESS = 80; const DELETE_MODULE_DIRS_PROGRESS = 100; async function deleteAppDirs(paths) { @@ -27,7 +28,7 @@ async function deleteAppDirs(paths) { if (await exists(appPath)) { const error = await new Promise(resolve => rimraf(appPath, originalFs, resolve)); if (error) { - log(` Could not delete folder ${appPath}`); + log(`❌ Could not delete folder ${appPath}`); log(`❌ ${error.message}`); return error; } @@ -37,6 +38,26 @@ async function deleteAppDirs(paths) { } } +async function renameAsar(paths) { + const progressPerLoop = (RENAME_ASAR_PROGRESS - progress.value) / paths.length; + for (const discordPath of paths) { + const appAsar = path.join(discordPath, "app.asar"); + const discordAsar = path.join(discordPath, "discord.asar"); + log("Renaming " + discordAsar); + try { + if (originalFs.existsSync(appAsar)) await fs.rename(appAsar, discordAsar); + log("✅ Rename successful"); + progress.set(progress.value + progressPerLoop); + } + catch (error) { + log(`❌ Could not rename asar ${discordAsar}`); + log(`❌ ${error.message}`); + return error; + } + + } +} + const platforms = {stable: "Discord", ptb: "Discord PTB", canary: "Discord Canary"}; async function deleteModuleDirs(config) { const size = Object.keys(config).length; @@ -98,13 +119,13 @@ export default async function(config) { const paths = Object.values(config); - lognewline("Killing Discord..."); - const killErr = await kill(channels, (KILL_DISCORD_PROGRESS - progress.value) / channels.length, false); // await killProcesses(channels); + lognewline("Stopping Discord..."); + const killErr = await kill(channels, (KILL_DISCORD_PROGRESS - progress.value) / channels.length); // await killProcesses(channels); if (killErr) { showKillNotice(); return fail(); } - log("✅ Discord Killed"); + log("✅ Discord stopped"); progress.set(KILL_DISCORD_PROGRESS); @@ -114,6 +135,14 @@ export default async function(config) { if (deleteShimErr) return fail(); log("✅ Shims deleted"); progress.set(DELETE_APP_DIRS_PROGRESS); + + + await new Promise(r => setTimeout(r, 200)); + lognewline("Renaming asars..."); + const renameAsarErr = await renameAsar(paths); + if (renameAsarErr) return fail(); + log("✅ Asars renamed"); + progress.set(DELETE_APP_DIRS_PROGRESS); await new Promise(r => setTimeout(r, 200)); diff --git a/src/renderer/actions/uninstall.js b/src/renderer/actions/uninstall.js index ac3b6347..67855e64 100644 --- a/src/renderer/actions/uninstall.js +++ b/src/renderer/actions/uninstall.js @@ -11,11 +11,14 @@ import fail from "./utils/fail"; import exists from "./utils/exists"; import reset from "./utils/reset"; import kill from "./utils/kill"; -import {showRestartNotice} from "./utils/notices"; +import restart from "./utils/restart"; +import {showRestartNotice, showKillNotice} from "./utils/notices"; import doSanityCheck from "./utils/sanity"; -const DELETE_SHIM_PROGRESS = 85; +const KILL_DISCORD_PROGRESS = 25; +const RENAME_ASAR_PROGRESS = 50; +const DELETE_SHIM_PROGRESS = 75; const RESTART_DISCORD_PROGRESS = 100; @@ -46,6 +49,26 @@ async function deleteShims(paths) { } } +async function renameAsar(paths) { + const progressPerLoop = (RENAME_ASAR_PROGRESS - progress.value) / paths.length; + for (const discordPath of paths) { + const appAsar = path.join(discordPath, "app.asar"); + const discordAsar = path.join(discordPath, "discord.asar"); + log("Renaming " + discordAsar); + try { + if (originalFs.existsSync(appAsar)) await fs.rename(appAsar, discordAsar); + log("✅ Rename successful"); + progress.set(progress.value + progressPerLoop); + } + catch (error) { + log(`❌ Could not rename asar ${discordAsar}`); + log(`❌ ${error.message}`); + return error; + } + + } +} + export default async function(config) { await reset(); @@ -56,6 +79,15 @@ export default async function(config) { const channels = Object.keys(config); const paths = Object.values(config); + lognewline("Stopping Discord..."); + const killErr = await kill(channels, (RESTART_DISCORD_PROGRESS - progress.value) / channels.length); + if (killErr) { + showKillNotice(); + return fail(); + } + log("✅ Discord stopped"); + progress.set(KILL_DISCORD_PROGRESS); + lognewline("Deleting shims..."); const deleteErr = await deleteShims(paths); @@ -64,9 +96,9 @@ export default async function(config) { progress.set(DELETE_SHIM_PROGRESS); - lognewline("Killing Discord..."); - const killErr = await kill(channels, (RESTART_DISCORD_PROGRESS - progress.value) / channels.length); - if (killErr) showRestartNotice(); // No need to bail out + lognewline("Restarting Discord..."); + const restartErr = await restart(channels, (RESTART_DISCORD_PROGRESS - progress.value) / channels.length); + if (restartErr) showRestartNotice(); // No need to bail out and show failed else log("✅ Discord restarted"); progress.set(RESTART_DISCORD_PROGRESS); diff --git a/src/renderer/actions/utils/fail.js b/src/renderer/actions/utils/fail.js index ec01a5e9..56881785 100644 --- a/src/renderer/actions/utils/fail.js +++ b/src/renderer/actions/utils/fail.js @@ -1,7 +1,7 @@ import {log} from "./log"; import {action, status} from "../../stores/installation"; -const discordURL = "https://discord.gg/0Tmfo5ZbORCRqbAd"; +const discordURL = "https://betterdiscord.app/invite"; export default function fail() { log(""); diff --git a/src/renderer/actions/utils/kill.js b/src/renderer/actions/utils/kill.js index 2924ba72..a9273d96 100644 --- a/src/renderer/actions/utils/kill.js +++ b/src/renderer/actions/utils/kill.js @@ -3,17 +3,19 @@ import path from "path"; import findProcess from "find-process"; import kill from "tree-kill"; import {shell} from "electron"; -import {progress} from "../../stores/installation"; +import {progress, bins} from "../../stores/installation"; import {log} from "./log"; const platforms = {stable: "Discord", ptb: "Discord PTB", canary: "Discord Canary"}; -export default async function killProcesses(channels, progressPerLoop, shouldRestart = true) { +export default async function killProcesses(channels, progressPerLoop) { + bins.set({}); for (const channel of channels) { let processName = platforms[channel]; if (process.platform === "darwin") processName = platforms[channel]; // Discord Canary and Discord PTB on Mac + // else if (process.platform === "win32") processName = `${platforms[channel].replace(" ", "")}.exe`; // DiscordCanary and DiscordPTB on Windows/Linux else processName = platforms[channel].replace(" ", ""); // DiscordCanary and DiscordPTB on Windows/Linux - log("Attempting to kill " + processName); + log(`Attempting to stop ${platforms[channel]}`); try { const results = await findProcess("name", processName, true); if (!results || !results.length) { @@ -25,14 +27,18 @@ export default async function killProcesses(channels, progressPerLoop, shouldRes const parentPids = results.map(p => p.ppid); const discordPid = results.find(p => parentPids.includes(p.pid)); const bin = process.platform === "darwin" ? path.resolve(discordPid.bin, "..", "..", "..") : discordPid.bin; - await new Promise(r => kill(discordPid.pid, r)); - if (shouldRestart) setTimeout(() => shell.openPath(bin), 1000); + const killErr = await new Promise(r => kill(discordPid.pid, "SIGKILL", r)); + if (killErr) throw killErr; + bins.update(o => { + o[channel] = bin; + return o; + }); + // if (shouldRestart) setTimeout(() => shell.openPath(bin), 1000); progress.set(progress.value + progressPerLoop); } catch (err) { - const symbol = shouldRestart ? "⚠️" : "❌"; - log(`${symbol} Could not kill ${platforms[channel]}`); - log(`${symbol} ${err.message}`); + log(`❌ Could not stop ${platforms[channel]}`); + log(`❌ ${err.message}`); return err; } } diff --git a/src/renderer/actions/utils/reset.js b/src/renderer/actions/utils/reset.js index 908ed113..30451e54 100644 --- a/src/renderer/actions/utils/reset.js +++ b/src/renderer/actions/utils/reset.js @@ -1,9 +1,10 @@ import logs from "../../stores/logs"; -import {progress, status} from "../../stores/installation"; +import {bins, progress, status} from "../../stores/installation"; export default async function reset() { logs.set([]); progress.set(0); status.set(""); + bins.set({}); await new Promise(r => setTimeout(r, 500)); } \ No newline at end of file diff --git a/src/renderer/actions/utils/restart.js b/src/renderer/actions/utils/restart.js new file mode 100644 index 00000000..109de02b --- /dev/null +++ b/src/renderer/actions/utils/restart.js @@ -0,0 +1,20 @@ + +import {shell} from "electron"; +import {progress, bins} from "../../stores/installation"; +import {log} from "./log"; + +const platforms = {stable: "Discord", ptb: "Discord PTB", canary: "Discord Canary"}; +export default async function killProcesses(channels, progressPerLoop) { + for (const channel of channels) { + log(`Attempting to start ${platforms[channel]}`); + try { + setTimeout(() => shell.openPath(bins.value[channel]), 1000); + progress.set(progress.value + progressPerLoop); + } + catch (err) { + log(`❌ Could not start ${platforms[channel]}`); + log(`❌ ${err.message}`); + return err; + } + } +} \ No newline at end of file diff --git a/src/renderer/common/SocialLinks.svelte b/src/renderer/common/SocialLinks.svelte index e57ae1cc..32a5caa7 100644 --- a/src/renderer/common/SocialLinks.svelte +++ b/src/renderer/common/SocialLinks.svelte @@ -3,7 +3,7 @@ const webUrl = "https://betterdiscord.app"; const githubUrl = "http://github.com/BetterDiscord/BetterDiscord"; - const donateUrl = "https://www.patreon.com/Zerebos"; + const donateUrl = "https://github.com/sponsors/rauenzi/";