'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { Readable } = require('stream');
const { Response } = require('node-fetch');
const {
createGameUpdateService, normalizeManifest, officialSonyUrl,
ps4VersionUrl, parseVersionXml, findVersionFileUri,
} = require('../../src/services/game-update-service');
test('Sony update manifests require contiguous hash-verified official pieces', () => {
const source = 'https://gs2.ww.prod.dl.playstation.net/path/CUSA00001-update.json';
const manifest = normalizeManifest({
originalFileSize: 12,
packageDigest: 'a'.repeat(64),
pieces: [
{ url: 'https://gs2.ww.prod.dl.playstation.net/path/CUSA00001-part1.pkg', fileOffset: 0, fileSize: 5, hashValue: 'b'.repeat(40) },
{ url: 'https://gs2.ww.prod.dl.playstation.net/path/CUSA00001-part2.pkg', fileOffset: 5, fileSize: 7, hashValue: 'c'.repeat(40) },
],
}, source, 'CUSA00001');
assert.equal(manifest.gameId, 'CUSA00001');
assert.equal(manifest.originalFileSize, 12);
assert.equal(manifest.pieces.length, 2);
assert.equal(manifest.packageDigest, 'a'.repeat(64));
assert.throws(() => normalizeManifest({
originalFileSize: 12,
pieces: [{ url: 'https://evil.example/CUSA00001.pkg', fileOffset: 0, fileSize: 12, hashValue: 'b'.repeat(40) }],
}, source), /playstation\.net/);
assert.throws(() => normalizeManifest({
originalFileSize: 12,
pieces: [{ url: 'https://gs2.ww.prod.dl.playstation.net/CUSA00001.pkg', fileOffset: 1, fileSize: 11, hashValue: 'b'.repeat(40) }],
}, source), /aaneensluitend/);
});
test('Sony URL validation blocks credentials, ports and lookalike domains', () => {
assert.equal(officialSonyUrl('http://gs2.ww.prod.dl.playstation.net/file.json').hostname, 'gs2.ww.prod.dl.playstation.net');
assert.throws(() => officialSonyUrl('https://playstation.net.evil.example/file.json'), /playstation\.net/);
assert.throws(() => officialSonyUrl('https://user:pass@playstation.net/file.json'), /credentials/);
assert.throws(() => officialSonyUrl('https://playstation.net:444/file.json'), /poort/);
});
test('PS4 version URLs and Sony XML manifests are discovered without pasted JSON URLs', () => {
assert.equal(
ps4VersionUrl('CUSA03041'),
'http://gs-sec.ww.np.dl.playstation.net/plo/np/CUSA03041/f3bc1dbcdfc190b2efdda011bdd49cbe344c0ea418faf1b511616b8460f8a835/CUSA03041-ver.xml',
);
const parsed = parseVersionXml(`
`, 'CUSA03041');
assert.equal(parsed.version, '01.32');
assert.equal(parsed.manifestUrl, 'http://gs2.ww.prod.dl.playstation.net/path/CUSA03041-update.json');
assert.throws(() => parseVersionXml('', 'CUSA03041'), /declaraties/);
assert.throws(() => parseVersionXml('', 'CUSA03041'), /hoort niet/);
});
test('PS5 versionFileUri is found in bounded nested param JSON structures', () => {
const uri = 'https://sgst.prod.dl.playstation.net/sgst/prod/00/np/PPSA00001_00/example-version.xml';
assert.equal(findVersionFileUri({ application: { metadata: { versionFileUri: uri } } }), uri);
assert.equal(findVersionFileUri({ application: {} }), '');
});
test('automatic discovery resolves a PS4 version XML into the existing safe manifest plan', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-update-discovery-'));
const gameId = 'CUSA03041';
const versionUrl = ps4VersionUrl(gameId);
const manifestUrl = `https://gs2.ww.prod.dl.playstation.net/path/${gameId}-update.json`;
const packageUrl = `https://gs2.ww.prod.dl.playstation.net/path/${gameId}.pkg`;
const fetchImpl = async url => {
if (url === versionUrl) return new Response(``);
if (url === manifestUrl) return new Response(JSON.stringify({
originalFileSize: 4,
pieces: [{ url: packageUrl, fileOffset: 0, fileSize: 4, hashValue: 'b'.repeat(40) }],
}));
throw new Error(`Unexpected URL ${url}`);
};
const service = createGameUpdateService({
db: { listGames: () => [{ game_id: gameId }], listBackups: () => [] },
ftp: {},
paths: { backups: path.join(root, 'backups'), cache: path.join(root, 'cache') },
fetchImpl,
});
try {
const plan = await service.inspectAutomatic({ gameId });
assert.equal(plan.manifest.gameId, gameId);
assert.equal(service.catalog().games[0].discovery.status, 'available');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('package downloads disable the JSON size cap but enforce exact bytes and hashes', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-update-'));
const payload = Buffer.from('safe-package');
const gameId = 'CUSA00001';
const manifestUrl = `https://gs2.ww.prod.dl.playstation.net/path/${gameId}-update.json`;
const packageUrl = `https://gs2.ww.prod.dl.playstation.net/path/${gameId}.pkg`;
const manifest = {
originalFileSize: payload.length,
packageDigest: crypto.createHash('sha256').update(payload).digest('hex'),
pieces: [{
url: packageUrl,
fileOffset: 0,
fileSize: payload.length,
hashValue: crypto.createHash('sha1').update(payload).digest('hex'),
}],
};
const fetchImpl = async (url, options) => {
if (url === manifestUrl) return new Response(JSON.stringify(manifest), { headers: { 'content-type': 'application/json' } });
assert.equal(url, packageUrl);
assert.equal(options.size, 0);
return new Response(Readable.from([payload]), { headers: { 'content-length': String(payload.length) } });
};
const service = createGameUpdateService({
db: { listGames: () => [] },
ftp: {},
paths: { backups: path.join(root, 'backups'), cache: path.join(root, 'cache') },
settings: { get: (key, fallback) => key === 'publicOrigin' ? 'https://chimera.example.test' : fallback },
fetchImpl,
});
try {
const plan = await service.inspectManifest({ url: manifestUrl, gameId });
const queued = service.queueDownload({ token: plan.token });
let job;
for (let attempt = 0; attempt < 100; attempt += 1) {
job = service.listJobs().find(item => item.id === queued.id);
if (['completed', 'failed'].includes(job.status)) break;
await new Promise(resolve => setTimeout(resolve, 10));
}
assert.equal(job.status, 'completed', job.error);
assert.deepEqual(fs.readFileSync(job.localPath), payload);
const handoff = await service.preparePackageHandoff({ id: job.id });
assert.match(handoff.url, /^https:\/\/chimera\.example\.test\/game-update-package\/[A-Za-z0-9_-]{43}\/CUSA00001\.pkg$/);
assert.equal(service.resolvePackageHandoff(new URL(handoff.url).pathname).sha256, manifest.packageDigest);
assert.equal(handoff.installExecuted, false);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});