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

+485
View File
@@ -0,0 +1,485 @@
'use strict';
// State
const state = {
connected: false,
games: [],
backups: [],
sortCol: localStorage.getItem('chimera.games.sortCol') || 'title',
sortDir: localStorage.getItem('chimera.games.sortDir') || 'asc',
filter: localStorage.getItem('chimera.games.filter') || 'all',
platformFilter: ['all', 'ps4', 'ps5', 'unknown'].includes(localStorage.getItem('chimera.games.platform'))
? localStorage.getItem('chimera.games.platform')
: 'all',
search: '',
firmwareFilter: 'all',
gameView: localStorage.getItem('chimera.games.view') || 'grid',
gameRenderLimit: 160,
currentGame: null,
};
// Shared hash summary maps — populated by loadGames() and loadBackporks().
// _gameHashSummary: game_id → summary for hash_type='game'
// _backporkHashSummary: 'game_id|firmware_label' → summary for hash_type='backpork'
const _gameHashSummary = new Map();
const _backporkHashSummary = new Map();
const _pageLoadTimestamps = new Map();
const _pageLoadModes = new Map();
const _pageLoadInFlight = new Map();
let _pageWarmTimer = null;
let _pageWarmRunning = false;
// Feature code is route-loaded. Background refreshes only revisit modules that a user
// already opened; they never download the entire application after startup.
const _PAGE_WARM_ORDER = [];
const _PAGE_STALE_MS = {
dashboard: 15_000,
transfers: 5_000,
'system-view': 20_000,
media: 30_000,
default: 45_000,
};
function _populateHashMaps(summaries) {
_gameHashSummary.clear();
_backporkHashSummary.clear();
for (const s of summaries) {
if (s.hash_type === 'backpork') {
_backporkHashSummary.set(s.game_id + '|' + (s.firmware_label || ''), s);
} else {
_gameHashSummary.set(s.game_id, s);
}
}
}
// System View state
let _svStream = null;
let _svMuted = false;
let _svAudioCtx = null; // AudioContext for low-latency capture-card audio
let _svAudioGain = null; // GainNode — controls mute without stopping the stream
let _svHotkeys = { mute: 'm', gif: 'g', video: 'v', fullscreen: 'f', popout: 'p' };
let _svRecording = false;
let _svRecFrames = []; // Uint8ClampedArray[] — one per captured frame
let _svRecInterval = null;
let _svRecTimer = null;
let _svRecStart = 0;
let _svRecCanvas = null; // lazy-created hidden 640×360 canvas
let _svRecCtx = null;
const GIF_W = 640, GIF_H = 360;
// Video recording state
let _svVidRecorder = null;
let _svVidWritable = null;
let _svVidChunks = null;
let _svVidTimer = null;
let _svVidStart = 0;
let _svVidPaused = false;
let _svVidBytes = 0;
let _svVidMimeType = '';
let _svVidExt = '';
// Helpers
const $ = id => document.getElementById(id);
const _dialogReturnFocus = new WeakMap();
function dialogFocusable(container) {
if (!container) return [];
return [...container.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')]
.filter(element => !element.hidden && element.getAttribute('aria-hidden') !== 'true' && element.getClientRects().length);
}
function openAccessibleDialog(overlay, initialFocus = null) {
if (!overlay) return;
_dialogReturnFocus.set(overlay, document.activeElement instanceof HTMLElement ? document.activeElement : null);
overlay.hidden = false;
overlay.setAttribute('aria-hidden', 'false');
document.body.classList.add('chimera-dialog-open');
requestAnimationFrame(() => (initialFocus || dialogFocusable(overlay)[0] || overlay.querySelector('[role="dialog"]'))?.focus?.());
}
function closeAccessibleDialog(overlay) {
if (!overlay) return;
overlay.hidden = true;
overlay.setAttribute('aria-hidden', 'true');
const returnFocus = _dialogReturnFocus.get(overlay);
_dialogReturnFocus.delete(overlay);
if (![...document.querySelectorAll('[role="dialog"][aria-modal="true"]')]
.some(dialog => !dialog.closest('[hidden]') && dialog.getClientRects().length)) {
document.body.classList.remove('chimera-dialog-open');
}
if (returnFocus?.isConnected) requestAnimationFrame(() => returnFocus.focus());
}
document.addEventListener('keydown', event => {
const tab = event.target.closest?.('[role="tab"]');
if (tab && ['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
const tabs = [...tab.closest('[role="tablist"]')?.querySelectorAll('[role="tab"]') || []].filter(item => !item.disabled && !item.hidden);
if (tabs.length) {
event.preventDefault();
const current = Math.max(0, tabs.indexOf(tab));
const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (current + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;
tabs[next].focus();
tabs[next].click();
return;
}
}
if (event.key !== 'Tab') return;
const dialogs = [...document.querySelectorAll('[role="dialog"][aria-modal="true"]')]
.filter(dialog => !dialog.closest('[hidden]') && dialog.getClientRects().length);
const dialog = dialogs.at(-1);
if (!dialog) return;
const focusable = dialogFocusable(dialog);
if (!focusable.length) { event.preventDefault(); dialog.focus(); return; }
const first = focusable[0];
const last = focusable.at(-1);
if (event.shiftKey && (document.activeElement === first || !dialog.contains(document.activeElement))) { event.preventDefault(); last.focus(); }
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
});
document.addEventListener('error', event => {
const image = event.target;
if (!(image instanceof HTMLImageElement) || !image.dataset.imageError) return;
const fallbackSrc = image.dataset.fallbackSrc;
if (fallbackSrc && image.dataset.fallbackAttempted !== 'true' && image.currentSrc !== fallbackSrc) {
image.dataset.fallbackAttempted = 'true';
image.classList.add('is-fallback-artwork');
image.src = fallbackSrc;
return;
}
image.hidden = true;
if (image.dataset.imageError === 'fallback' && image.nextElementSibling) image.nextElementSibling.hidden = false;
}, true);
function setGameWorkspaceContext(gameId, target) {
const normalized = String(gameId || '').trim().toUpperCase();
if (!/^(?:CUSA|PPSA)\d{5}$/.test(normalized)) return false;
sessionStorage.setItem('chimera.gameWorkspace', JSON.stringify({ gameId: normalized, target: String(target || ''), at: Date.now() }));
return true;
}
function consumeGameWorkspaceContext(target) {
try {
const raw = sessionStorage.getItem('chimera.gameWorkspace');
const context = raw ? JSON.parse(raw) : null;
if (!context || context.target !== target || Date.now() - Number(context.at || 0) > 5 * 60 * 1000) return null;
sessionStorage.removeItem('chimera.gameWorkspace');
return /^(?:CUSA|PPSA)\d{5}$/.test(String(context.gameId || '')) ? context.gameId : null;
} catch (_) {
sessionStorage.removeItem('chimera.gameWorkspace');
return null;
}
}
function _isNarrowLayout() {
return window.matchMedia('(max-width: 900px)').matches;
}
function setMobileNavOpen(open) {
document.body.classList.toggle('mobile-nav-open', !!open && _isNarrowLayout());
const isOpen = document.body.classList.contains('mobile-nav-open');
const toggle = $('mobile-nav-toggle');
if (toggle) {
toggle.setAttribute('aria-expanded', String(isOpen));
toggle.setAttribute('aria-label', isOpen ? 'Close navigation' : 'Open navigation');
toggle.title = isOpen ? 'Close navigation' : 'Open navigation';
toggle.textContent = isOpen ? '×' : '☰';
}
const companionMore = $('chimera-companion-more');
if (companionMore) companionMore.setAttribute('aria-expanded', String(isOpen));
const scrim = $('sidebar-scrim');
if (scrim) scrim.hidden = !isOpen;
const companionDock = $('chimera-companion-dock');
if (companionDock) {
companionDock.toggleAttribute('inert', isOpen);
if (isOpen) companionDock.setAttribute('aria-hidden', 'true');
else companionDock.removeAttribute('aria-hidden');
}
if (isOpen) {
requestAnimationFrame(() => document.querySelector('.chimera-route-link.active, .chimera-domain-home.active')?.scrollIntoView({ block: 'nearest' }));
}
}
function closeMobileNav() {
setMobileNavOpen(false);
}
function applyDensity(value) {
const density = value === 'compact' ? 'compact' : 'comfortable';
document.body.dataset.density = density;
return density;
}
function _pageLoader(page, background = false) {
const loaders = {
dashboard: () => loadDashboard(),
games: () => loadGames(),
packages: () => loadPackages(),
backporks: () => loadBackporks(),
backups: () => loadBackups(),
database: () => loadDatabase(),
settings: () => loadSettings({ background }),
jailbreak: () => loadJailbreak(),
psnotify: () => loadPsNotify(),
xavatar: () => loadXavatar(),
'system-view': () => loadSystemView(background ? { passive: true } : {}),
transfers: () => loadTransfers(),
media: () => loadMedia(),
cheats: () => loadCheats({ background }),
garlic: () => loadGarlic(),
'elf-arsenal': () => (typeof loadElfArsenal === 'function' ? loadElfArsenal({ background }) : null),
'pfs-ripper': () => (typeof loadPfsRipper === 'function' ? loadPfsRipper({ background }) : null),
savemgr: () => loadSaveMgr({ background }),
ecosystem: () => (typeof loadEcosystem === 'function' ? loadEcosystem({ background }) : null),
operations: () => (typeof loadOperations === 'function' ? loadOperations({ background }) : null),
};
return loaders[page] || null;
}
function _pageStaleMs(page) {
return _PAGE_STALE_MS[page] || _PAGE_STALE_MS.default;
}
function pageIsStale(page) {
const last = _pageLoadTimestamps.get(page) || 0;
return (Date.now() - last) > _pageStaleMs(page);
}
function ensurePageReady(page, { background = false, force = false } = {}) {
const needsActiveHydrate = !background && _pageLoadModes.get(page) === 'background';
if (!force && !needsActiveHydrate && !pageIsStale(page)) return _pageLoadInFlight.get(page) || Promise.resolve();
if (_pageLoadInFlight.has(page)) return _pageLoadInFlight.get(page);
const run = Promise.resolve()
.then(() => window.ChimeraModuleLoader?.ensureForPage(page))
.then(() => {
const loader = _pageLoader(page, background);
return loader ? loader() : null;
})
.then(() => {
_pageLoadTimestamps.set(page, Date.now());
const priorMode = _pageLoadModes.get(page);
_pageLoadModes.set(page, background && priorMode === 'active' ? 'active' : (background ? 'background' : 'active'));
})
.catch(err => {
if (background) {
const method = isExpectedUnavailable(err) ? 'info' : 'warn';
console[method](`[page-load] ${page} was not preloaded: ${err.message}`);
return;
}
throw err;
})
.finally(() => {
if (_pageLoadInFlight.get(page) === run) _pageLoadInFlight.delete(page);
});
_pageLoadInFlight.set(page, run);
return run;
}
function scheduleBackgroundWarm(delay = 600) {
if (_pageWarmTimer || _pageWarmRunning) return;
_pageWarmTimer = setTimeout(() => {
_pageWarmTimer = null;
startBackgroundWarm().catch(() => {});
}, delay);
}
async function startBackgroundWarm() {
if (_pageWarmRunning) return;
_pageWarmRunning = true;
try {
for (const page of _PAGE_WARM_ORDER) {
const active = document.querySelector('.page.active')?.id?.replace('page-', '');
if (page === active) continue;
await ensurePageReady(page, { background: true });
await new Promise(resolve => setTimeout(resolve, 250));
}
} finally {
_pageWarmRunning = false;
scheduleBackgroundWarm(_PAGE_STALE_MS.default);
}
}
function fmt(bytes) {
const n = Number(bytes);
if (!n || isNaN(n)) return '—';
const gb = n / 1073741824;
return gb >= 1 ? `${gb.toFixed(2)} GB` : `${(n / 1048576).toFixed(1)} MB`;
}
function yesNo(v) {
return v ? '<span class="pill pill-yes">Yes</span>' : '<span class="pill pill-no">No</span>';
}
function escHtml(s) {
return String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// Pinnable cards registry
const PINNABLE_CARDS = [
{ id: 'payload-update', label: 'Payload Update Checker', page: 'jailbreak', hint: 'Check sources for payload updates and push to PS5.' },
{ id: 'payload-mgr', label: 'Payload Sender', page: 'jailbreak', type: 'widget', hint: 'Quick-send a payload to PS5 via TCP.' },
{ id: 'psnotify', label: 'Send Notification', page: 'psnotify', type: 'widget', hint: 'Send a PS5 notification from the dashboard.' },
{ id: 'xavatar-convert', label: 'XAvatar Quick Send', page: 'xavatar', type: 'widget', hint: 'Convert an image and send to PS5 as .xavatar.' },
{ id: 'xavatar-library', label: 'XAvatar Library', page: 'xavatar', hint: 'Manage .xavatar files on your PS5.' },
{ id: 'transfers', label: 'Transfer Manager', page: 'transfers', hint: 'Monitor active and queued FTP transfers.' },
{ id: 'games', label: 'Games Library', page: 'games', hint: 'Browse and manage your PS5 game collection.' },
{ id: 'cheats', label: 'Cheats', page: 'cheats', hint: 'Browse and install game cheats.' },
{ id: 'media', label: 'Media', page: 'media', hint: 'Browse screenshots and video clips from PS5.' },
{ id: 'system-view', label: 'System View', page: 'system-view', hint: 'View your PS5 via capture card stream.' },
{ id: 'backups', label: 'Backups', page: 'backups', hint: 'Browse and manage game backup files.' },
// Other page widgets
{ id: 'dash-savemgr', label: 'Save Manager', page: 'savemgr', type: 'widget', hint: 'Browse and download PS5 save files.' },
];
const _PIN_PAGE_LABELS = {
jailbreak: 'Payload Workspace', psnotify: 'Notifications', xavatar: 'XAvatar',
transfers: 'Transfers', games: 'Games', cheats: 'Cheats & patches', media: 'Media',
'system-view': 'System View', backups: 'Backups', savemgr: 'Local Save Manager',
ecosystem: 'Console Status', operations: 'Operations',
};
function setStatus(msg, type = '') {
const el = $('sb-msg');
el.textContent = msg;
el.style.color = type === 'error' ? 'var(--red)' : type === 'ok' ? 'var(--green)' : 'var(--yellow)';
if (msg) setTimeout(() => { if (el.textContent === msg) el.textContent = ''; }, 5000);
}
function titleHue(id){let h=0;for(const c of String(id||''))h=(h*31+c.charCodeAt(0))%360;return h;}
// Server messages are Dutch; translated at display time via serverError.* keys.
function friendlyMessage(text) {
const message = String(text ?? '').trim();
if (!/^en(?:-|$)/i.test(document.documentElement.lang || 'en')) return message;
const mapped = window.i18n?.strings?.['serverError.' + message];
if (mapped) return mapped;
return message
.replace(/niet bereikbaar/gi, 'unreachable')
.replace(/niet beschikbaar/gi, 'unavailable')
.replace(/niet geconfigureerd/gi, 'not configured')
.replace(/ontbreekt/gi, 'is missing');
}
function showToast(msg, type = 'info', duration = 5000) {
const icons = { ok: '✓', error: '✕', info: '●' };
const safeType = Object.hasOwn(icons, type) ? type : 'info';
const message = friendlyMessage(msg);
if (!message) return null;
const container = $('toast-container');
const key = `${safeType}:${message.replace(/\s+/g, ' ').toLocaleLowerCase()}`;
const existing = [...container.querySelectorAll('.toast')].find(item => item.dataset.toastKey === key);
if (existing) {
const count = Number(existing.dataset.toastCount || 1) + 1;
existing.dataset.toastCount = String(count);
let badge = existing.querySelector('.toast-count');
if (!badge) {
badge = document.createElement('span');
badge.className = 'toast-count';
existing.querySelector('.toast-body')?.after(badge);
}
badge.textContent = `×${count}`;
clearTimeout(existing._dismissTimer);
existing._dismissTimer = setTimeout(() => existing._dismiss?.(), duration);
return existing;
}
const toast = document.createElement('div');
toast.className = `toast toast-${safeType}`;
toast.dataset.toastKey = key;
toast.dataset.toastCount = '1';
toast.setAttribute('role', safeType === 'error' ? 'alert' : 'status');
const closeLabel = window.t?.('notifications.close', 'Close notification') || 'Close notification';
const icon = document.createElement('span');
icon.className = 'toast-icon';
icon.setAttribute('aria-hidden', 'true');
icon.textContent = icons[safeType];
const body = document.createElement('span');
body.className = 'toast-body';
body.textContent = message;
const close = document.createElement('button');
close.className = 'toast-close';
close.type = 'button';
close.setAttribute('aria-label', closeLabel);
close.textContent = '×';
toast.append(icon, body, close);
while (container.children.length >= 4) container.firstElementChild?.remove();
container.appendChild(toast);
const dismiss = () => {
if (!toast.isConnected || toast.classList.contains('toast-out')) return;
toast.classList.add('toast-out');
toast.addEventListener('animationend', () => toast.remove(), { once: true });
setTimeout(() => toast.remove(), 350);
};
toast._dismiss = dismiss;
close.addEventListener('click', dismiss);
toast._dismissTimer = setTimeout(dismiss, duration);
toast.addEventListener('mouseenter', () => clearTimeout(toast._dismissTimer));
toast.addEventListener('mouseleave', () => { toast._dismissTimer = setTimeout(dismiss, 2000); });
return toast;
}
// ── Navigation ────────────────────────────────────────────────────────────────
function navigate(page) {
document.querySelectorAll('.nav-link').forEach(a => a.classList.toggle('active', a.dataset.page === page));
document.querySelectorAll('.page').forEach(p => p.classList.toggle('active', p.id === `page-${page}`));
// Embed pages need #content to be overflow:hidden so the iframe can fill available height
const isEmbed = page === 'savemgr';
document.getElementById('content').classList.toggle('embed-page-active', isEmbed);
ensurePageReady(page, { force: pageIsStale(page) });
scheduleBackgroundWarm(800);
closeMobileNav();
syncPinStates();
}
$('mobile-nav-toggle')?.addEventListener('click', () => {
setMobileNavOpen(!document.body.classList.contains('mobile-nav-open'));
});
$('sidebar-scrim')?.addEventListener('click', closeMobileNav);
window.addEventListener('resize', () => {
if (!_isNarrowLayout()) closeMobileNav();
});
async function syncPinStates() {
try {
const s = await window.pork.getSettings();
const pinned = new Set(s.pinnedCards || []);
document.querySelectorAll('.btn-pin').forEach(btn =>
btn.classList.toggle('pinned', pinned.has(btn.dataset.pinId))
);
} catch (_) {}
}
async function loadJailbreak() {
// Reset UI
$('payload-check-status').textContent = '';
$('payload-sources-check-list').innerHTML = '';
if ($('payload-show-expert')) {
$('payload-show-expert').checked = localStorage.getItem('chimera.payload.showExpert') === '1';
}
try { await loadPayloadCatalog(); } catch (error) {
$('payload-sources-check-list').innerHTML = `<p class="hint">${escHtml(window.t('payload.catalogLoadFailed', 'Catalog could not be loaded'))}: ${escHtml(error.message)}</p>`;
}
await renderLocalPayloads();
await renderPayloadSender();
await renderAutoloaderSnapshots();
try {
const settings = await window.pork.getSettings();
if ($('pldmgr-port')) $('pldmgr-port').value = settings.payloadManagerPort || 8084;
if (!settings.ftpHost) {
if ($('service-matrix-summary')) $('service-matrix-summary').textContent = window.t('services.ps5NotConfigured', 'PS5 not configured');
if ($('service-matrix-grid')) $('service-matrix-grid').innerHTML = `<p class="hint">${escHtml(window.t('services.configureProfile', 'Configure a console profile before checking services.'))}</p>`;
if ($('pldmgr-version')) $('pldmgr-version').textContent = 'offline';
if ($('pldmgr-status')) $('pldmgr-status').textContent = window.t('services.ps5NotConfigured', 'PS5 not configured');
return;
}
await loadPs5ServiceMatrix();
await connectPs5PayloadManager({ quiet: true });
} catch (_) {}
}
function isExpectedUnavailable(error) {
if (error?.expected || ['NOT_CONFIGURED', 'UNAVAILABLE', 'UPSTREAM_UNAVAILABLE'].includes(error?.code)) return true;
return /niet (?:bereikbaar|verbonden|beschikbaar|actief|geconfigureerd|gevonden)|maak eerst verbinding|not (?:reachable|connected|available|active|configured|found|mounted)|connect (?:to )?(?:the )?PS5|offline|ECONNREFUSED|timed? out|reageerde niet op tijd|HTTP (?:403|429|5\d\d)|rate.?limit|ontbreekt/i.test(String(error?.message || ''));
}