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,160 @@
|
||||
'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 xavatar = require('../../src/xavatarElectronModule');
|
||||
const {
|
||||
PINNED_PS5_PROFILE_PROVIDER,
|
||||
createPs5UploadProfileProvider,
|
||||
} = require('../../src/services/ps5upload-profile-provider');
|
||||
const { createPs5ProfileManagementService } = require('../../src/services/ps5-profile-management-service');
|
||||
|
||||
const png = label => Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), Buffer.from(label)]);
|
||||
const digest = value => crypto.createHash('sha256').update(value).digest('hex');
|
||||
|
||||
function fixture(t, overrides = {}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-profile-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
let username = 'User1';
|
||||
let avatar = png('before');
|
||||
let generation = 1;
|
||||
const calls = [];
|
||||
const provider = overrides.provider || {
|
||||
probe: async () => ({ available: true, ...PINNED_PS5_PROFILE_PROVIDER, capabilities: { profileInventory: true, localUsername: true, homeScreenNameSync: true, avatarRead: true, avatarApply: true, avatarClear: false } }),
|
||||
listUsers: async () => ({ observedAt: 1_900_000_000_000 + generation, foregroundUid: 0x10000001, users: [{ uid: 0x10000001, uidHex: '0x10000001', username, accountId: 'must-not-escape', homePath: '/user/home/10000001' }] }),
|
||||
currentAvatar: async ({ uid }) => ({ uid, bytes: avatar ? Buffer.from(avatar) : null, sha256: avatar ? digest(avatar) : null }),
|
||||
renameUser: async input => { calls.push({ op: 'rename', ...input }); username = input.name; generation += 1; return { ok: true, homeScreenNameSynced: true }; },
|
||||
applyAvatar: async input => { calls.push({ op: 'avatar', uid: input.uid, bytes: Buffer.from(input.imageBytes) }); avatar = Buffer.from(input.imageBytes); generation += 1; return { ok: true, filesCopied: 11, currentAvatarSha256: digest(avatar) }; },
|
||||
};
|
||||
const service = createPs5ProfileManagementService({
|
||||
provider,
|
||||
stateFile: path.join(root, 'state.json'),
|
||||
recoveryRoot: path.join(root, 'recovery'),
|
||||
idFactory: (() => { let n = 0; return () => `profile-op-${++n}`; })(),
|
||||
now: () => 1_900_000_000_000 + generation,
|
||||
});
|
||||
return { calls, provider, root, service, setAvatar(value) { avatar = value; }, setUsername(value) { username = value; generation += 1; } };
|
||||
}
|
||||
|
||||
async function avatarArchive() {
|
||||
const rgba = Buffer.alloc(32 * 32 * 4, 0xff);
|
||||
return (await xavatar.convertFromRGBA(rgba, 32, 32, { filename: 'profile' })).buffer;
|
||||
}
|
||||
|
||||
test('inventory exposes only stable local-user identity and current avatar evidence', async t => {
|
||||
const f = fixture(t);
|
||||
const result = await f.service.inspect();
|
||||
assert.equal(result.available, true);
|
||||
assert.deepEqual(result.users[0], { uid: 0x10000001, uidHex: '0x10000001', username: 'User1', foreground: true, avatar: { available: true, sha256: digest(png('before')) } });
|
||||
assert.equal(Object.hasOwn(result.users[0], 'accountId'), false);
|
||||
assert.equal(JSON.stringify(result).includes('/user/home'), false);
|
||||
assert.equal(result.semantics.accountIdentityMutable, false);
|
||||
});
|
||||
|
||||
test('rename planning validates uid and PS5 16-character local display names', async t => {
|
||||
const f = fixture(t);
|
||||
await assert.rejects(() => f.service.prepareRename({ uid: 7, name: 'New name' }), error => error.code === 'PS5_PROFILE_USER_UNKNOWN');
|
||||
for (const name of ['', ' '.repeat(3), '12345678901234567', 'bad\nname']) {
|
||||
await assert.rejects(() => f.service.prepareRename({ uid: 0x10000001, name }), error => error.code === 'PS5_PROFILE_NAME');
|
||||
}
|
||||
const plan = await f.service.prepareRename({ uid: 0x10000001, name: 'Chimera' });
|
||||
assert.equal(plan.status, 'prepared');
|
||||
assert.equal(plan.confirmation, 'RENAME PS5 USER 0x10000001');
|
||||
assert.equal(plan.snapshot.username, 'User1');
|
||||
assert.equal(Object.hasOwn(plan, 'accountId'), false);
|
||||
});
|
||||
|
||||
test('stale user evidence blocks rename before a provider mutation', async t => {
|
||||
const f = fixture(t);
|
||||
const plan = await f.service.prepareRename({ uid: 0x10000001, name: 'Chimera' });
|
||||
f.setUsername('ChangedElsewhere');
|
||||
await assert.rejects(() => f.service.apply({ id: plan.id, confirmation: plan.confirmation }), error => error.code === 'PS5_PROFILE_STALE');
|
||||
assert.equal(f.calls.length, 0);
|
||||
});
|
||||
|
||||
test('rename requires exact confirmation, verifies post-read and can roll back', async t => {
|
||||
const f = fixture(t);
|
||||
const plan = await f.service.prepareRename({ uid: 0x10000001, name: 'Chimera' });
|
||||
await assert.rejects(() => f.service.apply({ id: plan.id, confirmation: 'RENAME' }), error => error.code === 'PS5_PROFILE_CONFIRMATION');
|
||||
const applied = await f.service.apply({ id: plan.id, confirmation: plan.confirmation });
|
||||
assert.equal(applied.status, 'verified');
|
||||
assert.equal(applied.postRead.username, 'Chimera');
|
||||
assert.equal(applied.rebootRequired, true);
|
||||
assert.deepEqual(Object.keys(f.calls[0]).sort(), ['name', 'op', 'uid']);
|
||||
const rolledBack = await f.service.rollback({ id: plan.id, confirmation: 'ROLLBACK PS5 PROFILE 0x10000001' });
|
||||
assert.equal(rolledBack.status, 'rolled_back');
|
||||
assert.equal(rolledBack.postRead.username, 'User1');
|
||||
});
|
||||
|
||||
test('avatar planning accepts only a bounded canonical xavatar archive and requires recovery', async t => {
|
||||
const f = fixture(t); const archive = await avatarArchive();
|
||||
const plan = await f.service.prepareAvatar({ uid: 0x10000001, archive });
|
||||
assert.equal(plan.status, 'prepared');
|
||||
assert.equal(plan.confirmation, 'APPLY PS5 AVATAR 0x10000001');
|
||||
assert.match(plan.desired.avatarSha256, /^[a-f0-9]{64}$/);
|
||||
assert.ok(fs.existsSync(plan.snapshot.avatarBackupPath));
|
||||
await assert.rejects(() => f.service.prepareAvatar({ uid: 0x10000001, archive: Buffer.alloc(10) }), error => error.code === 'PS5_PROFILE_AVATAR_ARCHIVE');
|
||||
|
||||
const recoveryFilesBeforeFailure = fs.readdirSync(path.join(f.root, 'recovery')).sort();
|
||||
f.setAvatar(null);
|
||||
await assert.rejects(() => f.service.prepareAvatar({ uid: 0x10000001, archive }), error => error.code === 'PS5_PROFILE_RECOVERY_UNAVAILABLE');
|
||||
assert.deepEqual(fs.readdirSync(path.join(f.root, 'recovery')).sort(), recoveryFilesBeforeFailure);
|
||||
});
|
||||
|
||||
test('avatar apply verifies provider evidence and rollback restores the previous image', async t => {
|
||||
const f = fixture(t); const before = png('before'); const plan = await f.service.prepareAvatar({ uid: 0x10000001, archive: await avatarArchive() });
|
||||
const applied = await f.service.apply({ id: plan.id, confirmation: plan.confirmation });
|
||||
assert.equal(applied.status, 'verified');
|
||||
assert.notEqual(applied.postRead.avatar.sha256, digest(before));
|
||||
assert.equal(f.calls[0].op, 'avatar');
|
||||
const rolledBack = await f.service.rollback({ id: plan.id, confirmation: 'ROLLBACK PS5 PROFILE 0x10000001' });
|
||||
assert.equal(rolledBack.status, 'rolled_back');
|
||||
assert.equal(rolledBack.postRead.avatar.sha256, digest(before));
|
||||
});
|
||||
|
||||
test('pinned PS5Upload provider is loopback-only and calls only profile routes', async t => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-profile-provider-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const calls = [];
|
||||
const fetch = async (url, options = {}) => {
|
||||
calls.push({ url: String(url), options });
|
||||
if (String(url).endsWith('/api/version')) return { ok: true, json: async () => ({ version: PINNED_PS5_PROFILE_PROVIDER.version }) };
|
||||
if (String(url).includes('/api/profile/info')) return { ok: true, json: async () => ({ ok: true, uid: 0x10000001, users: [{ uid: 0x10000001, uid_hex: '0x10000001', username: 'User1' }], slots: [{ id: 'secret' }] }) };
|
||||
if (String(url).includes('/api/profile/avatar/current')) return { ok: true, json: async () => ({ data_url: `data:image/png;base64,${png('current').toString('base64')}` }) };
|
||||
if (String(url).endsWith('/api/profile/local-username')) return { ok: true, json: async () => ({ ok: true }) };
|
||||
return { ok: true, json: async () => ({ uid: 0x10000001, username: 'Chimera', files_copied: 11 }) };
|
||||
};
|
||||
assert.throws(() => createPs5UploadProfileProvider({ engineOrigin: 'http://192.168.1.8:19113', consoleHost: '192.168.1.9', sharedRoot: root, fetch }), error => error.code === 'PS5_PROFILE_ENGINE_ORIGIN');
|
||||
assert.throws(() => createPs5UploadProfileProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: '192.168.1.9', fetch }), error => error.code === 'PS5_PROFILE_SHARED_ROOT');
|
||||
const provider = createPs5UploadProfileProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: '192.168.1.9', sharedRoot: root, fetch });
|
||||
assert.equal((await provider.probe()).available, true);
|
||||
assert.equal((await provider.listUsers()).users[0].username, 'User1');
|
||||
assert.equal((await provider.currentAvatar({ uid: 0x10000001 })).bytes.subarray(0, 8).equals(png('').subarray(0, 8)), true);
|
||||
await provider.renameUser({ uid: 0x10000001, name: 'Chimera' });
|
||||
await provider.applyAvatar({ uid: 0x10000001, username: 'Chimera', imageBytes: png('next') });
|
||||
assert.equal(calls.every(call => new URL(call.url).hostname === '127.0.0.1'), true);
|
||||
assert.equal(calls.some(call => ['/api/profile/activate', '/api/profile/clear-slot', '/api/profile/username'].includes(new URL(call.url).pathname)), false);
|
||||
assert.equal(calls.some(call => new URL(call.url).pathname === '/api/profile/local-username'), true);
|
||||
});
|
||||
|
||||
test('server, Electron, web and UI expose only guarded local profile operations', () => {
|
||||
const registry = fs.readFileSync(path.join(__dirname, '../../src/server/create-registry.js'), 'utf8');
|
||||
const main = fs.readFileSync(path.join(__dirname, '../../main.js'), 'utf8');
|
||||
const preload = fs.readFileSync(path.join(__dirname, '../../preload.js'), 'utf8');
|
||||
const web = fs.readFileSync(path.join(__dirname, '../../renderer/pork-web-shim.js'), 'utf8');
|
||||
const html = fs.readFileSync(path.join(__dirname, '../../renderer/index.html'), 'utf8');
|
||||
const renderer = fs.readFileSync(path.join(__dirname, '../../renderer/modules/xavatar.js'), 'utf8');
|
||||
for (const command of ['ps5-profile:inspect', 'ps5-profile:prepare-rename', 'ps5-profile:prepare-avatar', 'ps5-profile:apply', 'ps5-profile:rollback', 'ps5-profile:list-operations']) {
|
||||
assert.match(registry, new RegExp(command)); assert.match(main, new RegExp(command)); assert.match(preload, new RegExp(command)); assert.match(web, new RegExp(command));
|
||||
}
|
||||
assert.match(html, /ps5-profile-workspace/);
|
||||
assert.match(html, /PS5Upload 5\.4\.19/);
|
||||
assert.match(renderer, /loadPs5LocalProfiles/);
|
||||
assert.match(renderer, /result\.version \|\| '5\.4\.19'/);
|
||||
assert.doesNotMatch(renderer, /PS5Upload 5\.2\.1/);
|
||||
assert.match(renderer, /ROLLBACK PS5 PROFILE/);
|
||||
assert.doesNotMatch(renderer, /account.?id/i);
|
||||
});
|
||||
Reference in new issue
Block a user