Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:38:08 +02:00
commit c737a37f71
531 files changed
+119270

No files matched your search

+160
View File
@@ -0,0 +1,160 @@
// ── 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 = `
<div class="embed-toolbar">
<div class="embed-controls">
<span class="embed-page-title">PFS Ripper</span>
<button id="pfs-add" class="btn btn-sm">+ Add Folder</button>
<button id="pfs-rescan" class="btn btn-xs">⟳ Rescan</button>
<span id="pfs-count" class="hint"></span>
</div>
<div class="embed-controls">
<input id="pfs-search" type="text" placeholder="Search title or ID…" style="width:220px" />
</div>
</div>
<div id="pfs-folders" style="display:flex;flex-wrap:wrap;gap:6px;padding:8px 12px"></div>
<div id="pfs-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;padding:12px"></div>`;
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 = '<span class="hint">No library folders yet — click “Add Folder”.</span>';
return;
}
el.innerHTML = folders.map(f =>
`<span class="pill" style="display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border:1px solid var(--border);border-radius:12px;font-size:12px">
<code style="font-size:11px">${_pfsEsc(f)}</code>
<button class="btn btn-xs btn-red pfs-folder-rm" data-folder="${_pfsEsc(f)}" title="Remove">✕</button>
</span>`).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 = '<div class="embed-empty-message" style="padding:16px">Scanning…</div>';
_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 = '<div class="embed-empty-message" style="padding:16px">No images found. Add a folder containing .ffpkg / .pkg / .exfat images.</div>';
return;
}
grid.innerHTML = list.map((it, i) => `
<div class="pfs-card" style="border:1px solid var(--border);border-radius:8px;overflow:hidden;display:flex;flex-direction:column">
<div class="pfs-art" data-gid="${_pfsEsc(it.gameId)}" style="aspect-ratio:1;background:rgba(var(--accent-rgb),.08);display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--text-dim)">
${it.gameId ? _pfsEsc(it.gameId) : '—'}
</div>
<div style="padding:8px;display:flex;flex-direction:column;gap:2px;flex:1">
<div style="font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${_pfsEsc(it.file)}">${_pfsEsc(it.gameId || it.file)}</div>
<div class="hint" style="font-size:11px">
<span class="pill-num" style="padding:0 4px">${_pfsEsc(it.ext)}</span>
${it.version ? ' v' + _pfsEsc(it.version) : ''} · ${_pfsFmt(it.size)}
</div>
<div style="display:flex;gap:4px;margin-top:6px;flex-wrap:wrap">
<button class="btn btn-xs pfs-export" data-i="${i}" title="Copy to a folder">Export</button>
<button class="btn btn-xs pfs-ftp" data-i="${i}" title="Send to PS5 via FTP">→ PS5</button>
<button class="btn btn-xs pfs-reveal" data-i="${i}" title="Show in Explorer">📂</button>
</div>
</div>
</div>`).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 = `<img src="${_pfsEsc(url)}" alt="" style="width:100%;height:100%;object-fit:cover" data-image-error="hide">`;
}
} 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();
}