#!/usr/bin/env node /** * p2manager - Player2 Manager (Node.js Edition) * (C) 2025 Alex Mueller / OptimiDev */ import fs from "fs"; import os from "os"; import path from "path"; import { execSync } from "child_process"; import inquirer from "inquirer"; import chalk from "chalk"; import fetch from "node-fetch"; import { Command } from "commander"; // ───────────────────────────────────────────── // Platform Detection // ───────────────────────────────────────────── const isMac = process.platform === "darwin"; const isLinux = process.platform === "linux"; // ───────────────────────────────────────────── // Paths and URLs // ───────────────────────────────────────────── const APP_DIR = isMac ? "/Applications/Player2.app" : "/opt/player2"; const EXEC_PATH = isMac ? "/Applications/Player2.app/Contents/MacOS/Player2" : path.join(APP_DIR, "Player2.AppImage"); const SYMLINK = isMac ? "/usr/local/bin/player2" : "/usr/bin/player2"; const DESKTOP_FILE = isMac ? path.join(os.homedir(), "Desktop/player2.desktop") : "/usr/share/applications/player2.desktop"; const LATEST_URL = isMac ? "https://player2.game/mac-intel" : "https://player2.game/linux"; // ───────────────────────────────────────────── // Warning // ───────────────────────────────────────────── console.log(chalk.bgYellow("!!WARNING!!")) console.log(chalk.bgYellow("This code is made to run Player2 on unsupported devices like Macs with Intel and Linux")) console.log(chalk.bgYellow("If you get any problems"), chalk.bgRed("DO NOT REPORT TO PLAYER2")) console.log(chalk.bgYellow("Report the issues on: https://git.optimihost.com/NaChlorid/p2manager/issues")) // ───────────────────────────────────────────── // Utils // ───────────────────────────────────────────── const log = (...args) => console.log(chalk.cyan("[P2Manager]"), ...args); const warn = (...args) => console.log(chalk.bgYellow("[warn]"), ...args); const error = (...args) => console.error(chalk.bgRed("[error]"), ...args); function requireRoot() { if (isLinux && process.getuid() !== 0) { error("This command requires root privileges. Run with sudo."); process.exit(1); } } function ensureDir(dir) { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); } function safeExec(cmd) { try { execSync(cmd, { stdio: "ignore" }); } catch { warn(`Command failed (ignored): ${cmd}`); } } async function downloadFile(url, dest) { log(`Downloading: ${url}`); const res = await fetch(url); if (!res.ok) throw new Error(`Download failed: ${res.statusText}`); const fileStream = fs.createWriteStream(dest); await new Promise((resolve, reject) => { res.body.pipe(fileStream); res.body.on("error", reject); fileStream.on("finish", resolve); }); } // ───────────────────────────────────────────── // Core Functions // ───────────────────────────────────────────── async function installPlayer2({ patches = true, monitor = false, noUI = false }) { requireRoot(); log(`Installing latest Player2 for ${isMac ? "macOS" : "Linux"}...`); if (isMac) { const dmgPath = "/tmp/Player2.dmg"; await downloadFile(LATEST_URL, dmgPath); log("Mounting DMG..."); safeExec(`hdiutil attach "${dmgPath}" -nobrowse -quiet`); safeExec(`cp -R "/Volumes/Player2/Player2.app" /Applications/`); safeExec(`hdiutil detach "/Volumes/Player2" -quiet`); fs.unlinkSync(dmgPath); } else { ensureDir(APP_DIR); await downloadFile(LATEST_URL, EXEC_PATH); fs.chmodSync(EXEC_PATH, 0o755); } try { fs.symlinkSync(EXEC_PATH, SYMLINK); } catch { warn("Symlink already exists or cannot be created. Skipping."); } createDesktopFile(); if (patches && isLinux) applyPatches(); if (monitor && isLinux) setupMonitor(); log(chalk.green("✅ Player2 installed successfully!")); } function createDesktopFile() { const execCmd = isMac ? "/Applications/Player2.app/Contents/MacOS/Player2" : SYMLINK; const desktopEntry = `[Desktop Entry] Type=Application Name=Player2 Exec=${execCmd} Icon=player2 Comment=Player2 Game Client Categories=Game;`; ensureDir(path.dirname(DESKTOP_FILE)); fs.writeFileSync(DESKTOP_FILE, desktopEntry); log("Desktop entry created."); } function applyPatches() { log("Applying WebKit patch..."); ensureDir(APP_DIR); fs.writeFileSync(path.join(APP_DIR, "patch.env"), "WEBKIT_DISABLE_DMABUF_RENDERER=1\n"); log("Patch file written."); } function setupMonitor() { log("Setting up P2Monitor..."); ensureDir("/etc/p2monitor"); fs.writeFileSync( "/etc/p2monitor/monitor.py", `#!/usr/bin/env python3 import time while True: time.sleep(5)` ); fs.chmodSync("/etc/p2monitor/monitor.py", 0o755); fs.writeFileSync( "/etc/systemd/system/p2monitor.service", `[Unit] Description=Player2 Log Monitor After=network.target [Service] Type=simple ExecStart=/usr/bin/python3 /etc/p2monitor/monitor.py Restart=always User=root [Install] WantedBy=multi-user.target` ); safeExec("systemctl daemon-reload"); safeExec("systemctl enable p2monitor"); safeExec("systemctl start p2monitor"); log("P2Monitor service installed and started (Linux only)."); } function uninstallAll() { requireRoot(); log("Uninstalling Player2 components..."); const removeTargets = [ APP_DIR, SYMLINK, DESKTOP_FILE, "/etc/p2monitor", "/etc/systemd/system/p2monitor.service" ]; removeTargets.forEach((p) => { if (fs.existsSync(p)) { fs.rmSync(p, { recursive: true, force: true }); log(`Removed: ${p}`); } }); safeExec("systemctl daemon-reload"); log(chalk.green("✅ Uninstallation complete.")); } async function updatePlayer2() { requireRoot(); if (!fs.existsSync(EXEC_PATH)) { error("Player2 is not installed. Run 'p2manager install' first."); process.exit(1); } log("Updating Player2..."); await downloadFile(LATEST_URL, EXEC_PATH); fs.chmodSync(EXEC_PATH, 0o755); log(chalk.green("✅ Player2 updated successfully!")); } function showStatus() { console.log(chalk.bold("\nPlayer2 Manager Status")); console.log("──────────────────────────────"); console.log("Platform:", isMac ? "macOS" : "Linux"); console.log("Installed:", fs.existsSync(EXEC_PATH) ? "✅ Yes" : "❌ No"); console.log("Executable:", EXEC_PATH); console.log("Symlink:", fs.existsSync(SYMLINK) ? "✅ Present" : "❌ Missing"); console.log("Desktop Entry:", fs.existsSync(DESKTOP_FILE) ? "✅ Present" : "❌ Missing"); if (isLinux) console.log("Monitor Service:", fs.existsSync("/etc/p2monitor") ? "✅ Installed" : "❌ Not installed"); console.log(""); } // ───────────────────────────────────────────── // Interactive UI // ───────────────────────────────────────────── async function interactiveInstall() { const answers = await inquirer.prompt([ { type: "checkbox", name: "components", message: "Select components to install:", choices: [ { name: "Player2 Core", value: "core", checked: true }, ...(isLinux ? [{ name: "WebKit Patch", value: "patch", checked: true }] : []), ...(isLinux ? [{ name: "P2Monitor (Linux only)", value: "monitor" }] : []) ] } ]); await installPlayer2({ patches: answers.components.includes("patch"), monitor: answers.components.includes("monitor") }); } async function interactiveUninstall() { await inquirer.prompt([ { type: "checkbox", name: "remove", message: "Select components to uninstall:", choices: [ { name: "Player2 Core", value: "core", checked: true }, ...(isLinux ? [{ name: "Patches", value: "patch", checked: true }] : []), ...(isLinux ? [{ name: "P2Monitor", value: "monitor" }] : []) ] } ]); uninstallAll(); } // ───────────────────────────────────────────── // CLI Entrypoint // ───────────────────────────────────────────── const program = new Command(); program .name("p2manager") .description("Manage Player2 installation, patches, and updates") .version("1.1"); program .command("install") .option("--no-ui", "Run without interactive UI") .action(async (opts) => { if (opts.noUi) { await installPlayer2({ patches: true, monitor: false, noUI: true }); } else { await interactiveInstall(); } }); program .command("uninstall") .option("--no-ui", "Run without interactive UI") .action(async (opts) => { if (opts.noUi) uninstallAll(); else await interactiveUninstall(); }); program.command("update").action(updatePlayer2); program.command("status").action(showStatus); program.parse();