import pkg from "crypto-js"; const { AES, enc } = pkg; // biome-ignore lint: This is how you import fs from node import * as fs from "node:fs"; const SAVE_KEY = "x0i2O7WRiANTqPmZ"; /** * A map of condensed keynames to their associated full names * NOTE: Update this if `src/system/game-data#systemShortKeys` ever changes! */ const systemShortKeys = { seenAttr: "$sa", caughtAttr: "$ca", natureAttr: "$na", seenCount: "$s", caughtCount: "$c", hatchedCount: "$hc", ivs: "$i", moveset: "$m", eggMoves: "$em", candyCount: "$x", friendship: "$f", abilityAttr: "$a", passiveAttr: "$pa", valueReduction: "$vr", classicWinCount: "$wc", }; /** * Replace the shortened key names with their full names * @param {string} dataStr - The string to convert * @returns {string} The string with shortened keynames replaced with full names */ function convertSystemDataStr(dataStr) { const fromKeys = Object.values(systemShortKeys); const toKeys = Object.keys(systemShortKeys); for (const k in fromKeys) { dataStr = dataStr.replace(new RegExp(`${fromKeys[k].replace("$", "\\$")}`, "g"), toKeys[k]); } return dataStr; } /** * Decrypt a save * @param {string} path - The path to the encrypted save file * @returns {string} The decrypted save data */ function decryptSave(path) { // Check if the file exists if (!fs.existsSync(path)) { console.error(`File not found: ${path}`); process.exit(1); } let fileData; try { fileData = fs.readFileSync(path, "utf8"); } catch (e) { switch (e.code) { case "ENOENT": console.error(`File not found: ${path}`); break; case "EACCES": console.error(`Could not open ${path}: Permission denied`); break; case "EISDIR": console.error(`Unable to read ${path} as it is a directory`); break; default: console.error(`Error reading file: ${e.message}`); } process.exit(1); } return convertSystemDataStr(AES.decrypt(fileData, SAVE_KEY).toString(enc.Utf8)); } /* Print the usage message and exits */ function printUsage() { console.log(` Usage: node decrypt-save.js [save-file] Arguments: file-path Path to the encrypted save file to decrypt. save-file Path to where the decrypted data should be written. If not provided, the decrypted data will be printed to the console. Options: -h, --help Show this help message and exit. Description: This script decrypts an encrypted pokerogue save file `); } /** * Write `data` to `filePath`, gracefully communicating errors that arise * @param {string} filePath * @param {string} data */ function writeToFile(filePath, data) { try { fs.writeFileSync(filePath, data); } catch (e) { switch (e.code) { case "EACCES": console.error(`Could not open ${filePath}: Permission denied`); break; case "EISDIR": console.error(`Unable to write to ${filePath} as it is a directory`); break; default: console.error(`Error writing file: ${e.message}`); } process.exit(1); } } function main() { let args = process.argv.slice(2); // Get options const options = args.filter(arg => arg.startsWith("-")); // get args args = args.filter(arg => !arg.startsWith("-")); if (args.length === 0 || options.includes("-h") || options.includes("--help") || args.length > 2) { printUsage(); process.exit(0); } // If the user provided a second argument, check if the file exists already and refuse to write to it. if (args.length === 2) { const destPath = args[1]; if (fs.existsSync(destPath)) { console.error(`Refusing to overwrite ${destPath}`); process.exit(1); } } // Otherwise, commence decryption. const decrypt = decryptSave(args[0]); if (args.length === 1) { process.stdout.write(decrypt); process.exit(0); } writeToFile(destPath, decrypt); } main();