pokerogue/scripts/decrypt-save.js
Sirz Benjie 6766940fa1
[Misc] Make the repo REUSE compliant (#6474)
* Add license information

* Add reuse lint workflow

* Add snippets for spdx

* fix: minor wording adjustments and typo fixes

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* chore: add FileContributor attributions for Bertie

Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com>

* begin licensing some audio assets

* Add pokemon reborn sound affect attribution

* Annotate Leavannite's section

* Add more licensing info

* Add license info to license files ._.

* Move ps1 files out of public

* Add license for animation jsons

* Add license for bat scripts in public

* Update licensing in scripts

* Fix typo in license ref

* Fix AGPL-3.0-or-later

* Add license info to typedoc.config.js

* Add MIT license for snippets

* chore: update license info for files in scripts

* chore: update license info

* chore: update license info

* chore: update license info

* Remove licenses used only by public before linting with reuse

* Add license info to new files added by docker PR

* chore: apply biome

* fix: add back linting workflow lost during merge

* Add attribution based on Hanniel's information

* Add attribution based on Officer Porkchop and Green Ninja's information

* add attribution to unicorn_power for reshiram/zekrom/kyurem epic variant

* Fixup minor typo

* Adjust sprite test to not think REUSE.toml is a sprite json

* Add missing continue-on-error to workflow

* fix: address kev's comments from code review

* docs: minor touchups

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com>
2025-09-23 08:49:03 -05:00

171 lines
4.4 KiB
JavaScript

/*
* SPDX-FileCopyrightText: 2024-2025 Pagefault Games
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
// Usage: node decrypt-save.js <encrypted-file> [save-file]
import fs from "node:fs";
import crypto_js from "crypto-js";
const { AES, enc } = crypto_js;
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) {
if (!(e instanceof Error)) {
console.error(`Unrecognized error: ${e}`);
process.exit(1);
}
// @ts-expect-error - e is usually a SystemError (all of which have codes)
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 <encrypted-file> [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) {
if (!(e instanceof Error)) {
console.error("Unknown error detected: ", e);
process.exitCode = 1;
return;
}
// @ts-expect-error - e is usually a SystemError (all of which have codes)
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.exitCode = 1;
return;
}
}
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(args[1], decrypt);
}
main();