for (const href of ['styles/ps4-packages.css', 'styles/ps5-native-packages.css']) { if (!document.head.querySelector(`link[href="${href}"]`)) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; document.head.appendChild(link); } } document.querySelectorAll('.modal-tab').forEach(tab => tab.addEventListener('click', () => { document.querySelectorAll('.modal-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); t.tabIndex = -1; }); document.querySelectorAll('.modal-panel').forEach(p => { p.classList.remove('active'); p.hidden = true; }); tab.classList.add('active'); tab.setAttribute('aria-selected', 'true'); tab.tabIndex = 0; const panel = $(`mpanel-${tab.dataset.mtab}`); panel.classList.add('active'); panel.hidden = false; }) ); function resetModalTabs() { document.querySelectorAll('.modal-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); t.tabIndex = -1; }); document.querySelectorAll('.modal-panel').forEach(p => { p.classList.remove('active'); p.hidden = true; }); const first = document.querySelector('.modal-tab[data-mtab="info"]'); first.classList.add('active'); first.setAttribute('aria-selected', 'true'); first.tabIndex = 0; $('mpanel-info').classList.add('active'); $('mpanel-info').hidden = false; } // Promote the four facts people open the dialog for; the full table still follows. function renderModalMetrics(g, infoRows) { let host = $('modal-metrics'); if (!$('mpanel-info')) return; if (!host) { host = document.createElement('div'); host.id = 'modal-metrics'; host.className = 'modal-metrics'; $('mpanel-info').insertBefore(host, $('modal-info')); } const at = k => (infoRows.find(r => r[0] === k) || [])[1] || '—'; const on = window.chimeraLibraryModel.isInstalled(g), copy = at('Local Copy') === 'Yes'; host.innerHTML = [ ['Size', at('Size'), ''], ['Version', at('Version'), ''], ['On console', on ? 'Installed' : 'Not installed', on ? 'ok' : 'idle'], ['Local copy', copy ? 'Backed up' : 'None', copy ? 'ok' : 'warn'], ].map(([l, v, t]) => `
${escHtml(log)}` : ''}
${job.error ? `${escHtml(ps4PackageText('ps4Packages.jobs.empty', 'No package activity for this title yet.'))}
`; window.i18n?.apply($('ps4-packages-overlay')); } async function openPs4PackageWorkspace() { const game = state.currentGame; if (!game || !/^CUSA\d{5}$/.test(game.game_id)) return; $('ps4-packages-game').textContent = `${game.prospero_name || game.title || game.game_id} · ${game.game_id}`; const registeredBackups = await window.pork.listBackups(game.game_id).catch(() => []); const folderBackup = registeredBackups.find(item => item.source !== 'backpork' && item.backup_type === 'folder'); $('ps4-package-base').value = folderBackup?.backup_path || ''; $('ps4-package-update').value = ''; $('ps4-package-create').disabled = true; if (typeof openAccessibleDialog === 'function') openAccessibleDialog($('ps4-packages-overlay'), $('ps4-packages-close')); else $('ps4-packages-overlay').hidden = false; try { await refreshPs4PackageWorkspace(); } catch (error) { setPs4PackageReadiness(error.message, 'blocked'); } } function closePs4PackageWorkspace() { if (typeof closeAccessibleDialog === 'function') closeAccessibleDialog($('ps4-packages-overlay')); else $('ps4-packages-overlay').hidden = true; } $('btn-modal-ps4-packages').addEventListener('click', openPs4PackageWorkspace); ['ps4-packages-close', 'ps4-packages-done'].forEach(id => $(id).addEventListener('click', closePs4PackageWorkspace)); ['base', 'update'].forEach(type => $(`ps4-package-${type}-browse`).addEventListener('click', async () => { const folder = await window.pork.selectFolder(); if (!folder) return; $(`ps4-package-${type}`).value = folder; $('ps4-package-create').disabled = !$('ps4-package-base').value; })); $('ps4-package-refresh').addEventListener('click', () => refreshPs4PackageWorkspace().catch(error => setPs4PackageReadiness(error.message, 'blocked'))); $('ps4-package-create').addEventListener('click', async () => { const game = state.currentGame; const sources = [{ sourcePath: $('ps4-package-base').value, componentType: 'base' }]; if ($('ps4-package-update').value) sources.push({ sourcePath: $('ps4-package-update').value, componentType: 'update' }); $('ps4-package-create').disabled = true; setPs4PackageReadiness(ps4PackageText('ps4Packages.create.running', 'Validating dump evidence and building verified packages…')); try { const job = await window.pork.ps4PackageCreate({ expectedTitleId: game.game_id, sources }); await window.pork.ps4PackageRun(job.id); setPs4PackageReadiness(ps4PackageText('ps4Packages.create.completed', 'PKG backup completed, hash-verified and registered.'), 'ready'); await refreshPs4PackageWorkspace(); } catch (error) { setPs4PackageReadiness(error.message, 'blocked'); } finally { $('ps4-package-create').disabled = !$('ps4-package-base').value; } }); $('ps4-package-restore').addEventListener('click', async () => { const game = state.currentGame; const packageSetId = Number($('ps4-package-set').value); if (!packageSetId) return; try { const plan = await window.pork.ps4PackageRestorePlan({ titleId: game.id, packageSetId }); const detail = plan.orderedComponents.map(item => `${item.type.toUpperCase()} · ${item.version || '—'} · ${item.sha256.slice(0, 12)}…`).join('\n'); const confirmation = await showExactConfirmation(`${ps4PackageText('ps4Packages.restore.confirm', 'Hand off this verified package set to etaHEN DPI in the exact order below?')}\n\n${detail}\n\n${ps4PackageText('ps4Packages.restore.boundary', 'A successful handoff is not installed evidence. A console scan remains required.')}`, 'INSTALL PS4 PACKAGES'); if (!confirmation) return; setPs4PackageReadiness(ps4PackageText('ps4Packages.restore.running', 'Sending ordered PKG handoffs…')); const result = await window.pork.ps4PackageRestore({ titleId: game.id, packageSetId, confirmation }); setPs4PackageReadiness(result.status === 'awaiting_post_scan' ? ps4PackageText('ps4Packages.restore.awaitingScan', 'Handoffs accepted. Installation is not yet verified; run the post-install scan.') : result.status, 'ready'); } catch (error) { setPs4PackageReadiness(error.message, 'blocked'); } }); $('ps4-package-verify').addEventListener('click', async () => { const game = state.currentGame; setPs4PackageReadiness(ps4PackageText('ps4Packages.verify.running', 'Scanning the console for installed evidence…')); try { const result = await window.pork.ps4PackageRestoreVerify(game.game_id); setPs4PackageReadiness(result.verified ? ps4PackageText('ps4Packages.verify.completed', 'Console scan verified the installation.') : ps4PackageText('ps4Packages.verify.notObserved', 'The console scan did not observe the installation yet.'), result.verified ? 'ready' : 'blocked'); } catch (error) { setPs4PackageReadiness(error.message, 'blocked'); } }); $('ps4-package-jobs').addEventListener('click', async event => { const button = event.target.closest('[data-ps4-resume]'); if (!button) return; try { await window.pork.ps4PackageResume(button.dataset.ps4Resume); await window.pork.ps4PackageRun(button.dataset.ps4Resume); await refreshPs4PackageWorkspace(); } catch (error) { setPs4PackageReadiness(error.message, 'blocked'); } }); function ps5NativePackageText(key, fallback) { return typeof window.t === 'function' ? window.t(key, fallback) : fallback; } function ps5A10Text(key) { return ps5NativePackageText(`ps5NativePackages.a10Control.${key}`, key); } function confirmPs5A10(prompt, boundary, phrase, evidence = '') { return showExactConfirmation(`${ps5A10Text(prompt)}${evidence ? `\n\n${evidence}` : ''}\n\n${ps5A10Text(boundary)}`, phrase); } let ps5ConsoleDiscovery = null; let ps5PreparedSources = null; let ps5NativeInstallOperationId = null; let ps5UpstreamControlOperationId = null; let ps5LaunchReferenceOperationId = null; let ps5A8ControlPlan = null; let ps5A8ControlOperationId = null; let ps5A10ControlPlan = null; let ps5A10ControlOperationId = null; let ps5P15R2LastPreflight = null; let ps5P15R2QualificationAttempted = false; const ps5Control = suffix => $(`ps5-upstream-control-${suffix}`); const ps5A8Control = suffix => $(`ps5-a8-control-${suffix}`); const ps5A10Control = suffix => $(`ps5-a10-control-${suffix}`); const ps5LaunchReference = suffix => $(`ps5-launch-reference-${suffix}`); function ps5SourcesFromStagedJob(job) { if (!job?.stagedPath || !job?.manifest?.components) return null; const sources = job.manifest.components.filter(item => item.packageBuild?.supported === true).map(item => ({ componentType: item.type, sourcePath: `${job.stagedPath}/components/${item.id}` })); return sources.some(item => item.componentType === 'base') ? sources : null; } function setPs5NativePackageReadiness(message, stateName = '') { const element = $('ps5-native-package-readiness'); element.textContent = message; element.dataset.state = stateName; } function setPs5FpkgEvidence(id, message, stateName = 'unknown') { const element = $(id); if (!element) return; element.textContent = message; element.dataset.state = stateName; } function renderPs5RestoreQualification(operation) { const launchButton = $('ps5-native-package-launch'); if (!operation) { setPs5FpkgEvidence('ps5-native-restore-evidence', ps5NativePackageText('ps5NativePackages.evidence.notInstalled', 'Not installed'), 'unproven'); launchButton.disabled = true; return; } ps5NativeInstallOperationId = operation.id; if (operation.status === 'launch_verified') { setPs5FpkgEvidence('ps5-native-restore-evidence', ps5NativePackageText('ps5NativePackages.launch.qualified', 'Installed · Launch verified · Qualified for restore'), 'qualified'); launchButton.disabled = true; } else if (operation.status === 'launch_failed') { setPs5FpkgEvidence('ps5-native-restore-evidence', `${ps5NativePackageText('ps5NativePackages.launch.failed', 'Installed · launch failed · Not qualified for restore')} · ${operation.error?.code || 'PS5_FPKG_LAUNCH_UNVERIFIED'}`, 'failed'); launchButton.disabled = true; } else if (operation.status === 'launching') { setPs5FpkgEvidence('ps5-native-restore-evidence', ps5NativePackageText('ps5NativePackages.launch.observing', 'Installed · observing exact title process…'), 'running'); launchButton.disabled = true; } else if (operation.status === 'interrupted' && operation.launchOutcome === 'outcome_unknown') { setPs5FpkgEvidence('ps5-native-restore-evidence', ps5NativePackageText('ps5NativePackages.launch.unknown', 'Installed · launch outcome unknown · Not qualified'), 'failed'); launchButton.disabled = true; } else if (operation.status === 'installed_observed') { setPs5FpkgEvidence('ps5-native-restore-evidence', ps5NativePackageText('ps5NativePackages.launch.pending', 'Installed · launch verification pending'), 'pending'); launchButton.disabled = false; } else { setPs5FpkgEvidence('ps5-native-restore-evidence', ps5NativePackageText('ps5NativePackages.evidence.installPending', 'Installation not yet proven'), 'unproven'); launchButton.disabled = true; } } function renderPs5UpstreamControl(control = {}) { const ready = control.status === 'ready'; const proven = control.installTrust?.status === 'post-scan-proven'; ps5Control('trust').textContent = proven ? ps5NativePackageText('ps5NativePackages.control.proven', 'Post-scan proven') : ps5NativePackageText('ps5NativePackages.control.unproven', 'Unproven'); ps5Control('trust').dataset.trust = proven ? 'post-scan-proven' : 'unproven'; ps5Control('status').textContent = ready ? `${ps5NativePackageText('ps5NativePackages.control.ready', 'Pinned upstream control is verified and registered.')} · ${String(control.sha256 || '').slice(0, 12)}…` : ps5NativePackageText('ps5NativePackages.control.notImported', 'Control package not imported.'); ps5Control('import').disabled = ready; ps5Control('restore').disabled = !ready; ps5Control('verify').disabled = !ps5UpstreamControlOperationId; } function renderPs5A8Control(control = {}, operation = null) { const ready = control.status === 'ready'; const retry = control.status === 'built_verified'; const drift = control.status === 'artifact_drift'; const awaitingScan = operation?.status === 'awaiting_post_scan'; const installed = operation?.status === 'installed_observed'; const launching = operation?.status === 'launching'; const launchVerified = operation?.status === 'launch_verified'; const launchFailed = operation?.status === 'launch_failed'; const installInFlight = ['preparing', 'staging', 'installing'].includes(operation?.status); ps5A8Control('trust').removeAttribute('data-i18n'); ps5A8Control('status').removeAttribute('data-i18n'); ps5A8Control('trust').textContent = launchVerified ? ps5NativePackageText('ps5NativePackages.a8Control.launchVerified', 'Installed · launch verified') : launchFailed ? ps5NativePackageText('ps5NativePackages.a8Control.launchFailed', 'Installed · launch failed') : installed || launching ? ps5NativePackageText('ps5NativePackages.a8Control.installedPendingLaunch', 'Installed · launch unproven') : installInFlight || awaitingScan ? ps5NativePackageText('ps5NativePackages.a8Control.installUnprovenTrust', 'Install result · scan required') : ready ? ps5NativePackageText('ps5NativePackages.a8Control.readyTrust', 'Host ready · install unproven') : retry ? ps5NativePackageText('ps5NativePackages.a8Control.retryTrust', 'Verified · registration pending') : drift ? ps5NativePackageText('ps5NativePackages.a8Control.driftTrust', 'Artifact drift blocked') : ps5NativePackageText('ps5NativePackages.a8Control.unprepared', 'Not prepared'); ps5A8Control('trust').dataset.trust = launchVerified ? 'qualified' : launchFailed ? 'failed' : installed || launching ? 'post-scan-proven' : installInFlight || awaitingScan ? 'pending' : drift ? 'failed' : 'unproven'; ps5A8Control('status').textContent = launchVerified ? ps5NativePackageText('ps5NativePackages.a8Control.launchVerified', 'Installed and exact-title launch verified.') : launchFailed ? `${ps5NativePackageText('ps5NativePackages.a8Control.launchFailed', 'Installed, but launch qualification failed.')} · ${operation.error?.code || 'PS5_FPKG_LAUNCH_UNVERIFIED'}` : launching ? ps5NativePackageText('ps5NativePackages.a8Control.launching', 'Observing the exact CHIM00002 process…') : installed ? ps5NativePackageText('ps5NativePackages.a8Control.installedPendingLaunch', 'Installation proven. Exact-title launch remains unproven.') : awaitingScan ? ps5NativePackageText('ps5NativePackages.a8Control.awaitingScan', 'Installer completed. Run the independent CHIM00002 post-scan.') : installInFlight ? ps5NativePackageText('ps5NativePackages.a8Control.installing', 'Installing the exact CHIM00002 control…') : ready ? `${ps5NativePackageText('ps5NativePackages.a8Control.ready', 'Frozen metadata-qualified CHIM00002 artifact is verified and registered. Installation and launch remain unproven.')} · ${String(control.sha256 || '').slice(0, 12)}…` : retry ? ps5NativePackageText('ps5NativePackages.a8Control.registrationPending', 'Artifact verified; retry host registration without rebuilding.') : drift ? ps5NativePackageText('ps5NativePackages.a8Control.drift', 'Selected artifact changed and is blocked.') : ps5NativePackageText('ps5NativePackages.a8Control.notPrepared', 'Frozen artifact is included but not registered.'); const active = installInFlight || awaitingScan || installed || launching || launchVerified; ps5A8Control('prepare').disabled = ready || drift || control.status === 'server-required'; ps5A8Control('preflight').disabled = !ready || active || control.status === 'server-required'; ps5A8Control('install').disabled = !ready || !ps5A8ControlPlan || active; ps5A8Control('verify').disabled = !awaitingScan; ps5A8Control('launch').disabled = !installed; } function renderPs5A10Control(control = {}, operation = null, runtime = {}) { const ui = ps5A10Control; const op = operation?.status || ''; const ready = control.status === 'ready'; const drift = control.status === 'artifact_drift'; const scan = op === 'awaiting_post_scan'; const ins = op === 'installed_observed'; const run = op === 'launching'; const flight = ['preparing', 'staging', 'installing'].includes(op); const active = runtime?.diagnostic?.engine?.available === true && runtime.diagnostic.engine.processOwned === true; const exact = active && runtime?.diagnosticIdentity?.status === 'exact' && runtime.diagnosticIdentity.mutationAllowed === true; const runtimeEligible = ['installed_observed', 'launch_failed', 'launch_verified'].includes(op); const launchEligible = op === 'installed_observed'; const state = op === 'launch_verified' ? 'launchVerified' : op === 'launch_failed' ? 'launchFailed' : op === 'interrupted' ? 'launchInterrupted' : run ? 'launching' : exact ? 'runtimeExact' : active ? 'runtimeActive' : ins ? 'installedPendingRuntime' : scan ? 'awaitingScan' : flight ? 'installing' : ready ? 'ready' : control.status === 'built_verified' ? 'registrationPending' : drift ? 'drift' : 'notPrepared'; const tk = { launchVerified: 'launchVerified', launchFailed: 'launchFailed', launchInterrupted: 'launchInterrupted', runtimeExact: 'runtimeExactTrust', runtimeActive: 'runtimeActiveTrust', installedPendingRuntime: 'installedPendingRuntime', awaitingScan: 'installUnprovenTrust', installing: 'installUnprovenTrust', ready: 'readyTrust', registrationPending: 'retryTrust', drift: 'driftTrust', notPrepared: 'unprepared', launching: 'installedPendingRuntime' }[state]; const t = ui('trust'); const status = ui('status'); t.removeAttribute('data-i18n'); status.removeAttribute('data-i18n'); t.textContent = ps5A10Text(tk); t.dataset.trust = state === 'launchVerified' ? 'qualified' : ['launchFailed', 'drift'].includes(state) ? 'failed' : active || ins || run ? 'post-scan-proven' : scan || flight ? 'pending' : 'unproven'; const sk = state === 'launchVerified' ? 'launchVerifiedDetail' : state === 'launchFailed' ? 'launchFailedDetail' : state === 'launchInterrupted' ? 'launchInterruptedDetail' : state; status.textContent = `${ps5A10Text(sk)}${state === 'launchFailed' ? ` · ${operation.error?.code || 'PS5_FPKG_LAUNCH_UNVERIFIED'}` : state === 'ready' ? ` · ${String(control.sha256 || '').slice(0, 12)}…` : ''}`; const s = $('ps5-a10-control-evidence-summary'); const id = String(operation?.id || ''); if (s.dataset.operationId !== id) { s.hidden = true; s.textContent = ''; s.dataset.operationId = id; } const busy = flight || scan || ins || run || op.startsWith('launch_'); ui('prepare').disabled = ready || drift || control.status === 'server-required'; ui('preflight').disabled = !ready || busy || control.status === 'server-required'; ui('install').disabled = !ready || !ps5A10ControlPlan || busy; ui('verify').disabled = !scan; ui('stage').disabled = !runtimeEligible || active; ui('payload').disabled = !runtimeEligible || !active || exact; ui('launch').disabled = !launchEligible || !exact; ui('restore').disabled = !active; ui('evidence').disabled = !operation?.launchEvidence || !['launch_failed', 'launch_verified', 'interrupted'].includes(op); } function renderPs5P15R2Qualification(status = {}, preflight = ps5P15R2LastPreflight) { const matrix = Array.isArray(preflight?.matrix) ? preflight.matrix : []; const body = $('ps5-p15-r2-matrix'); body.innerHTML = matrix.length ? matrix.map(row => { const kstuff = row.gate === 'kstuff' ? preflight?.evidence?.kstuff : null; const evidence = kstuff ? `${escHtml(JSON.stringify({ reason: row.reason || null, ...kstuff }))}`
: escHtml(row.reason || 'exact');
return `${escHtml(ps5NativePackageText(`ps5NativePackages.compare.next.${result.nextAction}`, result.nextAction))}
`; } async function refreshPs5NativePackageWorkspace() { const game = state.currentGame; if (!game || !isPs5NativeWorkspaceTitle(game.game_id)) return; const [builder, jobs, stageJobs, packageData, control, a8Control, a10Control, a10Runtime, launchReference, installs, r2Status] = await Promise.all([window.pork.ps5NativePackageBuilderStatus(), window.pork.ps5NativePackageJobs(), window.pork.ps5ConsoleStagingJobs(), window.pork.packageRecipesList(game.id), window.pork.ps5UpstreamControl('status'), window.pork.ps5A8DistinctControl('status'), window.pork.ps5A10TransitionControl('status'), window.pork.ps5P15A10('runtime-status'), window.pork.ps5LaunchReference('status'), window.pork.ps5NativePackageRestoreList(), window.pork.ps5P15R2Status()]); const awaiting = (installs || []).filter(item => item.status === 'awaiting_post_scan').sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0)); const a8Operation = (installs || []).filter(item => item.titleId === 'CHIM00002' && Number(item.packageSetId) === Number(a8Control.packageSetId) && item.components?.length === 1 && item.components[0]?.sha256 === a8Control.sha256 && Number(item.components[0]?.size) === Number(a8Control.size)).sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0))[0] || null; const a10Operation = (installs || []).filter(item => item.titleId === 'CHIM00003' && Number(item.packageSetId) === Number(a10Control.packageSetId) && item.components?.length === 1 && item.components[0]?.sha256 === a10Control.sha256 && Number(item.components[0]?.size) === Number(a10Control.size)).sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0))[0] || null; ps5UpstreamControlOperationId = awaiting.find(item => item.titleId === 'PPSA99099')?.id || null; ps5LaunchReferenceOperationId = awaiting.find(item => item.titleId === 'FAKE00000')?.id || null; ps5A8ControlOperationId = a8Operation?.id || null; ps5A10ControlOperationId = a10Operation?.id || null; if (a8Operation || a8Control.status !== 'ready') ps5A8ControlPlan = null; if (a10Operation || a10Control.status !== 'ready') ps5A10ControlPlan = null; renderPs5UpstreamControl(control); renderPs5P15R2Qualification(r2Status); renderPs5A8Control(a8Control, a8Operation); renderPs5A10Control(a10Control, a10Operation, a10Runtime); renderPs5LaunchReference(launchReference); setPs5NativePackageReadiness(builder.available ? ps5NativePackageText('ps5NativePackages.builder.ready', 'The pinned native package bridge is ready.') : ps5NativePackageText('ps5NativePackages.builder.blocked', 'The revision-pinned LibProsperoPkg bridge is not configured.'), builder.available ? 'ready' : 'blocked'); $('ps5-native-package-create').disabled = !builder.available || !$('ps5-native-package-source').value; const sets = (packageData.packageSets || []).filter(item => item.platform === 'ps5' && item.manifest?.packageFormat?.container === 'FIH'); $('ps5-native-package-set').innerHTML = sets.length ? sets.map(item => ``).join('') : ``; const comparisonOptions = sets.map(item => ``).join(''); $('ps5-package-comparison-candidate').innerHTML = comparisonOptions || ''; $('ps5-package-comparison-reference').innerHTML = comparisonOptions || ''; if (sets.length > 1) $('ps5-package-comparison-reference').value = String(sets[1].id); $('ps5-package-comparison-run').disabled = sets.length < 2; $('ps5-native-package-restore').disabled = !sets.length; const titleJobs = jobs.filter(job => job.titleId === game.game_id).sort((a, b) => Number(b.createdAt) - Number(a.createdAt)); const titleStageJobs = stageJobs.filter(job => job.titleId === game.game_id).sort((a, b) => Number(b.createdAt) - Number(a.createdAt)); const qualificationOperation = selectPs5RestoreQualificationOperation(installs, game.game_id); renderPs5RestoreQualification(qualificationOperation); const captured = titleStageJobs.find(job => job.status === 'staged' && job.manifest?.components?.length); if (captured) { const components = captured.manifest.components; const complete = components.every(item => item.preservation?.complete === true && item.preservation?.verified === true); const files = components.reduce((sum, item) => sum + Number(item.preservation?.fileCount || item.fileCount || 0), 0); setPs5FpkgEvidence('ps5-native-preservation-evidence', complete ? `${ps5NativePackageText('ps5NativePackages.evidence.backupSafe', 'Backup safe')} · ${files.toLocaleString()} ${ps5NativePackageText('ps5NativePackages.evidence.filesVerified', 'files verified')}` : ps5NativePackageText('ps5NativePackages.evidence.backupIncomplete', 'Backup incomplete'), complete ? 'safe' : 'failed'); const baseReadiness = components.find(item => item.type === 'base')?.fpkgReadiness; if (baseReadiness?.ready === true) { const modules = Number(baseReadiness.modules?.total || 0); const unresolved = Number(baseReadiness.modules?.unresolved || 0); setPs5FpkgEvidence('ps5-native-fpkg-readiness-evidence', `${ps5NativePackageText('ps5NativePackages.evidence.ready', 'Ready for fPKG')} · ${modules} ${ps5NativePackageText('ps5NativePackages.evidence.modulesInspected', 'modules inspected')} · ${unresolved} ${ps5NativePackageText('ps5NativePackages.evidence.unresolved', 'unresolved')}`, 'ready'); } else { const unresolved = Number(baseReadiness?.modules?.unresolved || 0); setPs5FpkgEvidence('ps5-native-fpkg-readiness-evidence', `${ps5NativePackageText('ps5NativePackages.evidence.buildBlocked', 'fPKG build blocked')} · ${unresolved} ${ps5NativePackageText('ps5NativePackages.evidence.unresolved', 'unresolved')}`, 'blocked'); } } else { setPs5FpkgEvidence('ps5-native-preservation-evidence', ps5NativePackageText('ps5NativePackages.evidence.notCaptured', 'Not captured'), 'unknown'); setPs5FpkgEvidence('ps5-native-fpkg-readiness-evidence', ps5NativePackageText('ps5NativePackages.evidence.notAnalyzed', 'Not analyzed'), 'unknown'); } if (!ps5PreparedSources) ps5PreparedSources = ps5SourcesFromStagedJob(titleStageJobs.find(job => job.status === 'staged')); if (ps5PreparedSources?.length) $('ps5-native-package-source').value = ps5PreparedSources.find(item => item.componentType === 'base')?.sourcePath || ''; $('ps5-native-package-create').disabled = !builder.available || !$('ps5-native-package-source').value; const stageRows = titleStageJobs.map(job => `${escHtml(ps5NativePackageText('ps5NativePackages.jobs.empty', 'No native package activity for this title yet.'))}
`; window.i18n?.apply($('ps5-native-packages-overlay')); } async function openPs5NativePackageWorkspace() { const game = state.currentGame; if (!game || !isPs5NativeWorkspaceTitle(game.game_id)) return; ps5ConsoleDiscovery = null; ps5PreparedSources = null; ps5A8ControlPlan = null; ps5A10ControlPlan = null; ps5P15R2LastPreflight = null; ps5P15R2QualificationAttempted = false; const r4Card = $('ps5-p15-r2-card'); r4Card.className = 'ps5-upstream-control ps5-p15-r2-qualification'; r4Card.setAttribute('aria-labelledby', 'ps5-p15-r2-title'); $('ps5-p15-r2-title').textContent = ps5NativePackageText('ps5NativePackages.r4.title', 'PPSA99099 read-only qualification'); $('ps5-p15-r2-state').className = 'ps5-upstream-control-status'; $('ps5-p15-r2-matrix').closest('table').className = 'ps5-p15-r2-matrix'; $('ps5-p15-r2-preflight').className = 'btn btn-accent'; $('ps5-p15-r2-preflight').textContent = ps5NativePackageText('ps5NativePackages.r4.preflight', 'Collect read-only preflight'); $('ps5-p15-r2-qualify').className = 'btn btn-teal'; $('ps5-p15-r2-qualify').textContent = ps5NativePackageText('ps5NativePackages.r4.qualify', 'Run one qualification'); $('ps5-native-packages-game').textContent = `${game.prospero_name || game.title || game.game_id} · ${game.game_id}`; const backups = await window.pork.listBackups(game.game_id).catch(() => []); const folder = backups.find(item => item.source !== 'backpork' && item.backup_type === 'folder'); $('ps5-native-package-source').value = folder?.backup_path || ''; if (typeof openAccessibleDialog === 'function') openAccessibleDialog($('ps5-native-packages-overlay'), $('ps5-native-packages-close')); else $('ps5-native-packages-overlay').hidden = false; try { await refreshPs5NativePackageWorkspace(); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } } function closePs5NativePackageWorkspace() { if (typeof closeAccessibleDialog === 'function') closeAccessibleDialog($('ps5-native-packages-overlay')); else $('ps5-native-packages-overlay').hidden = true; } $('btn-modal-ps5-native-packages').addEventListener('click', openPs5NativePackageWorkspace); ['ps5-native-packages-close', 'ps5-native-packages-done'].forEach(id => $(id).addEventListener('click', closePs5NativePackageWorkspace)); $('ps5-native-package-source-browse').addEventListener('click', async () => { const folder = await window.pork.selectFolder(); if (!folder) return; ps5PreparedSources = null; $('ps5-native-package-source').value = folder; await refreshPs5NativePackageWorkspace().catch(error => setPs5NativePackageReadiness(error.message, 'blocked')); }); $('ps5-native-package-refresh').addEventListener('click', () => refreshPs5NativePackageWorkspace().catch(error => setPs5NativePackageReadiness(error.message, 'blocked'))); $('ps5-package-comparison-run').addEventListener('click', async () => { const game = state.currentGame; const candidatePackageSetId = Number($('ps5-package-comparison-candidate').value); const referencePackageSetId = Number($('ps5-package-comparison-reference').value); if (!candidatePackageSetId || !referencePackageSetId) return; $('ps5-package-comparison-run').disabled = true; try { renderPs5PackageComparison(await window.pork.ps5PackageComparison({ titleId: game.id, candidatePackageSetId, referencePackageSetId })); } catch (error) { $('ps5-package-comparison-result').innerHTML = `${escHtml(error.message)}
`; } finally { $('ps5-package-comparison-run').disabled = false; } }); ps5Control('import').addEventListener('click', async () => { ps5Control('import').disabled = true; ps5Control('status').textContent = ps5NativePackageText('ps5NativePackages.control.importing', 'Downloading and verifying the pinned upstream control…'); try { renderPs5UpstreamControl(await window.pork.ps5UpstreamControl('import')); } catch (error) { ps5Control('status').textContent = error.message; ps5Control('import').disabled = false; } }); ps5Control('restore').addEventListener('click', async () => { const confirmation = await showExactConfirmation(`${ps5NativePackageText('ps5NativePackages.control.confirm', 'Install the exact hash-pinned PPSA99099 upstream control package?')}\n\n${ps5NativePackageText('ps5NativePackages.control.boundary', 'This changes the PS5. Only a fresh post-install console scan may prove installation.')}`, 'INSTALL PS5 UPSTREAM CONTROL PACKAGE'); if (!confirmation) return; ps5Control('restore').disabled = true; ps5Control('status').textContent = ps5NativePackageText('ps5NativePackages.control.starting', 'Starting the guarded upstream control installation…'); try { const result = await window.pork.ps5UpstreamControl('restore', { confirmation }); ps5UpstreamControlOperationId = result.operationId; void monitorPs5ReferenceInstall(result.operationId, { active: () => ps5UpstreamControlOperationId, element: ps5Control, key: 'control', label: 'control package', awaiting: 'Installer completed. Run the independent control post-scan.', dpiAwaiting: 'Standalone DPI accepted the package. Only the independent control post-scan can prove installation.', completed: ps5NativePackageText('ps5NativePackages.control.proven', 'Post-scan proven') }); } catch (error) { ps5Control('status').textContent = error.message; ps5Control('restore').disabled = false; } }); ps5Control('verify').addEventListener('click', async () => { if (!ps5UpstreamControlOperationId) return; ps5Control('verify').disabled = true; ps5Control('status').textContent = ps5NativePackageText('ps5NativePackages.control.scanning', 'Scanning the console for fresh PPSA99099 evidence…'); try { const result = await window.pork.ps5UpstreamControl('restore-verify', { operationId: ps5UpstreamControlOperationId }); ps5Control('status').textContent = result.verified ? ps5NativePackageText('ps5NativePackages.control.proven', 'Post-scan proven') : ps5NativePackageText('ps5NativePackages.control.notObserved', 'The control package was not observed by the fresh console scan.'); ps5Control('trust').textContent = result.verified ? ps5NativePackageText('ps5NativePackages.control.proven', 'Post-scan proven') : ps5NativePackageText('ps5NativePackages.control.unproven', 'Unproven'); ps5Control('trust').dataset.trust = result.verified ? 'post-scan-proven' : 'unproven'; if (!result.verified) ps5Control('verify').disabled = false; } catch (error) { ps5Control('status').textContent = error.message; ps5Control('verify').disabled = false; } }); ps5A8Control('prepare').addEventListener('click', async () => { ps5A8ControlPlan = null; ps5A8Control('prepare').disabled = true; ps5A8Control('status').textContent = ps5NativePackageText('ps5NativePackages.a8Control.preparing', 'Verifying and registering the exact frozen control artifact…'); try { renderPs5A8Control(await window.pork.ps5A8DistinctControl('prepare')); await refreshPs5NativePackageWorkspace(); } catch (error) { ps5A8Control('status').textContent = error.message; ps5A8Control('prepare').disabled = false; } }); ps5A8Control('preflight').addEventListener('click', async () => { ps5A8ControlPlan = null; ps5A8Control('preflight').disabled = true; ps5A8Control('install').disabled = true; ps5A8Control('status').textContent = ps5NativePackageText('ps5NativePackages.a8Control.preflighting', 'Running a complete fresh console scan to prove CHIM00002 is absent…'); try { ps5A8ControlPlan = await window.pork.ps5A8DistinctControl('install-plan'); ps5A8Control('status').textContent = ps5NativePackageText('ps5NativePackages.a8Control.preflightReady', 'Target absence proven. The single-use install approval expires in two minutes.'); ps5A8Control('install').disabled = false; } catch (error) { ps5A8Control('status').textContent = error.message; ps5A8Control('preflight').disabled = false; } }); ps5A8Control('install').addEventListener('click', async () => { if (!ps5A8ControlPlan) return; const plan = ps5A8ControlPlan; const confirmation = await showExactConfirmation(`${ps5NativePackageText('ps5NativePackages.a8Control.confirm', 'Install the exact frozen metadata-qualified CHIM00002 control after the target-absent scan?')}\n\n${plan.artifactSha256}\n\n${ps5NativePackageText('ps5NativePackages.a8Control.boundary', 'This changes the PS5. Installer completion is not installation proof; a fresh post-scan and separate launch qualification remain mandatory.')}`, plan.confirmation); if (!confirmation) return; ps5A8Control('install').disabled = true; ps5A8Control('status').textContent = ps5NativePackageText('ps5NativePackages.a8Control.installing', 'Starting the exact single-use CHIM00002 installation…'); try { const result = await window.pork.ps5A8DistinctControl('install', { planDigest: plan.planDigest, confirmation }); ps5A8ControlPlan = null; ps5A8ControlOperationId = result.operationId; void monitorPs5ReferenceInstall(result.operationId, { active: () => ps5A8ControlOperationId, element: ps5A8Control, key: 'a8Control', label: 'CHIM00002 control', awaiting: 'Installer completed. Run the independent CHIM00002 post-scan.', dpiAwaiting: 'Standalone DPI accepted CHIM00002. Only the independent post-scan can prove installation.' }); } catch (error) { ps5A8ControlPlan = null; ps5A8Control('status').textContent = error.message; ps5A8Control('preflight').disabled = false; } }); ps5A8Control('verify').addEventListener('click', async () => { if (!ps5A8ControlOperationId) return; ps5A8Control('verify').disabled = true; ps5A8Control('status').textContent = ps5NativePackageText('ps5NativePackages.a8Control.scanning', 'Scanning the console for fresh CHIM00002 installation evidence…'); try { const result = await window.pork.ps5NativePackageRestoreVerify({ titleId: 'CHIM00002', operationId: ps5A8ControlOperationId }); if (result.operation) renderPs5A8Control({ status: 'ready' }, result.operation); if (!result.verified) { ps5A8Control('status').textContent = ps5NativePackageText('ps5NativePackages.a8Control.notObserved', 'CHIM00002 was not observed by the fresh console scan.'); ps5A8Control('verify').disabled = false; } } catch (error) { ps5A8Control('status').textContent = error.message; ps5A8Control('verify').disabled = false; } }); ps5A8Control('launch').addEventListener('click', async () => { if (!ps5A8ControlOperationId) return; try { const plan = await window.pork.ps5NativePackageRestoreLaunchPlan(ps5A8ControlOperationId); const confirmation = await showExactConfirmation(`${ps5NativePackageText('ps5NativePackages.a8Control.launchConfirm', 'Launch exact title CHIM00002 and observe its process?')}\n\n${plan.packageIdentitySha256}\n\n${ps5NativePackageText('ps5NativePackages.a8Control.launchBoundary', 'The launch request alone is not success. Chimera requires a stable exact-title process and retains terminal failure evidence.')}`, plan.confirmation); if (!confirmation) return; ps5A8Control('launch').disabled = true; ps5A8Control('status').textContent = ps5NativePackageText('ps5NativePackages.a8Control.launching', 'Requesting CHIM00002 and observing its exact process…'); const operation = await window.pork.ps5NativePackageRestoreLaunch({ operationId: ps5A8ControlOperationId, confirmation }); renderPs5A8Control({ status: 'ready' }, operation); void monitorPs5A8ControlLaunch(operation.id); } catch (error) { ps5A8Control('status').textContent = error.message; } }); $('ps5-a10-control-card').addEventListener('click', async event => { const button = event.target.closest('button[id^="ps5-a10-control-"]'); if (!button || button.disabled) return; const action = button.id.slice('ps5-a10-control-'.length); const status = ps5A10Control('status'); button.disabled = true; try { if (action === 'prepare') { ps5A10ControlPlan = null; status.textContent = ps5A10Text('preparing'); await window.pork.ps5A10TransitionControl('prepare'); } else if (action === 'preflight') { ps5A10ControlPlan = null; status.textContent = ps5A10Text('preflighting'); ps5A10ControlPlan = await window.pork.ps5A10TransitionControl('install-plan'); status.textContent = ps5A10Text('preflightReady'); ps5A10Control('install').disabled = false; return; } else if (action === 'install') { const plan = ps5A10ControlPlan; if (!plan) return; const confirmation = await confirmPs5A10('confirm', 'boundary', plan.confirmation, plan.artifactSha256); if (!confirmation) { button.disabled = false; return; } status.textContent = ps5A10Text('installing'); const result = await window.pork.ps5A10TransitionControl('install', { planDigest: plan.planDigest, confirmation }); ps5A10ControlPlan = null; ps5A10ControlOperationId = result.operationId; void monitorPs5ReferenceInstall(result.operationId, { active: () => ps5A10ControlOperationId, element: ps5A10Control, key: 'a10Control', label: 'CHIM00003', awaiting: ps5A10Text('awaitingScan'), dpiAwaiting: ps5A10Text('dpiAwaitingScan') }); return; } else if (action === 'verify') { status.textContent = ps5A10Text('scanning'); const result = await window.pork.ps5NativePackageRestoreVerify({ titleId: 'CHIM00003', operationId: ps5A10ControlOperationId }); if (!result.verified) { status.textContent = ps5A10Text('notObserved'); button.disabled = false; return; } } else if (action === 'stage') { const plan = await window.pork.ps5P15A10('runtime-plan'); const confirmation = await confirmPs5A10('stageConfirm', 'stageBoundary', plan.confirmation, plan.diagnosticEngineSha256); if (!confirmation) { status.textContent = ps5A10Text('planExpires'); return; } await window.pork.ps5P15A10('runtime-stage', { planDigest: plan.planDigest, confirmation }); } else if (action === 'payload') { const confirmation = await confirmPs5A10('payloadConfirm', 'payloadBoundary', 'START PS5 A10 PROFILE PAYLOAD'); if (!confirmation) { button.disabled = false; return; } await window.pork.ps5P15A10('payload-start', { confirmation }); } else if (action === 'launch') { const plan = await window.pork.ps5NativePackageRestoreLaunchPlan(ps5A10ControlOperationId); const confirmation = await confirmPs5A10('launchConfirm', 'launchBoundary', plan.confirmation, plan.packageIdentitySha256); if (!confirmation) { button.disabled = false; return; } status.textContent = ps5A10Text('launching'); const operation = await window.pork.ps5NativePackageRestoreLaunch({ operationId: ps5A10ControlOperationId, confirmation }); void monitorPs5A10ControlLaunch(operation.id); return; } else if (action === 'restore') { const confirmation = await confirmPs5A10('restoreConfirm', 'restoreBoundary', 'RESTORE PS5 PRODUCTION PROFILE RUNTIME'); if (!confirmation) { button.disabled = false; return; } await window.pork.ps5P15A10('runtime-restore', { confirmation }); } else if (action === 'evidence') { const e = await window.pork.ps5NativePackageRetainedLaunchEvidence(ps5A10ControlOperationId), a = e.analysis || {}, s = $('ps5-a10-control-evidence-summary'), b = a.firstUnprovenBoundary === 'after-accepted-shellui-lnc-before-stable-exact-title-process' ? ps5A10Text('evidencePostLnc') : a.stableExactTitleProcess ? ps5A10Text('evidenceVerified') : ps5A10Text('evidenceInconclusive'); s.hidden = false; s.dataset.operationId = String(e.operation.id); s.textContent = `${b} · ${ps5A10Text('evidenceExactProcesses')}: ${Number(a.exactTitleProcessObservationCount || 0)} · ${e.retention?.rawSourceComplete ? ps5A10Text('evidenceCaptureComplete') : ps5A10Text('evidenceCapturePartial')} · SHA-256 ${String(e.integrity?.retainedLaunchEvidenceSha256 || '').slice(0, 12)}… · ${ps5A10Text('evidenceDownloaded')}`; (window.pork.download || window.xavatarAPI?.download)(new TextEncoder().encode(JSON.stringify(e, null, 2)), `chimera-ps5-launch-evidence-${e.operation.titleId}-${e.operation.id}.json`); button.disabled = false; return; } await refreshPs5NativePackageWorkspace(); } catch (error) { status.textContent = error.message; button.disabled = false; } }); $('ps5-p15-r2-preflight').addEventListener('click', async () => { const button = $('ps5-p15-r2-preflight'); button.disabled = true; $('ps5-p15-r2-state').textContent = ps5NativePackageText('ps5NativePackages.r4.collecting', 'Collecting fresh read-only hardware evidence…'); try { ps5P15R2LastPreflight = await window.pork.ps5P15R2Preflight(); renderPs5P15R2Qualification(await window.pork.ps5P15R2Status(), ps5P15R2LastPreflight); } catch (error) { $('ps5-p15-r2-state').textContent = error.message; } finally { button.disabled = false; } }); $('ps5-p15-r2-qualify').addEventListener('click', async () => { if (ps5P15R2LastPreflight?.allGatesPass !== true || ps5P15R2QualificationAttempted) return; ps5P15R2QualificationAttempted = true; $('ps5-p15-r2-qualify').disabled = true; $('ps5-p15-r2-state').textContent = ps5NativePackageText('ps5NativePackages.r4.qualifying', 'Recollecting every gate for the single qualification…'); try { const status = await window.pork.ps5P15R2Qualify(); const before = ps5P15R2LastPreflight.matrix.map(row => `${row.gate}:${row.result}`).join('|'); const after = (status.lastQualification?.matrix || []).map(row => `${row.gate}:${row.result}`).join('|'); if (!status.profile || status.readiness?.launchEligible !== true || before !== after) throw new Error(ps5NativePackageText('ps5NativePackages.r4.drift', 'Qualification failed closed because independently recollected evidence drifted.')); renderPs5P15R2Qualification(status, status.lastQualification); } catch (error) { $('ps5-p15-r2-state').textContent = error.message; } }); window.addEventListener('chimera:language-changed', () => { const e = $('ps5-a10-control-evidence-summary'); e.hidden = true; e.textContent = ''; }); ps5LaunchReference('import').addEventListener('click', async () => { ps5LaunchReference('import').disabled = true; ps5LaunchReference('status').textContent = ps5NativePackageText('ps5NativePackages.launchReference.importing', 'Downloading and verifying the pinned websrv launcher…'); try { renderPs5LaunchReference(await window.pork.ps5LaunchReference('import')); } catch (error) { ps5LaunchReference('status').textContent = error.message; ps5LaunchReference('import').disabled = false; } }); ps5LaunchReference('restore').addEventListener('click', async () => { const confirmation = await showExactConfirmation(`${ps5NativePackageText('ps5NativePackages.launchReference.confirm', 'Install the exact hash-pinned FAKE00000 websrv launch reference?')}\n\n${ps5NativePackageText('ps5NativePackages.launchReference.boundary', 'This changes the PS5. A post-scan proves installation only; you must launch it manually to prove execution.')}`, 'INSTALL PS5 LAUNCH REFERENCE'); if (!confirmation) return; ps5LaunchReference('restore').disabled = true; ps5LaunchReference('status').textContent = ps5NativePackageText('ps5NativePackages.launchReference.starting', 'Starting the guarded launch-reference installation…'); try { const result = await window.pork.ps5LaunchReference('restore', { confirmation }); ps5LaunchReferenceOperationId = result.operationId; void monitorPs5ReferenceInstall(result.operationId, { active: () => ps5LaunchReferenceOperationId, element: ps5LaunchReference, key: 'launchReference', label: 'reference', awaiting: 'Installer completed. Run the post-scan.' }); } catch (error) { ps5LaunchReference('status').textContent = error.message; ps5LaunchReference('restore').disabled = false; } }); ps5LaunchReference('verify').addEventListener('click', async () => { if (!ps5LaunchReferenceOperationId) return; ps5LaunchReference('verify').disabled = true; ps5LaunchReference('status').textContent = ps5NativePackageText('ps5NativePackages.launchReference.scanning', 'Scanning the console for fresh FAKE00000 evidence…'); try { const result = await window.pork.ps5LaunchReference('restore-verify', { operationId: ps5LaunchReferenceOperationId }); ps5LaunchReference('status').textContent = result.verified ? ps5NativePackageText('ps5NativePackages.launchReference.manualLaunch', 'Installation proven. Launch Homebrew Launcher manually on the PS5; launch is not yet proven.') : ps5NativePackageText('ps5NativePackages.launchReference.notObserved', 'The launch reference was not observed by the fresh console scan.'); ps5LaunchReference('trust').textContent = result.verified ? ps5NativePackageText('ps5NativePackages.launchReference.launchRequired', 'Installed · launch required') : ps5NativePackageText('ps5NativePackages.launchReference.unproven', 'Unproven'); ps5LaunchReference('trust').dataset.trust = result.verified ? 'post-scan-proven' : 'unproven'; if (!result.verified) ps5LaunchReference('verify').disabled = false; } catch (error) { ps5LaunchReference('status').textContent = error.message; ps5LaunchReference('verify').disabled = false; } }); $('ps5-staging-discover').addEventListener('click', async () => { const game = state.currentGame; $('ps5-staging-discover').disabled = true; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.stage.discovering', 'Discovering exact title-bound console mounts…')); try { ps5ConsoleDiscovery = await window.pork.ps5ConsoleStagingDiscover(game.game_id); $('ps5-staging-components').innerHTML = ps5ConsoleDiscovery.components.length ? ps5ConsoleDiscovery.components.map(item => ``).join('') : `${escHtml(ps5NativePackageText('ps5NativePackages.stage.none', 'No exact app0 or patch0 mount was found for this title.'))}
`; $('ps5-staging-run').disabled = !ps5ConsoleDiscovery.components.length; setPs5NativePackageReadiness(ps5ConsoleDiscovery.components.length ? ps5NativePackageText('ps5NativePackages.stage.discovered', 'Console components found. Review the selection before staging.') : ps5NativePackageText('ps5NativePackages.stage.none', 'No exact app0 or patch0 mount was found for this title.'), ps5ConsoleDiscovery.components.length ? 'ready' : 'blocked'); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } finally { $('ps5-staging-discover').disabled = false; } }); $('ps5-staging-run').addEventListener('click', async () => { if (!ps5ConsoleDiscovery) return; const componentIds = [...document.querySelectorAll('[data-ps5-stage-component]:checked')].map(item => item.dataset.ps5StageComponent); $('ps5-staging-run').disabled = true; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.stage.running', 'Inventorying, transferring and hashing every selected file…')); try { const created = await window.pork.ps5ConsoleStagingCreate({ discoveryId: ps5ConsoleDiscovery.id, componentIds }); const completed = await window.pork.ps5ConsoleStagingRun(created.id); ps5PreparedSources = ps5SourcesFromStagedJob(completed); const base = ps5PreparedSources?.find(item => item.componentType === 'base'); if (base) $('ps5-native-package-source').value = base.sourcePath; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.stage.completed', 'Console copy staged atomically with complete per-file hash evidence.'), 'ready'); await refreshPs5NativePackageWorkspace(); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); $('ps5-staging-run').disabled = false; } }); $('ps5-native-package-create').addEventListener('click', async () => { const game = state.currentGame; $('ps5-native-package-create').disabled = true; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.create.running', 'Verifying decrypted modules and building a native FIH package…')); try { const input = ps5PreparedSources?.length ? { sources: ps5PreparedSources, expectedTitleId: game.game_id } : { sourcePath: $('ps5-native-package-source').value, expectedTitleId: game.game_id }; const job = await window.pork.ps5NativePackageCreate(input); await window.pork.ps5NativePackageRun(job.id); void monitorPs5NativePackageBuild(job.id); await refreshPs5NativePackageWorkspace(); setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.create.queued', 'Build accepted. It continues on the server if this browser session closes.'), 'running'); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } finally { $('ps5-native-package-create').disabled = !$('ps5-native-package-source').value; } }); $('ps5-native-package-restore').addEventListener('click', async () => { const game = state.currentGame; const packageSetId = Number($('ps5-native-package-set').value); if (!packageSetId) return; try { const plan = await window.pork.ps5NativePackageRestorePlan({ titleId: game.id, packageSetId }); const detail = plan.orderedComponents.map(item => `${item.type.toUpperCase()} · ${item.version || '—'} · ${item.sha256.slice(0, 12)}…`).join('\n'); const confirmation = await showExactConfirmation(`${ps5NativePackageText('ps5NativePackages.restore.confirm', 'Install this verified PS5 debug .pkg set?')}\n\n${detail}\n\n${ps5NativePackageText('ps5NativePackages.restore.boundary', 'PS5Upload will install each .pkg in order. Completion still requires an independent console scan.')}`, 'INSTALL PS5 DEBUG PACKAGE'); if (!confirmation) return; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.restore.running', 'Starting the hash-verified PS5 .pkg installation…'), 'running'); const result = await window.pork.ps5NativePackageRestore({ titleId: game.id, packageSetId, confirmation }); ps5NativeInstallOperationId = result.operationId; $('ps5-native-package-cancel').disabled = true; void monitorPs5NativePackageInstall(result.operationId); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } }); $('ps5-native-package-cancel').addEventListener('click', async () => { if (!ps5NativeInstallOperationId) return; try { const confirmation = await showExactConfirmation(ps5NativePackageText('ps5NativePackages.restore.cancelConfirm', 'Cancel the active PS5 .pkg installation? The server copy remains intact.'), 'CANCEL PS5 PACKAGE INSTALL'); if (!confirmation) return; await window.pork.ps5NativePackageRestoreCancel(ps5NativeInstallOperationId, confirmation); ps5NativeInstallOperationId = null; $('ps5-native-package-cancel').disabled = true; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.restore.cancelled', 'PS5 .pkg installation cancelled. Source packages were kept.'), 'blocked'); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } }); $('ps5-native-package-verify').addEventListener('click', async () => { const game = state.currentGame; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.verify.running', 'Scanning the console for independent installation evidence…')); try { const result = await window.pork.ps5NativePackageRestoreVerify({ titleId: game.game_id, operationId: ps5NativeInstallOperationId }); if (result.operation) renderPs5RestoreQualification(result.operation); setPs5NativePackageReadiness(result.verified ? ps5NativePackageText('ps5NativePackages.verify.completedPendingLaunch', 'Installation proven. Launch verification is still required.') : ps5NativePackageText('ps5NativePackages.verify.notObserved', 'The console scan has not observed this installation.'), result.verified ? 'ready' : 'blocked'); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } }); async function monitorPs5NativePackageLaunch(operationId) { while (ps5NativeInstallOperationId === operationId) { await new Promise(resolve => setTimeout(resolve, 2000)); let operation; try { operation = await window.pork.ps5NativePackageRestoreLaunchStatus(operationId); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); return; } renderPs5RestoreQualification(operation); if (operation.status === 'launch_verified') { setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.launch.qualified', 'Installed · Launch verified · Qualified for restore'), 'ready'); return; } if (operation.status === 'launch_failed') { setPs5NativePackageReadiness(`${ps5NativePackageText('ps5NativePackages.launch.failedBoundary', 'Installed — launch failed. Not qualified for restore.')} · ${operation.error?.code || ''}`, 'blocked'); return; } if (operation.status !== 'launching') { setPs5NativePackageReadiness(operation.error?.message || operation.status, 'blocked'); return; } } } $('ps5-native-package-launch').addEventListener('click', async () => { if (!ps5NativeInstallOperationId) return; try { const plan = await window.pork.ps5NativePackageRestoreLaunchPlan(ps5NativeInstallOperationId); const confirmation = await showExactConfirmation(`${ps5NativePackageText('ps5NativePackages.launch.confirm', 'Launch this exact installed title and observe its process?')}\n\n${plan.titleId}\n${plan.packageIdentitySha256}\n\n${ps5NativePackageText('ps5NativePackages.launch.boundary', 'A launch request alone is not success. Chimera requires a stable exact-title process and stores failure evidence.')}`, plan.confirmation); if (!confirmation) return; $('ps5-native-package-launch').disabled = true; setPs5NativePackageReadiness(ps5NativePackageText('ps5NativePackages.launch.starting', 'Requesting the exact title launch; qualification is still pending…'), 'running'); const operation = await window.pork.ps5NativePackageRestoreLaunch({ operationId: ps5NativeInstallOperationId, confirmation }); renderPs5RestoreQualification(operation); void monitorPs5NativePackageLaunch(operation.id); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } }); $('ps5-native-package-jobs').addEventListener('click', async event => { const packageButton = event.target.closest('[data-ps5-native-resume]'); const stageButton = event.target.closest('[data-ps5-stage-resume]'); if (!packageButton && !stageButton) return; try { if (stageButton) { await window.pork.ps5ConsoleStagingResume(stageButton.dataset.ps5StageResume); await window.pork.ps5ConsoleStagingRun(stageButton.dataset.ps5StageResume); } else { await window.pork.ps5NativePackageResume(packageButton.dataset.ps5NativeResume); await window.pork.ps5NativePackageRun(packageButton.dataset.ps5NativeResume); void monitorPs5NativePackageBuild(packageButton.dataset.ps5NativeResume); } await refreshPs5NativePackageWorkspace(); } catch (error) { setPs5NativePackageReadiness(error.message, 'blocked'); } }); $('btn-modal-backup').addEventListener('click', async () => { if (!state.currentGame || !state.connected) return; const { ftp_path, game_id } = state.currentGame; if (!ftp_path) { setStatus('No FTP path recorded for this game — run a scan first', 'error'); return; } try { await window.pork.ftpDownloadFolder(game_id); setStatus('Backup queued; follow its progress in Transfers.', 'ok'); closeModal(); } catch (e) { setStatus(e.message, 'error'); } }); $('btn-modal-install').addEventListener('click', async () => { if (!state.currentGame || !state.connected) return; const g = state.currentGame; const ftpPath = await ensureGameFtpPath(g.game_id); if (!ftpPath) return; // user cancelled path selection let backups; try { backups = await window.pork.listBackups(g.game_id); } catch { backups = []; } if (!backups.length) { setStatus('No local backups found — add one via the Manage tab', 'error'); return; } const latest = backups.sort((a, b) => (b.id || 0) - (a.id || 0))[0]; try { await window.pork.ftpUpload({ localPath: latest.backup_path, remotePath: ftpPath }); setStatus('Install queued — see Transfers for progress', 'ok'); closeModal(); } catch (e) { setStatus(`Install failed: ${e.message}`, 'error'); } }); $('btn-modal-refresh').addEventListener('click', async () => { if (!state.currentGame) return; const { game_id } = state.currentGame; $('btn-modal-refresh').disabled = true; setStatus('Refreshing metadata…'); try { await window.pork.prosperoFetch(game_id); setStatus('Metadata refreshed', 'ok'); } catch (e) { setStatus(`Refresh failed: ${e.message}`, 'error'); } finally { $('btn-modal-refresh').disabled = false; } }); function closeModal() { if (typeof closeAccessibleDialog === 'function') closeAccessibleDialog($('modal-overlay')); else $('modal-overlay').hidden = true; state.currentGame = null; $('modal-banner').style.backgroundImage = ''; const icon = $('modal-icon'); icon.src = ''; icon.hidden = true; $('modal-description').hidden = true; $('modal-description').textContent = ''; $('modal-hash-section').hidden = true; $('modal-hash-progress').hidden = true; $('hash-progress-fill').style.width = '0%'; uploadLocalPath = null; uploadInProgress = false; porkInProgress = false; $('manage-remote-path').value = ''; $('manage-remote-path').readOnly = true; $('btn-manage-upload').disabled = true; $('manage-upload-log').hidden = true; $('mtab-manage-count').textContent = ''; $('manage-backpork-section').hidden = true; $('manage-pork-progress').hidden = true; $('pork-progress-fill').style.width = '0%'; } $('modal-close').addEventListener('click', closeModal); $('btn-modal-close2').addEventListener('click', closeModal); $('modal-overlay').addEventListener('click', e => { if (e.target === $('modal-overlay')) closeModal(); }); $('modal-workspace-links')?.addEventListener('click', event => { const button = event.target.closest('[data-game-route][data-game-target]'); const gameId = state.currentGame?.game_id; if (!button || !gameId || !setGameWorkspaceContext(gameId, button.dataset.gameTarget)) return; closeModal(); window.chimeraNavigate?.(button.dataset.gameRoute); }); document.addEventListener('keydown', e => { if (e.key === 'Escape' && $('modal-overlay') && !$('modal-overlay').hidden) closeModal(); });