// ── Y2JB Update Generator ─────────────────────────────────────────────────── const y2jbGen = { entries: [], payloads: [], catalog: [] }; const y2jbGenBytes = value => { const bytes = Number(value || 0); if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; }; function y2jbGenRender() { const list = $('y2jbgen-list'); if (!list) return; if (!y2jbGen.entries.length) { list.innerHTML = '

No entries yet. Add payload files, then order payloads, delays, and messages.

'; return; } list.innerHTML = y2jbGen.entries.map((entry, index) => { const value = escHtml(entry.value || ''); const title = entry.type === 'payload' ? 'Payload' : entry.type === 'delay' ? 'Delay (ms)' : 'Message'; const placeholder = entry.type === 'payload' ? 'payload.elf' : entry.type === 'delay' ? '1000' : 'Shown during autoload'; return `
${index + 1}
`; }).join(''); } function y2jbGenSetStatus(text, type = '') { const status = $('y2jbgen-status'); status.textContent = text; status.className = `hint ${type}`; } async function y2jbGenLoadCatalog() { const select = $('y2jbgen-catalog'); if (!select || !window.pork.y2jbGenCatalog) return; select.disabled = true; select.innerHTML = ''; try { const packet = await window.pork.y2jbGenCatalog(); y2jbGen.catalog = packet.items || []; select.innerHTML = y2jbGen.catalog.length ? y2jbGen.catalog.map(item => ``).join('') : ''; y2jbGenSetStatus(y2jbGen.catalog.length ? `${y2jbGen.catalog.length} local catalog payload${y2jbGen.catalog.length === 1 ? '' : 's'} available.` : 'The local Payload Catalog is empty.'); } catch (error) { select.innerHTML = ''; y2jbGenSetStatus(`Catalog error: ${error.message}`, 'error'); } select.disabled = false; } $('btn-y2jbgen-refresh-catalog').addEventListener('click', y2jbGenLoadCatalog); $('btn-y2jb-discover')?.addEventListener('click', async event => { const button = event.currentTarget; button.disabled = true; try { const release = await window.pork.y2jbDiscover(true); $('y2jb-upstream-status').innerHTML = `${escHtml(release.version || 'Latest official Y2JB release')}${escHtml(release.source)} · ${y2jbGenBytes(release.size || 0)} · ${release.digest ? 'published SHA-256 available' : 'ZIP validation; no published SHA-256'}`; } catch (error) { $('y2jb-upstream-status').innerHTML = `Release discovery unavailable${escHtml(error.message)}`; } finally { button.disabled = false; } }); $('btn-y2jb-stage')?.addEventListener('click', async event => { if (!await showConfirm('Download the discovered official Y2JB ZIP, validate it and copy it to the configured PS5 update folder? The update is not executed.')) return; const button = event.currentTarget; button.disabled = true; try { const result = await window.pork.y2jbApply(); $('y2jb-upstream-status').innerHTML = `Verified update staged${escHtml(result.version || 'Y2JB')} · ${escHtml(result.remotePath)} · SHA-256 ${escHtml(result.sha256.slice(0, 16))}… · not executed`; } catch (error) { $('y2jb-upstream-status').innerHTML = `Staging failed${escHtml(error.message)}`; } finally { button.disabled = false; } }); $('btn-y2jbgen-add-catalog').addEventListener('click', () => { const selected = [...$('y2jbgen-catalog').selectedOptions]; let added = 0; for (const option of selected) { const payload = y2jbGen.catalog.find(item => item.id === option.value); if (!payload || y2jbGen.entries.some(entry => entry.type === 'payload' && entry.value === payload.name)) continue; y2jbGen.entries.push({ type: 'payload', value: payload.name, catalogId: payload.id }); added += 1; } y2jbGenRender(); y2jbGenSetStatus(added ? `Added ${added} catalog payload${added === 1 ? '' : 's'} ✓` : 'No new catalog payloads selected.'); }); $('btn-y2jbgen-add-payloads').addEventListener('click', async () => { const btn = $('btn-y2jbgen-add-payloads'); btn.disabled = true; y2jbGenSetStatus('Choosing payload files…'); try { const chosen = await window.pork.y2jbGenSelectPayloads(); let added = 0; for (const payload of chosen || []) { if (y2jbGen.payloads.some((item) => item.name === payload.name) || y2jbGen.entries.some(entry => entry.type === 'payload' && entry.value === payload.name)) continue; y2jbGen.payloads.push(payload); y2jbGen.entries.push({ type: 'payload', value: payload.name }); added += 1; } y2jbGenRender(); y2jbGenSetStatus(added ? `Added ${added} payload file${added === 1 ? '' : 's'} ✓` : 'No new payload files selected.'); } catch (error) { y2jbGenSetStatus(`Error: ${error.message}`, 'error'); } btn.disabled = false; }); $('btn-y2jbgen-add-delay').addEventListener('click', () => { y2jbGen.entries.push({ type: 'delay', value: '1000' }); y2jbGenRender(); }); $('btn-y2jbgen-add-message').addEventListener('click', () => { y2jbGen.entries.push({ type: 'message', value: 'Starting next payload' }); y2jbGenRender(); }); $('y2jbgen-list').addEventListener('input', (event) => { const index = Number(event.target.dataset.y2jbValue); if (Number.isInteger(index) && y2jbGen.entries[index]) y2jbGen.entries[index].value = event.target.value; }); $('y2jbgen-list').addEventListener('change', (event) => { const index = Number(event.target.dataset.y2jbType); if (Number.isInteger(index) && y2jbGen.entries[index]) { y2jbGen.entries[index].type = event.target.value; if (event.target.value === 'delay' && !/^\d+$/.test(y2jbGen.entries[index].value)) y2jbGen.entries[index].value = '1000'; y2jbGenRender(); } }); $('y2jbgen-list').addEventListener('click', (event) => { const button = event.target.closest('button'); if (!button) return; const up = Number(button.dataset.y2jbUp); const down = Number(button.dataset.y2jbDown); const remove = Number(button.dataset.y2jbRemove); if (Number.isInteger(up) && y2jbGen.entries[up] && up > 0) { [y2jbGen.entries[up - 1], y2jbGen.entries[up]] = [y2jbGen.entries[up], y2jbGen.entries[up - 1]]; } else if (Number.isInteger(down) && y2jbGen.entries[down] && down < y2jbGen.entries.length - 1) { [y2jbGen.entries[down + 1], y2jbGen.entries[down]] = [y2jbGen.entries[down], y2jbGen.entries[down + 1]]; } else if (Number.isInteger(remove) && y2jbGen.entries[remove]) { y2jbGen.entries.splice(remove, 1); } else { return; } y2jbGenRender(); }); $('btn-y2jbgen-build').addEventListener('click', async () => { const btn = $('btn-y2jbgen-build'); btn.disabled = true; y2jbGenSetStatus('Validating and building ZIP…'); try { const referenced = new Set(y2jbGen.entries.filter(entry => entry.type === 'payload').map(entry => entry.value)); const catalogPayloadIds = y2jbGen.entries .filter(entry => entry.type === 'payload' && entry.catalogId && referenced.has(entry.value)) .map(entry => entry.catalogId); const result = await window.pork.y2jbGenBuild({ entries: y2jbGen.entries.map(({ catalogId, ...entry }) => entry), payloads: y2jbGen.payloads.filter(payload => referenced.has(payload.name)), catalogPayloadIds, }); if (result?.canceled) { y2jbGenSetStatus('Save cancelled.'); } else { y2jbGenSetStatus(`Built ✓ ${result.outputZip} (${result.entryCount} entries)`); } } catch (error) { y2jbGenSetStatus(`Error: ${error.message}`, 'error'); } btn.disabled = false; }); y2jbGenLoadCatalog();