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,165 @@
|
||||
'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 { createPs4PackageBackupService } = require('../../src/services/ps4-package-backup-service');
|
||||
const { PINNED_PS4_BUILDER, createPs4PkgBuilderProvider } = require('../../src/services/ps4-pkg-builder-provider');
|
||||
const database = require('../../src/database');
|
||||
const { registerPs4PackageManifest } = require('../../src/services/ps4-package-registration-service');
|
||||
|
||||
function sfo(values) {
|
||||
const entries = Object.entries(values);
|
||||
const keys = Buffer.from(`${entries.map(([key]) => key).join('\0')}\0`);
|
||||
const encoded = entries.map(([, value]) => Buffer.from(`${value}\0`));
|
||||
const data = Buffer.concat(encoded);
|
||||
const table = Buffer.alloc(entries.length * 16);
|
||||
let keyOffset = 0;
|
||||
let dataOffset = 0;
|
||||
entries.forEach(([key], index) => {
|
||||
const value = encoded[index];
|
||||
table.writeUInt16LE(keyOffset, index * 16);
|
||||
table.writeUInt16LE(0x0204, index * 16 + 2);
|
||||
table.writeUInt32LE(value.length, index * 16 + 4);
|
||||
table.writeUInt32LE(value.length, index * 16 + 8);
|
||||
table.writeUInt32LE(dataOffset, index * 16 + 12);
|
||||
keyOffset += Buffer.byteLength(key) + 1;
|
||||
dataOffset += value.length;
|
||||
});
|
||||
const header = Buffer.alloc(20);
|
||||
header.writeUInt32LE(0x46535000, 0);
|
||||
header.writeUInt32LE(0x00000101, 4);
|
||||
header.writeUInt32LE(20 + table.length, 8);
|
||||
header.writeUInt32LE(20 + table.length + keys.length, 12);
|
||||
header.writeUInt32LE(entries.length, 16);
|
||||
return Buffer.concat([header, table, keys, data]);
|
||||
}
|
||||
|
||||
function writeDump(root, name, { titleId = 'CUSA12345', category = 'gd', version = '01.00', keystone = category === 'gd' } = {}) {
|
||||
const folder = path.join(root, name);
|
||||
fs.mkdirSync(path.join(folder, 'sce_sys'), { recursive: true });
|
||||
fs.writeFileSync(path.join(folder, 'eboot.bin'), 'verified executable');
|
||||
fs.writeFileSync(path.join(folder, 'sce_sys', 'param.sfo'), sfo({
|
||||
TITLE: 'Test Game', TITLE_ID: titleId, CATEGORY: category, APP_VER: version,
|
||||
CONTENT_ID: `UP0001-${titleId}_00-TESTGAME00000001`,
|
||||
}));
|
||||
if (keystone) fs.writeFileSync(path.join(folder, 'sce_sys', 'keystone'), 'key');
|
||||
return folder;
|
||||
}
|
||||
|
||||
function fixture(t, overrides = {}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps4-package-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const libraryRoot = path.join(root, 'library');
|
||||
const outputRoot = path.join(root, 'packages');
|
||||
fs.mkdirSync(libraryRoot, { recursive: true });
|
||||
const registrations = [];
|
||||
const provider = overrides.provider || {
|
||||
probe: async () => ({ available: true, ...PINNED_PS4_BUILDER }),
|
||||
build: async input => {
|
||||
fs.mkdirSync(path.dirname(input.outputPath), { recursive: true });
|
||||
fs.writeFileSync(input.outputPath, `pkg:${input.component.type}:${input.component.titleId}`);
|
||||
return { provider: PINNED_PS4_BUILDER, outputPath: input.outputPath };
|
||||
},
|
||||
};
|
||||
const service = createPs4PackageBackupService({
|
||||
allowedRoots: () => [libraryRoot, outputRoot], outputRoot, stateFile: path.join(root, 'state', 'jobs.json'),
|
||||
provider, registrar: async manifest => { registrations.push(manifest); return { packageSetId: 77 }; },
|
||||
idFactory: overrides.idFactory || (() => 'job-1'), now: overrides.now || (() => 1_700_000_000_000),
|
||||
});
|
||||
return { root, libraryRoot, outputRoot, registrations, service };
|
||||
}
|
||||
|
||||
test('validates base and patch dumps from SFO evidence rather than folder names', async t => {
|
||||
const f = fixture(t);
|
||||
const base = writeDump(f.libraryRoot, 'anything', { category: 'gd' });
|
||||
const patch = writeDump(f.libraryRoot, 'also-not-trusted', { category: 'gp', version: '01.23', keystone: false });
|
||||
const baseResult = await f.service.inspect({ sourcePath: base, componentType: 'base', expectedTitleId: 'CUSA12345' });
|
||||
const patchResult = await f.service.inspect({ sourcePath: patch, componentType: 'update', expectedTitleId: 'CUSA12345' });
|
||||
assert.equal(baseResult.ready, true);
|
||||
assert.deepEqual({ titleId: patchResult.titleId, version: patchResult.version, category: patchResult.category }, { titleId: 'CUSA12345', version: '01.23', category: 'gp' });
|
||||
});
|
||||
|
||||
test('rejects incomplete, mismatched and symlink-escaped dumps', async t => {
|
||||
const f = fixture(t);
|
||||
const missingKeystone = writeDump(f.libraryRoot, 'missing-key', { keystone: false });
|
||||
await assert.rejects(() => f.service.inspect({ sourcePath: missingKeystone, componentType: 'base' }), error => error.code === 'PS4_DUMP_KEYSTONE_REQUIRED');
|
||||
const wrong = writeDump(f.libraryRoot, 'wrong-title');
|
||||
await assert.rejects(() => f.service.inspect({ sourcePath: wrong, componentType: 'base', expectedTitleId: 'CUSA99999' }), error => error.code === 'PS4_DUMP_TITLE_MISMATCH');
|
||||
const outside = path.join(f.root, 'outside'); fs.mkdirSync(outside); fs.writeFileSync(path.join(outside, 'payload.bin'), 'x');
|
||||
const linked = writeDump(f.libraryRoot, 'linked');
|
||||
fs.symlinkSync(outside, path.join(linked, 'escape'), process.platform === 'win32' ? 'junction' : 'dir');
|
||||
await assert.rejects(() => f.service.inspect({ sourcePath: linked, componentType: 'base' }), error => error.code === 'PS4_DUMP_SYMLINK');
|
||||
});
|
||||
|
||||
test('builds a deterministic ordered package set, hashes outputs and registers only after verification', async t => {
|
||||
const f = fixture(t);
|
||||
const base = writeDump(f.libraryRoot, 'base');
|
||||
const update = writeDump(f.libraryRoot, 'update', { category: 'gp', version: '01.10', keystone: false });
|
||||
const created = await f.service.create({ expectedTitleId: 'CUSA12345', sources: [{ sourcePath: update, componentType: 'update' }, { sourcePath: base, componentType: 'base' }] });
|
||||
assert.equal(created.status, 'queued');
|
||||
const completed = await f.service.run(created.id);
|
||||
assert.equal(completed.status, 'completed');
|
||||
assert.deepEqual(completed.children.map(item => item.type), ['base', 'update']);
|
||||
assert.equal(f.registrations.length, 1);
|
||||
assert.deepEqual(f.registrations[0].recipe.installOrder.map(item => item.componentId), ['base', 'update']);
|
||||
for (const component of f.registrations[0].components) {
|
||||
assert.match(component.sha256, /^[a-f0-9]{64}$/);
|
||||
assert.equal(component.sha256, crypto.createHash('sha256').update(fs.readFileSync(component.absolutePath)).digest('hex'));
|
||||
}
|
||||
});
|
||||
|
||||
test('active jobs become interrupted after restart and require explicit resume', async t => {
|
||||
const f = fixture(t);
|
||||
const base = writeDump(f.libraryRoot, 'base');
|
||||
const job = await f.service.create({ sources: [{ sourcePath: base, componentType: 'base' }] });
|
||||
const stored = JSON.parse(fs.readFileSync(path.join(f.root, 'state', 'jobs.json'), 'utf8'));
|
||||
stored[0].status = 'building'; stored[0].children[0].status = 'building';
|
||||
fs.writeFileSync(path.join(f.root, 'state', 'jobs.json'), JSON.stringify(stored));
|
||||
const restarted = createPs4PackageBackupService({ allowedRoots: () => [f.libraryRoot, f.outputRoot], outputRoot: f.outputRoot, stateFile: path.join(f.root, 'state', 'jobs.json'), provider: { probe: async () => ({ available: true }), build: async () => ({}) }, registrar: async () => ({}) });
|
||||
assert.equal(restarted.get(job.id).status, 'interrupted');
|
||||
assert.equal(restarted.resume(job.id).status, 'queued');
|
||||
});
|
||||
|
||||
test('creates a short-lived hash-bound package handoff without exposing arbitrary files', async t => {
|
||||
const f = fixture(t);
|
||||
const pkg = path.join(f.outputRoot, 'verified.pkg'); fs.writeFileSync(pkg, 'verified package');
|
||||
const sha256 = crypto.createHash('sha256').update('verified package').digest('hex');
|
||||
const exposed = await f.service.expose({ filePath: pkg, sha256, size: 16, origin: 'https://chimera.example.test' });
|
||||
assert.match(exposed.url, /^https:\/\/chimera\.example\.test\/ps4-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: 16, origin: 'https://chimera.example.test' }), error => error.code === 'PS4_PACKAGE_HANDOFF_HASH');
|
||||
await assert.rejects(() => f.service.expose({ filePath: pkg, sha256, size: 16, origin: 'http://chimera.example.test' }), error => error.code === 'PS4_PACKAGE_ORIGIN');
|
||||
});
|
||||
|
||||
test('pinned provider invokes only the fixed executable without a shell or caller-controlled arguments', async t => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps4-builder-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const executable = path.join(root, PINNED_PS4_BUILDER.executable);
|
||||
fs.writeFileSync(executable, 'tool');
|
||||
const projectPath = path.join(root, 'work', 'project.gp4'); const outputPath = path.join(root, 'work', 'out.pkg'); const tempPath = path.join(root, 'work', 'tmp');
|
||||
fs.mkdirSync(path.dirname(projectPath), { recursive: true }); fs.writeFileSync(projectPath, '<psproject />');
|
||||
const calls = [];
|
||||
const sourcePath = path.join(root, 'source'); fs.mkdirSync(sourcePath);
|
||||
const provider = createPs4PkgBuilderProvider({ toolRoot: root, allowedWorkRoots: [path.join(root, 'work')], allowedSourceRoots: [sourcePath], runner: async input => { calls.push(input); fs.writeFileSync(outputPath, 'pkg'); return { code: 0, output: 'ok' }; } });
|
||||
assert.equal((await provider.probe()).available, true);
|
||||
await provider.build({ projectPath, outputPath, tempPath, sourcePath, component: { type: 'base', titleId: 'CUSA12345' } });
|
||||
assert.equal(calls[0].command, fs.realpathSync(executable));
|
||||
assert.equal(calls[0].shell, false);
|
||||
assert.equal(calls[0].cwd, fs.realpathSync(sourcePath));
|
||||
assert.deepEqual(calls[0].args.slice(0, 5), ['img_create', '--no_progress_bar', '--oformat', 'pkg', '--tmp_path']);
|
||||
await assert.rejects(() => provider.build({ projectPath: path.join(fixture(t).root, 'evil.gp4'), outputPath, tempPath, sourcePath, component: { type: 'base' } }), error => error.code === 'PS4_BUILDER_PATH');
|
||||
});
|
||||
|
||||
test('registers verified outputs as a package set and available local copy without marking a console install', async t => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps4-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, 'CUSA12345', 'job-1'); fs.mkdirSync(folder, { recursive: true });
|
||||
const pkg = path.join(folder, 'base.pkg'); fs.writeFileSync(pkg, 'pkg');
|
||||
const manifest = { schemaVersion: 1, kind: 'chimera.ps4-package-backup.v1', createdAt: 100, title: { platform: 'ps4', titleId: 'CUSA12345', name: 'Test Game' }, components: [{ id: 'base', type: 'base', version: '01.00', contentId: 'UP0001-CUSA12345_00-TESTGAME00000001', size: 3, sha256: crypto.createHash('sha256').update('pkg').digest('hex'), absolutePath: pkg, originalRelativePath: 'base.pkg', normalizedRelativePath: 'base/base.pkg', required: true, order: 1, adapterHint: 'ps4-package', provenance: [] }], dependencies: [], recipe: { installOrder: [{ componentId: 'base', order: 1 }] } };
|
||||
const registered = registerPs4PackageManifest({ db: database, outputRoot, manifest });
|
||||
assert.ok(registered.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);
|
||||
});
|
||||
Reference in new issue
Block a user