// ── 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 = `
`; 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 = '';
_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 = '';
return;
}
grid.innerHTML = list.map((it, i) => `
`).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 = `