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

Auto relaunch #517

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2ac01b9
added types
yofukashino Jul 16, 2023
edba415
added util functions
yofukashino Jul 16, 2023
ae7daf7
main changes in script
yofukashino Jul 16, 2023
a93391e
cspell fix
yofukashino Jul 16, 2023
c510c93
timeout
yofukashino Jul 16, 2023
1835ddf
added the function to injector
yofukashino Jul 16, 2023
8d03383
use new function
yofukashino Jul 16, 2023
fb64269
linux support added???
yofukashino Jul 18, 2023
aa55735
prettier is ***
yofukashino Jul 18, 2023
bfdf699
Merge branch 'main' into auto-relaunch
yofukashino Jul 19, 2023
5ad3e4b
Merge branch 'main' into auto-relaunch
yofukashino Aug 22, 2023
5a9f609
Merge branch 'main' into auto-relaunch
yofukashino Sep 24, 2023
5c2ddf4
requested changes
yofukashino Sep 24, 2023
c9aeec3
Merge branch 'main' into auto-relaunch
yofukashino Sep 30, 2023
1167e1c
Merge branch 'main' into auto-relaunch
yofukashino Oct 19, 2023
e2b2613
warnings
yofukashino Oct 19, 2023
971cad8
Merge branch 'main' into auto-relaunch
yofukashino Apr 5, 2024
3c1d139
Merge branch 'main' into auto-relaunch
yofukashino Apr 13, 2024
4e356ae
Merge branch 'main' into auto-relaunch
yofukashino Jul 6, 2024
365c37f
prettier and dev mode fix
yofukashino Jul 11, 2024
890561f
prettier
yofukashino Jul 11, 2024
63bc230
await
yofukashino Jul 11, 2024
ea52882
FRCIK KEYBOARD SHORTCUT
yofukashino Jul 11, 2024
f7f85aa
right place
yofukashino Jul 11, 2024
4da86b0
fix: find parent pid on windows and kill all processes on linux
FedeIlLeone Jul 12, 2024
f4c66f0
Merge branch 'main' into auto-relaunch
FedeIlLeone Oct 18, 2024
d586b36
Merge branch 'main' into auto-relaunch
yofukashino Oct 18, 2024
d65b9c9
macos support
yofukashino Oct 18, 2024
dab08db
refractor
yofukashino Oct 18, 2024
6e166ff
Merge branch 'main' into auto-relaunch
yofukashino Nov 11, 2024
ed3c4f4
Merge branch 'main' into auto-relaunch
yofukashino Nov 13, 2024
b0a0bbf
Merge branch 'main' into auto-relaunch
yofukashino Nov 26, 2024
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
25 changes: 17 additions & 8 deletions scripts/inject/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import "./checks/elevate.mjs";
import "./checks/env.mjs";

import { join } from "path";
import { smartInject } from "./injector.mjs";
import { AnsiEscapes, getCommand } from "./util.mjs";
import { inject, uninject } from "./injector.mjs";

import { createContext, getPositionalArg } from "@marshift/argus";
import { existsSync } from "fs";
import * as darwin from "./platforms/darwin.mjs";
import * as linux from "./platforms/linux.mjs";
import * as win32 from "./platforms/win32.mjs";
import { DiscordPlatform } from "./types.mjs";
import { existsSync } from "fs";
import { createContext, getPositionalArg } from "@marshift/argus";
import type { DiscordPlatform } from "./types.mjs";

const platformModules = {
darwin,
Expand All @@ -23,6 +23,7 @@ const ctx = createContext(process.argv);

export const exitCode = ctx.hasOptionalArg(/--no-exit-codes/) ? 0 : 1;
const prod = ctx.hasOptionalArg(/--production/);
const noRelaunch = ctx.hasOptionalArg(/--no-relaunch/);
export const entryPoint = ctx.getOptionalArg(/--entryPoint/);

if (!(process.platform in platformModules)) {
Expand Down Expand Up @@ -97,7 +98,7 @@ const run = async (cmd = ctx.getPositionalArg(2), replug = false): Promise<void>

if (cmd === "inject") {
try {
result = await inject(platformModule, platform, prod);
result = await smartInject(cmd, replug, platformModule, platform, prod, noRelaunch);
} catch (e) {
console.error(
`${AnsiEscapes.RED}An error occurred while trying to inject into Discord!${AnsiEscapes.RESET}`,
Expand All @@ -114,7 +115,11 @@ const run = async (cmd = ctx.getPositionalArg(2), replug = false): Promise<void>
"\n",
);
console.log(
`You now have to completely close the Discord client, from the system tray or through the task manager.\n
`${
noRelaunch
? `You now have to completely close the Discord client, from the system tray or through the task manager.\n`
: "Your Discord client has been restarted automatically.\n"
}
To plug into a different platform, use the following syntax: ${AnsiEscapes.BOLD}${
AnsiEscapes.GREEN
}${getCommand({ action: replug ? "replug" : "plug", prod })}${AnsiEscapes.RESET}`,
Expand All @@ -124,7 +129,7 @@ To plug into a different platform, use the following syntax: ${AnsiEscapes.BOLD}
}
} else if (cmd === "uninject") {
try {
result = await uninject(platformModule, platform);
result = await smartInject(cmd, replug, platformModule, platform, prod, noRelaunch);
} catch (e) {
console.error(
`${AnsiEscapes.RED}An error occurred while trying to uninject from Discord!${AnsiEscapes.RESET}`,
Expand All @@ -142,7 +147,11 @@ To plug into a different platform, use the following syntax: ${AnsiEscapes.BOLD}
"\n",
);
console.log(
`You now have to completely close the Discord client, from the system tray or through the task manager.\n
`${
noRelaunch
? `You now have to completely close the Discord client, from the system tray or through the task manager.\n`
: "Your Discord client has been restarted automatically.\n"
}
To unplug from a different platform, use the following syntax: ${AnsiEscapes.BOLD}${
AnsiEscapes.GREEN
}${getCommand({ action: "unplug", prod })}${AnsiEscapes.RESET}`,
Expand Down
102 changes: 90 additions & 12 deletions scripts/inject/injector.mts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { execSync } from "child_process";
import { existsSync } from "fs";
import { chown, copyFile, mkdir, rename, rm, stat, writeFile } from "fs/promises";
import path, { join, sep } from "path";
import { fileURLToPath } from "url";
import { entryPoint as argEntryPoint, exitCode } from "./index.mjs";
import { AnsiEscapes, getCommand } from "./util.mjs";
import { execSync } from "child_process";
import { DiscordPlatform, PlatformModule } from "./types.mjs";
import { CONFIG_PATH } from "../../src/util.mjs";
import { existsSync } from "fs";
import { entryPoint as argEntryPoint, exitCode } from "./index.mjs";
import type { DiscordPlatform, PlatformModule, ProcessInfo } from "./types.mjs";
import {
AnsiEscapes,
PlatformNames,
getCommand,
getProcessInfoByName,
getUserData,
killProcessByPID,
openProcess,
} from "./util.mjs";

const dirname = path.dirname(fileURLToPath(import.meta.url));
let processInfo: ProcessInfo | ProcessInfo[] | null;

export const isDiscordInstalled = async (appDir: string, silent?: boolean): Promise<boolean> => {
try {
Expand Down Expand Up @@ -112,6 +121,7 @@ export const inject = async (
const entryPoint =
argEntryPoint ??
(prod ? join(CONFIG_PATH, "replugged.asar") : join(dirname, "..", "..", "dist/main.js"));

const entryPointDir = path.dirname(entryPoint);

if (appDir.includes("flatpak")) {
Expand Down Expand Up @@ -191,14 +201,82 @@ export const uninject = async (
return false;
}

await rm(appDir, { recursive: true, force: true });
await rename(join(appDir, "..", "app.orig.asar"), appDir);
// For discord_arch_electron
if (existsSync(join(appDir, "..", "app.orig.asar.unpacked"))) {
await rename(
join(appDir, "..", "app.orig.asar.unpacked"),
join(appDir, "..", "app.asar.unpacked"),
try {
await rm(appDir, { recursive: true, force: true });
await rename(join(appDir, "..", "app.orig.asar"), appDir);
// For discord_arch_electron
if (existsSync(join(appDir, "..", "app.orig.asar.unpacked"))) {
await rename(
join(appDir, "..", "app.orig.asar.unpacked"),
join(appDir, "..", "app.asar.unpacked"),
);
}
} catch {
console.error(
`${AnsiEscapes.RED}Failed to rename app.asar while unplugging. If Discord is open, make sure it is closed.${AnsiEscapes.RESET}`,
);
process.exit(exitCode);
}

return true;
};

export const smartInject = async (
cmd: "uninject" | "inject",
replug: boolean,
platformModule: PlatformModule,
platform: DiscordPlatform,
production: boolean,
noRelaunch: boolean,
): Promise<boolean> => {
let result;

const processName =
process.platform === "darwin"
? PlatformNames[platform]
: PlatformNames[platform].replace(" ", "");
if (!noRelaunch) {
try {
if ((replug && cmd === "uninject") || !replug) {
processInfo = getProcessInfoByName(processName)!;
if (Array.isArray(processInfo)) {
await Promise.all(processInfo.map((info) => killProcessByPID(info.pid)));
} else {
await killProcessByPID(processInfo?.pid);
}
}
} catch {}
}

result =
cmd === "uninject"
? await uninject(platformModule, platform)
: await inject(platformModule, platform, production);

if (!noRelaunch) {
if (((replug && cmd !== "uninject") || !replug) && processInfo) {
const appDir = await platformModule.getAppDir(platform);
switch (process.platform) {
case "win32":
openProcess(
join(appDir, "..", "..", "..", "Update"),
["--processStart", `${processName}.exe`],
{ detached: true, stdio: "ignore" },
);
break;
case "linux":
openProcess(join(appDir, "..", "..", processName), [], {
...getUserData(),
detached: true,
stdio: "ignore",
});
break;
case "darwin":
openProcess(`open -a "${processName}.app"`);
break;
}
}
}

return result;
};
11 changes: 11 additions & 0 deletions scripts/inject/types.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@ export type DiscordPlatform = "stable" | "ptb" | "canary" | "dev";
export interface PlatformModule {
getAppDir: (platform: DiscordPlatform) => Promisable<string>;
}

export interface ProcessInfo {
pid: number;
ppid: number;
}

export interface UserData {
env: NodeJS.ProcessEnv;
uid: number;
gid: number;
}
79 changes: 78 additions & 1 deletion scripts/inject/util.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DiscordPlatform } from "./types.mjs";
import { type SpawnOptions, execSync, spawn } from "child_process";
import type { DiscordPlatform, ProcessInfo, UserData } from "./types.mjs";

export const AnsiEscapes = {
RESET: "\x1b[0m",
Expand Down Expand Up @@ -29,3 +30,79 @@ export const getCommand = ({
cmd += ` ${platform || `[${Object.keys(PlatformNames).join("|")}]`}`;
return cmd;
};

export const getProcessInfoByName = (processName: string): ProcessInfo | ProcessInfo[] | null => {
try {
const isWindows = process.platform === "win32";
const command = isWindows
? `wmic process where (Name="${processName}.exe") get ProcessId,ParentProcessId /FORMAT:CSV`
: `ps -eo cmd,ppid,pid | grep -E "(^|/)${processName}(\\s|$)" | grep -v grep`;
const output = execSync(command).toString();

if (!output.trim()) {
return null;
}

const lines = output
.trim()
.split(isWindows ? "\r\r\n" : "\n")
.slice(1);
const processInfo = lines.map((line) => {
const parts = isWindows ? line.split(",") : line.trim().split(/\s+/);
return {
ppid: parseInt(parts[1], 10),
pid: parseInt(parts[2], 10),
};
});

if (isWindows) {
const parentPIDs = processInfo.map((process) => process.ppid);
const mainProcess = processInfo.find((process) => parentPIDs.includes(process.pid));
return mainProcess || null;
} else {
return processInfo || null;
}
} catch {
return null;
}
};

export const killCheckProcessExists = (pid: number): boolean => {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
};

export const killProcessByPID = (pid: number): Promise<void> => {
return new Promise((resolve) => {
if (!pid) resolve();
process.kill(pid, "SIGTERM");
const checkInterval = setInterval(() => {
yofukashino marked this conversation as resolved.
Show resolved Hide resolved
if (!killCheckProcessExists(pid)) {
clearInterval(checkInterval);
resolve();
}
}, 1000);
setTimeout(() => {
clearInterval(checkInterval);
resolve();
}, 6000);
});
};

export const openProcess = (command: string, args?: string[], options?: SpawnOptions): void => {
void (process.platform === "darwin"
? execSync(command)
: spawn(command, args ?? [], options ?? {}).unref());
};

export const getUserData = (): UserData => {
const name = execSync("logname", { encoding: "utf8" }).toString().trim().replace(/\n$/, "");
const env = Object.assign({}, process.env, { HOME: `/home/${name}` });
const uid = execSync(`id -u ${name}`, { encoding: "utf8" }).toString().trim().replace(/\n$/, "");
const gid = execSync(`id -g ${name}`, { encoding: "utf8" }).toString().trim().replace(/\n$/, "");
return { env, uid: Number(uid), gid: Number(gid) };
};