diff --git a/README.md b/README.md index 2ffcb66..26cf278 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,7 @@ | bunRPC-darwin-x64 | macOS | x64 | ✅ | ✅ | | bunRPC-darwin-arm64 | macOS | arm64 | ✅ | N/A | -On x64 platforms, bunRPC uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `baseline` build of bunRPC is for older CPUs that don't support these optimizations. You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or your users see `"Illegal instruction"` errors, you might need to use the baseline version. \ No newline at end of file +On x64 platforms, bunRPC uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `baseline` build of bunRPC is for older CPUs that don't support these optimizations. You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or your users see `"Illegal instruction"` errors, you might need to use the baseline version. + +# Help wanted +Someone please ensure src/process/native/darwin/index.js works, I don't have macos installed on my mac mini, and hackintosh is not worth setting up for this. \ No newline at end of file diff --git a/src/process/index.js b/src/process/index.js new file mode 100644 index 0000000..0fe80b6 --- /dev/null +++ b/src/process/index.js @@ -0,0 +1,103 @@ +import * as Natives from './native/index.js'; +const Native = Natives[process.platform]; + +const timestamps = {}; +const names = {}; +const pids = {}; + +export default class { + constructor(handlers) { + if (!Native) return; + + this.handlers = handlers; + + this.scan = this.scan.bind(this); + + this.getDB(); + + this.scan(); + setInterval(this.scan, 10000); // every 10 seconds instead of 5 + } + + async getDB() { + if (this.DetectableDB) return this.DetectableDB; + + const data = await fetch("https://discord.com/api/v9/applications/detectable") + + this.DetectableDB = await data.json(); + + return this.DetectableDB; + } + + async scan() { + const processes = await Native.getProcesses(); + const ids = []; + + const DetectableDB = await this.getDB(); + + for (const [pid, _path, args] of processes) { + const path = _path.toLowerCase().replaceAll('\\', '/'); + const toCompare = []; + const splitPath = path.split('/'); + for (let i = 1; i < splitPath.length; i++) { + toCompare.push(splitPath.slice(-i).join('/')); + } + + for (const p of toCompare.slice()) { + toCompare.push(p.replace('64', '')); + toCompare.push(p.replace('.x64', '')); + toCompare.push(p.replace('x64', '')); + toCompare.push(p.replace('_64', '')); + } + + for (const { executables, id, name } of DetectableDB) { + if (executables?.some(x => { + if (x.is_launcher) return false; + if (x.name[0] === '>' ? x.name.substring(1) !== toCompare[0] : !toCompare.some(y => x.name === y)) return false; + if (args && x.arguments) return args.join(" ").indexOf(x.arguments) > -1; + return true; + })) { + names[id] = name; + pids[id] = pid; + + ids.push(id); + if (!timestamps[id]) { + timestamps[id] = Date.now(); + } + + this.handlers.message({ + socketId: id + }, { + cmd: 'SET_ACTIVITY', + args: { + activity: { + application_id: id, + name, + timestamps: { + start: timestamps[id] + } + }, + pid + } + }); + } + } + } + + for (const id in timestamps) { + if (!ids.includes(id)) { + delete timestamps[id]; + + this.handlers.message({ + socketId: id + }, { + cmd: 'SET_ACTIVITY', + args: { + activity: null, + pid: pids[id] + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/process/native/darwin/index.js b/src/process/native/darwin/index.js new file mode 100644 index 0000000..58c457c --- /dev/null +++ b/src/process/native/darwin/index.js @@ -0,0 +1,21 @@ +import { exec } from 'node:child_process'; + +export const getProcesses = () => { + return new Promise((resolve, reject) => { + exec("ps -axo pid,comm", (error, stdout) => { + if (error) { + return reject(error); // Handle errors + } + + const processes = stdout + .toString() + .split('\n') // Split by new lines + .slice(1) // Skip the header line + .map(line => line.trim().split(/\s+/, 2)) // Split into PID and command + .filter(parts => parts.length === 2) // Ensure both PID and command exist + .map(([pid, command]) => [Number(pid), command]); + + resolve(processes); + }); + }); +}; diff --git a/src/process/native/index.js b/src/process/native/index.js new file mode 100644 index 0000000..0ea8b0a --- /dev/null +++ b/src/process/native/index.js @@ -0,0 +1,3 @@ +export * as darwin from './darwin/index.js'; +export * as linux from './linux/index.js'; +export * as win32 from './win32/index.js'; \ No newline at end of file diff --git a/src/process/native/linux/index.js b/src/process/native/linux/index.js new file mode 100644 index 0000000..cff0777 --- /dev/null +++ b/src/process/native/linux/index.js @@ -0,0 +1,21 @@ +import { readdir } from "node:fs/promises"; +import { file } from "bun" + +export const getProcesses = async () => { + const directories = await readdir("/proc"); + + const processPromises = directories + .filter(pid => +pid > 0) // Filter valid PIDs upfront + .map(async (pid) => { + try { + const cmdline = await file(`/proc/${pid}/cmdline`).text(); + const [command, ...args] = cmdline.split("\0"); // Destructure for clarity + return [+pid, command, args]; + } catch { + return null; // Return null on failure + } + }); + + const processes = await Promise.all(processPromises); + return processes.filter(Boolean); // Filter out null entries +}; diff --git a/src/process/native/win32/index.js b/src/process/native/win32/index.js new file mode 100644 index 0000000..37bc481 --- /dev/null +++ b/src/process/native/win32/index.js @@ -0,0 +1,18 @@ +import { exec } from 'node:child_process'; + +export const getProcesses = () => { + return new Promise((resolve) => { + exec("wmic process get ProcessID,ExecutablePath /format:csv", (error, output) => { + + const processes = output + .toString() + .split('\r\n') // Split by new lines + .slice(2) // Remove headers + .map(line => line.trim().split(',').reverse()) // Split, reverse, and trim + .filter(parsed => parsed[1]) // Filter out invalid paths + .map(([executablePath, processId]) => [Number(processId) || processId, executablePath]); // Parse IDs + + resolve(processes); + }); + }); +}; diff --git a/src/server.js b/src/server.js index 551b707..3fffe50 100644 --- a/src/server.js +++ b/src/server.js @@ -3,6 +3,8 @@ import { EventEmitter } from "node:events"; import ipc from "./transports/ipc.js"; import ws from "./transports/ws.js"; +import process from "./process/index.js" + let socketId = 0; export default class extends EventEmitter { @@ -19,6 +21,8 @@ export default class extends EventEmitter { // Ensure you have the latest bun version installed if you encounter issues. new ipc(handlers); new ws(handlers); + + new process(handlers); } onConnection(socket) { diff --git a/src/transports/ipc.js b/src/transports/ipc.js index 8c0dc9d..5b71023 100644 --- a/src/transports/ipc.js +++ b/src/transports/ipc.js @@ -1,16 +1,15 @@ import { join } from "node:path"; -import { platform, env } from "node:process"; import { unlinkSync } from "node:fs"; import { createServer, createConnection } from "node:net"; const SOCKET_PATH = - platform === "win32" + process.platform === "win32" ? "\\\\?\\pipe\\discord-ipc" : join( - env.XDG_RUNTIME_DIR || env.TMPDIR || env.TMP || env.TEMP || "/tmp", - "discord-ipc", - ); + process.env.XDG_RUNTIME_DIR || process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp", + "discord-ipc", + ); const Types = { HANDSHAKE: 0, @@ -116,7 +115,7 @@ const socketIsAvailable = async (socket) => { try { socket.end(); socket.destroy(); - } catch {} + } catch { } }; const possibleOutcomes = Promise.race([ @@ -145,10 +144,10 @@ const getAvailableSocket = async (tries = 0) => { const socket = createConnection(path); if (await socketIsAvailable(socket)) { - if (platform !== "win32") + if (process.process.process.process.process.process.process.process.process.process.process.platform !== "win32") try { unlinkSync(path); - } catch {} + } catch { } return path; }