// ── PS5 Autoloader ───────────────────────────────────────────────────────────── let _alSequence = []; // { type: 'payload'|'delay', filename?: string, ms?: number } let _alHeader = ''; // comment block preserved from original file const _AL_DEFAULT_HEADER = `# # ps5_autoloader - autoload.txt # Edited with Chimera # ----------------------------------------------------------------------------------------- # Usage: # - Put one filename per line (e.g., payload.elf or script.js). # - Supported payload types: .elf, .bin, .js # - Lines starting with '!' are sleep commands (example: !1000 sleeps for 1000 ms). # ----------------------------------------------------------------------------------------- `; function _alParse(content) { if (!content) return { header: _AL_DEFAULT_HEADER, entries: [] }; const lines = content.split(/\r?\n/); const headerLines = []; const entries = []; let headerDone = false; for (const line of lines) { const t = line.trim(); if (!headerDone) { if (t.startsWith('#') || t === '') { headerLines.push(line); continue; } headerDone = true; } if (t === '' || t.startsWith('#')) continue; if (t.startsWith('!')) { const ms = parseInt(t.slice(1), 10); if (!isNaN(ms)) entries.push({ type: 'delay', ms }); } else { entries.push({ type: 'payload', filename: t }); } } const header = headerLines.join('\n').trimEnd() + '\n\n'; return { header: header || _AL_DEFAULT_HEADER, entries }; } function _alSerialize(header, entries) { const lines = [header.trimEnd(), '']; for (const e of entries) lines.push(e.type === 'delay' ? `!${e.ms}` : e.filename); return lines.join('\n') + '\n'; } let _alDragIdx = -1; function _alSecsFmt(ms) { const s = ms / 1000; return s === Math.floor(s) ? s.toFixed(1) + 's' : s.toFixed(2) + 's'; } function _alBuildRow(entry, i) { const del = ``; if (entry.type === 'delay') { const capped = Math.min(entry.ms, 10000); const presets = [250, 500, 1000, 2000, 3000, 5000].map(p => { const lbl = p < 1000 ? `${p}ms` : `${p / 1000}s`; const active = entry.ms === p ? ' al-preset-active' : ''; return ``; }).join(''); return `
⋮⋮ ⏱
Wait ms (${_alSecsFmt(entry.ms)})
${presets}
${del}
`; } const ext = (entry.filename.split('.').pop() || '').toLowerCase(); const extClass = { elf: 'al-ext-elf', bin: 'al-ext-bin', js: 'al-ext-js' }[ext] || ''; return `
⋮⋮ ${escHtml(ext || '?')} ${escHtml(entry.filename)} ${del}
`; } function _alRenderSequence() { const seq = $('autoloader-sequence'); if (!seq) return; if (!_alSequence.length) { seq.innerHTML = `
📄
No entries yet.
Click a file on the left to add it to the sequence.
`; return; } seq.innerHTML = _alSequence.map(_alBuildRow).join(''); seq.querySelectorAll('.al-row[draggable]').forEach(row => { row.addEventListener('dragstart', e => { _alDragIdx = parseInt(row.dataset.idx, 10); row.classList.add('al-dragging'); e.dataTransfer.effectAllowed = 'move'; }); row.addEventListener('dragend', () => { row.classList.remove('al-dragging'); seq.querySelectorAll('.al-drag-over').forEach(r => r.classList.remove('al-drag-over')); }); row.addEventListener('dragover', e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; seq.querySelectorAll('.al-drag-over').forEach(r => r.classList.remove('al-drag-over')); if (parseInt(row.dataset.idx, 10) !== _alDragIdx) row.classList.add('al-drag-over'); }); row.addEventListener('drop', e => { e.preventDefault(); let toIdx = parseInt(row.dataset.idx, 10); if (_alDragIdx === -1 || _alDragIdx === toIdx) return; const [item] = _alSequence.splice(_alDragIdx, 1); if (_alDragIdx < toIdx) toIdx--; _alSequence.splice(toIdx, 0, item); _alDragIdx = -1; _alRenderSequence(); $('btn-autoloader-save').disabled = false; }); }); } function _alRenderFiles(files) { const list = $('autoloader-file-list'); if (!list) return; const payloads = files.filter(f => !f.isDir && /\.(elf|bin|js)$/i.test(f.name)); if (!payloads.length) { list.innerHTML = '

No .elf / .bin / .js files found in /data/ps5_autoloader.

'; return; } list.innerHTML = payloads.map(f => { const ext = (f.name.split('.').pop() || '').toLowerCase(); const extClass = { elf: 'al-ext-elf', bin: 'al-ext-bin', js: 'al-ext-js' }[ext] || ''; return `
${escHtml(ext)} ${escHtml(f.name)} +
`; }).join(''); } async function loadAutoloader() { const btn = $('btn-autoloader-refresh'); const status = $('autoloader-status'); btn.disabled = true; status.textContent = 'Loading…'; try { const files = await window.pork.autoloaderList(); const content = await window.pork.autoloaderRead(); const { header, entries } = _alParse(content); _alHeader = header; _alSequence = entries; _alRenderFiles(files); _alRenderSequence(); $('btn-autoloader-save').disabled = false; status.textContent = content ? `Loaded \u2014 ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'}` : 'autoload.txt not found \u2014 sequence is empty'; } catch (e) { status.textContent = `Error: ${e.message}`; } finally { btn.disabled = false; } } async function saveAutoloader() { const btn = $('btn-autoloader-save'); const status = $('autoloader-status'); btn.disabled = true; status.textContent = 'Saving\u2026'; try { const content = _alSerialize(_alHeader, _alSequence); await window.pork.autoloaderSave(content); status.textContent = 'Saved to PS5 \u2713'; } catch (e) { status.textContent = `Error: ${e.message}`; btn.disabled = false; } } $('btn-autoloader-refresh').addEventListener('click', loadAutoloader); $('btn-autoloader-save').addEventListener('click', saveAutoloader); const _persistButton = $('btn-pldmgr-persist-repair'); if (_persistButton && window.pork.payloadManagerAutoloadRepairPlan) { $('pldmgr-persistence-panel').hidden = false; _persistButton.addEventListener('click', async () => { const status = $('pldmgr-persistence-status'); _persistButton.disabled = true; status.textContent = window.t('payload.persistence.preflight'); try { const plan = await window.pork.payloadManagerAutoloadRepairPlan(); if (plan.blocked) { throw new Error(window.tf('payload.persistence.blocked', 'Other boot entries found: {entries}. Chimera will not remove them.', { entries: plan.unsupportedEntries.join(', ') })); } const confirmation = await showExactConfirmation( window.t('payload.persistence.confirm'), plan.confirmation ); if (!confirmation) return void (status.textContent = window.t('payload.persistence.cancelled')); status.textContent = window.t('payload.persistence.applying'); const result = await window.pork.payloadManagerAutoloadRepairApply({ planDigest: plan.planDigest, confirmation }); status.textContent = window.tf('payload.persistence.success', 'Persistent v0.5.1 verified. Snapshot: {snapshot}. Reboot check required.', { snapshot: result.snapshotId }); await loadAutoloader(); } catch (error) { status.textContent = window.tf('payload.persistence.error', 'Persistence repair failed: {message}', { message: error.message }); } finally { _persistButton.disabled = false; } }); } const _rollbackButton = $('btn-pldmgr-persist-rollback'); if (_rollbackButton && window.pork.payloadManagerAutoloadRollbackPlan) { _rollbackButton.addEventListener('click', async () => { const status = $('pldmgr-persistence-status'); _rollbackButton.disabled = true; status.textContent = window.t('payload.persistence.rollbackPreflight'); try { const plan = await window.pork.payloadManagerAutoloadRollbackPlan(); if (plan.alreadyRestored) return void (status.textContent = window.t('payload.persistence.cancelled')); const confirmation = await showExactConfirmation(window.t('payload.persistence.rollbackConfirm'), plan.confirmation); if (!confirmation) return void (status.textContent = window.t('payload.persistence.cancelled')); status.textContent = window.t('payload.persistence.rollbackApplying'); const result = await window.pork.payloadManagerAutoloadRollbackApply({ planDigest: plan.planDigest, confirmation }); status.textContent = window.tf('payload.persistence.rollbackSuccess', 'Original boot state restored. Safety snapshot: {snapshot}. Reboot required.', { snapshot: result.safetySnapshotId }); await loadAutoloader(); } catch (error) { status.textContent = window.tf('payload.persistence.error', 'Persistence repair failed: {message}', { message: error.message }); } finally { _rollbackButton.disabled = false; } }); } function _etaHenPersistenceAction({ buttonId, plan, apply, preflight, confirm, success, snapshot = 'snapshotId' }) { const button = $(buttonId); if (!button || !window.pork[plan]) return; button.addEventListener('click', async () => { const status = $('etahen-persistence-status'); button.disabled = true; status.textContent = window.t(preflight); try { const packet = await window.pork[plan](); const entries = [...(packet.unexpectedFiles || []), ...(packet.unsupportedEntries || [])]; if (packet.blocked) throw new Error(window.tf('payload.etahenPersistence.blocked', 'Other boot content found: {entries}.', { entries: entries.join(', ') })); if (packet.alreadyRestored) return void (status.textContent = window.t('payload.persistence.cancelled')); const confirmation = await showExactConfirmation(window.t(confirm), packet.confirmation); if (!confirmation) return void (status.textContent = window.t('payload.persistence.cancelled')); const result = await window.pork[apply]({ planDigest: packet.planDigest, confirmation }); status.textContent = window.tf(success, 'etaHEN 2.6B boot state updated. Snapshot: {snapshot}. Reboot required.', { snapshot: result[snapshot] }); await loadAutoloader(); } catch (error) { status.textContent = window.tf('payload.etahenPersistence.error', 'etaHEN persistence failed: {message}', { message: error.message }); } finally { button.disabled = false; } }); } _etaHenPersistenceAction({ buttonId: 'btn-etahen-persist', plan: 'etaHenAutoloadPersistencePlan', apply: 'etaHenAutoloadPersistenceApply', preflight: 'payload.etahenPersistence.preflight', confirm: 'payload.etahenPersistence.confirm', success: 'payload.etahenPersistence.success' }); _etaHenPersistenceAction({ buttonId: 'btn-etahen-persist-rollback', plan: 'etaHenAutoloadPersistenceRollbackPlan', apply: 'etaHenAutoloadPersistenceRollback', preflight: 'payload.etahenPersistence.rollbackPreflight', confirm: 'payload.etahenPersistence.rollbackConfirm', success: 'payload.etahenPersistence.rollbackSuccess', snapshot: 'safetySnapshotId' }); function _fixedPayloadStart({ buttonId, api, exact, confirmKey, startingKey, startedKey }) { const button = $(buttonId); if (!button || !window.pork[api]) return; button.addEventListener('click', async () => { const status = $('etahen-persistence-status'); const confirmation = await showExactConfirmation(window.t(confirmKey), exact); if (!confirmation) return void (status.textContent = window.t('payload.persistence.cancelled')); button.disabled = true; status.textContent = window.t(startingKey); try { const result = await window.pork[api]({ confirmation }); status.textContent = window.tf(startedKey, '', { bytes: result.bytes }); } catch (error) { status.textContent = window.tf('payload.etahenPersistence.error', '', { message: error.message }); } finally { button.disabled = false; } }); } _fixedPayloadStart({ buttonId: 'btn-etahen-direct-start', api: 'etaHenDirectStart', exact: 'START DIRECT ETAHEN 2.6B', confirmKey: 'payload.etahenPersistence.startConfirm', startingKey: 'payload.etahenPersistence.starting', startedKey: 'payload.etahenPersistence.started' }); _fixedPayloadStart({ buttonId: 'btn-pldmgr-verified-start', api: 'payloadManagerVerifiedStart', exact: 'START VERIFIED PS5 PAYLOAD MANAGER 0.5.1', confirmKey: 'payload.persistence.startConfirm', startingKey: 'payload.persistence.starting', startedKey: 'payload.persistence.started' }); $('btn-autoloader-add-delay').addEventListener('click', () => { _alSequence.push({ type: 'delay', ms: 1000 }); _alRenderSequence(); $('btn-autoloader-save').disabled = false; }); $('autoloader-sequence').addEventListener('click', e => { const btn = e.target.closest('[data-al]'); if (!btn) return; const action = btn.dataset.al; const idx = parseInt(btn.dataset.idx, 10); if (action === 'remove') { _alSequence.splice(idx, 1); _alRenderSequence(); $('btn-autoloader-save').disabled = false; } else if (action === 'preset') { const ms = parseInt(btn.dataset.ms, 10); _alSequence[idx].ms = ms; _alRenderSequence(); $('btn-autoloader-save').disabled = false; } else if (action === 'ins-delay') { _alSequence.splice(idx + 1, 0, { type: 'delay', ms: 1000 }); _alRenderSequence(); $('btn-autoloader-save').disabled = false; } }); $('autoloader-sequence').addEventListener('input', e => { const el = e.target; const al = el.dataset.al; if (!al) return; const idx = parseInt(el.dataset.idx, 10); if (al === 'delay-num' || al === 'delay-slider') { const ms = Math.max(0, parseInt(el.value, 10) || 0); _alSequence[idx].ms = ms; const row = el.closest('.al-row'); const num = row.querySelector('[data-al="delay-num"]'); const slider = row.querySelector('[data-al="delay-slider"]'); const secs = row.querySelector('.al-delay-secs'); if (num) num.value = ms; if (slider) slider.value = Math.min(ms, 10000); if (secs) secs.textContent = `(${_alSecsFmt(ms)})`; row.querySelectorAll('.al-preset').forEach(p => p.classList.toggle('al-preset-active', parseInt(p.dataset.ms, 10) === ms) ); $('btn-autoloader-save').disabled = false; } }); // ── Autoloader Snapshots ───────────────────────────────────────────────────────────────────────────── async function renderAutoloaderSnapshots() { const list = $('snapshot-list'); if (!list) return; list.innerHTML = '

Loading…

'; try { const snaps = await window.pork.snapshotList(); if (!snaps.length) { list.innerHTML = '

No snapshots yet. Take one above.

'; return; } list.innerHTML = snaps.map(s => { const d = new Date(s.createdAt); const dateStr = d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const fc = (s.files || []).length; const alStr = s.hasAutoload ? ' + autoload.txt' : ''; return `
${escHtml(s.label || 'Snapshot')} ${dateStr} \u2014 ${fc} payload${fc !== 1 ? 's' : ''}${alStr}
`; }).join(''); list.querySelectorAll('.btn-snap-restore').forEach(btn => { btn.addEventListener('click', async () => { const id = btn.dataset.snapId; const confirmation = await showExactConfirmation( 'Restore this snapshot to your PS5?\n\nAll payload files and autoload.txt will be uploaded back to /data/ps5_autoloader, overwriting whatever is currently there.', 'RESTORE AUTOLOADER' ); if (!confirmation) return; const orig = btn.textContent; btn.disabled = true; btn.textContent = '⋯ Uploading…'; try { const res = await window.pork.snapshotRestore(id, confirmation); btn.textContent = '✓ Restored'; const errNote = res.errors?.length ? ` (${res.errors.length} file${res.errors.length !== 1 ? 's' : ''} failed)` : ''; showToast(`Snapshot restored ✓ — ${res.fileCount} file${res.fileCount !== 1 ? 's' : ''} uploaded to PS5${errNote}`, res.errors?.length ? 'info' : 'ok'); setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 3000); } catch (er) { btn.textContent = orig; btn.disabled = false; showToast('Restore failed: ' + er.message, 'error'); } }); }); list.querySelectorAll('.btn-snap-export').forEach(btn => { btn.addEventListener('click', async () => { const id = btn.dataset.snapId; const orig = btn.textContent; btn.disabled = true; btn.textContent = '⋯'; try { const res = await window.pork.snapshotExport(id); if (res?.canceled) { btn.textContent = orig; btn.disabled = false; return; } btn.textContent = '✓ Exported'; showToast('Snapshot exported ✓', 'ok'); setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 2500); } catch (er) { btn.textContent = orig; btn.disabled = false; showToast('Export failed: ' + er.message, 'error'); } }); }); list.querySelectorAll('.btn-snap-delete').forEach(btn => { btn.addEventListener('click', async () => { const id = btn.dataset.snapId; const confirmation = await showExactConfirmation('Delete this snapshot? This cannot be undone.', 'VERWIJDER'); if (!confirmation) return; try { await window.pork.snapshotDelete(id, confirmation); await renderAutoloaderSnapshots(); } catch (er) { showToast('Delete failed: ' + er.message, 'error'); } }); }); } catch (e) { list.innerHTML = `

Error: ${escHtml(e.message)}

`; } } $('btn-snapshot-import').addEventListener('click', async () => { const btn = $('btn-snapshot-import'); const status = $('snapshot-take-status'); const orig = btn.textContent; btn.disabled = true; btn.textContent = '⋯ Importing…'; status.textContent = ''; try { let res = await window.pork.snapshotImport(); if (res?.canceled) { btn.textContent = orig; btn.disabled = false; return; } if (res?.needsLabel) { const label = await showPrompt('Enter a label for this snapshot:', 'Imported ZIP'); if (label === null) { btn.textContent = orig; btn.disabled = false; return; } // user cancelled res = await window.pork.snapshotImport({ label: label.trim() || 'Imported ZIP', filePath: res.filePath }); if (res?.canceled) { btn.textContent = orig; btn.disabled = false; return; } } status.textContent = `Imported ✓ — "${res.meta.label}"`; await renderAutoloaderSnapshots(); } catch (e) { status.textContent = 'Import failed: ' + e.message; } finally { btn.textContent = orig; btn.disabled = false; } }); $('btn-snapshot-take').addEventListener('click', async () => { const btn = $('btn-snapshot-take'); const status = $('snapshot-take-status'); const label = $('snapshot-label').value.trim() || 'Snapshot'; btn.disabled = true; status.textContent = 'Downloading from PS5\u2026'; try { const { meta } = await window.pork.snapshotTake(label); const fc = meta.files.length; const alStr = meta.hasAutoload ? ' + autoload.txt' : ''; status.textContent = `Saved \u2713 \u2014 ${fc} payload${fc !== 1 ? 's' : ''}${alStr}`; $('snapshot-label').value = ''; await renderAutoloaderSnapshots(); } catch (e) { status.textContent = 'Error: ' + e.message; } finally { btn.disabled = false; } });