'use strict'; let _ecosystemBuilt = false; let _ecosystemLoaded = false; let _ecosystemActiveTab = 'overview'; let _ecosystemEtaHash = null; let _ecosystemEtaProposed = null; let _ecosystemCheatTimer = null; let _ecosystemCheatOffset = 0; let _ecosystemCheatRequest = 0; const ECOSYSTEM_CHEAT_PAGE_SIZE = 50; let _timelineFilterTimer = null; let _ecosystemSaveCapabilities = null; let _gameUpdatePlan = null; let _gameUpdateRefreshTimer = null; let _websrvFsPath = '/'; let _websrvSmbPath = ''; let _pacbrewPacket = null; let _itemzflowPacket = null; let _remotePlayPacket = null; let _remotePlayPollTimer = null; let _ecosystemComponentsPacket = null; function ecosystemDate(value) { if (!value) return 'Not yet'; try { return new Date(value).toLocaleString(window.i18n?.current || 'en'); } catch (_) { return 'Unknown'; } } function ecosystemBytes(value) { const bytes = Number(value || 0); if (!bytes) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let amount = bytes; let unit = 0; while (amount >= 1024 && unit < units.length - 1) { amount /= 1024; unit += 1; } return `${amount >= 10 ? amount.toFixed(0) : amount.toFixed(1)} ${units[unit]}`; } function ecosystemStatus(status, label = null) { const normalized = ['compatible', 'online', 'ok'].includes(status) ? 'compatible' : ['incompatible', 'offline', 'error'].includes(status) ? 'warning' : status || 'unknown'; const text = label || ({ compatible: 'Compatible', warning: 'Warning', unknown: 'Unknown' }[normalized] || status || 'Unknown'); return `${escHtml(text)}`; } function buildEcosystemPage() { if (_ecosystemBuilt) return; const page = $('page-ecosystem'); page.innerHTML = `
${[ ['overview', 'Command Center'], ['components', 'Components'], ['updates', 'Payload updates'], ['game-updates', 'Game updates'], ['etahen', 'Console profile'], ['live', 'Live Session'], ['remote-play', 'Remote Play'], ['files', 'Files'], ['homebrew', 'Homebrew'], ['sources', 'Sources'], ['plugins', 'Plugins'], ['cheats', 'Cheats & patches'], ['saves', 'Save Vault'], ['itemzflow', 'Itemzflow'], ['platform-lab', window.t('platformLab.tab', 'Platform Lab')], ['research', 'Research Lab'], ['timeline', 'Timeline'], ].map(([id, label], index) => ``).join('')}

Loading overview…

`; bindEcosystemEvents(); window.ChimeraLinkUx?.mount({ root: $('etahen-link-root'), loadStatus: () => window.pork.etahenLinkInventoryStatus(), translate: (key, fallback) => window.t?.(key, fallback) || fallback }); _ecosystemBuilt = true; } async function ecosystemBusy(button, task) { const prior = button?.textContent; if (button) { button.disabled = true; button.textContent = window.t('common.loading', 'Loading…'); } try { return await task(); } finally { if (button) { button.disabled = false; button.textContent = prior; } } } function localizedReadinessStep(step) { if (window.i18n?.current === 'nl') return step; const english = { profile: { label: 'Console profile', detail: step.state === 'ready' ? step.detail : 'IP address not configured' }, transport: { label: 'File transport', detail: step.state === 'ready' ? 'FTP transport reachable' : 'etaHEN FTP or ftpsrv is required for files' }, elfldr: { label: 'ELF Loader', detail: 'Foundation for Payload Manager and multiple homebrew services' }, 'payload-manager': { label: 'Payload Manager', detail: 'Catalog, imports and explicit payload launch' }, websrv: { label: 'websrv', detail: 'Homebrew, filesystem and additional network services' }, diagnostics: { label: 'Diagnostics', detail: 'Klog is optional and only needed during investigation' }, }; return { ...step, ...(english[step.id] || {}) }; } function localizedEcosystemAlert(alert, field) { const fallback = alert?.[field] || ''; const count = String(alert?.title || '').match(/\d+(?:\/\d+)?/)?.[0] || ''; return window.tf?.(`navigation.alerts.${alert?.id}.${field}`, fallback, { count }) || fallback; } function localizedEcosystemError(value) { const parts = [...new Set(String(value || '').split(/\s+·\s+/).map(part => part.trim()).filter(Boolean))]; const message = parts.join(' · '); if (window.i18n?.current === 'nl') return message; return message .replace(/Configureer eerst een geldig PS5-hostadres(?: in Instellingen)?\.?/gi, 'Configure a valid PS5 host in Settings first.') // i18n: normalize persisted server copy .replace(/Maak eerst verbinding met de PS5 via FTP\.?/gi, 'Connect to the PS5 through FTP first.') // i18n: normalize persisted server copy .replace(/Start ([^.]+) op de PS5\.?/gi, 'Start $1 on the PS5.'); } function localizedEcosystemActivity(value) { const message = String(value || ''); if (window.i18n?.current === 'nl') return message; return message .replace('Console Health wacht op configuratie', 'Console Health is waiting for configuration') .replace(/(\d+) aandachtspunt\(en\) gevonden\./, '$1 attention item(s) found.'); // i18n: normalize persisted activity copy. } function ecosystemCapabilityText(key, fallback) { return window.t?.(`ecosystem.capabilities.${key}`, fallback) || fallback; } function renderCapabilityGraph(graph) { if (!graph?.features) return ''; const groups = ['core', 'connected', 'planned']; return `

${ecosystemCapabilityText('title', 'Capability map')}

${ecosystemCapabilityText('description', 'Live, conditional and planned functionality for the active console profile.')}

${ecosystemStatus(graph.summary?.blocked ? 'warning' : 'compatible', `${graph.summary?.available || 0}/${graph.summary?.total || 0} ${ecosystemCapabilityText('available', 'available')}`)}
${[ ['available', graph.summary?.available || 0], ['conditional', graph.summary?.conditional || 0], ['blocked', graph.summary?.blocked || 0], ['bindings', graph.bindings?.length || 0], ].map(([label, count]) => `
${count}${escHtml(ecosystemCapabilityText(`status.${label}`, label))}
`).join('')}
${groups.map(group => `

${escHtml(ecosystemCapabilityText(`group.${group}`, group))}

${graph.features.filter(item => item.group === group).map(item => `
${escHtml(ecosystemCapabilityText(`feature.${item.id}`, item.id))}${escHtml(ecosystemCapabilityText(`reason.${item.reasonCode}`, item.reasonCode))} · ${escHtml(ecosystemCapabilityText(`mode.${item.mode}`, item.mode))}
${ecosystemStatus(item.status === 'available' ? 'compatible' : item.status === 'conditional' ? 'warning' : item.status === 'blocked' ? 'offline' : 'unknown', ecosystemCapabilityText(`status.${item.status}`, item.status))}
`).join('')}
`).join('')}

${graph.freshness?.stale ? ecosystemCapabilityText('stale', 'Upstream evidence is stale; refresh components before relying on version-sensitive actions.') : ecosystemCapabilityText('fresh', 'Upstream evidence is current.')}

`; } function renderEcosystemOverview(data, healthPacket = {}, activity = [], healthHistory = [], capabilityGraph = null) { const services = data.services?.services || []; const online = services.filter(service => service.reachable).length; const report = healthPacket.report || null; const healthAge = report ? Math.max(0, Date.now() - Number(report.generatedAt || 0)) : null; const healthStale = healthAge !== null && healthAge > 5 * 60 * 1000; const alerts = report?.alerts || []; const openAlerts = alerts.filter(alert => !alert.acknowledged); const readiness = data.readiness || { state: 'not-configured', steps: [] }; const readinessSteps = (readiness.steps || []).map(localizedReadinessStep); const isDutch = window.i18n?.current === 'nl'; const readinessLabels = new Map(readinessSteps.map(step => [step.id, step.label])); $('ecosystem-overview').innerHTML = `

Console Readiness

Dependencies and safe next steps. Chimera starts nothing automatically.

${ecosystemStatus(readiness.state === 'ready' ? 'compatible' : readiness.state === 'attention' ? 'warning' : 'unknown', readiness.state === 'ready' ? 'Ready' : readiness.state === 'attention' ? 'Action needed' : 'Not configured')}
${[ [isDutch ? 'Kernverbinding' : 'Core connection', readinessSteps.filter(step => step.required)], [isDutch ? 'Optionele diensten' : 'Optional services', readinessSteps.filter(step => !step.required)], ].map(([label, steps]) => `

${label}${steps.filter(step => step.state === 'ready').length}/${steps.length}

${steps.map((step, index) => ``).join('')}
`).join('')}
${renderCapabilityGraph(capabilityGraph)}
${[ ['Console Health', report ? (report.score == null ? 'Not configured' : `${report.score}/100`) : 'Not yet'], ['Services online', `${online}/${services.length}`], ['Managed assets', (data.homebrew?.count || 0) + (data.plugins?.count || 0) + (data.cheats?.count || 0)], ['Protected data', `${data.saveVault?.snapshots || 0} snapshots`], ].map(([label, value]) => `
${escHtml(value)}${escHtml(label)}
`).join('')}

${isDutch ? 'PS5-diensten' : 'PS5 services'}

${data.services?.error ? `

${escHtml(localizedEcosystemError(data.services.error))}

` : services.map(service => `
${escHtml(service.label)}${isDutch ? 'Poort' : 'Port'} ${service.port}${service.error ? ` · ${escHtml(service.error)}` : ''}
${ecosystemStatus(service.reachable ? 'online' : 'offline', service.reachable ? 'Online' : 'Offline')}
`).join('')}

Safety policy

No automatic executionUpdates are never started immediately on the PS5.

Source and checksumOfficial upstream and published digests remain visible.

Expert isolationShell and GDB are not proxied through the public Chimera interface.

${isDutch ? 'Actiecentrum' : 'Action Center'}

${report ? `${isDutch ? 'Scan van' : 'Scan from'} ${ecosystemDate(report.generatedAt)}${healthStale ? ` · ${isDutch ? 'verouderd' : 'stale'}` : ''} · ${openAlerts.length} open` : (isDutch ? 'Voer een diepe healthscan uit voor concrete acties.' : 'Run a deep health scan for concrete actions.')}

${alerts.length ? alerts.map(alert => `
${escHtml(localizedEcosystemAlert(alert, 'title'))}${escHtml(localizedEcosystemAlert(alert, 'detail'))}
${ecosystemStatus(alert.severity === 'critical' ? 'error' : alert.severity === 'warning' ? 'warning' : 'unknown', alert.severity)}${alert.action ? `` : ''}${alert.acknowledged ? 'Read' : ''}
`).join('') : '
✓ No known issues.
'}

Health trend & activity

${healthHistory.some(item => item.score != null) ? `
${healthHistory.filter(item => item.score != null).slice(0, 20).reverse().map(item => `
${escHtml(item.score)}
`).join('')}
` : '

The trend appears after multiple configured health scans.

'}
${activity.length ? activity.slice(0, 12).map(item => `
${escHtml(localizedEcosystemActivity(item.title))}${escHtml(localizedEcosystemActivity(item.detail || item.type))}
`).join('') : '

No management activity recorded yet.

'}
`; const health = $('ecosystem-health'); health.textContent = report ? (report.score == null ? 'Health · not configured' : `Health ${report.score}/100 · ${report.grade}${healthStale ? ' · stale' : ''}`) : (services.length ? `${online}/${services.length} PS5 services online` : (data.services?.error || 'PS5 not configured')); health.className = `operations-health ${report?.score == null ? '' : !healthStale && (report?.score >= 75 || (!report && online)) ? 'healthy' : 'warning'}`; const badge = $('nav-ecosystem-badge'); const issues = openAlerts.filter(alert => ['critical', 'warning'].includes(alert.severity)).length || Number(data.plugins?.conflicts || 0); if (badge) { badge.hidden = issues === 0; badge.textContent = issues || ''; } } async function loadEcosystemOverview() { const [data, healthPacket, activity, healthHistory, capabilityGraph] = await Promise.all([window.pork.ecosystemOverview(), window.pork.ecosystemHealthLatest(), window.pork.ecosystemActivity(50), window.pork.ecosystemHealthHistory(30), window.pork.ecosystemCapabilityGraph()]); renderEcosystemOverview(data, healthPacket, activity, healthHistory, capabilityGraph); _ecosystemLoaded = true; } function ecosystemComponentGroup(component) { if (['experimental-os', 'expert', 'kernel'].includes(component.kind) || component.risk === 'expert') return 'Experimental & expert'; if (['catalog', 'plugin', 'plugin-catalog', 'database'].includes(component.kind)) return 'Catalogs, plugins & data'; if (['homebrew', 'client-companion', 'download-tool', 'remote-play-helper', 'account-tool', 'diagnostic'].includes(component.kind)) return 'Applications & tools'; return 'Core services'; } function renderEcosystemComponents() { const result = _ecosystemComponentsPacket; if (!result) return; const query = String($('ecosystem-components-search')?.value || '').trim().toLowerCase(); const platform = $('ecosystem-components-platform')?.value || 'all'; const risk = $('ecosystem-components-risk')?.value || 'all'; const filtered = result.components.filter(component => { const haystack = `${component.name} ${component.kind} ${component.platform} ${(component.dependencies || []).join(' ')}`.toLowerCase(); return (!query || haystack.includes(query)) && (platform === 'all' || component.platform === platform) && (risk === 'all' || component.risk === risk); }); const groups = new Map(); for (const component of filtered) { const group = ecosystemComponentGroup(component); if (!groups.has(group)) groups.set(group, []); groups.get(group).push(component); } const counts = { visible: filtered.length, total: result.components.length, compatible: filtered.filter(component => component.compatibility?.status === 'compatible').length, attention: filtered.filter(component => component.compatibility?.status === 'incompatible' || component.risk === 'expert').length, }; counts.sourceDependent = Math.max(0, counts.visible - counts.compatible - counts.attention); $('ecosystem-components').innerHTML = `
${counts.visible}/${counts.total}Visible components
${counts.compatible}Compatible
${counts.attention}Needs attention
${counts.sourceDependent}Source-dependent
${ecosystemDate(result.checkedAt)}Last upstream check
${groups.size ? [...groups.entries()].map(([label, components]) => `

${escHtml(label)}

${components.length} component${components.length === 1 ? '' : 's'}
${components.map(component => `
${escHtml(component.name)}${escHtml(component.kind)} · ${escHtml(component.platform)} · ${escHtml(component.trust)}
${escHtml(component.release?.tag || 'Unknown version')}${ecosystemStatus(component.compatibility?.status, component.compatibility?.status === 'incompatible' ? 'Incompatible' : component.compatibility?.status === 'compatible' ? 'Compatible' : 'Source-dependent')}

${escHtml(component.compatibility?.reason || '')}${component.dependencies?.length ? ` Depends on: ${component.dependencies.map(escHtml).join(', ')}.` : ''}

`).join('')}
`).join('') : '
No matching componentsClear one or more filters to expand the catalog.
'}`; } async function loadEcosystemComponents() { _ecosystemComponentsPacket = await window.pork.ecosystemComponents(); renderEcosystemComponents(); } function ecosystemUpdateState(item) { const labels = { current: 'Current', ahead: 'Console newer', 'version-different': 'Manual review', 'update-available': 'Update', staged: 'Staged', 'installed-unknown': 'Release unknown', 'not-tracked': 'Not tracked' }; const status = ['current', 'ahead'].includes(item.state) ? 'compatible' : ['update-available', 'version-different'].includes(item.state) ? 'warning' : 'unknown'; return ecosystemStatus(status, labels[item.state] || item.state); } function ecosystemEvidenceSummary(item) { if (!item.installedSource) return ` ${window.t('ecosystem.payloadEvidence.unavailable', 'evidence unavailable')}`; const confidence = item.installedSourceConfidence || 'unknown'; const seenAt = item.installedSourceSeenAt ? ecosystemDate(item.installedSourceSeenAt) : window.t('ecosystem.payloadEvidence.unknownTime', 'unknown time'); return ` ${'\u00B7'} ${escHtml(window.t('ecosystem.payloadEvidence.source', 'source'))}: ${escHtml(item.installedSource)} ${'\u00B7'} ${escHtml(window.t('ecosystem.payloadEvidence.confidence', 'confidence'))}: ${escHtml(confidence)} ${'\u00B7'} ${escHtml(window.t('ecosystem.payloadEvidence.seen', 'seen'))}: ${escHtml(seenAt)}`; } function ecosystemEvidenceConflictSummary(item) { if (!item.installedVersionConflicts?.length) return ''; return ` ${'\u2014'} conflicts ${item.installedVersionConflicts.map(conflict => `${escHtml(conflict.version)} (${escHtml(conflict.sources.join(', '))})`).join(' ; ')}`; } const ecosystemBullet = '\u00B7'; function renderEcosystemUpdates(inventory, plan) { $('ecosystem-update-summary').innerHTML = `
${[ ['Tracked', inventory.stats.total], ['Current', inventory.stats.current], ['Detected', inventory.stats.detected], ['Console newer', inventory.stats.ahead], ['Updates ready', inventory.stats.updates], ['Blocked steps', plan.blocked], ].map(([label, value]) => `
${escHtml(value)}${escHtml(label)}
`).join('')}
Safe dependency order${plan.steps.length ? plan.steps.map(step => step.component.name).join(' → ') : 'No tracked updates yet. Record the installed console version below to enable reliable comparison.'}
`; const groups = [ { id: 'ready', title: 'Ready to prepare', description: 'A newer upstream asset can be downloaded and verified locally.', items: inventory.components.filter(item => item.state === 'update-available') }, { id: 'version', title: 'Version required', description: 'Enter the version currently installed on your console.', items: inventory.components.filter(item => ['installed-unknown', 'not-tracked', 'staged'].includes(item.state)) }, { id: 'review', title: 'Manual review', description: 'Potential downgrade or incomparable version; Chimera will not prepare it automatically.', items: inventory.components.filter(item => ['ahead', 'version-different'].includes(item.state)) }, { id: 'current', title: 'Current', description: 'Installed and upstream versions match.', items: inventory.components.filter(item => item.state === 'current') }, ]; const renderRow = item => { const step = plan.steps.find(entry => entry.component.id === item.id); const conflicts = ecosystemEvidenceConflictSummary(item); const summaryLine = `${escHtml(item.kind)} ${'\u00B7'} console ${escHtml(item.installedVersion || 'unknown')}${ecosystemEvidenceSummary(item)} ${'\u00B7'} upstream ${escHtml(item.latestVersion || 'unknown')} ${'\u00B7'} locally staged ${escHtml(item.stagedVersion || 'no')}`; const details = `${conflicts}${conflicts && (item.dependencies?.length || step?.blockers?.length || item.compatibility?.reason) ? ' \u00B7 ' : ''}` + `${item.dependencies?.length ? `Depends on ${item.dependencies.map(escHtml).join(', ')}` : ''}${conflicts && item.dependencies?.length ? '. ' : ''}` + `${step?.blockers?.length ? `Blocked: ${step.blockers.map(escHtml).join(' ')}` : item.compatibility?.reason || ''}`; const actions = item.assets.length ? `${['ahead', 'version-different'].includes(item.state) ? 'Downgrade blocked' : ''}` : 'Sync to discover assets'; const tracking = window.t(`ecosystem.payloadEvidence.mode.${item.trackingMode}`, item.trackingMode); const automaticButton = item.manualEvidence ? `` : ''; return `
${escHtml(item.name)}${ecosystemUpdateState(item)}
${summaryLine}${escHtml(tracking)}${details ? ` ${ecosystemBullet} ${details}` : ''}
${automaticButton}${actions}
`; }; $('ecosystem-update-plan').innerHTML = groups.map(group => `

${escHtml(group.title)}

${escHtml(group.description)}
${group.items.length}
${group.items.length ? `
${group.items.map(renderRow).join('')}
` : '

No components in this section.

'}
`).join(''); } async function loadEcosystemUpdates() { const [inventory, plan, policy, beaconArtifact] = await Promise.all([ window.pork.ecosystemInventory(), window.pork.ecosystemUpdatePlan(), window.pork.ecosystemSupplyPolicy(), window.pork.chimeraBeaconArtifact(), ]); $('ecosystem-repository-url').value = `${window.location.origin}/payloads.json`; renderEcosystemUpdates(inventory, plan); $('ecosystem-supply-official').checked = policy.officialSourcesOnly; $('ecosystem-supply-https').checked = policy.requireHttps; $('ecosystem-supply-hash').checked = policy.requirePublishedHashForExecution; if (!state.connected) { renderPayloadRuntimeUnavailable('The PS5 target is offline. Local update planning remains available above.'); return; } const timeout = (promise, ms) => Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error('The PS5 runtime check timed out.')), ms)), ]); try { const [watchdog, runtimeUpdates] = await Promise.all([ timeout(window.pork.ecosystemPayloadWatchdog(), 4_000), timeout(window.pork.ecosystemPayloadUpdates(), 4_000), ]); renderPayloadRuntime(watchdog, runtimeUpdates, beaconArtifact); } catch (error) { renderPayloadRuntimeUnavailable(localizedEcosystemError(error.message)); } } function renderPayloadRuntimeUnavailable(message) { $('ecosystem-watchdog').innerHTML = `
Runtime status unavailable${escHtml(message)}
`; $('ecosystem-runtime-updates').innerHTML = '
Safety boundaryCatalog synchronization, version comparison and verified staging remain available locally. Runtime installation is never attempted while the PS5 target is offline.
'; } function renderPayloadRuntime(watchdog, updates, beaconArtifact) { $('ecosystem-watchdog').innerHTML = `
${[ ['Library', watchdog.manager?.installed || 0], ['Running', watchdog.stats?.running || 0], ['Autoload offline', watchdog.stats?.autoloadOffline || 0], ['Issues', watchdog.stats?.issues || 0], ['Updates', updates.stats?.updates || 0], ].map(([label, value]) => `
${escHtml(value)}${escHtml(label)}
`).join('')}
${watchdog.services.map(service => `
${escHtml(service.name)}${escHtml(service.filename || 'no payload file')} · port ${escHtml(service.port)}${service.installedVersion ? ` · file ${escHtml(service.installedVersion)}` : ''}${service.runtimeVersion ? ` · runtime ${escHtml(service.runtimeVersion)}` : ''}
${ecosystemStatus(service.running ? 'online' : service.state === 'autoload-offline' ? 'warning' : 'unknown', service.running ? 'Running' : service.autoload ? 'Autoload · offline' : service.installed ? 'Installed' : 'Not present')}
`).join('')}
${watchdog.issues?.length ? `
Attention required${watchdog.issues.map(issue => `${escHtml(issue.message)}`).join('')}
` : '
✓ No autoload or runtime issues found.
'}`; const actionable = updates.candidates.filter(item => item.status === 'update-available'); $('ecosystem-runtime-updates').innerHTML = `
Chimera Sentinel ${escHtml(beaconArtifact.version)} · ${beaconArtifact.built ? 'build ready' : 'not built'}Paired read-only UDP ${beaconArtifact.port}: HMAC-SHA256, fixed bounded telemetry and visible legacy Beacon 0.1 detection. No writes, persistence, serial number, memory access or command execution.${beaconArtifact.sha256 ? `SHA-256 ${escHtml(beaconArtifact.sha256)}` : 'The ELF appears after a complete container build.'}
${beaconArtifact.publicUrl ? `Download verified ELF` : ''}
Safe Payload Manager update flow${updates.stats.current} current · ${updates.stats.review} need manual review · ${updates.stats.newer} locally newer. Installing only adds the selected file; starting and restarting remain disabled.
${actionable.length ? `
${actionable.map(item => `
${escHtml(item.installed.name)}${escHtml(item.installed.version || 'unknown')} → ${escHtml(item.latest.version || 'unknown')} · ${escHtml(item.latest.sourceName)}${escHtml(item.latest.description || '')}
`).join('')}
` : '

No safely comparable newer Payload Manager versions found.

'}${updates.recommendations?.length ? `

Matching extensions · never installed automatically

${updates.recommendations.map(item => `
${escHtml(item.name)}${escHtml(item.version || 'unknown version')} · ${escHtml(item.risk)} · ${escHtml(item.reason)}
${ecosystemStatus(item.risk === 'expert' ? 'warning' : 'unknown', 'Via Payload Manager')}
`).join('')}
` : ''}`; } function etaPatchFromForm() { const patch = {}; $('ecosystem-etahen-fields').querySelectorAll('[data-eta-key]').forEach(input => { patch[input.dataset.etaKey] = input.type === 'checkbox' ? (input.checked ? 1 : 0) : Number(input.value); }); return patch; } function renderEtaDiff(result) { _ecosystemEtaProposed = result.proposed; $('ecosystem-etahen-diff').innerHTML = `${(result.warnings || []).map(warning => `

âš  ${escHtml(warning)}

`).join('')}${result.diff?.length ? result.diff.map(item => `
${escHtml(item.label)}${escHtml(item.before ?? '—')} → ${escHtml(item.after ?? '—')}
`).join('') : '
✓ No changes.
'}`; $('ecosystem-etahen-apply').disabled = !result.diff?.length; } async function loadEtaHen() { const [config, history] = await Promise.all([window.pork.ecosystemEtaHenGet(), window.pork.ecosystemEtaHenHistory()]); _ecosystemEtaHash = config.hash; _ecosystemEtaProposed = null; $('ecosystem-etahen-raw').value = config.raw; const preset = $('ecosystem-etahen-preset'); preset.innerHTML = '' + Object.entries(config.presets || {}).map(([id, value]) => ``).join(''); $('ecosystem-etahen-fields').innerHTML = Object.entries(config.schema || {}).map(([key, definition]) => { const value = config.values?.[key] ?? '0'; return ``; }).join(''); $('ecosystem-etahen-history').innerHTML = history.length ? `

Restore points

${history.slice(0, 20).map(item => `
${escHtml(item.label)}${ecosystemDate(item.createdAt)} · ${item.diff?.length || 0} change(s)
`).join('')}` : '

No configuration restore points yet.

'; $('ecosystem-etahen-diff').innerHTML = '

Choose a preset or change known fields, then create a preview.

'; $('ecosystem-etahen-apply').disabled = true; } function renderHomebrew(result) { $('ecosystem-homebrew').innerHTML = result.items?.length ? result.items.map(item => `
${item.iconUrl ? `` : '
HB
'}
${escHtml(item.name)}${escHtml(item.location)} · ${escHtml(item.executable)}${item.duplicate ? ' · duplicate' : ''}${escHtml(item.path)}
`).join('') : '
No homebrew foundStart websrv or FTP and run a new scan.
'; } function renderPacbrew(packet) { _pacbrewPacket = packet; const query = String($('ecosystem-pacbrew-search').value || '').trim().toLowerCase(); const kind = $('ecosystem-pacbrew-kind').value; const filtered = (packet.packages || []).filter(item => (!query || item.name.toLowerCase().includes(query)) && (kind === 'all' || item.kind === kind)); const counts = Object.fromEntries(['application', 'service', 'library', 'toolchain'].map(type => [type, (packet.packages || []).filter(item => item.kind === type).length])); $('ecosystem-pacbrew').innerHTML = ` ${packet.warning ? `
${packet.stale ? 'Cached catalog' : 'Fallback transport active'}

${escHtml(packet.warning)}

` : ''}
${filtered.length}/${packet.packages?.length || 0}Visible recipes
${counts.application || 0}Applications
${counts.library || 0}Libraries
${counts.service || 0}Services

Release ${escHtml(packet.release?.tag || 'unknown')} · checked ${ecosystemDate(packet.checkedAt)} · ${escHtml(packet.transport || 'cached')} · recipes are never executed

${filtered.slice(0, 500).map(item => `
${escHtml(item.name)}${escHtml(item.kind)} · source ${escHtml(String(item.sourceSha || '').slice(0, 12))}
${ecosystemStatus('unknown', 'Recipe only')}
`).join('')}
`; } async function loadHomebrew() { const [homebrew, pacbrew] = await Promise.all([window.pork.ecosystemHomebrewList(), window.pork.ecosystemPacbrewCatalog()]); renderHomebrew(homebrew); renderPacbrew(pacbrew); } function renderWebsrvDiscovery(result) { const services = result.services || []; $('ecosystem-websrv-discovery').innerHTML = services.length ? `
${services.length} mDNS service(s) discovered by websrv
${services.map(service => { const name = service.name || service.instance || service.hostname || service.host || 'Onbekende dienst'; const detail = [service.type, service.address || service.host, service.port].filter(Boolean).join(' · '); return `${escHtml(name)}${detail ? ` ${escHtml(detail)}` : ''}`; }).join('')}
` : '

websrv found no mDNS services on the PS5 network.

'; } function renderSources(sources) { $('ecosystem-sources').innerHTML = sources.length ? sources.map(source => `
${escHtml(source.label)}${escHtml(source.protocol.toUpperCase())} · ${escHtml(source.host)}:${source.port}${escHtml(source.path)} · password ${source.passwordConfigured ? 'configured' : 'not configured'}
`).join('') : '

No remote sources configured yet.

'; } function renderEzRemote(packet) { $('ezremote-status').innerHTML = packet.reachable ? `Online · ${escHtml(packet.version || 'unknown version')}${escHtml(packet.host)}:${packet.port} · resume after server restart supported · pause/cancel unsupported` : `Offline op ${escHtml(packet.host || 'PS5')}:${packet.port || 6701}${escHtml(packet.error || 'Start ezRemote Server op de PS5.')}`; const rows = packet.downloads || []; $('ezremote-downloads').innerHTML = rows.length ? `
${rows.slice(0, 100).map(item => `
${escHtml(item.path || 'Unknown destination')}${ecosystemBytes(item.transferred)} / ${item.size ? ecosystemBytes(item.size) : '?'} · ${item.percent}%
${ecosystemStatus(item.state === 'success' ? 'online' : item.state === 'failed' ? 'offline' : 'warning', item.state)}
`).join('')}
` : '

ezRemote Server reports no downloads yet.

'; } async function loadSources() { const [sources, status] = await Promise.all([window.pork.ecosystemSources(), window.pork.ezRemoteStatus()]); renderSources(sources); const select = $('ezremote-source'); const selected = select.value; select.innerHTML = '' + sources.filter(source => source.enabled !== false).map(source => ``).join(''); if (sources.some(source => source.id === selected)) select.value = selected; renderEzRemote(status); } function renderPlugins(result, catalog = []) { const summary = `
${result.items?.length || 0} plugins · ${(result.roots || []).length} available locationsLast scan: ${result.scannedAt ? ecosystemDate(result.scannedAt) : 'not yet'}. ${result.skippedUnmountedUsbRoots || 0} unmounted USB locations were skipped without generating errors.
`; const installed = result.items?.length ? result.items.map(item => `
${escHtml(item.name)}${escHtml(item.location)} · ${ecosystemBytes(item.size)}${item.duplicate ? ' · duplicate' : ''}${item.effective ? ' · highest active priority' : ''}${escHtml(item.path)}
${ecosystemStatus(item.enabled ? 'compatible' : 'unknown', item.enabled ? 'Enabled' : 'Disabled')}
`).join('') : '
No installed plugins foundChoose a reviewed source below or upload a local ELF/PRX through the guarded installer.
'; const reviewed = catalog.length ? `

Reviewed plugin sources

Discovery only · installation remains explicit
${catalog.map(item => `
${escHtml(item.name)}${escHtml(item.platform)} · ${escHtml(item.trust)} · ${escHtml(item.release?.tag || 'version unknown')}

${escHtml(item.compatibility?.reason || 'Review the upstream release before installing.')}

`).join('')}
` : '

No reviewed plugin sources are present in the component catalog.

'; $('ecosystem-plugins').innerHTML = `${summary}

Installed plugins

${installed}${reviewed}`; } async function loadPlugins() { const [plugins, components] = await Promise.all([ window.pork.ecosystemPlugins(), window.pork.ecosystemComponents().catch(() => ({ components: [] })), ]); const catalog = (components.components || []).filter(item => /plugin/i.test(`${item.kind} ${item.name}`)); renderPlugins(plugins, catalog); } async function loadCheatsV2() { const gameContext = consumeGameWorkspaceContext('cheats'); if (gameContext) { $('ecosystem-cheats-search').value = gameContext; _ecosystemCheatOffset = 0; } const request = ++_ecosystemCheatRequest; const filters = { query: $('ecosystem-cheats-search').value, platform: $('ecosystem-cheats-platform').value, kind: $('ecosystem-cheats-kind').value, installedOnly: $('ecosystem-cheats-installed').checked, limit: ECOSYSTEM_CHEAT_PAGE_SIZE, offset: _ecosystemCheatOffset }; const result = await window.pork.ecosystemCheats(filters); if (request !== _ecosystemCheatRequest) return; _ecosystemCheatOffset = result.offset || 0; const first = result.total ? _ecosystemCheatOffset + 1 : 0; const last = _ecosystemCheatOffset + result.items.length; const list = result.items.length ? `
${result.items.map(item => { const match = item.compatibility === 'exact' ? ecosystemStatus('compatible', 'Exact version') : item.compatibility === 'title-match' ? ecosystemStatus('compatible', 'Title match') : item.compatibility === 'version-mismatch' ? ecosystemStatus('warning', 'Version mismatch') : item.compatibility === 'version-unknown' ? ecosystemStatus('unknown', 'Version unknown') : ecosystemStatus('unknown', 'Not local'); return `
${escHtml(item.installedTitle || item.title)}${escHtml(item.gameId || 'Unlinked')} · ${escHtml(item.platform)} · ${escHtml(item.kind)} · ${escHtml(item.format.toUpperCase())} · ${escHtml(item.sourceName)}${item.version ? `Target version ${escHtml(item.version)}` : 'No target version supplied'}${item.installedVersion ? ` · installed ${escHtml(item.installedVersion)}` : ''}${item.conflict ? ' · multiple formats available' : ''}
${match}${item.deployable ? '' : `View only`}
`; }).join('')}
` : '
No cheats or patches matchAdjust the filters or synchronize the reviewed sources to refresh the catalog.
'; const compatibility = result.compatibility || {}; $('ecosystem-cheats').innerHTML = `

${first}–${last} of ${result.total} results · ${Number(compatibility.exact || 0) + Number(compatibility.titleMatch || 0)} recommended · ${Number(compatibility.versionMismatch || 0)} mismatched · synced ${ecosystemDate(result.syncedAt)}

${(result.errors || []).map(error => `

${escHtml(error.sourceId)}: ${escHtml(error.error)}

`).join('')}${list}`; } function renderRemoteSaves(result) { const groups = new Map(); for (const save of result.saves || []) { const id = String(save.title_id || 'UNKNOWN'); if (!groups.has(id)) groups.set(id, { titleId: id, title: save.title_name || id, saves: [] }); groups.get(id).saves.push(save); } $('ecosystem-remote-saves').innerHTML = groups.size ? [...groups.values()].map(group => `
${escHtml(group.title)}${escHtml(group.titleId)} · ${group.saves.length} save(s) · ${group.saves.some(save => save.type === 'ps5') ? 'PS5' : 'PS4'}
`).join('') : '

No saves found.

'; } async function loadSaveVault() { const gameContext = consumeGameWorkspaceContext('saves'); const [vaultResult, capabilitiesResult, gamesResult] = await Promise.allSettled([ window.pork.ecosystemSaveVault(), window.pork.ecosystemSaveRestoreCapabilities(), window.pork.listGames({}), ]); const allSnapshots = vaultResult.status === 'fulfilled' && Array.isArray(vaultResult.value) ? vaultResult.value : []; const capabilities = capabilitiesResult.status === 'fulfilled' && capabilitiesResult.value ? capabilitiesResult.value : { automatedRestore: false, error: capabilitiesResult.reason?.message || 'Restore capabilities could not be verified.' }; const snapshots = gameContext ? allSnapshots.filter(item => item.titleId === gameContext) : allSnapshots; const gamesById = new Map((gamesResult.status === 'fulfilled' ? gamesResult.value : []).map(game => [game.game_id, game])); _ecosystemSaveCapabilities = capabilities; $('ecosystem-save-restore-safety').innerHTML = capabilities.automatedRestore ? 'Protected PS5 restore availablePPSA archives only. Mandatory SHA-256 and CRC dry run, target-user validation, pre-restore snapshot, exact confirmation and byte-for-byte post-check. Resign, forced import and automatic rollback remain disabled.' : `Restore remains blocked${escHtml(localizedEcosystemError(capabilities.reason || capabilities.error || 'Garlic does not prove the required import capabilities.'))}`; const vaultFailure = vaultResult.status === 'rejected' ? `
Local vault unavailable

${escHtml(localizedEcosystemError(vaultResult.reason?.message || 'The local snapshot index could not be loaded.'))}

` : ''; $('ecosystem-save-vault').innerHTML = `${vaultFailure}${gameContext ? `
Game workspace ${escHtml(gameContext)}Only snapshots for this game are shown. Refresh the page to see the complete Save Vault.
` : ''}${snapshots.length ? snapshots.map(item => { const game = gamesById.get(item.titleId); const title = game?.prospero_name || game?.title || item.titleId; return `
${game?.prospero_icon_url ? `` : ''}
${escHtml(title)}${escHtml(item.titleId)} · ${ecosystemDate(item.createdAt)} · ${ecosystemBytes(item.bytes)}${item.purpose ? ` · ${escHtml(item.purpose)}` : ''}${escHtml(String(item.sha256).slice(0, 18))}…
`; }).join('') : '
No snapshots yetCreate a verified snapshot after console saves have been discovered.
'}`; } function renderTimeline(result) { const state = result.state || {}; const options = result.options || {}; $('ecosystem-timeline-persist').checked = options.enabled === true; $('ecosystem-timeline-retention').value = options.retentionDays || 14; $('ecosystem-timeline-max').value = options.maxEvents || 5000; $('ecosystem-timeline-state').innerHTML = `

${state.connected ? `Connected on port ${state.port} since ${ecosystemDate(state.startedAt)}` : state.error ? `Not connected: ${escHtml(state.error)}` : 'Not connected.'} · ${result.filtered || 0}/${result.total || 0} events · persistence ${options.enabled ? 'on' : 'off'}

`; $('ecosystem-timeline-events').innerHTML = (result.events || []).slice().reverse().map(event => `
${escHtml(event.source)}${escHtml(event.message)}
`).join('') || '

No events yet.

'; } function renderGameUpdateJobs(jobs) { $('game-updates-jobs').innerHTML = jobs.length ? jobs.map(job => `
${escHtml(job.manifest.gameId)} · ${escHtml(job.manifest.filename)}${escHtml(job.status)} · ${Number(job.progress || 0)}% · ${ecosystemBytes(job.bytesDownloaded || job.manifest.originalFileSize)}${job.error ? `${escHtml(job.error)}` : job.remotePath ? `${escHtml(job.remotePath)}` : job.localPath ? `${escHtml(job.localPath)}` : ''}
${ecosystemStatus(job.status === 'completed' ? 'compatible' : job.status === 'failed' ? 'warning' : 'unknown', job.status)}${['queued', 'downloading', 'verifying'].includes(job.status) ? '' : ''}${job.status === 'completed' ? '' : ''}${!['queued', 'downloading', 'verifying', 'staging'].includes(job.status) ? '' : ''}
`).join('') : '

No game update jobs yet.

'; } async function loadGameUpdates() { const [catalog, jobs] = await Promise.all([window.pork.gameUpdatesCatalog(), window.pork.gameUpdatesJobs()]); const discovered = catalog.games.filter(game => game.discovery?.status === 'available').length; const updateStateLabel = state => window.t(`ecosystem.gameUpdates.state.${state}`, state); const evidenceRows = catalog.games.map(game => { const locations = (game.locations || []).map(location => { const observed = location.observedAt ? new Date(location.observedAt).toLocaleString() : window.t('ecosystem.gameUpdates.evidenceUnknown', 'evidence time unknown'); const label = location.volumeLabel || location.location || location.volumeId || window.t('ecosystem.gameUpdates.locationUnknown', 'unknown location'); return `${escHtml(label)} · ${escHtml(location.version || '?')} · ${escHtml(location.source || 'unknown')} (${escHtml(location.confidence || 'unknown')}) · ${escHtml(observed)} · ${escHtml(updateStateLabel(location.status))}`; }).join(''); return `
${escHtml(game.title)}${escHtml(game.gameId)} · ${escHtml(game.latestVersion || '?')} · ${escHtml(game.sourceEvidence?.source || 'unknown')}${game.discovery?.reason ? ` · ${escHtml(game.discovery.reason)}` : ''}${locations || `${escHtml(window.t('ecosystem.gameUpdates.noConfirmedLocations', 'No confirmed console locations'))}`}
${ecosystemStatus(game.updateAvailable ? 'warning' : game.status === 'current' ? 'compatible' : 'unknown', updateStateLabel(game.status))}
`; }).join(''); $('game-updates-summary').innerHTML = `
${[[window.t('ecosystem.gameUpdates.gameCount', 'Games'), catalog.stats.total], [window.t('ecosystem.gameUpdates.officialManifests', 'Official manifests'), discovered], [window.t('ecosystem.gameUpdates.available', 'Possible updates'), catalog.stats.updates]].map(([label, value]) => `
${escHtml(value)}${escHtml(label)}
`).join('')}
${escHtml(window.t('ecosystem.gameUpdates.discoveryEvidence', 'Library discovery evidence'))} · ${catalog.games.length}
${evidenceRows}
`; $('game-updates-game').innerHTML = `` + catalog.games.map(game => ``).join(''); renderGameUpdateJobs(jobs); } function renderGameUpdatePlan(plan) { _gameUpdatePlan = plan; $('game-updates-plan').innerHTML = `${escHtml(plan.manifest.gameId)} · ${escHtml(plan.manifest.filename)}${ecosystemBytes(plan.manifest.originalFileSize)} · ${plan.manifest.pieces.length} part(s) · free space ${ecosystemBytes(plan.availableBytes)} · ${plan.manifest.packageDigest ? 'complete SHA-256 available' : 'no complete SHA-256 in manifest'}`; $('game-updates-download-confirm')?.addEventListener('click', async click => { if (!await showConfirm('Download this official Sony package to Chimera backup storage? Every part and the complete package will be verified. Nothing is installed on the PS5.')) return; try { await ecosystemBusy(click.currentTarget, () => window.pork.gameUpdatesDownload(plan.token)); _gameUpdatePlan = null; showToast('Game update job started.', 'ok'); await loadGameUpdates(); } catch (error) { showToast(error.message, 'error', 10000); } }); } function renderReadonlyFiles(targetId, packet, kind) { const rows = packet.roots?.length ? packet.roots : packet.items || []; $(targetId).innerHTML = rows.length ? rows.map(item => `
${item.isDirectory ? '📁' : '📄'} ${escHtml(item.name)}${item.isDirectory ? escHtml(item.mode || 'folder') : ecosystemBytes(item.size)}${item.mtime ? ` · ${ecosystemDate(item.mtime * 1000)}` : ''}
`).join('') : '

This folder is empty.

'; const pathTarget = kind === 'smb' ? 'websrv-smb-path' : 'websrv-files-path'; $(pathTarget).innerHTML = `${escHtml(packet.path || (kind === 'smb' ? 'SMB-root' : '/'))}Read-only · ${rows.length} item(s)${packet.parent !== null && packet.parent !== undefined ? `` : ''}`; } async function loadWebsrvFiles(remotePath = _websrvFsPath) { const packet = await window.pork.ecosystemWebsrvFiles(remotePath); _websrvFsPath = packet.path; renderReadonlyFiles('websrv-files-list', packet, 'fs'); } async function loadWebsrvSmb(smbPath = _websrvSmbPath) { const sourceId = $('websrv-smb-source').value; if (!sourceId) return; const packet = await window.pork.ecosystemWebsrvSmb(sourceId, smbPath); _websrvSmbPath = packet.path; renderReadonlyFiles('websrv-smb-list', packet, 'smb'); } async function loadFilesTab() { const sources = (await window.pork.ecosystemSources()).filter(source => source.protocol === 'smb'); const select = $('websrv-smb-source'); const selected = select.value; select.innerHTML = '' + sources.map(source => ``).join(''); if (sources.some(source => source.id === selected)) select.value = selected; if (!sources.length) { $('websrv-smb-path').innerHTML = 'No SMB sources configuredAdd a reviewed network source before browsing it through the PS5.'; $('websrv-smb-list').innerHTML = '
Connect a storage sourceSources are managed centrally so arbitrary hosts never appear here.
'; } await loadWebsrvFiles(_websrvFsPath); } async function loadLiveSession() { const packet = await window.pork.ecosystemLiveSession(); $('ecosystem-live-session').innerHTML = packet.online ? `
Live telemetry available${escHtml(packet.branding || 'ps5debug-NG')} · ${escHtml(packet.host)}:${escHtml(packet.port)} · checked ${ecosystemDate(packet.checkedAt)}
${ecosystemStatus('online', 'Read-only')}
${[['Protocol', packet.protocol || '?'], ['Firmware', packet.firmware || '?'], ['Platform', packet.platform === 5 ? 'PS5' : packet.platform || '?']].map(([label, value]) => `
${escHtml(value)}${escHtml(label)}
`).join('')}
${packet.foreground ? `
Foreground application

${escHtml(packet.foreground.game?.title || packet.foreground.titleId)}

${escHtml(packet.foreground.titleId)} · v${escHtml(packet.foreground.version || '?')} · PID ${escHtml(packet.foreground.pid)}

${escHtml(packet.foreground.contentId || packet.foreground.processName)}
` : '
Foreground application

No game active

The console service is online and ready to report the next foreground title.

'}
✓ Only five reviewed read-only commands are registered. Memory, attach, kernel, notification and reboot are not exposed.
` : `
1
ps5debug-NG 1.3.0 installedVerified official release staged through Payload Manager
2
Start payload when neededPort ${escHtml(packet.port)} must be reachable
3
Read sessionUse the button above to retry
Live telemetry unavailable

${escHtml(packet.error || 'Service offline.')}

`; } function remotePlaySelectedWorker(packet) { return (packet.workers || []).find(worker => worker.id === ($('remote-play-worker')?.value || packet.selectedWorkerId)) || null; } function renderRemotePlay(packet, companion) { _remotePlayPacket = packet; const discovery = packet.lastDiscovery || {}; const workers = packet.workers || []; const selectedId = $('remote-play-worker')?.value || packet.selectedWorkerId || ''; const selected = workers.find(worker => worker.id === selectedId) || workers[0] || null; const health = selected?.streamHealth || null; const clientName = packet.clientNickname || discovery.console?.name || ''; const registered = Boolean(selected?.registeredHosts?.includes(clientName)); const consoleReady = discovery.state === 'ready'; const consoleStandby = discovery.state === 'standby'; const checks = [ { label: 'PS5', ok: consoleReady, partial: consoleStandby, value: consoleReady ? window.t('remotePlay.state.ready', 'Ready') : consoleStandby ? window.t('remotePlay.state.standby', 'Rest mode') : discovery.online ? discovery.state : window.t('remotePlay.state.notDiscovered', 'Not discovered') }, { label: 'Worker', ok: selected?.online, value: selected?.online ? `${selected.label} online` : selected ? 'Offline' : window.t('remotePlay.state.notPaired', 'Not paired') }, { label: 'chiaki-ng', ok: selected?.client?.installed, value: selected?.client?.installed ? `${selected.client.version || window.t('remotePlay.state.installed', 'Installed')}${selected.client.managed ? ` · ${window.t('remotePlay.state.managed', 'Chimera-managed')}` : ''}` : window.t('remotePlay.state.notFound', 'Not found') }, { label: window.t('remotePlay.registration', 'Registration'), ok: registered, value: registered ? clientName : clientName ? window.tf('remotePlay.registrationMissing', '{name} is not registered locally', { name: clientName }) : window.t('remotePlay.consoleNameMissing', 'Console name missing') }, ]; $('remote-play-readiness').innerHTML = checks.map(check => `
${escHtml(check.label)}${escHtml(check.value)}
`).join(''); if (document.activeElement !== $('remote-play-nickname')) $('remote-play-nickname').value = packet.nickname || ''; if (document.activeElement !== $('remote-play-client-nickname')) $('remote-play-client-nickname').value = clientName; $('remote-play-key').value = ''; $('remote-play-display-mode').value = packet.displayMode || 'fullscreen'; $('remote-play-worker').innerHTML = workers.length ? workers.map(worker => ``).join('') : ``; $('remote-play-worker').value = selected?.id || ''; $('remote-play-status').innerHTML = `${escHtml(packet.registrationKeyConfigured ? window.t('remotePlay.profile.secured', 'Wake profile secured') : window.t('remotePlay.profile.keyMissing', 'Wake key missing'))}${escHtml(packet.nickname)} · ${escHtml(packet.host || window.t('remotePlay.profile.noHost', 'no PS5 host'))} · UDP ${packet.port} · ${escHtml(window.t('remotePlay.profile.sessionPorts', 'session ports'))} ${(packet.sessionPorts || []).join(', ')}${escHtml(window.t('remotePlay.profile.registrationKey', 'registration key'))} ${escHtml(packet.registrationKeyConfigured ? window.t('remotePlay.profile.keyEncrypted', 'AES-256-GCM encrypted and never returned') : window.t('remotePlay.profile.keyWakeOnly', 'needed only to wake from rest mode'))}`; $('remote-play-workers').innerHTML = workers.length ? workers.map(worker => `
${escHtml(worker.label)}${worker.online ? `${escHtml(worker.hostname || worker.platform || 'worker')} · ${worker.client?.installed ? `chiaki-ng ${escHtml(worker.client.version || 'found')}${worker.client.managed ? ' · managed' : ''}` : 'chiaki-ng missing'}` : worker.enrollmentPending ? 'Waiting for installation and first pairing' : `Offline${worker.lastSeenAt ? ` · last ${ecosystemDate(worker.lastSeenAt)}` : ''}`}
${ecosystemStatus(worker.online ? 'online' : worker.enrollmentPending ? 'unknown' : 'offline', worker.online ? 'Online' : worker.enrollmentPending ? 'Install' : 'Offline')}
`).join('') : '
No local worker yet

Download the package above on the device where you want to play.

'; const healthLabel = health?.status === 'healthy' ? window.t('remotePlay.health.healthy', 'Healthy') : health?.status === 'degraded' ? window.t('remotePlay.health.degraded', 'Degraded') : window.t('remotePlay.health.attention', 'Attention'); const healthTone = health?.status === 'healthy' ? 'online' : health?.status === 'degraded' ? 'error' : 'warning'; const healthMarkup = health ? `
Last stream · ${escHtml(healthLabel)}${escHtml(health.resolution || 'resolution unknown')} · ${escHtml(health.decoder || 'decoder unknown')}${health.rttMs != null ? ` · RTT ${escHtml(health.rttMs)} ms` : ''} · ${ecosystemDate(health.checkedAt)}Packet/FEC errors ${escHtml(health.packetErrors)} · duplicates ${escHtml(health.duplicateUnits)} · decoder buffer drops ${escHtml(health.decoderDrops)}${ecosystemStatus(healthTone, healthLabel)}
` : ''; const workflowCopy = { 'Console ontdekken': [window.t('remotePlay.workflow.discovery', 'Discover console'), window.t('remotePlay.workflow.discoveryDetail', 'LAN discovery through the upstream PS5 protocol on UDP 9302.')], Wakeprofiel: [window.t('remotePlay.workflow.wakeProfile', 'Wake profile'), window.t('remotePlay.workflow.wakeDetail', 'Wake uses only the encrypted rp_regist_key.')], 'Lokale worker': [window.t('remotePlay.workflow.worker', 'Local worker'), window.t('remotePlay.workflow.workerDetail', 'The HMAC-paired worker manages and starts only a hash-verified chiaki-ng client.')], 'chiaki-ng geregistreerd': [window.t('remotePlay.workflow.registered', 'chiaki-ng registered'), window.t('remotePlay.workflow.registeredDetail', 'The PSN Account ID and Link Device PIN remain only in the local chiaki-ng configuration.')], }; $('remote-play-companion').innerHTML = `
${(companion.workflow || []).map(step => { const translated = workflowCopy[step.id] || [step.id, step.detail]; return `
${escHtml(translated[0])}${escHtml(translated[1])}
${ecosystemStatus(step.ready ? 'online' : 'unknown', step.ready ? window.t('remotePlay.state.ready', 'Ready') : window.t('remotePlay.state.configure', 'Configure'))}
`; }).join('')}
${healthMarkup}
${escHtml(window.t('remotePlay.native.title', 'Why native?'))}${escHtml(window.t('remotePlay.native.reason', 'Video, audio, controller and haptics run natively on the device where chiaki-ng and the controller are present; Chimera orchestrates and monitors the session.'))}${escHtml(window.t('remotePlay.native.detail', 'The worker has no shell, receives no PS5 registration key and can perform only fixed chiaki-ng actions.'))}
`; const jobStatuses = { queued: window.t('remotePlay.jobs.queued', 'Queued'), dispatched: window.t('remotePlay.jobs.dispatched', 'Dispatched…'), completed: window.t('remotePlay.jobs.completed', 'Completed'), failed: window.t('remotePlay.jobs.failed', 'Failed'), expired: window.t('remotePlay.jobs.expired', 'Expired'), cancelled: window.t('remotePlay.jobs.cancelled', 'Cancelled') }; $('remote-play-jobs').innerHTML = packet.jobs?.length ? `
${packet.jobs.map(job => `
${escHtml(job.action)}${ecosystemDate(job.createdAt)}${job.result?.message ? ` · ${escHtml(job.result.message)}` : job.error ? ` · ${escHtml(job.error)}` : ''}
${ecosystemStatus(job.status === 'completed' ? 'online' : job.status === 'failed' ? 'error' : job.status === 'expired' ? 'offline' : 'unknown', jobStatuses[job.status] || job.status)}
`).join('')}
` : `

${escHtml(window.t('remotePlay.sessions.empty', 'No Remote Play action has run yet.'))}

`; const blockers = []; if (!selected?.online) blockers.push('worker offline'); if (!selected?.client?.installed) blockers.push(window.t('remotePlay.blocker.clientMissing', 'chiaki-ng missing')); if (!registered) blockers.push(window.t('remotePlay.blocker.notRegistered', 'console not registered locally')); if (!consoleReady && !(consoleStandby && packet.registrationKeyConfigured)) blockers.push(window.t('remotePlay.blocker.notReady', 'PS5 not ready')); $('remote-play-start').disabled = blockers.length > 0; $('remote-play-start-hint').textContent = blockers.length ? window.tf('remotePlay.blocker.prefix', 'Still needed: {items}.', { items: blockers.join(' · ') }) : consoleStandby ? window.t('remotePlay.ready.wakeFirst', 'Chimera will confirm wake first and then start the local stream.') : window.t('remotePlay.ready.start', 'All checks passed; the stream opens locally on the selected worker.'); $('remote-play-wake').disabled = !packet.registrationKeyConfigured || consoleReady; $('remote-play-diagnose').disabled = !selected?.online; $('remote-play-open-client').disabled = !selected?.online || !selected?.client?.installed; $('remote-play-install-client').disabled = !selected?.online || selected?.client?.installed; $('remote-play-update-client').disabled = !selected?.online || !selected?.client?.installed; $('remote-play-stop').disabled = !selected?.online || !selected?.session?.active; } async function loadRemotePlay({ discover = false, schedule = true } = {}) { if (discover) await window.pork.remotePlayDiscover().catch(() => null); const [packet, companion] = await Promise.all([window.pork.remotePlayStatus(), window.pork.remotePlayCompanion()]); renderRemotePlay(packet, companion); clearTimeout(_remotePlayPollTimer); if (schedule && _ecosystemActiveTab === 'remote-play') _remotePlayPollTimer = setTimeout(() => loadRemotePlay({ schedule: true }).catch(error => showToast(error.message, 'error')), 3_000); } async function loadItemzflow() { _itemzflowPacket = await window.pork.ecosystemItemzflowStatus(); const packet = _itemzflowPacket; $('itemzflow-summary').innerHTML = `
${[['Logs', `${packet.health?.available || 0}/${packet.logs?.length || 0}`], ['Errors', packet.health?.errors || 0], ['Warnings', packet.health?.warnings || 0], ['Games', packet.games?.installed || 0], ['Progress', packet.latestProgress?.progress != null ? `${packet.latestProgress.progress}%` : 'None active']].map(([label, value]) => `
${escHtml(value)}${escHtml(label)}
`).join('')}
${packet.error ? `

${escHtml(localizedEcosystemError(packet.error))}

` : `

${packet.lines} lines from ${packet.health?.available || 0} logs · ${ecosystemDate(packet.checkedAt)}

`}
${(packet.logs || []).map(log => `
${escHtml(log.label)}${escHtml(log.path)} · ${log.lines || 0} lines
${ecosystemStatus(log.online ? (log.errorCount ? 'warning' : 'online') : 'offline', log.online ? `${log.errorCount || 0} errors · ${log.warningCount || 0} warnings` : 'Not present')}
`).join('')}
${packet.nfsSources?.length ? `
NFS profiles${packet.nfsSources.map(source => `${escHtml(source.label)} · ${escHtml(source.host)}:${source.port}${escHtml(source.path)}`).join(' · ')}
` : ''}`; const selected = $('itemzflow-log-select').value; $('itemzflow-log-select').innerHTML = '' + (packet.logs || []).map(log => ``).join(''); if ((packet.logs || []).some(log => log.id === selected)) $('itemzflow-log-select').value = selected; renderItemzflowEvents(); } function renderItemzflowEvents() { if (!_itemzflowPacket) return; const source = $('itemzflow-log-select').value; const level = $('itemzflow-level').value; const jobsOnly = $('itemzflow-jobs-only').checked; const pool = jobsOnly ? _itemzflowPacket.events || [] : _itemzflowPacket.recentLogs || []; const events = pool.filter(event => (source === 'all' || event.source === source) && (level === 'all' || event.level === level)); $('itemzflow-events').innerHTML = events.map(event => `
${escHtml(event.sourceLabel || event.source)}${escHtml(event.type)} · ${escHtml(event.message)}${event.progress != null ? `${event.progress}%` : `${escHtml(event.level)}`}
`).join('') || '

No events match these filters.

'; } async function loadLinuxLab() { const packet = await window.pork.ecosystemLinuxLab(); const state = packet.status === 'compatible' ? 'Firmware explicitly supported' : packet.status === 'incompatible' ? 'Firmware unsupported' : 'Firmware unknown'; $('ecosystem-linux-summary').innerHTML = `
${[['Status', state], ['Profile', packet.firmware || 'Not entered'], ['Models', packet.supportedModels.join(' + ')], ['Execution', 'Blocked']].map(([label, value]) => `
${escHtml(value)}${escHtml(label)}
`).join('')}

Source reviewed ${escHtml(packet.sourceReviewedAt)} · supported firmware: ${packet.supportedFirmwares.map(escHtml).join(', ')}

`; $('ecosystem-linux-checks').innerHTML = (packet.requirements || []).map(check => `
${escHtml(check.label)}${escHtml(check.detail)}
${ecosystemStatus(check.status, check.status === 'compatible' ? 'Satisfied' : check.status === 'incompatible' ? 'Incompatible' : check.status === 'warning' ? 'Disable manually' : 'Check manually')}
`).join(''); $('ecosystem-linux-components').innerHTML = (packet.components || []).map(component => `
${escHtml(component.name)}${escHtml(component.release?.tag || 'release unknown')} · ${escHtml(component.risk)}
`).join(''); $('ecosystem-linux-policy').innerHTML = 'All dangerous actions are intentionally absentNo download, payload sender, exploit, boot, block-device write or automatic HEN/kstuff change. Use the official upstream guide outside Chimera and only after every preflight condition has been verified manually.'; } async function loadTimeline() { renderTimeline(await window.pork.ecosystemTimeline({ search: $('ecosystem-timeline-search')?.value || '', level: $('ecosystem-timeline-level')?.value || '', limit: 1000 })); } let _platformLabState = { artifacts: [], launchPlan: null }; function platformLabText(key, fallback) { return window.t(`platformLab.${key}`, fallback); } function platformLabError(id, error) { const target = $(id); if (target) target.innerHTML = `
${escHtml(platformLabText('unavailable', 'Unavailable'))}
${escHtml(error?.message || String(error))}
`; } function renderPlatformBindings(data) { $('platform-bindings').innerHTML = (data.bindings || []).map(binding => `
${escHtml(binding.label)}
${escHtml(binding.repository)} · ${escHtml(binding.latestTag || platformLabText('notChecked', 'not checked'))} · ${binding.assets?.length || 0} assets
${escHtml(binding.state)}${binding.latestTag ? `` : ''}
`).join('') || `

${escHtml(platformLabText('empty', 'No results.'))}

`; } function renderAutoloader(data) { const profiles = [...(data.presets || []), ...(data.profiles || [])]; _platformLabState.profiles = profiles; $('platform-autoloader-profile').innerHTML = profiles.map(profile => ``).join(''); const selected = profiles[0] || { id: 'custom-profile', name: 'Custom profile', entrypoints: ['y2jb'], titleIds: [], payloads: ['pldmgr.elf'], firmware: null, youtubeVersion: null }; $('platform-profile-json').value = JSON.stringify(selected, null, 2); $('platform-autoloader-output').innerHTML = `
${escHtml(platformLabText('autoloader.safe', 'Profiles are preview-only; no USB or console writes are performed.'))}
`; } function renderAutoloaderPreview(data) { $('platform-autoloader-output').innerHTML = `
${escHtml(data.profile.name)}
${escHtml(data.directory)} · ${escHtml((data.profile.entrypoints || []).join(', '))}
${escHtml(data.blocked ? platformLabText('blocked', 'blocked') : platformLabText('previewOnly', 'preview only'))}
${escHtml(data.autoloadText)}
${(data.compatibility || []).map(item => `
${escHtml(item.message)}
${escHtml(item.state)}
`).join('')}${(data.warnings || []).map(warning => `
${escHtml(warning)}
`).join('')}`; } function discoveryCount(value) { return Array.isArray(value) ? value.length : value && typeof value === 'object' ? Object.keys(value).length : 0; } function renderPlatformWebsrv(data) { $('platform-websrv').innerHTML = `
${discoveryCount(data.mdns)}mDNS
${data.capabilities?.smb ? '✓' : '−'}SMB
${data.capabilities?.filesystem ? '✓' : '−'}Filesystem
${(data.errors || []).map(error => `
${escHtml(error)}
`).join('')}${(data.blockedExternal || []).map(item => `
${escHtml(item.capability)}: ${escHtml(item.reason)}
`).join('')}
${escHtml(platformLabText('readOnly', 'Read-only discovery'))}
`; } function renderRemoteIntelligence(data) { const workers = data.workers || []; $('platform-remote').innerHTML = `
${data.counts?.online || 0}${escHtml(platformLabText('online', 'online'))}
${data.counts?.updates || 0}${escHtml(platformLabText('updates', 'updates'))}
${data.counts?.degraded || 0}${escHtml(platformLabText('attention', 'attention'))}
${workers.map(worker => `
${escHtml(worker.name || worker.id)}
${escHtml(worker.version || platformLabText('unknownVersion', 'unknown version'))} · ${escHtml((worker.recommendations || []).join(', ') || platformLabText('healthy', 'healthy'))}
${escHtml(worker.updateState)}
`).join('') || `

${escHtml(platformLabText('noWorkers', 'No Remote Play workers paired.'))}

`}`; } function renderShadps4Roots(data) { $('platform-shadps4-root').innerHTML = `${(data.roots || []).map(root => ``).join('')}`; } function renderShadps4Scan(data) { if (data.status === 'not-configured') { $('platform-shadps4').innerHTML = `

${escHtml(platformLabText('selectRootHint', 'Select a semantic root to scan.'))}

`; return; } const entries = [...(data.copies || []), ...(data.archives || [])]; $('platform-shadps4').innerHTML = `
${(data.copies || []).length}${escHtml(platformLabText('copies', 'copies'))}
${(data.archives || []).length}${escHtml(platformLabText('archiveCandidates', 'archive candidates'))}
${entries.slice(0, 50).map(item => `
${escHtml(item.titleId || item.name)}
${escHtml(item.relativePath || item.name)} · ${escHtml(item.format)}
${escHtml(platformLabText('emulatorCopy', 'emulator copy'))}${item.format === 'directory' ? `` : ''}
`).join('')}
${escHtml(platformLabText('notConsoleInstall', 'These records never imply a console installation.'))}
`; } function renderArtifactRoots(data) { const options = `${(data.roots || []).map(root => ``).join('')}`; $('platform-artifact-root').innerHTML = options; $('platform-stage-root').innerHTML = options; } function renderArtifacts(data) { _platformLabState.artifacts = data.artifacts || []; $('platform-artifacts').innerHTML = _platformLabState.artifacts.map(item => `
${item.lifecycleState === 'hashed' ? `` : `${escHtml(item.lifecycleState)}`}
`).join('') || `

${escHtml(platformLabText('noArtifacts', 'No inspected artifacts.'))}

`; } function renderContracts(data) { $('platform-contracts').innerHTML = (data.contracts || []).map(item => `
${escHtml(item.label)}
${escHtml(item.mode)}${item.missing.length ? ` · ${escHtml(item.missing.join(', '))}` : ''}
${escHtml(item.state)}
`).join(''); } function renderNotifications(data) { $('platform-notifications').innerHTML = (data.items || []).map(item => `
${escHtml(item.category)}
${escHtml(item.message)}
${escHtml(item.severity)}
`).join('') || `

${escHtml(platformLabText('noNotifications', 'No current recommendations.'))}

`; } function renderOperations(data) { $('platform-operations').innerHTML = (data.jobs || []).slice(0, 10).map(job => `
${escHtml(job.type)}
${escHtml(job.error || job.updatedAt)}
${escHtml(job.state)} · ${job.progress}%
`).join('') || `

${escHtml(platformLabText('noOperations', 'No background operations.'))}

`; } function renderLaunchRoots(data) { $('platform-shadps4-exe-root').innerHTML = `${(data.roots || []).map(root => ``).join('')}`; } function renderLaunchProbe(data) { $('platform-shadps4-launch').innerHTML = `
${data.available ? `${escHtml(data.executableLabel)} · ${escHtml(platformLabText('ready', 'ready'))}` : escHtml(platformLabText('launcherNotConfigured', 'Launcher not configured.'))} · shell=false
`; } function renderSearch(data) { $('platform-search-results').innerHTML = (data.results || []).map(item => `
${escHtml(item.title)}
${escHtml(item.kind)} · ${escHtml(item.detail)}
`).join('') || `

${escHtml(platformLabText('empty', 'No results.'))}

`; } async function loadPlatformLab() { const jobs = [ ['platform-bindings', window.pork.platformBindings(), renderPlatformBindings], ['platform-autoloader-output', window.pork.unifiedAutoloaderList(), renderAutoloader], ['platform-websrv', window.pork.platformWebsrvDiscover(), renderPlatformWebsrv], ['platform-remote', window.pork.remotePlayIntelligence(), renderRemoteIntelligence], ['platform-shadps4', window.pork.shadps4Roots(), renderShadps4Roots], ['platform-shadps4', window.pork.shadps4Scan(), renderShadps4Scan], ['platform-artifacts', window.pork.platformArtifactRoots(), renderArtifactRoots], ['platform-artifacts', window.pork.platformArtifacts(), renderArtifacts], ['platform-contracts', window.pork.platformContracts(), renderContracts], ['platform-notifications', window.pork.platformNotifications(), renderNotifications], ['platform-operations', window.pork.platformOperations(), renderOperations], ['platform-shadps4-launch', window.pork.shadps4LaunchRoots(), renderLaunchRoots], ['platform-shadps4-launch', window.pork.shadps4LaunchProbe(), renderLaunchProbe], ]; const results = await Promise.allSettled(jobs.map(job => job[1])); results.forEach((result, index) => result.status === 'fulfilled' ? jobs[index][2](result.value) : platformLabError(jobs[index][0], result.reason)); } async function loadEcosystemTab(tab) { if (tab === 'overview') return loadEcosystemOverview(); if (tab === 'components') return loadEcosystemComponents(); if (tab === 'updates') return loadEcosystemUpdates(); if (tab === 'game-updates') return loadGameUpdates(); if (tab === 'etahen') return loadEtaHen(); if (tab === 'live') return loadLiveSession(); if (tab === 'remote-play') return loadRemotePlay({ discover: !_remotePlayPacket }); if (tab === 'files') return loadFilesTab(); if (tab === 'homebrew') return loadHomebrew(); if (tab === 'sources') return loadSources(); if (tab === 'plugins') return loadPlugins(); if (tab === 'cheats') return loadCheatsV2(); if (tab === 'saves') return loadSaveVault(); if (tab === 'itemzflow') return loadItemzflow(); if (tab === 'platform-lab') return loadPlatformLab(); if (tab === 'research') return loadLinuxLab(); if (tab === 'timeline') return loadTimeline(); } function activateEcosystemTab(tab) { _ecosystemActiveTab = tab; document.querySelectorAll('.ecosystem-tab').forEach(button => { const active = button.dataset.ecosystemTab === tab; button.classList.toggle('active', active); button.setAttribute('aria-selected', String(active)); button.tabIndex = active ? 0 : -1; }); document.querySelectorAll('.ecosystem-panel').forEach(panel => { const active = panel.dataset.ecosystemPanel === tab; panel.classList.toggle('active', active); panel.hidden = !active; }); loadEcosystemTab(tab).catch(error => showToast(error.message, 'error')); } function navigateEcosystemTab(tab) { const route = window.ChimeraNavigationContract?.routes?.find(item => item.page === 'ecosystem' && item.tab === tab); if (route && typeof window.chimeraNavigate === 'function') return window.chimeraNavigate(route.id); return activateEcosystemTab(tab); } function bindEcosystemEvents() { document.querySelectorAll('.ecosystem-tab').forEach(button => button.addEventListener('click', () => navigateEcosystemTab(button.dataset.ecosystemTab))); $('ecosystem-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, () => loadEcosystemTab(_ecosystemActiveTab)).catch(error => showToast(error.message, 'error'))); $('ecosystem-health-run').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { await window.pork.ecosystemHealthRun(true); await loadEcosystemOverview(); showToast('Deep console health scan completed.', 'ok'); }).catch(error => showToast(error.message, 'error', 9000))); $('ecosystem-overview').addEventListener('click', async event => { const alert = event.target.closest('.ecosystem-alert'); if (!alert) return; try { if (event.target.closest('.ecosystem-alert-ack')) { await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemHealthAcknowledge(alert.dataset.id)); await loadEcosystemOverview(); } const open = event.target.closest('.ecosystem-alert-open'); if (open?.dataset.tab) navigateEcosystemTab(open.dataset.tab); if (open?.dataset.page) navigate(open.dataset.page); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-overview').addEventListener('change', async event => { if (event.target.id !== 'ecosystem-health-notifications') return; try { const packet = await window.pork.ecosystemHealthOptions(event.target.checked); event.target.checked = Boolean(packet.notificationsEnabled); showToast(packet.notificationsEnabled ? 'Health notifications enabled.' : 'Health notifications disabled.', 'ok'); } catch (error) { event.target.checked = !event.target.checked; showToast(error.message, 'error'); } }); $('ecosystem-components-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { await window.pork.ecosystemRefreshComponents(true); await loadEcosystemComponents(); showToast('Upstreamcomponenten bijgewerkt.', 'ok'); }).catch(error => showToast(error.message, 'error'))); $('ecosystem-components-search').addEventListener('input', renderEcosystemComponents); $('ecosystem-components-platform').addEventListener('change', renderEcosystemComponents); $('ecosystem-components-risk').addEventListener('change', renderEcosystemComponents); $('ecosystem-components').addEventListener('click', event => { const button = event.target.closest('.ecosystem-open-upstream'); if (button) window.pork.openShell(button.dataset.url); }); $('ecosystem-updates-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const result = await window.pork.ecosystemUpdatesRefresh(); await loadEcosystemUpdates(); showToast(`${result.stats?.total || 0} upstreamcomponenten en payloadcatalogi gesynchroniseerd.`, result.blocked ? 'info' : 'ok', 9000); }).catch(error => showToast(error.message, 'error', 9000))); $('ecosystem-watchdog-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadEcosystemUpdates).catch(error => showToast(error.message, 'error', 10000))); $('ecosystem-manager-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { await window.pork.ecosystemPayloadRefresh(); await loadEcosystemUpdates(); showToast('Payload Manager sources refreshed; nothing was installed or started.', 'ok', 9000); }).catch(error => showToast(error.message, 'error', 10000))); $('ecosystem-runtime-updates').addEventListener('click', async event => { const pairButton = event.target.closest('.chimera-sentinel-pair'); if (pairButton) { const confirmation = await showExactConfirmation(window.t('ecosystem.sentinel.pairWarning', 'Sentinel pairs only briefly after payload start; the key stays in RAM.'), 'PAIR SENTINEL'); if (!confirmation) return; try { await ecosystemBusy(pairButton, () => window.pork.chimeraSentinelPair({ confirmation, rotate: false })); showToast('Sentinel 0.2 paired. The console keeps the key only until the payload stops.', 'ok', 10000); await loadEcosystemUpdates(); } catch (error) { showToast(error.message, 'error', 10000); } return; } const button = event.target.closest('.ecosystem-runtime-install'); const row = event.target.closest('[data-runtime-filename]'); if (!button || !row) return; const required = `UPDATE ${row.dataset.runtimeFilename}`; const confirmation = await showExactConfirmation(`This installs only ${row.dataset.runtimeFilename} in Payload Manager and starts nothing.`, required); if (!confirmation) return; try { const result = await ecosystemBusy(button, () => window.pork.ecosystemPayloadInstall({ filename: row.dataset.runtimeFilename, sourceId: row.dataset.runtimeSource, confirmation })); showToast(`${result.filename} installed. The payload was not started.`, 'ok', 10000); await loadEcosystemUpdates(); } catch (error) { showToast(error.message, 'error', 10000); } }); $('ecosystem-supply-policy').addEventListener('submit', async event => { event.preventDefault(); try { await ecosystemBusy(event.submitter, () => window.pork.ecosystemSupplyPolicySave({ officialSourcesOnly: $('ecosystem-supply-official').checked, requireHttps: $('ecosystem-supply-https').checked, requirePublishedHashForExecution: $('ecosystem-supply-hash').checked })); showToast('Payload trust policy saved. Automatic execution remains disabled.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-supply-audit').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const report = await window.pork.ecosystemSupplyAudit(); const blocked = report.assets.filter(asset => asset.downloaded && asset.issues.length); $('ecosystem-supply-report').innerHTML = `${report.stats.verified} verified · ${report.stats.blocked} blocked${report.stats.assets} release assets from ${report.stats.sources} sources · checked ${ecosystemDate(report.checkedAt)}${blocked.length ? `
${blocked.slice(0, 30).map(asset => `

${escHtml(asset.source)} · ${escHtml(asset.name)} — ${asset.issues.map(escHtml).join(' ')}

`).join('')}
` : '
✓ Every downloaded payload satisfies the active policy.
'}`; showToast(`Payload Vault: ${report.stats.verified} verified, ${report.stats.blocked} blocked.`, report.stats.blocked ? 'info' : 'ok', 10000); }).catch(error => showToast(error.message, 'error', 10000))); $('ecosystem-repository-copy').addEventListener('click', async () => { const input = $('ecosystem-repository-url'); try { if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable'); await navigator.clipboard.writeText(input.value); showToast('Payload Manager source URL copied.', 'ok'); } catch (_) { input.focus(); input.select(); input.setSelectionRange(0, input.value.length); const copied = typeof document.execCommand === 'function' && document.execCommand('copy'); showToast(copied ? 'Payload Manager source URL copied.' : 'The URL is selected; copy it with Ctrl+C.', copied ? 'ok' : 'info'); } }); $('ecosystem-payload-manager-open').addEventListener('click', () => navigate('jailbreak')); $('game-updates-manifest-form').addEventListener('submit', async event => { event.preventDefault(); try { const plan = await ecosystemBusy(event.submitter, () => window.pork.gameUpdatesAutoInspect($('game-updates-game').value, true)); renderGameUpdatePlan(plan); } catch (error) { _gameUpdatePlan = null; $('game-updates-plan').innerHTML = `Manifest afgekeurd${escHtml(error.message)}`; showToast(error.message, 'error', 10000); } }); $('game-updates-manual-inspect').addEventListener('click', async event => { try { const url = $('game-updates-manifest-url').value.trim(); if (!url) throw new Error('Paste an official Sony JSON manifest URL first.'); renderGameUpdatePlan(await ecosystemBusy(event.currentTarget, () => window.pork.gameUpdatesInspect(url, $('game-updates-game').value))); } catch (error) { _gameUpdatePlan = null; $('game-updates-plan').innerHTML = `Manifest rejected${escHtml(error.message)}`; } }); $('game-updates-scan-all').addEventListener('click', async event => { try { const result = await ecosystemBusy(event.currentTarget, () => window.pork.gameUpdatesRefreshAll()); showToast(`${result.available}/${result.total} official manifests available.`, result.failed ? 'info' : 'ok', 9000); await loadGameUpdates(); } catch (error) { showToast(error.message, 'error', 10000); } }); $('game-updates-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadGameUpdates).catch(error => showToast(error.message, 'error'))); $('game-updates-jobs').addEventListener('click', async event => { const row = event.target.closest('.ecosystem-update-row'); const button = event.target.closest('button'); if (!row || !button) return; try { if (button.classList.contains('game-update-cancel') && await showConfirm('Cancel this download and remove the incomplete file?')) await window.pork.gameUpdatesCancel(row.dataset.id); if (button.classList.contains('game-update-stage')) { if (!await showConfirm('Copy the fully verified package to /data/chimera-updates on the PS5? This installs or starts nothing.')) return; const result = await ecosystemBusy(button, () => window.pork.gameUpdatesStage(row.dataset.id)); showToast(`Package verified and copied to ${result.remotePath}; installation was not performed.`, 'ok', 12000); } if (button.classList.contains('game-update-handoff')) { const result = await ecosystemBusy(button, () => window.pork.gameUpdatesHttpsHandoff(row.dataset.id)); await navigator.clipboard.writeText(result.url); showToast('Temporary verified HTTPS package URL copied; it expires in 30 minutes.', 'ok', 10000); } if (button.classList.contains('game-update-delete') && await showExactConfirmation('Permanently delete this job and local package file?', 'DELETE')) await window.pork.gameUpdatesDelete(row.dataset.id); await loadGameUpdates(); } catch (error) { showToast(error.message, 'error', 10000); } }); $('ecosystem-update-plan').addEventListener('click', async event => { const row = event.target.closest('.ecosystem-update-row'); if (!row) return; const button = event.target.closest('button'); try { if (button?.classList.contains('ecosystem-version-save')) { const version = row.querySelector('.ecosystem-version-input').value.trim(); await ecosystemBusy(button, () => window.pork.ecosystemTrackVersion(row.dataset.id, version)); await loadEcosystemUpdates(); showToast(window.t(version ? 'ecosystem.payloadEvidence.manualSaved' : 'ecosystem.payloadEvidence.manualCleared', version ? 'Manual override saved.' : 'Manual tracking cleared.'), 'ok'); } if (button?.classList.contains('ecosystem-follow-automatic')) { await ecosystemBusy(button, () => window.pork.ecosystemFollowAutomatic(row.dataset.id)); await loadEcosystemUpdates(); showToast(window.t('ecosystem.payloadEvidence.automaticSaved', 'Automatic evidence following enabled.'), 'ok'); } if (button?.classList.contains('ecosystem-update-prepare')) { const asset = row.querySelector('.ecosystem-asset-select')?.value; if (!await showConfirm('This only downloads the selected official release asset to Chimera and verifies available checksums. Nothing executes on the PS5. Continue?')) return; const result = await ecosystemBusy(button, () => window.pork.ecosystemUpdatePrepare({ componentId: row.dataset.id, assetName: asset })); await loadEcosystemUpdates(); showToast(`Update voorbereid: ${result.file?.name || result.asset?.name || asset}${result.file?.verified || result.verified ? ' · checksum geverifieerd' : ''}.`, result.file?.verified || result.verified ? 'ok' : 'info', 9000); } } catch (error) { showToast(error.message, 'error', 9000); } }); $('ecosystem-etahen-reload').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadEtaHen).catch(error => showToast(error.message, 'error'))); $('ecosystem-etahen-preview').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderEtaDiff(await window.pork.ecosystemEtaHenPreview({ raw: $('ecosystem-etahen-raw').value, preset: $('ecosystem-etahen-preset').value || null, patch: etaPatchFromForm() }))).catch(error => showToast(error.message, 'error'))); $('ecosystem-etahen-apply').addEventListener('click', async event => { if (!_ecosystemEtaProposed || !await showExactConfirmation('Apply this etaHEN configuration? Chimera creates a restore point first and verifies the upload.', 'PAS ETAHEN TOE')) return; try { await ecosystemBusy(event.currentTarget, () => window.pork.ecosystemEtaHenApply({ proposed: _ecosystemEtaProposed, expectedHash: _ecosystemEtaHash, label: $('ecosystem-etahen-preset').selectedOptions[0]?.textContent || 'Manual change' }, 'PAS ETAHEN TOE')); showToast('etaHEN configuration applied safely.', 'ok'); await loadEtaHen(); } catch (error) { showToast(error.message, 'error', 9000); } }); $('ecosystem-etahen-history').addEventListener('click', async event => { const row = event.target.closest('.ecosystem-history-row'); if (!row || !event.target.closest('.ecosystem-etahen-restore') || !await showExactConfirmation('Restore this etaHEN restore point?', 'HERSTEL')) return; try { await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemEtaHenRestore(row.dataset.id)); showToast('etaHEN configuration restored.', 'ok'); await loadEtaHen(); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-live-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadLiveSession).catch(error => showToast(error.message, 'error'))); $('remote-play-profile').addEventListener('submit', async event => { event.preventDefault(); try { await ecosystemBusy(event.submitter, () => window.pork.remotePlaySave({ nickname: $('remote-play-nickname').value, clientNickname: $('remote-play-client-nickname').value, workerId: $('remote-play-worker').value, displayMode: $('remote-play-display-mode').value, registrationKey: $('remote-play-key').value || undefined })); $('remote-play-key').value = ''; showToast('Remote Play profile saved safely.', 'ok'); await loadRemotePlay(); } catch (error) { showToast(error.message, 'error', 10000); } }); $('remote-play-worker').addEventListener('change', () => { if (_remotePlayPacket) loadRemotePlay().catch(error => showToast(error.message, 'error')); }); $('remote-play-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, () => loadRemotePlay({ schedule: true })).catch(error => showToast(error.message, 'error'))); $('remote-play-discover').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const result = await window.pork.remotePlayDiscover(); await loadRemotePlay(); showToast(result.online ? `PS5 ${result.console?.name || ''} is ${result.state}.` : 'The PS5 did not answer LAN discovery.', result.online ? 'ok' : 'error'); }).catch(error => showToast(error.message, 'error'))); $('remote-play-wake').addEventListener('click', async event => { if (!await showExactConfirmation('This sends a wake packet to the PS5 and waits up to 30 seconds for the console to become ready.', 'WEK PS5')) return; try { const result = await ecosystemBusy(event.currentTarget, () => window.pork.remotePlayWake()); showToast(result.ready ? (result.alreadyReady ? 'The PS5 was already ready.' : 'The PS5 is awake and ready.') : 'Wake packet sent, but the PS5 did not become ready in time.', result.ready ? 'ok' : 'error', 10000); await loadRemotePlay(); } catch (error) { showToast(error.message, 'error', 10000); } }); $('remote-play-start').addEventListener('click', async event => { const worker = remotePlaySelectedWorker(_remotePlayPacket || {}); if (!worker || !await showExactConfirmation(`Dit ${_remotePlayPacket?.lastDiscovery?.state === 'standby' ? 'wekt de PS5 en ' : ''}opent chiaki-ng op ${worker.label}.`, 'START REMOTE PLAY')) return; try { const result = await ecosystemBusy(event.currentTarget, () => window.pork.remotePlayStart({ workerId: worker.id, clientNickname: $('remote-play-client-nickname').value.trim(), displayMode: $('remote-play-display-mode').value, wakeIfNeeded: _remotePlayPacket?.lastDiscovery?.state === 'standby' })); showToast(`Remote Play-job ${result.job.id.slice(0, 8)} staat klaar voor ${worker.label}.`, 'ok', 9000); await loadRemotePlay(); } catch (error) { showToast(error.message, 'error', 12000); } }); $('remote-play-worker-package-form').addEventListener('submit', async event => { event.preventDefault(); try { await ecosystemBusy(event.submitter, () => window.pork.remotePlayWorkerPackage({ label: $('remote-play-worker-label').value.trim(), serverOrigin: window.location.origin })); showToast('Worker package created. The enrollment token expires in 30 minutes.', 'ok', 10000); await loadRemotePlay(); } catch (error) { showToast(error.message, 'error', 10000); } }); $('remote-play-workers').addEventListener('click', async event => { const row = event.target.closest('[data-remote-worker-id]'); if (!row || !event.target.closest('.remote-play-worker-remove') || !await showExactConfirmation('Revoke this worker, erase its HMAC key and cancel open jobs?', 'VERWIJDER')) return; try { await ecosystemBusy(event.target.closest('button'), () => window.pork.remotePlayWorkerRemove(row.dataset.remoteWorkerId)); showToast('Remote Play worker revoked.', 'ok'); await loadRemotePlay(); } catch (error) { showToast(error.message, 'error'); } }); const runWorkerAction = async (button, action, phrase = null) => { const worker = remotePlaySelectedWorker(_remotePlayPacket || {}); if (!worker) return showToast('Select a Remote Play worker first.', 'error'); if (phrase && !await showExactConfirmation(`Deze vaste actie wordt uitgevoerd op ${worker.label}.`, phrase)) return; try { const job = await ecosystemBusy(button, () => window.pork.remotePlayWorkerAction({ workerId: worker.id, action, confirmation: phrase || undefined })); showToast(`${action} is queued securely for the worker (${job.id.slice(0, 8)}).`, 'ok', 9000); await loadRemotePlay(); } catch (error) { showToast(error.message, 'error', 12000); } }; $('remote-play-diagnose').addEventListener('click', event => runWorkerAction(event.currentTarget, 'diagnose')); $('remote-play-open-client').addEventListener('click', event => runWorkerAction(event.currentTarget, 'open-client', 'OPEN CHIAKI')); $('remote-play-install-client').addEventListener('click', event => runWorkerAction(event.currentTarget, 'install-client', 'INSTALL CHIAKI')); $('remote-play-update-client').addEventListener('click', event => runWorkerAction(event.currentTarget, 'update-client', 'UPDATE CHIAKI')); $('remote-play-stop').addEventListener('click', event => runWorkerAction(event.currentTarget, 'stop-session', 'STOP REMOTE PLAY')); $('remote-play-profile-export').addEventListener('click', event => ecosystemBusy(event.currentTarget, () => window.pork.remotePlayProfileExport()).catch(error => showToast(error.message, 'error'))); $('remote-play-upstream').addEventListener('click', () => window.pork.openShell('https://streetpea.github.io/chiaki-ng/setup/configuration/')); $('websrv-files-root').addEventListener('click', event => ecosystemBusy(event.currentTarget, () => loadWebsrvFiles('/')).catch(error => showToast(error.message, 'error'))); $('websrv-files-path').addEventListener('click', event => { const button = event.target.closest('.websrv-files-back'); if (button) loadWebsrvFiles(button.dataset.parent).catch(error => showToast(error.message, 'error')); }); $('websrv-files-list').addEventListener('click', async event => { const row = event.target.closest('.websrv-file-row'); if (!row) return; try { if (event.target.closest('.websrv-file-open')) await loadWebsrvFiles(row.dataset.path); if (event.target.closest('.websrv-file-download')) await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemWebsrvDownload(row.dataset.path)); } catch (error) { showToast(error.message, 'error', 10000); } }); $('websrv-smb-source').addEventListener('change', () => { _websrvSmbPath = ''; if ($('websrv-smb-source').value) loadWebsrvSmb('').catch(error => showToast(error.message, 'error')); else { $('websrv-smb-list').innerHTML = '

Choose a saved SMB source.

'; $('websrv-smb-path').innerHTML = ''; } }); $('websrv-smb-path').addEventListener('click', event => { const button = event.target.closest('.websrv-smb-back'); if (button) loadWebsrvSmb(button.dataset.parent).catch(error => showToast(error.message, 'error')); }); $('websrv-smb-list').addEventListener('click', async event => { const row = event.target.closest('.websrv-file-row'); if (!row) return; try { if (event.target.closest('.websrv-file-open')) await loadWebsrvSmb(row.dataset.path); if (event.target.closest('.websrv-file-download')) await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemWebsrvSmbDownload($('websrv-smb-source').value, row.dataset.path)); } catch (error) { showToast(error.message, 'error', 10000); } }); $('ecosystem-homebrew-scan').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderHomebrew(await window.pork.ecosystemHomebrewScan())).catch(error => showToast(error.message, 'error'))); $('ecosystem-websrv-discover').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const result = await window.pork.ecosystemWebsrvDiscover(); renderWebsrvDiscovery(result); showToast(`${result.services?.length || 0} mDNS service(s) found.`, 'ok'); }).catch(error => showToast(error.message, 'error'))); $('ecosystem-homebrew-webui').addEventListener('click', async () => { try { const urls = await window.pork.ecosystemSourceWebUis(); window.pork.openShell(urls.websrv); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-homebrew').addEventListener('click', async event => { const row = event.target.closest('.ecosystem-homebrew-card'); if (!row || !event.target.closest('.ecosystem-homebrew-launch')) return; if (!await showExactConfirmation('websrv warns that launching can crash when other homebrew is already active.', 'START HOMEBREW')) return; try { await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemHomebrewLaunch(row.dataset.id, 'START HOMEBREW')); showToast('Homebrew launch sent to websrv.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-pacbrew-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderPacbrew(await window.pork.ecosystemPacbrewRefresh())).catch(error => showToast(error.message, 'error'))); $('ecosystem-pacbrew-search').addEventListener('input', () => { if (_pacbrewPacket) renderPacbrew(_pacbrewPacket); }); $('ecosystem-pacbrew-kind').addEventListener('change', () => { if (_pacbrewPacket) renderPacbrew(_pacbrewPacket); }); $('ecosystem-pacbrew').addEventListener('click', async event => { const upstream = event.target.closest('.ecosystem-open-upstream'); if (upstream) { window.pork.openShell(upstream.dataset.url); return; } const button = event.target.closest('.ecosystem-pacbrew-inspect'); const row = event.target.closest('[data-pacbrew-name]'); if (!button || !row) return; try { const item = await ecosystemBusy(button, () => window.pork.ecosystemPacbrewPackage(row.dataset.pacbrewName)); $('ecosystem-pacbrew-detail').innerHTML = `
${escHtml(item.pkgname || item.name)} · ${escHtml(item.version || 'git')}${escHtml(item.description || 'No description in PKGBUILD.')}Runtime: ${(item.dependencies || []).map(escHtml).join(', ') || 'none'} · build: ${(item.buildDependencies || []).map(escHtml).join(', ') || 'none'} · license: ${(item.licenses || []).map(escHtml).join(', ') || 'unknown'}Recipe ${escHtml(String(item.sourceSha || '').slice(0, 12))} was parsed as text only; nothing was executed.
`; } catch (error) { showToast(error.message, 'error', 10000); } }); $('ecosystem-source-form').addEventListener('submit', async event => { event.preventDefault(); const button = event.submitter; try { const sources = await ecosystemBusy(button, () => window.pork.ecosystemSourceSave({ id: $('ecosystem-source-id').value || undefined, label: $('ecosystem-source-label').value, protocol: $('ecosystem-source-protocol').value, host: $('ecosystem-source-host').value, port: $('ecosystem-source-port').value || undefined, path: $('ecosystem-source-path').value, username: $('ecosystem-source-user').value, password: $('ecosystem-source-password').value || undefined })); renderSources(sources); event.target.reset(); $('ecosystem-source-path').value = '/'; $('ecosystem-source-id').value = ''; showToast('Remote source saved with encryption.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-sources').addEventListener('click', async event => { const row = event.target.closest('.ecosystem-source-row'); if (!row) return; const sources = await window.pork.ecosystemSources(); const source = sources.find(item => item.id === row.dataset.id); if (!source) return; try { if (event.target.closest('.ecosystem-source-test')) { const result = await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemSourceTest(row.dataset.id)); showToast(result.ok ? `Source reachable in ${result.latencyMs} ms.` : `Source unreachable: ${result.error || result.status}`, result.ok ? 'ok' : 'error'); } if (event.target.closest('.ecosystem-source-edit')) { $('ecosystem-source-id').value = source.id; $('ecosystem-source-label').value = source.label; $('ecosystem-source-protocol').value = source.protocol; $('ecosystem-source-host').value = source.host; $('ecosystem-source-port').value = source.port; $('ecosystem-source-path').value = source.path; $('ecosystem-source-user').value = source.username || ''; $('ecosystem-source-password').value = ''; } if (event.target.closest('.ecosystem-source-delete') && await showExactConfirmation(`Delete ${source.label}?`, 'DELETE')) { renderSources(await window.pork.ecosystemSourceRemove(source.id)); } } catch (error) { showToast(error.message, 'error'); } }); $('ezremote-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadSources).catch(error => showToast(error.message, 'error'))); $('ezremote-download-form').addEventListener('submit', async event => { event.preventDefault(); const destinationPath = $('ezremote-destination').value.trim(); const required = `DOWNLOAD ${destinationPath}`; const confirmation = await showExactConfirmation('ezRemote writes to the PS5 and cannot safely cancel an active download.', required); if (!confirmation) return; try { const result = await ecosystemBusy(event.submitter, () => window.pork.ezRemoteDownload({ sourceId: $('ezremote-source').value, sourcePath: $('ezremote-source-path').value.trim(), destinationPath, size: Number($('ezremote-size').value || 0), confirmation, })); showToast(result.deduplicated ? 'Reused the existing ezRemote task.' : 'ezRemote download safely queued in the durable transfer queue.', 'ok', 9000); } catch (error) { showToast(error.message, 'error', 10000); } }); $('ecosystem-ezremote-webui').addEventListener('click', async () => { try { window.pork.openShell((await window.pork.ecosystemSourceWebUis()).ezremote); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-dpi-webui').addEventListener('click', async () => { try { window.pork.openShell((await window.pork.ecosystemSourceWebUis()).dpiV2); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-plugins-scan').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderPlugins(await window.pork.ecosystemPluginsScan())).catch(error => showToast(error.message, 'error'))); $('ecosystem-plugin-install').addEventListener('click', async event => { const targetRoot = $('ecosystem-plugin-target').value; const confirmation = await showExactConfirmation(`Install one selected ELF or PRX into ${targetRoot}? An existing file is backed up and the upload is verified.`, 'INSTALL PLUGIN'); if (!confirmation) return; try { const result = await ecosystemBusy(event.currentTarget, () => window.pork.ecosystemPluginInstall(targetRoot, confirmation)); if (!result) return; renderPlugins(result.inventory); showToast(`${result.filename} installed and verified · ${String(result.sha256).slice(0, 12)}…`, 'ok', 10000); } catch (error) { showToast(error.message, 'error', 10000); } }); $('ecosystem-plugins').addEventListener('click', async event => { const upstream = event.target.closest('.ecosystem-open-upstream'); if (upstream) { window.pork.openShell(upstream.dataset.url); return; } const row = event.target.closest('.ecosystem-plugin-row'); if (!row) return; try { if (event.target.closest('.ecosystem-plugin-inspect')) { const info = await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemPluginInspect(row.dataset.id)); let output = row.nextElementSibling?.classList.contains('ecosystem-plugin-inspection') ? row.nextElementSibling : null; if (!output) { output = document.createElement('pre'); output.className = 'ecosystem-plugin-inspection'; row.insertAdjacentElement('afterend', output); } output.textContent = `SHA-256: ${info.sha256}\nTitle IDs: ${info.titleIds.join(', ') || 'none reliably detected'}\nVersion candidates: ${info.versions.join(', ') || 'none detected'}`; } if (event.target.closest('.ecosystem-plugin-toggle')) { const currentEnabled = event.target.closest('button').textContent.includes('Disable'); if (!await showExactConfirmation(`${currentEnabled ? 'Disable' : 'Enable'} this plugin? Chimera creates a backup first and etaHEN must then be reloaded.`, 'WIJZIG PLUGIN')) return; const result = await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemPluginToggle(row.dataset.id, !currentEnabled, 'WIJZIG PLUGIN')); renderPlugins(result.inventory); } } catch (error) { showToast(error.message, 'error'); } }); const scheduleCheatLoad = () => { _ecosystemCheatOffset = 0; clearTimeout(_ecosystemCheatTimer); _ecosystemCheatTimer = setTimeout(() => loadCheatsV2().catch(error => showToast(error.message, 'error')), 250); }; $('ecosystem-cheats-search').addEventListener('input', scheduleCheatLoad); $('ecosystem-cheats-platform').addEventListener('change', scheduleCheatLoad); $('ecosystem-cheats-kind').addEventListener('change', scheduleCheatLoad); $('ecosystem-cheats-installed').addEventListener('change', scheduleCheatLoad); $('ecosystem-cheats-sync').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { await window.pork.ecosystemCheatsSync(); _ecosystemCheatOffset = 0; await loadCheatsV2(); showToast('Cheat and patch sources synchronized.', 'ok'); }).catch(error => showToast(error.message, 'error', 9000))); $('ecosystem-cheats').addEventListener('click', async event => { const pager = event.target.closest('.ecosystem-cheat-page'); if (pager && !pager.disabled) { _ecosystemCheatOffset = Number(pager.dataset.offset) || 0; await loadCheatsV2(); return; } const source = event.target.closest('.ecosystem-open-upstream'); if (source) return window.pork.openShell(source.dataset.url); const row = event.target.closest('.ecosystem-cheat-row'); if (!row || !event.target.closest('.ecosystem-cheat-deploy')) return; const versionWarning = row.querySelector('.operations-status-warning')?.textContent.includes('Version mismatch'); const conflict = row.classList.contains('conflict'); if (!await showExactConfirmation(`Send this cheat or patch to the documented PS5 folder?${versionWarning ? '\n\nThe game version differs; you explicitly confirm that you want to continue.' : ''}${conflict ? '\n\nOther formats for the same version will be replaced safely.' : ''}`, 'INSTALLEER CHEAT')) return; try { await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemCheatDeploy({ key: row.dataset.key, forceVersion: versionWarning, replaceConflicts: conflict, confirmation: 'INSTALLEER CHEAT' })); showToast('Cheat or patch installed with backup and checksum verification.', 'ok'); } catch (error) { showToast(error.message, 'error', 9000); } }); $('ecosystem-saves-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderRemoteSaves(await window.pork.ecosystemRemoteSaves())).catch(error => showToast(error.message, 'error'))); $('ecosystem-saves-all').addEventListener('click', async event => { if (!await showConfirm('Export every discovered title to Save Vault one by one? This can take a long time and use substantial storage.')) return; try { const result = await ecosystemBusy(event.currentTarget, () => window.pork.ecosystemSaveSnapshotAll()); showToast(`${result.succeeded}/${result.total} save backups created.`, result.failed ? 'info' : 'ok', 9000); await loadSaveVault(); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-remote-saves').addEventListener('click', async event => { const row = event.target.closest('.ecosystem-save-row'); if (!row || !event.target.closest('.ecosystem-save-snapshot')) return; try { await ecosystemBusy(event.target.closest('button'), () => window.pork.ecosystemSaveSnapshot(row.dataset.titleId)); showToast('Save Vault-snapshot gemaakt en geverifieerd.', 'ok'); await loadSaveVault(); } catch (error) { showToast(error.message, 'error'); } }); $('ecosystem-save-vault').addEventListener('click', async event => { const row = event.target.closest('.ecosystem-vault-row'); if (!row) return; const button = event.target.closest('button'); try { if (button?.classList.contains('ecosystem-save-verify')) { const result = await ecosystemBusy(button, () => window.pork.ecosystemSaveVerify(row.dataset.id)); showToast(result.ok ? `${result.titleId}: SHA-256, ZIP CRC and ${result.archive.entries.length} save file(s) are valid.` : `Snapshot rejected: ${result.error}`, result.ok ? 'ok' : 'error', 10000); } if (button?.classList.contains('ecosystem-save-restore')) { if (!_ecosystemSaveCapabilities?.automatedRestore) throw new Error('Restore capability is not active.'); let plan = await ecosystemBusy(button, () => window.pork.ecosystemSaveRestorePlan(row.dataset.id)); if (plan.requiresTargetUser) { const choices = plan.users.map(user => `${user.uid} (${user.name || 'name unknown'})`).join('\n'); const targetUid = await showPrompt(`Choose the exact target UID for ${plan.titleId}:\n\n${choices}`, plan.users[0]?.uid || ''); if (!targetUid) return; plan = await ecosystemBusy(button, () => window.pork.ecosystemSaveRestorePlan(row.dataset.id, targetUid.trim())); } const incoming = plan.impact.incomingSaves.map(save => `• ${save.name} — ${ecosystemBytes(save.bytes)} — ${String(save.sha256).slice(0, 12)}…`).join('\n'); const approved = await showConfirm( `DRY RUN — nothing has changed on the PS5 yet.\n\n` + `Title: ${plan.titleId}\nTarget user: ${plan.targetUid}\nExisting saves: ${plan.impact.existingSaves.length}\nIncoming saves: ${plan.impact.incomingSaves.length}\n\n${incoming}\n\n` + 'If you continue, Chimera first creates a new verified snapshot. Account mismatch, zeroed param.sfo or changed source or destination saves stop the process. A byte-for-byte export check follows. Automatic rollback is intentionally not performed. Continue to the exact confirmation?' ); if (!approved) return; const phrase = await showExactConfirmation('The dry run was approved. Now confirm the actual restore.', plan.confirmationPhrase); if (!phrase) return; const result = await ecosystemBusy(button, () => window.pork.ecosystemSaveRestore(plan.token, phrase)); showToast(`${result.titleId} restored safely: ${result.verifiedEntries} file(s) verified byte-for-byte. Pre- and post-snapshots were retained.`, 'ok', 15000); await loadSaveVault(); } if (button?.classList.contains('ecosystem-save-download')) await ecosystemBusy(button, () => window.pork.ecosystemSaveDownload(row.dataset.id)); if (button?.classList.contains('ecosystem-save-delete') && await showExactConfirmation('Permanently delete this local save backup?', 'DELETE')) { await window.pork.ecosystemSaveDelete(row.dataset.id); await loadSaveVault(); } } catch (error) { showToast(error.message, 'error', 15000); } }); $('itemzflow-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadItemzflow).catch(error => showToast(error.message, 'error'))); $('itemzflow-homebrew-open').addEventListener('click', () => navigateEcosystemTab('homebrew')); $('itemzflow-log-select').addEventListener('change', renderItemzflowEvents); $('itemzflow-level').addEventListener('change', renderItemzflowEvents); $('itemzflow-jobs-only').addEventListener('change', renderItemzflowEvents); $('platform-lab-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadPlatformLab).catch(error => showToast(error.message, 'error'))); $('platform-bindings-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderPlatformBindings(await window.pork.platformBindingsRefresh())).catch(error => showToast(error.message, 'error'))); $('platform-bindings').addEventListener('click', event => { const button = event.target.closest('.platform-binding-review'); if (!button) return; ecosystemBusy(button, async () => { await window.pork.platformBindingReview({ id: button.dataset.id, tag: button.dataset.tag }); renderPlatformBindings(await window.pork.platformBindings()); }).then(() => showToast(platformLabText('reviewed', 'Upstream evidence reviewed.'), 'ok')).catch(error => showToast(error.message, 'error')); }); $('platform-autoloader-preview').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderAutoloaderPreview(await window.pork.unifiedAutoloaderPreview({ id: $('platform-autoloader-profile').value }))).catch(error => showToast(error.message, 'error'))); $('platform-autoloader-profile').addEventListener('change', () => { const profile = (_platformLabState.profiles || []).find(item => item.id === $('platform-autoloader-profile').value); if (profile) $('platform-profile-json').value = JSON.stringify(profile, null, 2); }); $('platform-profile-save').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const profile = JSON.parse($('platform-profile-json').value); await window.pork.unifiedAutoloaderSave(profile); renderAutoloader(await window.pork.unifiedAutoloaderList()); }).then(() => showToast(platformLabText('profileSaved', 'Profile saved.'), 'ok')).catch(error => showToast(error.message, 'error'))); $('platform-profile-delete').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { await window.pork.unifiedAutoloaderRemove({ id: $('platform-autoloader-profile').value }); renderAutoloader(await window.pork.unifiedAutoloaderList()); }).then(() => showToast(platformLabText('profileDeleted', 'Profile deleted.'), 'ok')).catch(error => showToast(error.message, 'error'))); $('platform-profile-recovery').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const result = await window.pork.unifiedAutoloaderRecovery({ id: $('platform-autoloader-profile').value }); $('platform-profile-output').innerHTML = `
${escHtml(JSON.stringify(result, null, 2))}
`; }).catch(error => showToast(error.message, 'error'))); $('platform-websrv-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderPlatformWebsrv(await window.pork.platformWebsrvDiscover())).catch(error => showToast(error.message, 'error'))); $('platform-remote-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderRemoteIntelligence(await window.pork.remotePlayIntelligence())).catch(error => showToast(error.message, 'error'))); $('platform-shadps4-scan').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const rootId = $('platform-shadps4-root').value; if (rootId) await window.pork.shadps4Select({ rootId }); renderShadps4Scan(await window.pork.shadps4Scan()); }).catch(error => showToast(error.message, 'error'))); $('platform-artifact-inspect').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { await window.pork.platformArtifactInspect({ rootId: $('platform-artifact-root').value, relativePath: $('platform-artifact-relative').value }); renderArtifacts(await window.pork.platformArtifacts()); }).then(() => showToast(platformLabText('artifactHashed', 'Artifact hashed.'), 'ok')).catch(error => showToast(error.message, 'error'))); $('platform-artifacts').addEventListener('click', event => { const button = event.target.closest('.platform-artifact-review'); if (!button) return; ecosystemBusy(button, async () => { await window.pork.platformArtifactReview({ artifactId: button.dataset.id, sha256: button.dataset.sha }); renderArtifacts(await window.pork.platformArtifacts()); }).then(() => showToast(platformLabText('hashReviewed', 'Exact hash reviewed.'), 'ok')).catch(error => showToast(error.message, 'error')); }); $('platform-autoloader-stage').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { const artifactIds = [...document.querySelectorAll('.platform-artifact-select:checked')].map(input => input.value); const result = await window.pork.platformAutoloaderStage({ profileId: $('platform-autoloader-profile').value, targetRootId: $('platform-stage-root').value, artifactIds }); $('platform-stage-output').innerHTML = `
${escHtml(result.lifecycleState)} · ${escHtml(result.destinationLabel)}
`; renderArtifacts(await window.pork.platformArtifacts()); }).then(() => showToast(platformLabText('staged', 'Profile staged atomically.'), 'ok')).catch(error => showToast(error.message, 'error'))); $('platform-search-form').addEventListener('submit', event => { event.preventDefault(); ecosystemBusy(event.submitter, async () => renderSearch(await window.pork.platformSearch({ query: $('platform-search-query').value }))).catch(error => showToast(error.message, 'error')); }); $('platform-shadps4-configure').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => { await window.pork.shadps4LaunchConfigure({ rootId: $('platform-shadps4-exe-root').value, relativePath: $('platform-shadps4-exe-relative').value }); renderLaunchProbe(await window.pork.shadps4LaunchProbe()); }).then(() => showToast(platformLabText('launcherConfigured', 'Launcher configured.'), 'ok')).catch(error => showToast(error.message, 'error'))); $('platform-shadps4').addEventListener('click', event => { const button = event.target.closest('.platform-shadps4-plan'); if (!button) return; ecosystemBusy(button, async () => { _platformLabState.launchPlan = await window.pork.shadps4LaunchPlan({ itemId: button.dataset.id }); $('platform-shadps4-launch').innerHTML = `
${escHtml(_platformLabState.launchPlan.itemLabel)}
${escHtml(platformLabText('exactConfirmation', 'Enter the exact confirmation shown below.'))}
`; }).catch(error => showToast(error.message, 'error')); }); $('platform-shadps4-launch').addEventListener('click', event => { const button = event.target.closest('#platform-shadps4-launch-confirm'); if (!button || !_platformLabState.launchPlan) return; ecosystemBusy(button, async () => { const result = await window.pork.shadps4Launch({ token: _platformLabState.launchPlan.token, confirmation: $('platform-shadps4-confirmation').value }); _platformLabState.launchPlan = null; $('platform-shadps4-launch').innerHTML = `
${escHtml(platformLabText('started', 'Started'))} · PID ${result.pid}
`; }).catch(error => showToast(error.message, 'error')); }); $('ecosystem-linux-refresh').addEventListener('click', event => ecosystemBusy(event.currentTarget, loadLinuxLab).catch(error => showToast(error.message, 'error'))); $('ecosystem-linux-components').addEventListener('click', event => { const button = event.target.closest('.ecosystem-open-upstream'); if (button) window.pork.openShell(button.dataset.url); }); $('ecosystem-timeline-start').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderTimeline(await window.pork.ecosystemTimelineStart(Number($('ecosystem-timeline-port').value)))).catch(error => showToast(error.message, 'error'))); $('ecosystem-timeline-stop').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderTimeline(await window.pork.ecosystemTimelineStop())).catch(error => showToast(error.message, 'error'))); $('ecosystem-timeline-import').addEventListener('click', event => ecosystemBusy(event.currentTarget, async () => renderTimeline(await window.pork.ecosystemTimelineImport())).catch(error => showToast(error.message, 'error'))); $('ecosystem-timeline-options').addEventListener('submit', event => { event.preventDefault(); ecosystemBusy(event.submitter, async () => renderTimeline(await window.pork.ecosystemTimelineConfigure({ enabled: $('ecosystem-timeline-persist').checked, retentionDays: Number($('ecosystem-timeline-retention').value), maxEvents: Number($('ecosystem-timeline-max').value) }))).then(() => showToast('Timeline policy saved; exports remain redacted by default.', 'ok')).catch(error => showToast(error.message, 'error')); }); $('ecosystem-timeline-export').addEventListener('click', event => ecosystemBusy(event.currentTarget, () => window.pork.ecosystemTimelineExport({ search: $('ecosystem-timeline-search').value, level: $('ecosystem-timeline-level').value })).catch(error => showToast(error.message, 'error'))); const scheduleTimelineFilter = () => { clearTimeout(_timelineFilterTimer); _timelineFilterTimer = setTimeout(() => loadTimeline().catch(error => showToast(error.message, 'error')), 250); }; $('ecosystem-timeline-search').addEventListener('input', scheduleTimelineFilter); $('ecosystem-timeline-level').addEventListener('change', scheduleTimelineFilter); window.pork?.on?.('ecosystem:timeline', () => { if (_ecosystemActiveTab === 'timeline' && document.querySelector('#page-ecosystem.active')) loadTimeline().catch(() => {}); }); window.pork?.on?.('ecosystem:health', () => { if (_ecosystemActiveTab === 'overview' && document.querySelector('#page-ecosystem.active')) loadEcosystemOverview().catch(() => {}); }); window.pork?.on?.('platform:operation', () => { if (_ecosystemActiveTab === 'platform-lab') window.pork.platformOperations().then(renderOperations).catch(() => {}); }); window.pork?.on?.('game-updates:progress', () => { if (_ecosystemActiveTab !== 'game-updates' || !document.querySelector('#page-ecosystem.active')) return; clearTimeout(_gameUpdateRefreshTimer); _gameUpdateRefreshTimer = setTimeout(() => loadGameUpdates().catch(() => {}), 500); }); } async function loadEcosystem({ background = false } = {}) { buildEcosystemPage(); if (background) return loadEcosystemOverview(); const requestedTab = window.chimeraRequestedEcosystemTab; if (requestedTab) { _ecosystemActiveTab = requestedTab; window.chimeraRequestedEcosystemTab = null; document.querySelectorAll('.ecosystem-tab').forEach(button => { const active = button.dataset.ecosystemTab === requestedTab; button.classList.toggle('active', active); button.setAttribute('aria-selected', String(active)); button.tabIndex = active ? 0 : -1; }); document.querySelectorAll('.ecosystem-panel').forEach(panel => { const active = panel.dataset.ecosystemPanel === requestedTab; panel.classList.toggle('active', active); panel.hidden = !active; }); } const tabLoad = loadEcosystemTab(_ecosystemActiveTab); if (_ecosystemActiveTab !== 'overview' && !_ecosystemLoaded) { const headerLoad = loadEcosystemOverview().catch(error => { $('ecosystem-health').textContent = localizedEcosystemError(error.message || 'Status unavailable'); $('ecosystem-health').className = 'operations-health warning'; }); await Promise.all([tabLoad, headerLoad]); return; } await tabLoad; } // end