Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,143 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const {
|
||||
createPs5FpkgReadinessService,
|
||||
combinePreservationAndReadiness,
|
||||
} = require('../../src/services/ps5-fpkg-readiness-service');
|
||||
|
||||
const TITLE_ID = 'PPSA12345';
|
||||
const CONTENT_ID = 'UP0001-PPSA12345_00-CHIMERAFPKG00001';
|
||||
const ELF = Buffer.concat([Buffer.from([0x7f, 0x45, 0x4c, 0x46]), Buffer.alloc(32, 0x11)]);
|
||||
const SELF = Buffer.concat([Buffer.from([0x54, 0x14, 0xf5, 0xee]), Buffer.alloc(32, 0x22)]);
|
||||
|
||||
function fixture({ param = true, eboot = ELF } = {}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-fpkg-readiness-'));
|
||||
if (param) {
|
||||
fs.mkdirSync(path.join(root, 'sce_sys'), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, 'sce_sys', 'param.json'), JSON.stringify({ titleId: TITLE_ID, contentId: CONTENT_ID }));
|
||||
}
|
||||
if (eboot) fs.writeFileSync(path.join(root, 'eboot.bin'), eboot);
|
||||
const service = createPs5FpkgReadinessService({ allowedRoots: [root] });
|
||||
return { root, service };
|
||||
}
|
||||
|
||||
test('raw ELF base is ready with immutable tree and module provenance', async t => {
|
||||
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
const result = await f.service.analyze({ sourcePath: f.root, componentType: 'base', expectedTitleId: TITLE_ID, expectedContentId: CONTENT_ID });
|
||||
assert.equal(result.schema, 'chimera.ps5-fpkg-readiness.v1');
|
||||
assert.equal(result.state, 'ready');
|
||||
assert.equal(result.ready, true);
|
||||
assert.match(result.sourceTreeSha256, /^[a-f0-9]{64}$/);
|
||||
assert.equal(result.modules.total, 1);
|
||||
assert.equal(result.modules.plaintext, 1);
|
||||
assert.equal(result.modules.unresolved, 0);
|
||||
assert.equal(result.moduleEvidence[0].provenance, 'raw-elf-from-source');
|
||||
});
|
||||
|
||||
test('provider-decrypted stream claims become evidence only after local ELF reinspection', async t => {
|
||||
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
const service = createPs5FpkgReadinessService({
|
||||
allowedRoots: [f.root],
|
||||
providerEvidence: { files: { 'eboot.bin': { decryptionVerified: true, provider: 'pinned-capture-adapter' } } },
|
||||
});
|
||||
const result = await service.analyze({ sourcePath: f.root, componentType: 'base' });
|
||||
assert.equal(result.moduleEvidence[0].provenance, 'provider-decrypted-stream');
|
||||
fs.writeFileSync(path.join(f.root, 'eboot.bin'), SELF);
|
||||
const blocked = await service.analyze({ sourcePath: f.root, componentType: 'base' });
|
||||
assert.equal(blocked.moduleEvidence[0].provenance, 'signed-unresolved');
|
||||
assert.equal(blocked.ready, false);
|
||||
});
|
||||
|
||||
test('SELF with a matching decrypted ELF counterpart is ready and substituted', async t => {
|
||||
const f = fixture({ eboot: SELF }); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
fs.mkdirSync(path.join(f.root, 'decrypted'), { recursive: true });
|
||||
fs.writeFileSync(path.join(f.root, 'decrypted', 'eboot.bin'), ELF);
|
||||
const result = await f.service.analyze({ sourcePath: f.root, componentType: 'base' });
|
||||
assert.equal(result.state, 'ready');
|
||||
assert.equal(result.modules.substitutable, 1);
|
||||
assert.equal(result.moduleEvidence[0].provenance, 'decrypted-mirror-substitute');
|
||||
assert.match(result.moduleEvidence[0].counterpartSha256, /^[a-f0-9]{64}$/);
|
||||
});
|
||||
|
||||
test('opaque executable wrapper with an exact decrypted ELF mirror is ready and substituted', async t => {
|
||||
const opaque = Buffer.concat([Buffer.from([0x01, 0x02, 0x03, 0x04]), Buffer.alloc(32, 0x55)]);
|
||||
const f = fixture({ eboot: opaque }); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
fs.mkdirSync(path.join(f.root, 'decrypted'), { recursive: true });
|
||||
fs.writeFileSync(path.join(f.root, 'decrypted', 'eboot.bin'), ELF);
|
||||
const result = await f.service.analyze({ sourcePath: f.root, componentType: 'base' });
|
||||
assert.equal(result.state, 'ready');
|
||||
assert.equal(result.modules.substitutable, 1);
|
||||
assert.equal(result.moduleEvidence[0].format, 'opaque-executable');
|
||||
assert.equal(result.moduleEvidence[0].provenance, 'decrypted-mirror-substitute');
|
||||
});
|
||||
|
||||
test('SELF without suitable decrypted input blocks fPKG but not preservation', async t => {
|
||||
const f = fixture({ eboot: SELF }); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
const readiness = await f.service.analyze({ sourcePath: f.root, componentType: 'base' });
|
||||
assert.equal(readiness.state, 'blocked');
|
||||
assert.equal(readiness.modules.unresolved, 1);
|
||||
assert.equal(readiness.issues.some(issue => issue.code === 'PS5_FPKG_MODULE_UNRESOLVED'), true);
|
||||
const combined = combinePreservationAndReadiness({ complete: true, verified: true }, readiness);
|
||||
assert.equal(combined.preservation.complete, true);
|
||||
assert.equal(combined.preservation.verified, true);
|
||||
assert.equal(combined.fpkgReadiness.state, 'blocked');
|
||||
assert.equal(combined.backupFailed, false);
|
||||
});
|
||||
|
||||
test('missing eboot, missing param.json, param.sfo and identity mismatch remain distinct blockers', async t => {
|
||||
const noEboot = fixture({ eboot: null }); t.after(() => fs.rmSync(noEboot.root, { recursive: true, force: true }));
|
||||
assert.equal((await noEboot.service.analyze({ sourcePath: noEboot.root, componentType: 'base' })).issues.some(item => item.code === 'PS5_FPKG_EBOOT_REQUIRED'), true);
|
||||
|
||||
const noParam = fixture({ param: false }); t.after(() => fs.rmSync(noParam.root, { recursive: true, force: true }));
|
||||
fs.mkdirSync(path.join(noParam.root, 'sce_sys'), { recursive: true });
|
||||
fs.writeFileSync(path.join(noParam.root, 'sce_sys', 'param.sfo'), 'legacy');
|
||||
const missing = await noParam.service.analyze({ sourcePath: noParam.root, componentType: 'base' });
|
||||
assert.equal(missing.issues.some(item => item.code === 'PS5_FPKG_PARAM_REQUIRED'), true);
|
||||
assert.equal(missing.issues.some(item => item.code === 'PS5_FPKG_PARAM_SFO_UNSUPPORTED'), true);
|
||||
|
||||
const mismatch = fixture(); t.after(() => fs.rmSync(mismatch.root, { recursive: true, force: true }));
|
||||
const identity = await mismatch.service.analyze({ sourcePath: mismatch.root, componentType: 'base', expectedTitleId: 'PPSA99999' });
|
||||
assert.equal(identity.issues.some(item => item.code === 'PS5_FPKG_TITLE_MISMATCH'), true);
|
||||
});
|
||||
|
||||
test('updates are preserved but unsupported for native fPKG conversion', async t => {
|
||||
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
const result = await f.service.analyze({ sourcePath: f.root, componentType: 'update' });
|
||||
assert.equal(result.state, 'unsupported');
|
||||
assert.equal(result.ready, false);
|
||||
assert.equal(result.issues[0].code, 'PS5_FPKG_UPDATE_UNSUPPORTED');
|
||||
});
|
||||
|
||||
test('symlinks are rejected and same-size source mutation invalidates prior evidence', async t => {
|
||||
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
const first = await f.service.analyze({ sourcePath: f.root, componentType: 'base' });
|
||||
fs.writeFileSync(path.join(f.root, 'eboot.bin'), Buffer.concat([Buffer.from([0x7f, 0x45, 0x4c, 0x46]), Buffer.alloc(32, 0x44)]));
|
||||
await assert.rejects(() => f.service.assertUnchanged(first), error => error.code === 'PS5_FPKG_SOURCE_CHANGED');
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
fs.symlinkSync('/tmp', path.join(f.root, 'escape'));
|
||||
await assert.rejects(() => f.service.analyze({ sourcePath: f.root, componentType: 'base' }), error => error.code === 'PS5_FPKG_SOURCE_UNSAFE');
|
||||
}
|
||||
});
|
||||
|
||||
test('unsafe expected identity and paths fail closed instead of becoming readiness issues', async t => {
|
||||
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
||||
await assert.rejects(() => f.service.analyze({ sourcePath: path.join(f.root, '..'), componentType: 'base' }), error => error.code === 'PS5_FPKG_SOURCE_UNSAFE');
|
||||
await assert.rejects(() => f.service.analyze({ sourcePath: f.root, componentType: 'base', expectedContentId: '../PPSA12345' }), error => error.code === 'PS5_FPKG_IDENTITY_INVALID');
|
||||
});
|
||||
|
||||
test('console staging integrates local readiness without treating provider defaults as decryption proof', () => {
|
||||
const staging = fs.readFileSync('src/services/ps5-console-staging-service.js', 'utf8');
|
||||
const registry = fs.readFileSync('src/server/create-registry.js', 'utf8');
|
||||
assert.match(staging, /selfDecryption: 'unproven-local-reinspection-required'/);
|
||||
assert.match(staging, /await readinessService\.analyze/);
|
||||
assert.match(staging, /preservation = \{ complete: true, verified: true/);
|
||||
assert.match(staging, /executableProvenance: 'locally-reinspected-after-download'/);
|
||||
assert.match(registry, /createPs5FpkgReadinessService/);
|
||||
assert.match(registry, /readinessService: ps5FpkgReadiness/);
|
||||
});
|
||||
Reference in new issue
Block a user