BOSS/src/services/npfl.ts
2023-11-15 23:15:21 -05:00

114 lines
3.0 KiB
TypeScript

import crypto from 'node:crypto';
import express from 'express';
import subdomain from 'express-subdomain';
import { getTaskFilesWithAttributes } from '@/database';
const ALLOWED_QUERY_PARMS = [
'c', 'l',
'a1', 'a2', 'a3',
'ap', 'tm'
];
const npfl = express.Router();
npfl.get('/p01/filelist/:appID/:taskID', async (request: express.Request<{
appID: string;
taskID: string;
}, any, any, {
c?: string;
l?: string;
a1?: string;
a2?: string;
a3?: string;
ap?: string;
tm?: string;
}>, response) => {
for (const param in request.query) {
// * Beats me why the real server does this.
// * Just doing accurate emulation ¯\_(ツ)_/¯
if (!ALLOWED_QUERY_PARMS.includes(param)) {
response.sendStatus(403);
return;
}
}
const { appID, taskID } = request.params;
const country = request.query.c;
const language = request.query.l;
const attribute1 = request.query.a1;
const attribute2 = request.query.a2;
const attribute3 = request.query.a3;
const files = await getTaskFilesWithAttributes(false, appID, taskID, country, language, attribute1, attribute2, attribute3);
// * https://gist.github.com/DaniElectra/ada7ecc930a84432f2045f6fcabac84f#nintendo-boss-file-list-server-npfl
// *
// * File list has the following structure:
// *
// * - SHA1 hash of all following lines, including size line
// * - Size of whole file, include current line and hash line
// * - Size line is padded with spaces to 10 characters
// * - Size line has a trailing CRLF if no files found
// * - Files
// *
// * File lines have the following fields:
// *
// * - File name
// * - Unknown (password?)
// * - Attribute 1
// * - Attribute 2
// * - Attribute 3
// * - File size (0 is allowed)
// * - Updated time (seconds)
// *
// * All fields of a file line are separated by a tab (\t) and are present even if no value.
// * All line breaks are CRLF (\r\n).
// * There is always a trailing CRLF, even if no files are returned (minimum 3 lines).
// *
// * Example:
// *
// * 9268c2ae0c210df07a417683e35dd67b42df19f4
// * 132
// * 007 anniversary 7 tj 5_50 0 1523520046
// * 019 THANKYOU 19 tj 3_15 0 1557371320
let size = 0;
size += 40 + 2; // * SHA1 hash of the remaining file + CRLF
size += 10 + 2; // * Size line is padded to 10 chars + CRLF
let body = '';
for (const file of files) {
const params = [
file.name,
file.password, // * Unsure if this is really what this is for. Team Kirby Clash Deluxe uses this for passwords though
file.attribute1,
file.attribute2,
file.attribute3,
file.size,
file.updated/1000n // * Expects time as seconds, not milliseconds
];
const line = `${params.join('\t')}\r\n`;
body += line;
size += line.length;
}
const sizeLine = `${size}`.padStart(10, ' ');
body = `${sizeLine}\r\n${body}`;
const hash = crypto.createHash('sha1').update(body).digest('hex');
body = `${hash}\r\n${body}`;
response.setHeader('Content-Type', 'text/plain');
response.send(body);
});
const router = express.Router();
router.use(subdomain('npfl.c.app', npfl));
export default router;