diff --git a/Dockerfile b/Dockerfile index f6e25c7..4b3d7d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,7 +39,6 @@ RUN chown node:node ${app_dir} ENV NODE_ENV=production USER node -COPY --chown=node:node update-rotation.mjs ${app_dir} COPY --chown=node:node ./boss ${app_dir} COPY --chown=node:node seeding ${app_dir} COPY --chown=node:node package.json . diff --git a/README.md b/README.md index 8fd367b..365318c 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,20 @@ Configurations are loaded through environment variables. `.env` files are suppor | `PN_BOSS_CLI_WIIU_HMAC_KEY` | The BOSS WiiU HMAC key, needs to be dumped from a console | Optional | | `PN_BOSS_CLI_NPDI_URL` | The URL of the NPDI part the BOSS HTTP server, only needed when downloading | Optional | | `PN_BOSS_CLI_NPDI_HOST` | The Host header for the NPDI requests. Use when you don't have NPDI exposed to the internet | Optional | - \ No newline at end of file + +## Common CLI operations + +```sh +# Download taskfile and decrypt +./boss file ls # View list of files and their IDs +./boss file view --decrypt > output.txt # Download file and decrypt +``` + +```sh +# Update splatoon rotations +# Run the following for all of these BOSS app ids: +# - bb6tOEckvgZ50ciH +# - rjVlM7hUXPxmYQJh +# - zvGSM4kOrXpkKnpT +./boss file create schdat2 --name VSSetting.byaml --type AppData --notify-new app --file +``` diff --git a/src/cli/files.cmd.ts b/src/cli/files.cmd.ts index 0dd73b7..51ac755 100644 --- a/src/cli/files.cmd.ts +++ b/src/cli/files.cmd.ts @@ -113,7 +113,9 @@ const createCmd = new Command('create') .option('--country ', 'Countries for this task file') .option('--lang ', 'Languages for this task file') .option('--name-as-id', 'Force the name as the data ID') - .action(async (appId: string, taskId: string, opts: { name: string; country: string[]; lang: string[]; nameAsId?: boolean; type: string; file: string }) => { + .option('--notify-new ', 'Add entry to NotifyNew') + .option('--notify-led', 'Enable NotifyLED') + .action(async (appId: string, taskId: string, opts: { name: string; country: string[]; notifyNew: string[]; notifyLed: boolean; lang: string[]; nameAsId?: boolean; type: string; file: string }) => { const fileBuf = await fs.readFile(opts.file); const ctx = getCliContext(); const { file } = await ctx.grpc.uploadFile({ @@ -124,7 +126,9 @@ const createCmd = new Command('create') supportedLanguages: opts.lang, type: opts.type, nameEqualsDataId: opts.nameAsId ?? false, - data: fileBuf + data: fileBuf, + notifyOnNew: opts.notifyNew, + notifyLed: opts.notifyLed }); if (!file) { console.log(`Failed to create file!`); diff --git a/update-rotation.mjs b/update-rotation.mjs deleted file mode 100644 index 6569fa1..0000000 --- a/update-rotation.mjs +++ /dev/null @@ -1,157 +0,0 @@ -/* eslint-disable no-undef -- Tis a script */ - -import path from 'path'; -import crypto from 'crypto'; -import readline from 'readline'; -import fs from 'fs-extra'; -import dotenv from 'dotenv'; -import xml from 'xml-js'; -import { encryptWiiU } from '@pretendonetwork/boss-crypto'; - -dotenv.config(); - -function md5(input) { - return crypto.createHash('md5').update(input).digest('hex'); -} - -const BOSS_WIIU_AES_KEY_MD5_HASH = '5202ce5099232c3d365e28379790a919'; -const BOSS_WIIU_HMAC_KEY_MD5_HASH = 'b4482fef177b0100090ce0dbeb8ce977'; - -const { PN_BOSS_CONFIG_BOSS_WIIU_AES_KEY, PN_BOSS_CONFIG_BOSS_WIIU_HMAC_KEY } = process.env; - -if (!PN_BOSS_CONFIG_BOSS_WIIU_AES_KEY || md5(PN_BOSS_CONFIG_BOSS_WIIU_AES_KEY) !== BOSS_WIIU_AES_KEY_MD5_HASH) { - console.error('PN_BOSS_CONFIG_BOSS_WIIU_AES_KEY is not set or does not match the expected value'); - process.exit(1); -} -if (!PN_BOSS_CONFIG_BOSS_WIIU_HMAC_KEY || md5(PN_BOSS_CONFIG_BOSS_WIIU_HMAC_KEY) !== BOSS_WIIU_HMAC_KEY_MD5_HASH) { - console.error('PN_BOSS_CONFIG_BOSS_WIIU_HMAC_KEY is not set or does not match the expected value'); - process.exit(1); -} - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -const askQuestion = (question) => { - return new Promise((resolve) => { - rl.question(question, (answer) => { - resolve(answer); - }); - }); -}; - -const rootDir = import.meta.dirname; -const sourceFile = path.join(rootDir, 'VSSetting.byaml'); -const checksumFile = path.join(rootDir, 'cdn/VSSetting.byaml.checksum'); - -const exists = await fs.exists(sourceFile); -if (!exists) { - console.error('Source VSSetting.byaml file does not exist'); - process.exit(1); -} - -const sourceFileContents = await fs.readFile(sourceFile); - -const stat = await fs.stat(sourceFile); -if (stat.mtime.toDateString() !== new Date().toDateString()) { - const answer = await askQuestion(`The source file was not updated today (Updated ${stat.mtime.toDateString()}). Do you want to continue? (y/n) `); - if (answer.toLowerCase() !== 'y') { - process.exit(0); - } -} - -const checksumExists = await fs.exists(checksumFile); -const newChecksum = crypto.createHash('sha256').update(sourceFileContents).digest('hex'); -console.log(`Checksum for source file is ${newChecksum}`); -if (checksumExists) { - const checksum = await fs.readFile(checksumFile, 'utf8'); - - if (checksum === newChecksum) { - const answer = await askQuestion('The source file has not changed since the last run. Do you want to continue? (y/n) '); - if (answer.toLowerCase() !== 'y') { - process.exit(0); - } - } -} - -const titles = [ - 'bb6tOEckvgZ50ciH', - 'rjVlM7hUXPxmYQJh', - 'zvGSM4kOrXpkKnpT' -]; - -async function backupFile(filePath) { - const copyFilePath = path.join(path.dirname(filePath), `${path.basename(filePath)}.bak`); - const exists = await fs.exists(filePath); - if (!exists) { - console.log(`File ${filePath} does not exist, skipping backup...`); - return; - } - await fs.copyFile(filePath, copyFilePath); - console.log(`Backup created of ${filePath} at ${copyFilePath}`); -} - -for (const title of titles) { - console.log(`\n --- Processing ${title} ---`); - const decryptedDir = path.join(rootDir, `cdn/content/decrypted/${title}`); - const encryptedDir = path.join(rootDir, `cdn/content/encrypted/${title}`); - const taskSheetDir = path.join(rootDir, `cdn/tasksheet/1/${title}`); - await fs.ensureDir(decryptedDir); - await fs.ensureDir(encryptedDir); - await fs.ensureDir(taskSheetDir); - - const decryptedFilePath = path.join(decryptedDir, 'VSSetting.byaml'); - await backupFile(decryptedFilePath); - await fs.copyFile(sourceFile, decryptedFilePath); - - const encryptedContents = encryptWiiU(decryptedFilePath, PN_BOSS_CONFIG_BOSS_WIIU_AES_KEY, PN_BOSS_CONFIG_BOSS_WIIU_HMAC_KEY); - const hash = crypto.createHash('md5').update(encryptedContents).digest('hex'); - console.log(`Hash for title ${title} is ${hash}`); - - const encryptedFilePath = path.join(encryptedDir, hash); - await fs.writeFile(encryptedFilePath, encryptedContents); - console.log(`Encrypted file created at ${encryptedFilePath}`); - - const taskSheetFilePath = path.join(taskSheetDir, 'schdat2'); - await backupFile(taskSheetFilePath); - - const tasksheetContents = await fs.readFile(taskSheetFilePath, 'utf8'); - const xmlContents = xml.xml2js(tasksheetContents, { compact: true }); - - const dataId = parseInt(xmlContents.TaskSheet.Files.File.DataId._text); - if (isNaN(dataId)) { - console.error(`DataId for title ${title} is not a number, skipping...`); - continue; - } - console.log(`DataId for title ${title} is ${dataId}`); - const newDataId = dataId + 1; - console.log(`New DataId for title ${title} is ${newDataId}`); - - xmlContents.TaskSheet.Files.File.DataId._text = newDataId.toString(); - - const size = encryptedContents.length; - const oldSize = parseInt(xmlContents.TaskSheet.Files.File.Size._text); - if (size === oldSize) { - console.log(`Size for title ${title} is already updated, skipping update...`); - } else { - console.log(`Old size for title ${title} is ${oldSize}`); - console.log(`New size for title ${title} is ${size}`); - xmlContents.TaskSheet.Files.File.Size._text = size.toString(); - } - - const oldUrl = xmlContents.TaskSheet.Files.File.Url._text; - const newUrl = `https://npdi.cdn.pretendo.cc/p01/data/1/${title}/${newDataId}/${hash}`; - console.log(`Old URL for title ${title} is ${oldUrl}`); - console.log(`New URL for title ${title} is ${newUrl}`); - xmlContents.TaskSheet.Files.File.Url._text = newUrl; - - const newXmlContents = xml.js2xml(xmlContents, { spaces: 2, compact: true }); - await fs.writeFile(taskSheetFilePath, newXmlContents); - console.log(`Tasksheet file updated at ${taskSheetFilePath}`); -} - -rl.close(); - -console.log('All tasks completed successfully!'); -await fs.writeFile(checksumFile, newChecksum);