diff --git a/src/cli/files.cmd.ts b/src/cli/files.cmd.ts index f92ae21..a79c9fe 100644 --- a/src/cli/files.cmd.ts +++ b/src/cli/files.cmd.ts @@ -1,5 +1,8 @@ import fs from 'node:fs/promises'; +import { pipeline } from 'node:stream/promises'; +import { Readable } from 'node:stream'; import { Command } from 'commander'; +import { decryptWiiU } from '@pretendonetwork/boss-crypto'; import { getCliContext } from './utils'; const listCmd = new Command('ls') @@ -54,6 +57,42 @@ const viewCmd = new Command('view') }); }); +const downloadCmd = new Command('download') + .description('Download a task file') + .argument('', 'BOSS app that contains the task') + .argument('', 'Task that contains the task file') + .argument('', 'Task file ID to lookup', BigInt) + .option('-d, --decrypt', 'Decrypt the file before return') + .action(async (appId: string, taskId: string, dataId: bigint, ops: { decrypt: boolean }) => { + const ctx = getCliContext(); + const { files } = await ctx.grpc.listFiles({ + bossAppId: appId, + taskId: taskId + }); + const file = files.find(v => v.dataId === dataId); + if (!file) { + console.error(`Could not find task file with data ID ${dataId} in task ${taskId}`); + process.exit(1); + } + + const fetchResult = await fetch(`${ctx.npdiDomain}/p01/data/1/${file.bossAppId}/${file.dataId}/${file.hash}`); + if (fetchResult.status > 299) { + console.error(`Failed to download: invalid status code (${fetchResult.status})`); + process.exit(1); + } + + const arrayBuffer = await fetchResult.arrayBuffer(); + let buffer = Buffer.from(arrayBuffer); + + if (ops.decrypt) { + const keys = ctx.getWiiUKeys(); + const decrypted = decryptWiiU(buffer, keys.aesKey, keys.hmacKey); + buffer = decrypted.content; + } + + await pipeline(Readable.from(buffer), process.stdout); + }); + const createCmd = new Command('create') .description('Create a new task file') .argument('', 'BOSS app to store the task file in') @@ -103,4 +142,5 @@ export const fileCmd = new Command('file') .addCommand(listCmd) .addCommand(createCmd) .addCommand(deleteCmd) + .addCommand(downloadCmd) .addCommand(viewCmd);