Files
chimera-Public/test/server/ch-1102-ps5-native-packages.test.js
T

543 lines
38 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const crypto = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const test = require('node:test');
const database = require('../../src/database');
const {
createPs5NativePackageService,
inspectPs5ContainerFile,
} = require('../../src/services/ps5-native-package-service');
const {
PINNED_PS5_BUILDER,
createPs5NativePkgBuilderProvider,
runProcess,
} = require('../../src/services/ps5-native-pkg-builder-provider');
const { registerPs5NativePackageManifest } = require('../../src/services/ps5-native-package-registration-service');
function semanticCapabilities(overrides = {}) {
return { backupToDebugFpkg: true, dataDlcToDebugFpkg: true, updateToDebugFpkg: false, backupInspection: true, packageInspection: true, packageValidation: true, launchReadiness: true, licenseFreeBuild: true, finalizedFihOutput: true, ...overrides };
}
function semanticProbe(overrides = {}) { return { available: true, ...PINNED_PS5_BUILDER, capabilities: semanticCapabilities(), ...overrides }; }
function acceptedValidation() { return { accepted: true, validator: 'ProsperoPkgValidator', launchReady: true, unresolvedModules: [] }; }
function writeBackup(root, name = 'source', overrides = {}) {
const folder = path.join(root, name);
fs.mkdirSync(path.join(folder, 'sce_sys'), { recursive: true });
fs.writeFileSync(path.join(folder, 'eboot.bin'), Buffer.concat([Buffer.from([0x7f, 0x45, 0x4c, 0x46]), Buffer.alloc(128)]));
fs.writeFileSync(path.join(folder, 'sce_sys', 'param.json'), JSON.stringify({
titleId: 'PPSA12345',
contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001',
contentVersion: '01.00',
localizedParameters: { enUS: { titleName: 'Chimera PS5 Test' }, defaultLanguage: 'en-US' },
applicationDrmType: 'standard',
...overrides,
}));
return folder;
}
function writeDlcBackup(root, name = 'dlc-source', overrides = {}) {
const folder = path.join(root, name);
fs.mkdirSync(path.join(folder, 'sce_sys'), { recursive: true });
fs.mkdirSync(path.join(folder, 'data'), { recursive: true });
fs.writeFileSync(path.join(folder, 'data', 'owned-content.bin'), 'owned dlc fixture');
fs.writeFileSync(path.join(folder, 'sce_sys', 'param.json'), JSON.stringify({
titleId: 'PPSA12345',
contentId: 'UP0001-PPSA12345_00-CHIMERADLC000001',
contentVersion: '01.01',
localizedParameters: { enUS: { titleName: 'Chimera PS5 Test DLC' }, defaultLanguage: 'en-US' },
applicationDrmType: 'free',
...overrides,
}));
return folder;
}
function writeFih(filename, contentId = 'UP0001-PPSA12345_00-CHIMERATEST00001') {
const fih = Buffer.alloc(0x10000); const pfs = Buffer.alloc(0x10000); const cnt = Buffer.alloc(0x2900);
Buffer.from([0x7f, 0x46, 0x49, 0x48]).copy(fih); fih[4] = 1; fih.writeUInt16LE(3, 6); fih.writeUInt32LE(1, 8);
fih.writeBigUInt64LE(0x10000n, 0x10); fih.writeBigUInt64LE(0x10000n, 0x18); fih.writeBigUInt64LE(0x20000n, 0x58); fih.writeUInt32LE(1, 0xfc);
const gameDigest = crypto.createHash('sha3-256').update(pfs).digest(); for (const offset of [0x30, 0x70, 0xd0]) gameDigest.copy(fih, offset);
Buffer.from([0x7f, 0x43, 0x4e, 0x54]).copy(cnt); cnt.writeUInt32BE(1, 0x10); cnt.writeUInt16BE(1, 0x14); cnt.writeUInt16BE(1, 0x16); cnt.writeUInt32BE(0x2000, 0x18); cnt.writeBigUInt64BE(0x2000n, 0x20); cnt.writeBigUInt64BE(0x900n, 0x28);
Buffer.from(contentId, 'ascii').copy(cnt, 0x40, 0, 0x30); cnt.writeUInt32BE(0x20, 0x2000); cnt.writeUInt32BE(0x2100, 0x2010); cnt.writeUInt32BE(0x800, 0x2014);
for (const [record, offset] of [0x000, 0x180, 0x300, 0x480, 0x600].entries()) crypto.createHash('sha256').update(`${contentId}:${record}`).digest().copy(cnt, 0x2100 + offset);
crypto.createHash('sha3-256').update(cnt.subarray(0, 0xfe0)).digest().copy(cnt, 0xfe0);
fs.writeFileSync(filename, Buffer.concat([fih, pfs, cnt]));
}
function fixture(t, overrides = {}) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-pkg-'));
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const libraryRoot = path.join(root, 'library');
const outputRoot = path.join(root, 'vault');
fs.mkdirSync(libraryRoot, { recursive: true });
const registrations = [];
const builds = [];
const provider = overrides.provider || {
probe: async () => semanticProbe(),
build: async input => { builds.push(input); fs.mkdirSync(path.dirname(input.outputPath), { recursive: true }); writeFih(input.outputPath, input.contentId); return { provider: PINNED_PS5_BUILDER }; },
validatePackage: async () => acceptedValidation(),
};
const service = createPs5NativePackageService({
allowedRoots: () => [libraryRoot, outputRoot], outputRoot,
stateFile: path.join(root, 'state', 'jobs.json'), provider,
registrar: async manifest => { registrations.push(manifest); return { packageSetId: 91 }; },
idFactory: overrides.idFactory || (() => 'ps5-job-1'), now: overrides.now || (() => 1_800_000_000_000),
});
return { root, libraryRoot, outputRoot, registrations, builds, service };
}
test('validates a decrypted PS5 backup from param.json and executable evidence', async t => {
const f = fixture(t);
const source = writeBackup(f.libraryRoot);
const result = await f.service.inspectBackup({ sourcePath: source, expectedTitleId: 'PPSA12345' });
assert.equal(result.ready, true);
assert.equal(result.titleId, 'PPSA12345');
assert.equal(result.executable.format, 'raw-elf');
assert.equal(result.contentId, 'UP0001-PPSA12345_00-CHIMERATEST00001');
});
test('blocks opaque executables and identity mismatch while rejecting unsafe paths', async t => {
const f = fixture(t);
const opaque = writeBackup(f.libraryRoot, 'opaque');
fs.writeFileSync(path.join(opaque, 'eboot.bin'), 'not decrypted');
const opaqueResult = await f.service.inspectBackup({ sourcePath: opaque });
assert.equal(opaqueResult.state, 'blocked');
assert.ok(opaqueResult.issues.some(item => item.code === 'PS5_FPKG_MODULE_UNRESOLVED'));
const wrong = writeBackup(f.libraryRoot, 'wrong');
const mismatch = await f.service.inspectBackup({ sourcePath: wrong, expectedTitleId: 'PPSA99999' });
assert.equal(mismatch.state, 'blocked'); assert.ok(mismatch.issues.some(item => item.code === 'PS5_FPKG_TITLE_MISMATCH'));
const linked = writeBackup(f.libraryRoot, 'linked');
const outside = path.join(f.root, 'outside'); fs.mkdirSync(outside); fs.writeFileSync(path.join(outside, 'x'), 'x');
fs.symlinkSync(outside, path.join(linked, 'escape'), process.platform === 'win32' ? 'junction' : 'dir');
await assert.rejects(() => f.service.inspectBackup({ sourcePath: linked }), error => error.code === 'PS5_FPKG_SOURCE_UNSAFE');
await assert.rejects(() => f.service.inspectBackup({ sourcePath: outside }), error => error.code === 'PS5_BACKUP_CONTAINMENT');
});
test('accepts signed modules only when a matching decrypted raw-ELF substitute exists', async t => {
const f = fixture(t); const source = writeBackup(f.libraryRoot, 'signed-with-decrypted');
fs.writeFileSync(path.join(source, 'eboot.bin'), Buffer.concat([Buffer.from([0x54, 0x14, 0xf5, 0xee]), Buffer.alloc(128)]));
fs.mkdirSync(path.join(source, 'decrypted'), { recursive: true });
fs.writeFileSync(path.join(source, 'decrypted', 'eboot.bin'), Buffer.concat([Buffer.from([0x7f, 0x45, 0x4c, 0x46]), Buffer.alloc(128)]));
const result = await f.service.inspectBackup({ sourcePath: source });
assert.equal(result.executable.format, 'self-with-decrypted-substitutes');
assert.equal(result.executable.substitutedModuleCount, 1);
});
test('accepts data DLC without eboot but refuses update and entitlement-only package routes', async t => {
const f = fixture(t); const dlc = writeDlcBackup(f.libraryRoot);
const inspected = await f.service.inspectBackup({ sourcePath: dlc, expectedTitleId: 'PPSA12345', componentType: 'dlc' });
assert.equal(inspected.componentType, 'dlc');
assert.equal(inspected.executable.moduleCount, 0);
const update = await f.service.inspectBackup({ sourcePath: dlc, componentType: 'update' });
assert.equal(update.state, 'unsupported'); assert.equal(update.ready, false);
await assert.rejects(() => f.service.inspectBackup({ sourcePath: dlc, componentType: 'dlc-entitlement' }), error => error.code === 'PS5_FPKG_SOURCE_NOT_READY');
});
test('distinguishes genuine PS5 FIH/CNT containers from FFPKG mount images and renamed files', t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-format-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const fih = path.join(root, 'game.pkg'); writeFih(fih);
const cnt = path.join(root, 'metadata.pkg'); fs.writeFileSync(cnt, Buffer.concat([Buffer.from([0x7f, 0x43, 0x4e, 0x54]), Buffer.alloc(64)]));
const image = path.join(root, 'game.ffpkg'); fs.writeFileSync(image, Buffer.alloc(128));
const renamed = path.join(root, 'renamed.pkg'); fs.writeFileSync(renamed, Buffer.alloc(128));
const inspectedFih = inspectPs5ContainerFile(fih);
assert.deepEqual(inspectedFih.format, 'ps5-fih-debug-package');
assert.equal(inspectedFih.validation.installTrust.status, 'unproven');
assert.equal(inspectedFih.validation.installTrust.basis, 'structural-only');
assert.ok(inspectedFih.validation.warnings.some(item => item.code === 'APPINST_TRUST_UNPROVEN'));
assert.deepEqual(inspectPs5ContainerFile(cnt).format, 'ps5-cnt-container');
assert.equal(inspectPs5ContainerFile(image).format, 'ufs-mount-image');
assert.equal(inspectPs5ContainerFile(image).nativeInstallable, false);
assert.equal(inspectPs5ContainerFile(renamed).format, 'unknown-pkg');
assert.equal(inspectPs5ContainerFile(renamed).nativeInstallable, false);
});
test('accepts the upstream stock FIH image-count and opaque image-key profile without claiming launch compatibility', t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-stock-profile-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const stock = path.join(root, 'stock.pkg'); writeFih(stock, 'IV9999-FAKE00000_00-HOMEBREWLOADER01');
const bytes = fs.readFileSync(stock);
bytes.writeUInt32LE(0, 0xfc);
for (let index = 0; index < 0x800; index++) bytes[0x22100 + index] = ((index * 73 + 19) % 255) + 1;
crypto.createHash('sha3-256').update(bytes.subarray(0x20000, 0x20fe0)).digest().copy(bytes, 0x20fe0);
fs.writeFileSync(stock, bytes);
const inspected = inspectPs5ContainerFile(stock);
assert.equal(inspected.structurallyValid, true);
assert.equal(inspected.validation.validationLevel, 'fih-cnt-structural-v3');
assert.equal(inspected.validation.imageCount, 0);
assert.equal(inspected.validation.imageKey.profile, 'opaque-filled-0x800');
assert.equal(inspected.validation.routeCompatibility.status, 'unproven');
assert.ok(inspected.validation.warnings.some(item => item.code === 'FIH_IMAGE_COUNT_STOCK_PROFILE'));
assert.ok(inspected.validation.warnings.some(item => item.code === 'CNT_IMAGE_KEY_OPAQUE_PROFILE'));
});
test('fails closed when FIH geometry, CNT sealing or image-key records are corrupted', t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-structural-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const geometry = path.join(root, 'geometry.pkg'); writeFih(geometry);
const geometryFd = fs.openSync(geometry, 'r+'); fs.writeSync(geometryFd, Buffer.from([0x01]), 0, 1, 0x58); fs.closeSync(geometryFd);
let inspected = inspectPs5ContainerFile(geometry);
assert.equal(inspected.nativeInstallable, false);
assert.ok(inspected.validation.errors.some(item => item.code === 'FIH_CNT_GEOMETRY'));
const seal = path.join(root, 'seal.pkg'); writeFih(seal);
const sealFd = fs.openSync(seal, 'r+'); fs.writeSync(sealFd, Buffer.from([0xff]), 0, 1, 0x20000 + 0x100); fs.closeSync(sealFd);
inspected = inspectPs5ContainerFile(seal);
assert.ok(inspected.validation.errors.some(item => item.code === 'CNT_HEADER_SEAL'));
const key = path.join(root, 'key.pkg'); writeFih(key);
const keyFd = fs.openSync(key, 'r+'); fs.writeSync(keyFd, Buffer.alloc(0x800), 0, 0x800, 0x22100); fs.closeSync(keyFd);
inspected = inspectPs5ContainerFile(key);
assert.ok(inspected.validation.errors.some(item => item.code === 'CNT_IMAGE_KEY_PROFILE'));
const sparseKey = path.join(root, 'sparse-key.pkg'); writeFih(sparseKey);
const sparseKeyBytes = fs.readFileSync(sparseKey); sparseKeyBytes.fill(0, 0x22100, 0x22900); sparseKeyBytes[0x22100] = 1;
crypto.createHash('sha3-256').update(sparseKeyBytes.subarray(0x20000, 0x20fe0)).digest().copy(sparseKeyBytes, 0x20fe0); fs.writeFileSync(sparseKey, sparseKeyBytes);
inspected = inspectPs5ContainerFile(sparseKey);
assert.ok(inspected.validation.errors.some(item => item.code === 'CNT_IMAGE_KEY_PROFILE'));
});
test('builds, validates and hashes a real FIH package before registration', async t => {
const f = fixture(t);
const source = writeBackup(f.libraryRoot);
const created = await f.service.create({ sourcePath: source, expectedTitleId: 'PPSA12345' });
assert.equal(created.status, 'package_ready');
const complete = await f.service.run(created.id);
assert.equal(complete.status, 'artifact_registered');
assert.equal(complete.container.format, 'ps5-fih-debug-package');
assert.match(complete.sha256, /^[a-f0-9]{64}$/);
assert.equal(f.registrations.length, 1);
assert.equal(f.registrations[0].components[0].adapterHint, 'ps5-fpkg-finalized-fih');
assert.equal(f.registrations[0].components[0].sha256, crypto.createHash('sha256').update(fs.readFileSync(complete.outputPath)).digest('hex'));
});
test('builds one ordered native package set from base plus data DLC components', async t => {
const f = fixture(t); const base = writeBackup(f.libraryRoot, 'base'); const dlc = writeDlcBackup(f.libraryRoot, 'dlc');
const created = await f.service.create({ expectedTitleId: 'PPSA12345', sources: [{ sourcePath: base, componentType: 'base' }, { sourcePath: dlc, componentType: 'dlc' }] });
const complete = await f.service.run(created.id);
assert.equal(complete.status, 'artifact_registered');
assert.deepEqual(f.builds.map(item => item.componentType), ['base', 'dlc']);
assert.deepEqual(f.registrations[0].components.map(item => item.type), ['base', 'dlc']);
assert.deepEqual(f.registrations[0].recipe.installOrder.map(item => item.order), [1, 3]);
assert.deepEqual(f.registrations[0].dependencies, [{ componentId: f.registrations[0].components[1].id, requires: 'base' }]);
assert.equal(f.registrations[0].components.every(item => item.container?.format === 'ps5-fih-debug-package'), true);
});
test('a component identity change between planning and build fails closed', async t => {
const f = fixture(t); const base = writeBackup(f.libraryRoot, 'base'); const dlc = writeDlcBackup(f.libraryRoot, 'dlc');
const created = await f.service.create({ sources: [{ sourcePath: base, componentType: 'base' }, { sourcePath: dlc, componentType: 'dlc' }] });
const paramPath = path.join(dlc, 'sce_sys', 'param.json'); const param = JSON.parse(fs.readFileSync(paramPath, 'utf8')); param.contentId = 'UP0001-PPSA12345_00-CHIMERADLC000002'; fs.writeFileSync(paramPath, JSON.stringify(param));
await assert.rejects(() => f.service.run(created.id), error => error.code === 'PS5_FPKG_SOURCE_CHANGED');
assert.equal(f.registrations.length, 0);
});
test('a same-size payload mutation between planning and build fails closed', async t => {
const f = fixture(t); const base = writeBackup(f.libraryRoot, 'base'); const dlc = writeDlcBackup(f.libraryRoot, 'dlc');
const created = await f.service.create({ sources: [{ sourcePath: base, componentType: 'base' }, { sourcePath: dlc, componentType: 'dlc' }] });
const payloadPath = path.join(dlc, 'data', 'owned-content.bin'); const original = fs.readFileSync(payloadPath);
fs.writeFileSync(payloadPath, Buffer.alloc(original.length, 0x78));
await assert.rejects(() => f.service.run(created.id), error => error.code === 'PS5_FPKG_SOURCE_CHANGED');
assert.equal(f.builds.length, 0);
assert.equal(f.registrations.length, 0);
});
test('exposes only hash-bound native FIH packages through a short-lived HTTPS handoff', async t => {
const f = fixture(t); const pkg = path.join(f.outputRoot, 'verified.pkg'); writeFih(pkg); const bytes = fs.readFileSync(pkg); const sha256 = crypto.createHash('sha256').update(bytes).digest('hex');
const exposed = await f.service.expose({ filePath: pkg, sha256, size: bytes.length, origin: 'https://chimera.example.test' });
assert.match(exposed.url, /^https:\/\/chimera\.example\.test\/ps5-native-package\/[A-Za-z0-9_-]{43}\/verified\.pkg$/);
assert.equal(f.service.resolvePackageHandoff(new URL(exposed.url).pathname).filePath, fs.realpathSync(pkg));
await assert.rejects(() => f.service.expose({ filePath: pkg, sha256: '0'.repeat(64), size: bytes.length, origin: 'https://chimera.example.test' }), error => error.code === 'PS5_PACKAGE_HANDOFF_HASH');
await assert.rejects(() => f.service.expose({ filePath: pkg, sha256, size: bytes.length, origin: 'http://chimera.example.test' }), error => error.code === 'PS5_PACKAGE_ORIGIN');
});
test('active jobs become interrupted and require an explicit resume', async t => {
const f = fixture(t);
const source = writeBackup(f.libraryRoot);
const created = await f.service.create({ sourcePath: source });
const stateFile = path.join(f.root, 'state', 'jobs.json');
const stored = JSON.parse(fs.readFileSync(stateFile, 'utf8')); stored.jobs[0].status = 'building'; fs.writeFileSync(stateFile, JSON.stringify(stored));
const restarted = createPs5NativePackageService({ allowedRoots: () => [f.libraryRoot, f.outputRoot], outputRoot: f.outputRoot, stateFile, provider: f.service.__provider || { probe: async () => ({ available: true }), build: async () => ({}) }, registrar: async () => ({ packageSetId: 91 }) });
assert.equal(restarted.get(created.id).status, 'interrupted');
assert.equal(restarted.resume(created.id).status, 'package_ready');
});
test('requires a real registrar and never marks an unregistered artifact completed', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-registrar-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const libraryRoot = path.join(root, 'library'); const outputRoot = path.join(root, 'vault'); fs.mkdirSync(libraryRoot);
const provider = { probe: async () => semanticProbe(), build: async input => writeFih(input.outputPath, input.contentId), validatePackage: async () => acceptedValidation() };
assert.throws(() => createPs5NativePackageService({ allowedRoots: () => [libraryRoot, outputRoot], outputRoot, stateFile: path.join(root, 'missing.json'), provider }), /registrar/i);
let registration = {}; let builds = 0;
provider.build = async input => { builds++; fs.mkdirSync(path.dirname(input.outputPath), { recursive: true }); writeFih(input.outputPath, input.contentId); };
const service = createPs5NativePackageService({ allowedRoots: () => [libraryRoot, outputRoot], outputRoot, stateFile: path.join(root, 'jobs.json'), provider, registrar: async () => registration, idFactory: () => 'registration-job' });
const source = writeBackup(libraryRoot);
const created = await service.create({ sourcePath: source });
await assert.rejects(() => service.run(created.id), error => error.code === 'PS5_PACKAGE_REGISTRATION_INCOMPLETE');
assert.equal(service.get(created.id).status, 'registration_pending');
registration = { packageSetId: 123 };
service.resume(created.id);
const completed = await service.run(created.id);
assert.equal(completed.status, 'artifact_registered');
assert.equal(completed.registration.packageSetId, 123);
assert.equal(builds, 1);
});
test('rejects stale concurrent writers instead of clobbering job state', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-cas-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const libraryRoot = path.join(root, 'library'); const outputRoot = path.join(root, 'vault'); const stateFile = path.join(root, 'jobs.json'); fs.mkdirSync(libraryRoot);
const provider = { probe: async () => ({ available: true }), build: async () => ({}) }; const options = { allowedRoots: () => [libraryRoot, outputRoot], outputRoot, stateFile, provider, registrar: async () => ({ packageSetId: 1 }) };
const first = createPs5NativePackageService({ ...options, idFactory: () => 'first' });
const second = createPs5NativePackageService({ ...options, idFactory: () => 'second' });
const source = writeBackup(libraryRoot);
await first.create({ sourcePath: source });
await assert.rejects(() => second.create({ sourcePath: source }), error => error.code === 'PS5_PACKAGE_STATE_CONFLICT');
assert.deepEqual(JSON.parse(fs.readFileSync(stateFile, 'utf8')).jobs.map(job => job.id), ['first']);
});
test('fails closed without overwriting corrupted durable job state', t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-corrupt-state-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const outputRoot = path.join(root, 'vault'); const stateFile = path.join(root, 'jobs.json'); const original = '{broken-json'; fs.writeFileSync(stateFile, original);
const provider = { probe: async () => ({ available: true }), build: async () => ({}) };
assert.throws(() => createPs5NativePackageService({ allowedRoots: () => [root], outputRoot, stateFile, provider, registrar: async () => ({ packageSetId: 1 }) }), error => error.code === 'PS5_PACKAGE_STATE_CORRUPT');
assert.equal(fs.readFileSync(stateFile, 'utf8'), original);
});
test('resume reuses a hash-verified component checkpoint instead of rebuilding it', async t => {
const calls = []; let failDlc = true;
const provider = {
probe: async () => semanticProbe(),
build: async input => { calls.push(input.componentType); if (input.componentType === 'dlc' && failDlc) throw new Error('simulated DLC failure'); fs.mkdirSync(path.dirname(input.outputPath), { recursive: true }); writeFih(input.outputPath, input.contentId); },
validatePackage: async () => acceptedValidation(),
};
const f = fixture(t, { provider }); const base = writeBackup(f.libraryRoot, 'base'); const dlc = writeDlcBackup(f.libraryRoot, 'dlc');
const created = await f.service.create({ sources: [{ sourcePath: base, componentType: 'base' }, { sourcePath: dlc, componentType: 'dlc' }] });
await assert.rejects(() => f.service.run(created.id), /simulated DLC failure/);
failDlc = false; f.service.resume(created.id);
const complete = await f.service.run(created.id);
assert.equal(complete.status, 'artifact_registered');
assert.deepEqual(calls, ['base', 'dlc', 'dlc']);
});
test('start acknowledges immediately and runs the durable build asynchronously', async t => {
let releaseBuild; const gate = new Promise(resolve => { releaseBuild = resolve; });
const provider = {
probe: async () => semanticProbe(),
build: async input => { await gate; fs.mkdirSync(path.dirname(input.outputPath), { recursive: true }); writeFih(input.outputPath, input.contentId); },
validatePackage: async () => acceptedValidation(),
};
const f = fixture(t, { provider }); const created = await f.service.create({ sourcePath: writeBackup(f.libraryRoot) });
const accepted = f.service.start(created.id);
assert.equal(accepted.accepted, true); assert.equal(accepted.status, 'package_ready');
assert.equal(f.service.start(created.id).alreadyRunning, true);
releaseBuild();
for (let attempt = 0; attempt < 100 && f.service.get(created.id).status !== 'artifact_registered'; attempt++) await new Promise(resolve => setTimeout(resolve, 10));
assert.equal(f.service.get(created.id).status, 'artifact_registered');
});
test('service enforces the builder-advertised source ceiling before build', async t => {
let builds = 0;
const provider = {
probe: async () => semanticProbe({ capabilities: semanticCapabilities({ maxSourceBytes: 1 }) }),
build: async () => { builds++; },
validatePackage: async () => acceptedValidation(),
};
const f = fixture(t, { provider });
const created = await f.service.create({ sourcePath: writeBackup(f.libraryRoot) });
await assert.rejects(() => f.service.run(created.id), error => error.code === 'PS5_BUILDER_SOURCE_LIMIT' && error.details.maxSourceBytes === 1);
assert.equal(builds, 0);
});
test('pinned bridge uses a fixed no-shell JSON request protocol and rejects hostile paths', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-builder-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const toolRoot = path.join(root, 'tool'); const workRoot = path.join(root, 'work'); const sourceRoot = path.join(root, 'source');
fs.mkdirSync(toolRoot); fs.mkdirSync(workRoot); fs.mkdirSync(sourceRoot);
const executable = path.join(toolRoot, PINNED_PS5_BUILDER.executable); fs.writeFileSync(executable, 'bridge');
const outputPath = path.join(workRoot, 'out.pkg'); const calls = [];
const runner = async input => {
calls.push(input);
if (input.args[0] === 'probe') return { output: JSON.stringify(semanticProbe()) };
writeFih(outputPath); fs.writeFileSync(input.args[4], JSON.stringify({ ok: true, outputPath })); return { output: 'ok' };
};
const provider = createPs5NativePkgBuilderProvider({ toolRoot, allowedWorkRoots: [workRoot], allowedSourceRoots: [sourceRoot], runner });
assert.equal((await provider.probe()).available, true);
await provider.build({ sourcePath: sourceRoot, sourceSize: 0, outputPath, workPath: path.join(workRoot, 'job'), titleId: 'PPSA12345', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001' });
assert.equal(calls.at(-1).command, fs.realpathSync(executable));
assert.equal(calls.at(-1).shell, false);
assert.equal(calls.at(-1).args[0], 'build-backup');
assert.equal(calls.at(-1).args[1], '--request');
assert.equal(calls.at(-1).args[3], '--result');
assert.equal(JSON.parse(fs.readFileSync(calls.at(-1).args[2], 'utf8')).componentType, 'base');
await assert.rejects(() => provider.build({ sourcePath: sourceRoot, sourceSize: 0, outputPath, workPath: path.join(workRoot, 'update'), titleId: 'PPSA12345', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001', componentType: 'update' }), error => error.code === 'PS5_BUILDER_COMPONENT');
await assert.rejects(() => provider.build({ sourcePath: fspOutside(root), sourceSize: 0, outputPath, workPath: path.join(workRoot, 'evil'), titleId: 'PPSA12345', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001' }), error => error.code === 'PS5_BUILDER_PATH');
});
test('pinned bridge rejects a runtime that does not prove license-free base conversion', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-builder-license-free-'));
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const toolRoot = path.join(root, 'tool');
fs.mkdirSync(toolRoot);
fs.writeFileSync(path.join(toolRoot, PINNED_PS5_BUILDER.executable), 'bridge');
const runner = async () => ({ output: JSON.stringify({
...PINNED_PS5_BUILDER,
capabilities: {
...semanticCapabilities({ licenseFreeBuild: false }),
},
}) });
const provider = createPs5NativePkgBuilderProvider({ toolRoot, runner });
const probe = await provider.probe();
assert.equal(probe.available, false);
assert.equal(probe.reason, 'capability-mismatch');
});
test('pinned bridge persists bounded diagnostics when the native process fails', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-builder-failure-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const toolRoot = path.join(root, 'tool'); const workRoot = path.join(root, 'work'); const sourceRoot = path.join(root, 'source');
fs.mkdirSync(toolRoot); fs.mkdirSync(workRoot); fs.mkdirSync(sourceRoot);
fs.writeFileSync(path.join(toolRoot, PINNED_PS5_BUILDER.executable), 'bridge');
const runner = async input => {
if (input.args[0] === 'probe') return { output: JSON.stringify(semanticProbe()) };
throw Object.assign(new Error('PS5 packagebridge stopte met code 1.'), { code: 'PS5_BUILDER_FAILED', details: { code: 1, output: 'System.InvalidOperationException: exact native failure' } });
};
const provider = createPs5NativePkgBuilderProvider({ toolRoot, allowedWorkRoots: [workRoot], allowedSourceRoots: [sourceRoot], runner });
const workPath = path.join(workRoot, 'job');
await assert.rejects(() => provider.build({ sourcePath: sourceRoot, sourceSize: 0, outputPath: path.join(workRoot, 'out.pkg'), workPath, titleId: 'PPSA12345', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001' }), error => {
assert.equal(error.code, 'PS5_BUILDER_FAILED');
assert.match(error.details.output, /exact native failure/);
assert.equal(error.details.exitCode, 1);
return true;
});
const diagnostic = JSON.parse(fs.readFileSync(path.join(workPath, 'build-failure.json'), 'utf8'));
assert.equal(diagnostic.code, 'PS5_BUILDER_FAILED');
assert.equal(diagnostic.exitCode, 1);
assert.match(diagnostic.output, /exact native failure/);
});
test('native bridge process logging is capped while retaining a bounded output tail', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-builder-log-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const logPath = path.join(root, 'build.log');
const result = await runProcess({ command: process.execPath, args: ['-e', `process.stdout.write('x'.repeat(${9 * 1024 * 1024}))`], cwd: root, logPath });
assert.equal(result.code, 0);
assert.equal(result.output.length, 64_000);
assert.ok(fs.statSync(logPath).size <= 8 * 1024 * 1024);
});
test('failed package jobs retain structured provider diagnostics', async t => {
const provider = {
probe: async () => semanticProbe(),
build: async () => { throw Object.assign(new Error('bridge failed'), { code: 'PS5_BUILDER_FAILED', details: { exitCode: 1, output: 'precise failure', diagnosticPath: '/safe/work/build-failure.json' } }); },
validatePackage: async () => acceptedValidation(),
};
const f = fixture(t, { provider }); const source = writeBackup(f.libraryRoot);
const created = await f.service.create({ sourcePath: source });
await assert.rejects(() => f.service.run(created.id), /bridge failed/);
const failed = f.service.get(created.id);
assert.equal(failed.status, 'failed');
assert.deepEqual(failed.errorDetails, { code: 'PS5_BUILDER_FAILED', exitCode: 1, output: 'precise failure', diagnosticPath: '/safe/work/build-failure.json' });
});
test('bridge binds data DLC explicitly and exposes no update or entitlement-only build mode', () => {
const source = fs.readFileSync(path.join(__dirname, '../../tools/chimera-prospero-bridge/Program.cs'), 'utf8');
assert.match(source, /chimera-prospero-bridge-v4/);
assert.match(source, /ProsperoBackupConverter/);
assert.match(source, /ProsperoPkgValidator\.Validate/);
assert.match(source, /build-backup/);
assert.match(source, /build-data-dlc/);
assert.match(source, /inspect-backup/);
assert.match(source, /inspect-package/);
assert.match(source, /validate-package/);
assert.match(source, /licenseFreeBuild = true/);
assert.match(source, /backupBuildApi = "ProsperoPackageBuilder"/);
assert.match(source, /LicenseFree = true, FakeSignSelfModules = false/);
assert.match(source, /ProsperoFself\.MakeFself/);
assert.doesNotMatch(source, /patchSet/);
assert.doesNotMatch(source, /wideNapsU2cBase|stockImageKeyRsa3072|chimera-license-free-base-v16/);
assert.match(source, /ProsperoPackageMode\.AdditionalContentData/);
assert.doesNotMatch(source, /ProsperoPackageMode\.AdditionalContentNoData/);
assert.doesNotMatch(source, /ProsperoPackageMode\.(Update|Patch)/);
});
test('builder v4 request contains semantic inputs without legacy implementation tuning', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-builder-tuning-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const toolRoot = path.join(root, 'tool'); const workRoot = path.join(root, 'work'); const sourceRoot = path.join(root, 'source');
fs.mkdirSync(toolRoot); fs.mkdirSync(workRoot); fs.mkdirSync(sourceRoot);
fs.writeFileSync(path.join(toolRoot, PINNED_PS5_BUILDER.executable), 'bridge');
const outputPath = path.join(workRoot, 'out.pkg'); const calls = [];
const runner = async input => {
calls.push(input);
if (input.args[0] === 'probe') return { output: JSON.stringify(semanticProbe()) };
writeFih(outputPath);
fs.writeFileSync(input.args[4], JSON.stringify({ ok: true, outputPath }));
return { output: 'ok' };
};
const provider = createPs5NativePkgBuilderProvider({
toolRoot,
allowedWorkRoots: [workRoot],
allowedSourceRoots: [sourceRoot],
runner,
});
await provider.build({ sourcePath: sourceRoot, sourceSize: 0, outputPath, workPath: path.join(workRoot, 'job'), titleId: 'PPSA12345', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001' });
const buildCall = calls.at(-1);
const request = JSON.parse(fs.readFileSync(buildCall.args[2], 'utf8'));
assert.equal(request.operation, 'build-backup');
assert.equal(request.abi, 'chimera-prospero-bridge-v4');
assert.equal(request.tuning, undefined);
assert.equal(buildCall.env.CHIMERA_PROSPERO_COMPRESSION_WORKERS, undefined);
assert.match(buildCall.env.CHIMERA_PROSPERO_ALLOWED_SOURCE_ROOTS, /source/);
});
test('Docker builds the exact upstream revision with only the pinned streaming patch', () => {
const root = path.resolve(__dirname, '../..');
const dockerfile = fs.readFileSync(path.join(root, 'Dockerfile'), 'utf8');
assert.match(dockerfile, /9084c0e88cd8d7cd9c0b353b8b6b89f7d1cc4275/);
assert.match(dockerfile, /git .* status --porcelain/);
assert.match(dockerfile, /CHIMERA_UPSTREAM_PROVENANCE\.json/);
assert.match(dockerfile, /0001-stream-large-package-assembly\.patch/);
assert.doesNotMatch(dockerfile, /v16-patches|chimera-license-free-base-v16/);
assert.equal(fs.existsSync(path.join(root, 'tools/chimera-prospero-bridge/legacy/v16-patches/0001-parallel-fast-store.patch')), true);
});
test('builder rejects insufficient workspace before starting the bridge', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-builder-capacity-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const toolRoot = path.join(root, 'tool'); const workRoot = path.join(root, 'work'); const sourceRoot = path.join(root, 'source');
fs.mkdirSync(toolRoot); fs.mkdirSync(workRoot); fs.mkdirSync(sourceRoot);
fs.writeFileSync(path.join(toolRoot, PINNED_PS5_BUILDER.executable), 'bridge');
let buildRuns = 0;
const runner = async input => {
if (input.args[0] === 'probe') return { output: JSON.stringify(semanticProbe()) };
buildRuns++; return { output: '' };
};
const provider = createPs5NativePkgBuilderProvider({ toolRoot, allowedWorkRoots: [workRoot], allowedSourceRoots: [sourceRoot], runner });
await assert.rejects(() => provider.build({ sourcePath: sourceRoot, sourceSize: 10_000_000_000_000, outputPath: path.join(workRoot, 'out.pkg'), workPath: path.join(workRoot, 'job'), titleId: 'PPSA12345', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001' }), error => error.code === 'PS5_BUILDER_DISK');
assert.equal(buildRuns, 0);
});
function fspOutside(root) { const outside = path.join(root, 'not-allowed'); fs.mkdirSync(outside, { recursive: true }); return outside; }
test('registers a verified native package as a local package set without console-install evidence', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-registration-'));
database.configure({ dataDir: path.join(root, 'db'), logger: { info() {}, warn() {}, error() {} } }); await database.initialize();
t.after(() => { database.close(); fs.rmSync(root, { recursive: true, force: true }); });
const outputRoot = path.join(root, 'vault'); const folder = path.join(outputRoot, 'PPSA12345', 'job'); fs.mkdirSync(folder, { recursive: true });
const pkg = path.join(folder, 'PPSA12345-base.pkg'); writeFih(pkg); const bytes = fs.readFileSync(pkg);
const manifest = { schemaVersion: 1, kind: 'chimera.ps5-native-package.v1', createdAt: 100, title: { platform: 'ps5', titleId: 'PPSA12345', name: 'PS5 Test' }, packageFormat: { container: 'FIH', debug: true, retail: false }, components: [{ id: 'base', type: 'base', version: '01.00', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001', size: bytes.length, sha256: crypto.createHash('sha256').update(bytes).digest('hex'), absolutePath: pkg, required: true, order: 1, adapterHint: 'ps5-native-debug-package', provenance: [] }], dependencies: [], recipe: { installOrder: [{ componentId: 'base', order: 1 }] } };
const result = registerPs5NativePackageManifest({ db: database, outputRoot, manifest });
assert.ok(result.packageSetId);
assert.equal(database.runQuery('SELECT COUNT(*) AS count FROM local_copies')[0].count, 1);
assert.equal(database.runQuery('SELECT COUNT(*) AS count FROM console_installs')[0].count, 0);
});
test('rolls back title and package registration atomically when a component is invalid', async t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-registration-rollback-'));
database.configure({ dataDir: path.join(root, 'db'), logger: { info() {}, warn() {}, error() {} } }); await database.initialize();
t.after(() => { database.close(); fs.rmSync(root, { recursive: true, force: true }); });
const outputRoot = path.join(root, 'vault'); const folder = path.join(outputRoot, 'PPSA12345', 'job'); fs.mkdirSync(folder, { recursive: true });
const pkg = path.join(folder, 'invalid.pkg'); writeFih(pkg); const size = fs.statSync(pkg).size;
const beforeGames = database.runQuery('SELECT COUNT(*) AS count FROM games')[0].count;
const beforeTitles = database.runQuery('SELECT COUNT(*) AS count FROM titles')[0].count;
const manifest = { schemaVersion: 1, kind: 'chimera.ps5-native-package.v1', createdAt: 100, title: { platform: 'ps5', titleId: 'PPSA12345', name: 'Rollback Test' }, packageFormat: { container: 'FIH', debug: true, retail: false }, components: [{ id: 'base', type: 'base', version: '01.00', contentId: 'UP0001-PPSA12345_00-CHIMERATEST00001', size, sha256: 'not-a-hash', absolutePath: pkg, required: true, order: 1 }], dependencies: [], recipe: { installOrder: [{ componentId: 'base', order: 1 }] } };
assert.throws(() => registerPs5NativePackageManifest({ db: database, outputRoot, manifest }), error => error.code === 'PS5_PACKAGE_MANIFEST');
assert.equal(database.runQuery('SELECT COUNT(*) AS count FROM games')[0].count, beforeGames);
assert.equal(database.runQuery('SELECT COUNT(*) AS count FROM titles')[0].count, beforeTitles);
assert.equal(database.runQuery('SELECT COUNT(*) AS count FROM package_sets')[0].count, 0);
});