// ── PFS Ripper — PS5 PFS/image library browser ─────────────────────────────── // Native port of Deckerr97's "PFS Ripper": browse a library of PS5 image files, // show cover art (ProsperoPatches), search, view PPSA IDs + versions, export. let _pfsInit = false; let _pfsItems = []; let _pfsSearch = ''; const _pfsIconCache = new Map(); function _pfsEsc(s) { return (typeof escHtml === 'function') ? escHtml(s) : String(s ?? ''); } function _pfsToast(m, k) { if (typeof showToast === 'function') showToast(m, k); } function _pfsFmt(n) { if (!n) return ''; if (n < 1048576) return (n / 1024).toFixed(0) + ' KB'; if (n < 1073741824) return (n / 1048576).toFixed(1) + ' MB'; return (n / 1073741824).toFixed(2) + ' GB'; } function _pfsBuildUI() { const root = document.getElementById('page-pfs-ripper'); if (!root) return; root.innerHTML = `
PFS Ripper
`; document.getElementById('pfs-add').addEventListener('click', async () => { await window.pork.pfsFoldersAdd(); await _pfsRenderFolders(); await _pfsScan(); }); document.getElementById('pfs-rescan').addEventListener('click', () => _pfsScan()); document.getElementById('pfs-search').addEventListener('input', e => { _pfsSearch = e.target.value.toLowerCase(); _pfsRenderGrid(); }); } async function _pfsRenderFolders() { const el = document.getElementById('pfs-folders'); if (!el) return; const folders = (await window.pork.pfsFoldersList().catch(() => [])) || []; if (!folders.length) { el.innerHTML = 'No library folders yet — click “Add Folder”.'; return; } el.innerHTML = folders.map(f => ` ${_pfsEsc(f)} `).join(''); el.querySelectorAll('.pfs-folder-rm').forEach(b => b.addEventListener('click', async () => { await window.pork.pfsFoldersRemove(b.dataset.folder); await _pfsRenderFolders(); await _pfsScan(); })); } async function _pfsScan() { const grid = document.getElementById('pfs-grid'); if (grid) grid.innerHTML = '
Scanning…
'; _pfsItems = (await window.pork.pfsScan().catch(() => [])) || []; _pfsRenderGrid(); } function _pfsFiltered() { if (!_pfsSearch) return _pfsItems; return _pfsItems.filter(it => (it.gameId || '').toLowerCase().includes(_pfsSearch) || (it.file || '').toLowerCase().includes(_pfsSearch)); } function _pfsRenderGrid() { const grid = document.getElementById('pfs-grid'); const countEl = document.getElementById('pfs-count'); if (!grid) return; const list = _pfsFiltered(); if (countEl) countEl.textContent = `${list.length} image${list.length !== 1 ? 's' : ''}`; if (!list.length) { grid.innerHTML = '
No images found. Add a folder containing .ffpkg / .pkg / .exfat images.
'; return; } grid.innerHTML = list.map((it, i) => `
${it.gameId ? _pfsEsc(it.gameId) : '—'}
${_pfsEsc(it.gameId || it.file)}
${_pfsEsc(it.ext)} ${it.version ? ' v' + _pfsEsc(it.version) : ''} · ${_pfsFmt(it.size)}
`).join(''); // Lazy-load cover art via ProsperoPatches. grid.querySelectorAll('.pfs-art[data-gid]').forEach(el => { const gid = el.dataset.gid; if (!gid) return; _pfsLoadArt(gid, el); }); grid.querySelectorAll('.pfs-export').forEach(b => b.addEventListener('click', () => _pfsExport(list[+b.dataset.i]))); grid.querySelectorAll('.pfs-ftp').forEach(b => b.addEventListener('click', () => _pfsFtp(list[+b.dataset.i]))); grid.querySelectorAll('.pfs-reveal').forEach(b => b.addEventListener('click', () => window.pork.pfsReveal(list[+b.dataset.i].path))); } async function _pfsLoadArt(gid, el) { try { let url = _pfsIconCache.get(gid); if (url === undefined) { const result = await window.pork.prosperoFetchIcon(gid).catch(() => null); url = result?.iconUrl || ''; _pfsIconCache.set(gid, url || null); } if (url && el.isConnected) { el.innerHTML = ``; } } catch (_) { /* leave the ID placeholder */ } } async function _pfsExport(it) { if (!it) return; try { const r = await window.pork.pfsExportCopy(it.path); if (r && r.ok) _pfsToast('Exported to ' + r.dest, 'ok'); } catch (e) { _pfsToast('Export failed: ' + e.message, 'error'); } } async function _pfsFtp(it) { if (!it) return; try { await window.pork.pfsExportFtp(it.path); _pfsToast('Queued upload to PS5 — see Transfers', 'ok'); } catch (e) { _pfsToast('Send failed: ' + e.message, 'error'); } } // Entry point invoked by the page router (state.js pageLoaders). async function loadPfsRipper() { if (!_pfsInit) { _pfsInit = true; _pfsBuildUI(); } await _pfsRenderFolders(); await _pfsScan(); }