// ── Payload Manager ─────────────────────────────────────────────────────────── function renderPs5PayloadManagerObservability(snapshot) { const root = $('pldmgr-observability'); if (!root || !snapshot) return; const autoload = snapshot.autoload || {}; const processes = snapshot.processes || {}; const logs = snapshot.logs || {}; const stateLabels = { idle: window.t('payload.state.idle', 'idle'), countdown: window.t('payload.state.countdown', 'countdown'), running: window.t('payload.state.running', 'running'), done: window.t('payload.state.done', 'done'), unknown: window.t('common.unknown', 'unknown'), }; const stateClasses = { done: 'badge-green', running: 'badge-blue', countdown: 'badge-yellow', idle: '', unknown: 'badge-red' }; const state = autoload.supported ? (autoload.state || 'unknown') : 'unknown'; const daemonItems = (processes.items || []).filter(item => item.daemon).slice(0, 40); root.hidden = false; $('pldmgr-autoload-metric').textContent = autoload.supported ? `${autoload.done || 0}/${autoload.total || autoload.list?.length || 0}` : 'n/b'; $('pldmgr-process-metric').textContent = processes.supported ? String(processes.total || 0) : 'n/b'; $('pldmgr-memory-metric').textContent = processes.supported ? `${Number(processes.memoryMiB || 0).toLocaleString(window.i18n?.current || 'en', { maximumFractionDigits: 1 })} MiB` : 'n/a'; $('pldmgr-log-metric').textContent = logs.supported ? String(logs.total || 0) : 'n/b'; $('pldmgr-autoload-state').textContent = stateLabels[state] || state; $('pldmgr-autoload-state').className = `badge ${stateClasses[state] || ''}`.trim(); const detail = []; if (autoload.supported) { if (autoload.current) detail.push(window.tf('payload.current', 'Current: {value}', { value: autoload.current })); if (autoload.remaining > 0) detail.push(window.tf('payload.startsIn', 'starts in {seconds}s', { seconds: autoload.remaining })); if (autoload.list?.length) detail.push(window.tf('payload.itemsConfigured', '{count} item(s) configured', { count: autoload.list.length })); if (autoload.scanUsbPayloads) detail.push(window.t('payload.usbScanActive', 'USB scan active')); } else detail.push(autoload.unavailable || window.t('payload.endpointUnsupported', 'This endpoint is not supported by this Payload Manager version.')); $('pldmgr-autoload-detail').textContent = detail.join(' · '); $('pldmgr-process-list').innerHTML = processes.supported ? (daemonItems.length ? daemonItems.map(item => `
${escHtml(item.name)}PID ${item.pid}
${Number(item.memoryMiB || 0).toLocaleString(window.i18n?.current || 'en', { maximumFractionDigits: 1 })} MiB
`).join('') : `

${escHtml(window.t('payload.noDaemons', 'No separate payload daemons detected.'))}

`) : `

${escHtml(processes.unavailable || window.t('payload.processesUnsupported', 'Processes are unavailable in this Payload Manager version.'))}

`; $('pldmgr-log-output').textContent = logs.supported && logs.lines?.length ? `${logs.truncated ? `[${window.t('payload.latestLogsOnly', 'latest lines only')}]\n` : ''}${logs.lines.join('\n')}` : (logs.unavailable || window.t('payload.noLogs', 'No log lines yet.')); $('pldmgr-observability-note').textContent = window.tf( 'payload.snapshotNote', 'Read-only snapshot from {time}. Chimera never terminates a process automatically.', { time: new Date(snapshot.checkedAt || Date.now()).toLocaleTimeString(window.i18n?.current || 'en') }, ); } function renderPs5PayloadManagerInventory(inventory) { const list = $('pldmgr-installed-list'); if (!list) return; const payloads = (inventory?.payloads || []).slice(0, 200); list.innerHTML = payloads.length ? payloads.map(item => `
${escHtml(item.name || item.filename)}${escHtml(item.version || window.t('payload.unknownVersion', 'unknown version'))}${item.checksumVerified ? ` · ${escHtml(window.t('payload.checksumKnown', 'checksum known'))}` : ''}
`).join('') : `

${escHtml(window.t('payload.noneInstalled', 'No installed payloads reported.'))}

`; } async function refreshPs5PayloadManagerObservability({ quiet = false } = {}) { if (!window.pork.pldmgrObservability) return null; try { const result = await window.pork.pldmgrObservability(); renderPs5PayloadManagerObservability(result); return result; } catch (error) { if (!quiet) showToast(error.message, 'error'); return null; } } async function connectPs5PayloadManager({ quiet = false, persistPort = false } = {}) { const status = $('pldmgr-status'); const version = $('pldmgr-version'); const frame = $('pldmgr-frame'); if (!window.pork.pldmgrProbe || !status || !frame) return; if (!quiet) status.textContent = window.t('payload.connectingPs5', 'Connecting to PS5…'); try { const port = Number($('pldmgr-port')?.value || 8084); if (persistPort) await window.pork.setSettings({ payloadManagerPort: port }); const settings = await window.pork.getSettings(); if (!settings.ftpHost) throw new Error(window.t('payload.configureIp', 'Configure the PS5 IP address first.')); const [result] = await Promise.all([ window.pork.pldmgrProbe(), refreshPs5PayloadManagerObservability({ quiet: true }), ]); const versionValue = typeof result.version === 'string' ? result.version : (result.version?.version || result.version?.menu_version || 'online'); version.textContent = String(versionValue).replace(/^"|"$/g, '') || 'online'; const payloadCount = Number(result.inventory?.total || 0); renderPs5PayloadManagerInventory(result.inventory); const sourceCount = Number(result.sources?.total || 0); const repositoryCount = Number(result.repository?.total || 0); const autoloadRaw = result.autoload?.list || result.autoload?.AUTOLOAD_LIST || ''; const autoloadCount = Array.isArray(autoloadRaw) ? autoloadRaw.length : String(autoloadRaw || '').split(',').filter(Boolean).length; status.textContent = window.tf( 'payload.connectedSummary', 'Connected · {payloads} installed · {autoload} autoload · {sources} sources · {available} available', { payloads: payloadCount, autoload: autoloadCount, sources: sourceCount, available: repositoryCount }, ); const embedUrl = await window.pork.pldmgrEmbedUrl(); frame.dataset.url = embedUrl; if (!quiet || frame.dataset.userOpened === 'true') { frame.src = embedUrl; frame.hidden = false; } else { frame.hidden = true; } } catch (error) { version.textContent = 'offline'; frame.hidden = true; if ($('pldmgr-observability')) $('pldmgr-observability').hidden = true; status.textContent = `${window.t('payload.unreachable', 'Unavailable')}: ${error.message}`; } } $('btn-pldmgr-refresh')?.addEventListener('click', () => connectPs5PayloadManager()); $('btn-pldmgr-logs-refresh')?.addEventListener('click', () => refreshPs5PayloadManagerObservability()); $('pldmgr-installed-list')?.addEventListener('click', async event => { const button = event.target.closest('.pldmgr-safe-launch'); if (!button) return; const payloadPath = button.dataset.payloadPath; button.disabled = true; try { const preflight = await window.pork.pldmgrLaunchPreflight(payloadPath); let confirmConflict = false; if (preflight.conflict) { const conflictConfirmation = await showExactConfirmation( `${preflight.warning}\n\nActive: ${preflight.active.map(item => `${item.name} (PID ${item.pid})`).join(', ')}\n\nA duplicate instance can make the console unstable.`, 'START PAYLOAD', ); if (!conflictConfirmation) return; confirmConflict = true; } else if (!await showExactConfirmation(`Start ${preflight.selected} on the PS5?`, 'START PAYLOAD')) return; await window.pork.pldmgrLaunch(payloadPath, confirmConflict, 'START PAYLOAD'); showToast(`${preflight.selected} was sent to Payload Manager.`, 'ok'); setTimeout(() => refreshPs5PayloadManagerObservability({ quiet: true }), 1_000); } catch (error) { showToast(error.message, 'error'); } finally { button.disabled = false; } }); $('pldmgr-port')?.addEventListener('change', () => connectPs5PayloadManager({ persistPort: true })); $('btn-pldmgr-open')?.addEventListener('click', async () => { try { const frame = $('pldmgr-frame'); const url = frame?.dataset.url || await window.pork.pldmgrEmbedUrl(); if (window.pork.isWebMode && String(url).startsWith('/')) { frame.dataset.userOpened = 'true'; frame.src = url; frame.hidden = false; frame.scrollIntoView({ behavior: 'smooth', block: 'start' }); } else await window.pork.openShell(url); } catch (error) { setStatus(error.message, 'error'); } }); setInterval(() => { const page = $('page-jailbreak'); if (page?.classList.contains('active') && $('pldmgr-version')?.textContent !== 'offline') { refreshPs5PayloadManagerObservability({ quiet: true }); } }, 15_000); async function loadPs5ServiceMatrix() { const grid = $('service-matrix-grid'); const summary = $('service-matrix-summary'); const button = $('btn-service-matrix-refresh'); if (!grid || !window.pork.ps5ServiceMatrix) return; if (button) button.disabled = true; summary.textContent = 'controleren…'; try { const result = await window.pork.ps5ServiceMatrix(); summary.textContent = `${result.online}/${result.total} online`; grid.innerHTML = result.services.map(service => `
${escHtml(service.name)} :${service.port}${service.online && service.latencyMs != null ? ` · ${service.latencyMs} ms` : ''}

${service.online ? `${escHtml(service.section)} is reachable.` : escHtml(service.hint)}

`).join(''); } catch (error) { summary.textContent = 'check failed'; grid.innerHTML = `

${escHtml(error.message)}

`; } finally { if (button) button.disabled = false; } } $('btn-service-matrix-refresh')?.addEventListener('click', loadPs5ServiceMatrix); async function renderSettingsPayloadSources() { const sources = await window.pork.payloadSourcesList(); const cont = $('settings-payload-sources'); if (!sources.length) { cont.innerHTML = '

No sources yet. Add one below.

'; return; } const expertMode = document.body.classList.contains('chimera-expert-mode'); const visibleSources = sources.filter(source => expertMode || payloadCatalogGroup({ source, files: [] }) !== 'expert' ); const hiddenCount = sources.length - visibleSources.length; const hiddenNotice = hiddenCount > 0 ? `

${escHtml(window.tf( 'payload.settings.expertHidden', '{count} expert source(s) hidden. Enable Expert tools to manage them.', { count: hiddenCount }, ))}

` : ''; cont.innerHTML = hiddenNotice + visibleSources.map(s => `
${escHtml(s.name)} ${escHtml(s.github_url)} ${s.latest_tag ? `${escHtml(s.latest_tag)}` : ''} ${s.built_in ? `${escHtml(window.t('payload.builtIn', 'built-in'))}` : ''}
`).join(''); } window.addEventListener('chimera:expert-mode-changed', () => { if ($('page-settings')?.classList.contains('active')) renderSettingsPayloadSources().catch(error => showToast(error.message, 'error')); }); function payloadStatusBadge(file) { if (file.local_hash && file.local_version && file.version && file.local_version !== file.version) { return `↓ ${escHtml(window.t('payload.status.update', 'Update available'))}`; } if (file.local_hash && file.remote_hash) { if (file.local_hash === file.remote_hash) return `✓ ${escHtml(window.t('payload.status.current', 'Up to date'))}`; return `↓ ${escHtml(window.t('payload.status.update', 'Update available'))}`; } if (file.local_hash) return `${escHtml(window.t('payload.status.downloadedUnverified', 'Downloaded (no checksum)'))}`; if (file.remote_hash) return `${escHtml(window.t('payload.status.notDownloaded', 'Not downloaded'))}`; return '—'; } function payloadActionBtn(file, source_id) { const upToDate = !!(file.local_hash && ( (file.remote_hash && file.local_hash === file.remote_hash) || (!file.remote_hash && file.local_version && file.version && file.local_version === file.version) )); const hasUpdate = !!(file.local_hash && ( (file.remote_hash && file.local_hash !== file.remote_hash) || (file.local_version && file.version && file.local_version !== file.version) )); const dlLabel = hasUpdate ? `↓ ${escHtml(window.t('payload.action.update', 'Update'))}` : `↓ ${escHtml(window.t('payload.action.download', 'Download'))}`; const dlCls = hasUpdate ? 'btn btn-sm btn-teal btn-payload-download' : 'btn btn-sm btn-payload-download'; const dlBtn = !upToDate ? `` : ''; const pushBtn = file.local_path ? `` : ''; return (dlBtn || pushBtn) ? `${dlBtn}${pushBtn}` : ''; } function payloadHasUpdate(file) { return Boolean(file.local_hash && ( (file.remote_hash && file.local_hash !== file.remote_hash) || (file.local_version && file.version && file.local_version !== file.version) )); } function payloadCatalogGroup({ source, files }) { const fingerprint = `${source.name || ''} ${source.category || ''} ${source.description || ''} ${(files || []).map(file => file.asset_name).join(' ')}`.toLowerCase(); const expertCategory = ['kernel', 'debug', 'linux'].includes(String(source.category || '').toLowerCase()); if (expertCategory || /(kernel|kstuff|kpatch|exploit|debugger|gdb|elf\s*(?:ldr|loader)|binloader|shell(?:code)?|jailbreak|linux\s+loader)/.test(fingerprint)) return 'expert'; if ((files || []).some(payloadHasUpdate)) return 'updates'; if (source.trust === 'custom' || /(experimental|prototype|research|legacy)/.test(fingerprint)) return 'experimental'; if (source.trust === 'official' && (files || []).some(file => file.remote_hash)) return 'recommended'; return 'compatible'; } const PAYLOAD_GROUPS = [ { id: 'updates', label: 'Updates', detail: 'Downloaded payloads with a newer or changed upstream asset.' }, { id: 'recommended', label: 'Recommended', detail: 'Reviewed official sources with a published checksum.' }, { id: 'compatible', label: 'Compatible', detail: 'Normal payloads that still require a manual compatibility check.' }, { id: 'experimental', label: 'Experimental', detail: 'Custom, research or incomplete sources. Review before downloading.' }, { id: 'expert', label: 'Expert', detail: 'Low-level payloads that can affect console stability.' }, ]; function payloadSourceMarkup({ source, files, error }, forceOpen = false) { const usableFiles = (files || []).filter(file => /\.(bin|elf|js)$/i.test(file.asset_name) && !/sha256|checksums|sha2|sums/i.test(file.asset_name) ); const tag = source.latest_tag ? `${escHtml(source.latest_tag)}` : ''; const trust = escHtml(source.trust || 'custom'); const checked = source.last_checked ? `${escHtml(window.tf('payload.checkedAt', 'checked {date}', { date: new Date(source.last_checked).toLocaleString() }))}` : ''; const errRow = error ? `

${escHtml(error)}

` : ''; const rows = usableFiles.map(file => ` ${escHtml(file.asset_name)} ${payloadStatusBadge(file)} ${file.remote_hash ? file.remote_hash.slice(0, 12) + '…' : '—'} ${escHtml(file.version || '—')} · ${file.asset_size ? fmt(file.asset_size) : escHtml(window.t('payload.unknownSize', 'unknown size'))} ${payloadActionBtn(file, source.id)} `).join(''); return `
${escHtml(source.name)} ${usableFiles.length} ${tag} ${checked} ${trust} ${escHtml(source.category || 'other')}
${source.description ? `

${escHtml(source.description)}

` : ''} ${errRow} ${rows ? `
${rows}
` : `

${escHtml(window.t('payload.noAssets', 'No assets found.'))}

`}
`; } function renderPayloadSourceCheckList(results) { const cont = $('payload-sources-check-list'); if (!results || !results.length) { cont.innerHTML = `
⌁${escHtml(window.t('payload.empty.title', 'No payload sources match'))}${escHtml(window.t('payload.empty.detail', 'Adjust the filters or add a source in Settings.'))}
`; return; } const queryActive = Boolean(String($('payload-catalog-search')?.value || '').trim()); const grouped = Object.groupBy ? Object.groupBy(results, payloadCatalogGroup) : results.reduce((groups, item) => { const id = payloadCatalogGroup(item); (groups[id] ||= []).push(item); return groups; }, {}); cont.innerHTML = PAYLOAD_GROUPS.map(group => { const items = grouped[group.id] || []; if (!items.length) return ''; const label = window.t(`payload.groups.${group.id}.label`, group.label); const detail = window.t(`payload.groups.${group.id}.detail`, group.detail); const risk = group.id === 'expert' || group.id === 'experimental' ? group.id : 'standard'; return `
${escHtml(label)}${escHtml(detail)}${items.length}
${items.map(item => payloadSourceMarkup(item, queryActive)).join('')}
`; }).join(''); } let _payloadCatalog = null; function catalogResults(catalog) { return (catalog.sources || []).map(source => ({ source, files: (catalog.files || []).filter(file => file.source_id === source.id), error: source.last_error || null, })); } function applyPayloadCatalogFilters() { if (!_payloadCatalog) return; const query = String($('payload-catalog-search')?.value || '').trim().toLowerCase(); const category = String($('payload-catalog-category')?.value || 'all'); const showExpert = Boolean($('payload-show-expert')?.checked); const results = catalogResults(_payloadCatalog) .filter(({ source }) => category === 'all' || source.category === category) .map(item => ({ ...item, files: item.files.filter(file => !query || `${file.asset_name} ${item.source.name} ${item.source.description || ''}`.toLowerCase().includes(query)), })) .filter(item => !query || item.files.length || `${item.source.name} ${item.source.description || ''}`.toLowerCase().includes(query)) .filter(item => showExpert || payloadCatalogGroup(item) !== 'expert'); renderPayloadSourceCheckList(results); } function renderPayloadCatalog(catalog) { _payloadCatalog = catalog; const stats = catalog.stats || {}; $('payload-stat-assets').textContent = String(stats.available || 0); $('payload-stat-sources').textContent = String(stats.enabledSources || stats.sources || 0); $('payload-stat-downloaded').textContent = String(stats.downloaded || 0); $('payload-stat-updates').textContent = String(stats.updates || 0); $('payload-stat-verified').textContent = String(stats.verified || 0); const select = $('payload-catalog-category'); const selected = select.value || 'all'; const categories = [...new Set((catalog.sources || []).map(source => source.category || 'other'))].sort(); select.innerHTML = `` + categories.map(value => ``).join(''); select.value = categories.includes(selected) ? selected : 'all'; applyPayloadCatalogFilters(); } async function loadPayloadCatalog() { const catalog = await window.pork.payloadCatalog(); renderPayloadCatalog(catalog); return catalog; } async function renderLocalPayloads() { let files; try { files = await window.pork.payloadListLocal(); } catch { files = []; } const list = $('payload-local-list'); const empty = $('payload-local-empty'); if (!files.length) { list.innerHTML = ''; empty.hidden = false; return; } empty.hidden = true; list.innerHTML = files.map(f => { const ext = (f.name.split('.').pop() || '').toLowerCase(); const extClass = { elf: 'al-ext-elf', bin: 'al-ext-bin', js: 'al-ext-js' }[ext] || ''; return `
${escHtml(ext || '?')} ${escHtml(f.name)} ${fmt(f.size)} ${payloadStatusBadge(f)}
`; }).join(''); } async function renderPayloadSender() { let files; try { files = await window.pork.payloadListLocal(); } catch { files = []; } const list = $('payload-sender-list'); const empty = $('payload-sender-empty'); if (!files.length) { list.innerHTML = ''; empty.hidden = false; return; } empty.hidden = true; const status = $('payload-sender-status'); if (status) status.textContent = ''; list.innerHTML = files.map(f => { const ext = (f.name.split('.').pop() || '').toLowerCase(); const extClass = { elf: 'al-ext-elf', bin: 'al-ext-bin', js: 'al-ext-js' }[ext] || ''; return `
${escHtml(ext || '?')} ${escHtml(f.name)} ${fmt(f.size)}
`; }).join(''); } $('btn-payload-check-all').addEventListener('click', async () => { const btn = $('btn-payload-check-all'); const status = $('payload-check-status'); btn.disabled = true; status.textContent = 'Checking…'; try { const results = await window.pork.payloadCheckUpdates(); await loadPayloadCatalog(); await renderLocalPayloads(); status.textContent = `Done — ${results.length} source(s) checked`; } catch (e) { status.textContent = `Error: ${e.message}`; setStatus(`Payload check failed: ${e.message}`, 'error'); } finally { btn.disabled = false; } }); $('payload-catalog-search')?.addEventListener('input', applyPayloadCatalogFilters); $('payload-catalog-category')?.addEventListener('change', applyPayloadCatalogFilters); $('payload-show-expert')?.addEventListener('change', event => { localStorage.setItem('chimera.payload.showExpert', event.target.checked ? '1' : '0'); applyPayloadCatalogFilters(); }); $('btn-payload-update-downloaded')?.addEventListener('click', async () => { const btn = $('btn-payload-update-downloaded'); const status = $('payload-check-status'); btn.disabled = true; status.textContent = 'Checking and updating local payloads…'; try { const result = await window.pork.payloadUpdateDownloaded(); await loadPayloadCatalog(); await renderLocalPayloads(); await renderPayloadSender(); status.textContent = `${result.checked} checked locally · ${result.updated} updated${result.failed ? ` · ${result.failed} failed` : ''}`; } catch (error) { status.textContent = `Update failed: ${error.message}`; } finally { btn.disabled = false; } }); $('payload-sources-check-list').addEventListener('click', async e => { const pushBtn = e.target.closest('.btn-payload-push-remote'); if (pushBtn) { const local_path = pushBtn.dataset.localPath; const filename = pushBtn.dataset.filename; const origText = pushBtn.textContent; pushBtn.disabled = true; pushBtn.textContent = 'Queuing…'; try { await window.pork.payloadPush({ local_path, filename }); setStatus(`${filename} queued for FTP transfer to PS5`, 'ok'); navigate('transfers'); } catch (err) { setStatus(`FTP push failed: ${err.message}`, 'error'); pushBtn.disabled = false; pushBtn.textContent = origText; } return; } const dlBtn = e.target.closest('.btn-payload-download'); if (!dlBtn) return; const source_id = Number(dlBtn.dataset.sourceId); const asset_name = dlBtn.dataset.assetName; const asset_url = dlBtn.dataset.assetUrl; const version = dlBtn.dataset.version; dlBtn.disabled = true; dlBtn.textContent = 'Downloading…'; try { await window.pork.payloadDownload({ source_id, asset_name, asset_url, version }); setStatus(`Downloaded ${asset_name}`, 'ok'); await loadPayloadCatalog(); await renderLocalPayloads(); await renderPayloadSender(); } catch (err) { setStatus(`Download failed: ${err.message}`, 'error'); dlBtn.disabled = false; dlBtn.textContent = '↓ Retry'; } }); $('payload-local-list').addEventListener('click', async e => { const pushBtn = e.target.closest('.btn-payload-push'); if (pushBtn) { const local_path = pushBtn.dataset.localPath; const filename = pushBtn.dataset.filename; try { await window.pork.payloadPush({ local_path, filename }); setStatus(`${filename} queued for transfer`, 'ok'); navigate('transfers'); } catch (e2) { setStatus(`Push failed: ${e2.message}`, 'error'); } return; } const delBtn = e.target.closest('.btn-payload-delete-local'); if (delBtn) { const confirmation = await showExactConfirmation(`Delete local file "${delBtn.dataset.localPath}"?`, 'VERWIJDER'); if (!confirmation) return; try { const id = delBtn.dataset.id ? Number(delBtn.dataset.id) : null; const local_path = delBtn.dataset.localPath; await window.pork.payloadDeleteLocal({ id, local_path, confirmation }); await renderLocalPayloads(); await renderPayloadSender(); } catch (e2) { setStatus(`Delete failed: ${e2.message}`, 'error'); } } }); $('payload-sender-list').addEventListener('click', async e => { const btn = e.target.closest('.btn-payload-tcp-send'); if (!btn) return; const local_path = btn.dataset.localPath; const filename = btn.dataset.filename; const port = parseInt($('payload-sender-port')?.value || '9021', 10); const status = $('payload-sender-status'); btn.disabled = true; btn.textContent = 'Sending\u2026'; if (status) { status.textContent = `Sending ${filename} on port ${port}\u2026`; status.style.color = ''; } try { await window.pork.payloadTcpSend({ localPath: local_path, port, filename }); if (status) { status.textContent = `\u2713 ${filename} sent \u2014 PS5 notified`; status.style.color = 'var(--teal)'; } setStatus(`${filename} sent to PS5 on port ${port}`, 'ok'); } catch (e2) { if (status) { status.textContent = `Error: ${e2.message}`; status.style.color = 'var(--red)'; } setStatus(`TCP send failed: ${e2.message}`, 'error'); } finally { btn.disabled = false; btn.textContent = '\u21D2 Send'; } }); $('autoloader-file-list').addEventListener('click', async e => { const saveBtn = e.target.closest('.btn-al-save-local'); if (saveBtn) { e.stopPropagation(); // don't also add to sequence const filename = saveBtn.dataset.filename; const orig = saveBtn.textContent; saveBtn.disabled = true; saveBtn.textContent = '⋯'; try { await window.pork.autoloaderSaveLocal({ filename }); setStatus(`${filename} saved to local payloads`, 'ok'); saveBtn.textContent = '✓'; await renderLocalPayloads(); await renderPayloadSender(); setTimeout(() => { saveBtn.textContent = orig; saveBtn.disabled = false; }, 2000); } catch (e2) { setStatus(`Save failed: ${e2.message}`, 'error'); saveBtn.textContent = orig; saveBtn.disabled = false; } return; } const card = e.target.closest('[data-al-add]'); if (!card) return; _alSequence.push({ type: 'payload', filename: card.dataset.alAdd }); _alRenderSequence(); $('btn-autoloader-save').disabled = false; }); window.pork.on('payload:download:progress', ({ asset_name, percent }) => { const btn = document.querySelector(`.btn-payload-download[data-asset-name="${CSS.escape(asset_name)}"]`); if (btn) btn.textContent = `${percent}%`; });