'use strict'; const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('pork', { // Window controls minimize: () => ipcRenderer.send('window:minimize'), maximize: () => ipcRenderer.send('window:maximize'), close: () => ipcRenderer.send('window:close'), // Settings getSettings: () => ipcRenderer.invoke('settings:get'), setSettings: (s) => ipcRenderer.invoke('settings:set', s), isAdmin: () => ipcRenderer.invoke('app:is-admin'), // FTP ftpConnect: (c) => ipcRenderer.invoke('ftp:connect', c), ftpDisconnect: () => ipcRenderer.invoke('ftp:disconnect'), ftpStatus: () => ipcRenderer.invoke('ftp:status'), ftpScan: () => ipcRenderer.invoke('ftp:scan'), ftpDownload: (o) => ipcRenderer.invoke('ftp:download', o), ftpDownloadFolder: (game_id) => ipcRenderer.invoke('ftp:download-folder', { game_id }), ftpUpload: (o) => ipcRenderer.invoke('ftp:upload', o), ftpInstallPfs: (o) => ipcRenderer.invoke('ftp:install-pfs', o), ftpListDir: (p) => ipcRenderer.invoke('ftp:list-dir', { path: p }), ftpFindGame: (game_id) => ipcRenderer.invoke('ftp:find-game', { game_id }), ftpDeleteGame: (game_id, confirmation) => ipcRenderer.invoke('ftp:delete-game', { game_id, confirmation }), // Database listGames: (f) => ipcRenderer.invoke('db:games:list', f), listLibraryTitles: (f) => ipcRenderer.invoke('library:titles:list', f), etahenLinkInventoryStatus: () => ipcRenderer.invoke('etahen-link:inventory:status'), listStorageInventory: (f) => ipcRenderer.invoke('storage:inventory:list', f), labelStorageDevice: (input) => ipcRenderer.invoke('storage:inventory:label', input), storageGameLocator: (input) => ipcRenderer.invoke('storage:locator:list', input), guidedMoveList: (input) => ipcRenderer.invoke('guided-move:list', input), guidedMoveCreate: (input) => ipcRenderer.invoke('guided-move:create', input), guidedMoveCancel: (id) => ipcRenderer.invoke('guided-move:cancel', { id }), guidedMoveReconcile: (id) => ipcRenderer.invoke('guided-move:reconcile', { id }), gameMoveCapabilities: () => ipcRenderer.invoke('game-move:capabilities'), installDestinations: (input) => ipcRenderer.invoke('install:destinations:list', input), packageRecipesList: (titleId) => ipcRenderer.invoke('package-recipes:list', { titleId }), packageCatalog: () => ipcRenderer.invoke('package-catalog:list'), compatibilityGuidance: (input) => ipcRenderer.invoke('compatibility:guidance', input), packageRecipeSave: (input) => ipcRenderer.invoke('package-recipes:save', input), ps4PackageBuilderStatus: () => ipcRenderer.invoke('ps4-packages:builder-status'), ps4PackageInspect: input => ipcRenderer.invoke('ps4-packages:inspect', input), ps4PackageCreate: input => ipcRenderer.invoke('ps4-packages:create', input), ps4PackageJobs: () => ipcRenderer.invoke('ps4-packages:list'), ps4PackageJob: id => ipcRenderer.invoke('ps4-packages:get', { id }), ps4PackageRun: id => ipcRenderer.invoke('ps4-packages:run', { id }), ps4PackageResume: id => ipcRenderer.invoke('ps4-packages:resume', { id }), ps4PackageRestorePlan: input => ipcRenderer.invoke('ps4-packages:restore-plan', input), ps4PackageRestore: input => ipcRenderer.invoke('ps4-packages:restore', input), ps4PackageRestoreVerify: titleId => ipcRenderer.invoke('ps4-packages:restore-verify', { titleId }), ps5NativePackageBuilderStatus: () => ipcRenderer.invoke('ps5-native-packages:builder-status'), ps5NativePackageInspectBackup: input => ipcRenderer.invoke('ps5-native-packages:inspect-backup', input), ps5NativePackageInspectFile: filePath => ipcRenderer.invoke('ps5-native-packages:inspect-package', { filePath }), ps5PackageComparison: input => ipcRenderer.invoke('ps5-native-packages:compare', input), ps5UpstreamControl: (action, input) => ipcRenderer.invoke(`ps5-native-packages:control-${action}`, input), ps5A8DistinctControl: (action, input) => ipcRenderer.invoke(`ps5-native-packages:a8-distinct-control-${action}`, input), ps5A10TransitionControl: (action, input) => ipcRenderer.invoke(`ps5-native-packages:a10-transition-control-${action}`, input), ps5LaunchReference: (action, input) => ipcRenderer.invoke(`ps5-native-packages:launch-reference-${action}`, input), ps5NativePackageCreate: input => ipcRenderer.invoke('ps5-native-packages:create', input), ps5NativePackageJobs: () => ipcRenderer.invoke('ps5-native-packages:list'), ps5NativePackageJob: id => ipcRenderer.invoke('ps5-native-packages:get', { id }), ps5NativePackageRun: id => ipcRenderer.invoke('ps5-native-packages:run', { id }), ps5NativePackageResume: id => ipcRenderer.invoke('ps5-native-packages:resume', { id }), ps5ConsoleStagingProbe: () => ipcRenderer.invoke('ps5-console-staging:probe'), ps5ConsoleStagingDiscover: titleId => ipcRenderer.invoke('ps5-console-staging:discover', { titleId }), ps5ConsoleStagingCreate: input => ipcRenderer.invoke('ps5-console-staging:create', input), ps5ConsoleStagingJobs: () => ipcRenderer.invoke('ps5-console-staging:list'), ps5ConsoleStagingJob: id => ipcRenderer.invoke('ps5-console-staging:get', { id }), ps5ConsoleStagingRun: id => ipcRenderer.invoke('ps5-console-staging:run', { id }), ps5ConsoleStagingResume: id => ipcRenderer.invoke('ps5-console-staging:resume', { id }), ps5NativePackageRestorePlan: input => ipcRenderer.invoke('ps5-native-packages:restore-plan', input), ps5NativePackageRestore: input => ipcRenderer.invoke('ps5-native-packages:restore', input), ps5NativePackageRestoreStatus: operationId => ipcRenderer.invoke('ps5-native-packages:restore-status', { operationId }), ps5NativePackageRestoreCancel: (operationId, confirmation) => ipcRenderer.invoke('ps5-native-packages:restore-cancel', { operationId, confirmation }), ps5NativePackageRestoreVerify: input => ipcRenderer.invoke('ps5-native-packages:restore-verify', typeof input === 'object' ? input : { titleId: input }), ps5NativePackageRestoreLaunchPlan: operationId => ipcRenderer.invoke('ps5-native-packages:restore-launch-plan', { operationId }), ps5NativePackageRestoreLaunch: input => ipcRenderer.invoke('ps5-native-packages:restore-launch', input), ps5NativePackageRestoreLaunchStatus: operationId => ipcRenderer.invoke('ps5-native-packages:restore-launch-status', { operationId }), ps5NativePackageRetainedLaunchEvidence: operationId => ipcRenderer.invoke('ps5-native-packages:retained-launch-evidence', { operationId }), recordSemanticEvidence: (input) => ipcRenderer.invoke('library:evidence:override', input), listSemanticEvidence: (input) => ipcRenderer.invoke('library:evidence:list', input), legacySemanticDiagnostics: () => ipcRenderer.invoke('library:legacy-diagnostics'), updateGame: (o) => ipcRenderer.invoke('db:games:update', o), listBackups: (id) => ipcRenderer.invoke('db:backups:list', id), deleteBackup: (id, confirmation) => ipcRenderer.invoke('db:backups:delete', { id, confirmation }), addBackup: (game_id, backup_path) => ipcRenderer.invoke('db:backups:upsert', { game_id, backup_path }), getStats: () => ipcRenderer.invoke('db:stats'), dbQuery: (sql) => ipcRenderer.invoke('db:query', { sql }), dbClear: confirmation => ipcRenderer.invoke('db:clear', { confirmation }), dbClearSelective: (opts, confirmation) => ipcRenderer.invoke('db:clear-selective', { ...opts, confirmation }), dbDeleteRows: (table, ids, confirmation) => ipcRenderer.invoke('db:delete-rows', { table, ids, confirmation }), dbClearHashes: confirmation => ipcRenderer.invoke('db:clear-hashes', { confirmation }), exportSql: () => ipcRenderer.invoke('db:export:sql'), exportCsv: (t) => ipcRenderer.invoke('db:export:csv', { table: t }), // Local scanBackups: () => ipcRenderer.invoke('local:scan-backups'), selectFolder: () => ipcRenderer.invoke('dialog:select-folder'), openShell: (t) => ipcRenderer.invoke('shell:open', { target: t }), // Prospero metadata prosperoFetch: (game_id) => ipcRenderer.invoke('prospero:fetch', { game_id }), prosperoFetchIcon: (game_id) => ipcRenderer.invoke('prospero:fetch-icon', { game_id }), prosperoRefreshAll: () => ipcRenderer.invoke('prospero:refresh-all'), // Backpork folders backporksList: () => ipcRenderer.invoke('backporks:list'), backporksAdd: (name, path) => ipcRenderer.invoke('backporks:add', { name, path }), backporksAddRoot: (rootPath) => ipcRenderer.invoke('backporks:add-root', { rootPath }), backporksRemove: (id, confirmation) => ipcRenderer.invoke('backporks:remove', { id, confirmation }), backporksScan: (id) => ipcRenderer.invoke('backporks:scan', { id }), backporksScanAll: () => ipcRenderer.invoke('backporks:scan-all'), backporksGames: (folder) => ipcRenderer.invoke('backporks:games', { folder }), backporksEntries: (game_id) => ipcRenderer.invoke('backporks:entries', { game_id }), backporksPork: (game_id, folder_name, folder_path) => ipcRenderer.invoke('backporks:pork', { game_id, folder_name, folder_path }), backporksCreateFolder: (name, parentPath) => ipcRenderer.invoke('backporks:create-folder', { name, parentPath }), firmwareLabels: () => ipcRenderer.invoke('db:firmware:labels'), trophyActiveCatalogStatus: () => ipcRenderer.invoke('trophy:active-catalog:status'), trophyActiveCatalog: () => ipcRenderer.invoke('trophy:active-catalog'), trophyInstalledCatalogStatus: () => ipcRenderer.invoke('trophy:installed-catalog:status'), trophyInstalledCatalog: () => ipcRenderer.invoke('trophy:installed-catalog'), trophyImportedCatalog: () => ipcRenderer.invoke('trophy:imported-catalog:list'), trophyImportDefinition: (packageData) => ipcRenderer.invoke('trophy:imported-catalog:import', { package: packageData }), trophyUnlockOne: (input) => ipcRenderer.invoke('trophy:unlock-one', input), trophyHistoryImportStatus: () => ipcRenderer.invoke('trophy:history-import:status'), trophyHistoryAuthenticate: (input) => ipcRenderer.invoke('trophy:history-import:authenticate', input), trophyHistoryBrowserAuthenticate: () => ipcRenderer.invoke('trophy:history-import:browser-authenticate'), trophyHistoryRevoke: () => ipcRenderer.invoke('trophy:history-import:revoke'), trophyHistoryPreview: (input) => ipcRenderer.invoke('trophy:history-import:preview', input), trophyHistoryApply: (input) => ipcRenderer.invoke('trophy:history-import:apply', input), trophyHistorySnapshots: (input) => ipcRenderer.invoke('trophy:history-import:snapshots', input), trophyHistorySnapshot: (input) => ipcRenderer.invoke('trophy:history-import:snapshot', input), trophyProgressionStatus: () => ipcRenderer.invoke('trophy:progression:status'), trophyProgressionCatalogs: () => ipcRenderer.invoke('trophy:progression:catalogs'), trophyProgressionCreate: (input) => ipcRenderer.invoke('trophy:progression:create', input), trophyProgressionPlans: (input) => ipcRenderer.invoke('trophy:progression:plans', input || {}), trophyProgressionPlan: (input) => ipcRenderer.invoke('trophy:progression:plan', input), // Hash verification hashStatus: (game_id) => ipcRenderer.invoke('hash:status', { game_id }), hashCompute: (backup_path, game_id) => ipcRenderer.invoke('hash:compute', { backup_path, game_id }), hashAdd: (o) => ipcRenderer.invoke('hash:add', o), hashExport: () => ipcRenderer.invoke('hash:export'), hashBulk: (source) => ipcRenderer.invoke('hash:bulk', { source }), hashImportCommunity: () => ipcRenderer.invoke('hash:import-community'), hashCommunityInfo: () => ipcRenderer.invoke('hash:community-info'), hashGameSummary: () => ipcRenderer.invoke('hash:game-summary'), // Local Save Manager (garlic-savemgr) savemgrPushPayload: input => ipcRenderer.invoke('savemgr:push-payload', input), savemgrRequest: (path) => ipcRenderer.invoke('savemgr:request', { path }), savemgrUpload: (path, data) => ipcRenderer.invoke('savemgr:upload', { path, data }), savemgrDownload: (path, filename) => ipcRenderer.invoke('savemgr:download', { path, filename }), savemgrDecrypt: (data, filename) => ipcRenderer.invoke('savemgr:decrypt', { data, filename }), savemgrResign: (data, aid, filename) => ipcRenderer.invoke('savemgr:resign', { data, aid, filename }), savemgrIcon: () => ipcRenderer.invoke('savemgr:icon'), savemgrDumpUsb: (idx) => ipcRenderer.invoke('savemgr:dump-usb', { idx }), savemgrCreatePfs: (size) => ipcRenderer.invoke('savemgr:create-pfs', { size }), savemgrDownloadNew: (name, aid) => ipcRenderer.invoke('savemgr:download-new', { name, aid }), // PSNotify psnotifySend: (message, subMessage) => ipcRenderer.invoke('psnotify:send', { message, subMessage }), psnotifyHistory: () => ipcRenderer.invoke('psnotify:history'), psnotifyTest: () => ipcRenderer.invoke('psnotify:test'), psnotifyPushPayload: () => ipcRenderer.invoke('psnotify:pushPayload'), // Y2JB Updater y2jbCheck: () => ipcRenderer.invoke('y2jb:check'), y2jbApply: () => ipcRenderer.invoke('y2jb:apply'), y2jbGenCatalog: () => ipcRenderer.invoke('y2jbgen:catalog'), y2jbGenSelectPayloads: () => ipcRenderer.invoke('y2jbgen:select-payloads'), y2jbGenBuild: (o) => ipcRenderer.invoke('y2jbgen:build', o), systemStateStatus: () => ipcRenderer.invoke('system-state:status'), systemStateDeploy: () => ipcRenderer.invoke('system-state:deploy'), systemStateCommand: (o) => ipcRenderer.invoke('system-state:command', o), bdjbGenSelectPayloads: () => ipcRenderer.invoke('bdjbgen:select-payloads'), bdjbGenBuild: (o) => ipcRenderer.invoke('bdjbgen:build', o), // Transfer Manager transferState: () => ipcRenderer.invoke('transfer:state'), transferPause: () => ipcRenderer.invoke('transfer:pause'), transferResume: () => ipcRenderer.invoke('transfer:resume'), transferCancel: (id) => ipcRenderer.invoke('transfer:cancel', { id }), transferClearDone: () => ipcRenderer.invoke('transfer:clear-done'), transferSetConcurrent:(n) => ipcRenderer.invoke('transfer:set-concurrent', { n }), // Payload Manager payloadSourcesList: () => ipcRenderer.invoke('payload:sources:list'), payloadSourcesAdd: (name, url) => ipcRenderer.invoke('payload:sources:add', { name, url }), payloadSourcesRemove: (id, confirmation) => ipcRenderer.invoke('payload:sources:remove', { id, confirmation }), payloadSourcesToggle: (id) => ipcRenderer.invoke('payload:sources:toggle', { id }), payloadCheckUpdates: (id) => ipcRenderer.invoke('payload:check-updates', id != null ? { id } : {}), payloadDownload: (o) => ipcRenderer.invoke('payload:download', o), payloadListLocal: () => ipcRenderer.invoke('payload:list-local'), payloadListRemote: () => ipcRenderer.invoke('payload:list-remote'), payloadPush: (o) => ipcRenderer.invoke('payload:push', o), payloadDeleteRemote: (filename, confirmation) => ipcRenderer.invoke('payload:delete-remote', { filename, confirmation }), payloadDeleteLocal: (o) => ipcRenderer.invoke('payload:delete-local', o), // System View openSystemViewPopout: (videoId, audioId, resolution) => ipcRenderer.invoke('system-view:open-popout', { videoId, audioId, resolution }), systemViewPickSave: (opts) => ipcRenderer.invoke('system-view:pick-save', opts || {}), systemViewWriteFile: (o) => ipcRenderer.invoke('system-view:write-file', o), systemViewAppendFile: (o) => ipcRenderer.invoke('system-view:append-file', o), systemViewDeleteFile: (o) => ipcRenderer.invoke('system-view:delete-file', o), // Cheats cheatsStats: () => ipcRenderer.invoke('cheats:stats'), cheatsForGame: (cusa_id) => ipcRenderer.invoke('cheats:for-game', { cusa_id }), cheatsSearch: (query) => ipcRenderer.invoke('cheats:search', { query }), cheatsAll: () => ipcRenderer.invoke('cheats:all'), cheatsFetchIndex: () => ipcRenderer.invoke('cheats:fetch-index'), cheatsDownloadAll: () => ipcRenderer.invoke('cheats:download-all'), cheatsCancel: () => ipcRenderer.invoke('cheats:cancel'), cheatsUnmatched: () => ipcRenderer.invoke('cheats:unmatched'), cheatsAssign: (filename, cusaId) => ipcRenderer.invoke('cheats:assign', { filename, cusaId }), cheatsFtpInstall: (remotePath) => ipcRenderer.invoke('cheats:ftp-install', { remotePath }), // Media mediaScan: () => ipcRenderer.invoke('media:scan'), mediaDownload: (o) => ipcRenderer.invoke('media:download', o), mediaFetchThumb: (remotePath) => ipcRenderer.invoke('media:fetch-thumb', { remotePath }), mediaGetCachePath: (remotePath) => ipcRenderer.invoke('media:get-cache-path', { remotePath }), mediaFetchVideoPreview: (remotePath) => ipcRenderer.invoke('media:fetch-video-preview', { remotePath }), mediaOpen: (remotePath) => ipcRenderer.invoke('media:open', { remotePath }), mediaDiscordSend: (o) => ipcRenderer.invoke('media:discord-send', o), mediaFetchClipThumbs: (paths) => ipcRenderer.invoke('media:batch-clip-thumbs', paths), mediaLoadClipThumbs: () => ipcRenderer.invoke('media:clip-thumbs:load'), mediaCacheStatus: () => ipcRenderer.invoke('media:cache:status'), mediaCachePin: (id, pinned) => ipcRenderer.invoke('media:cache:pin', { id, pinned }), mediaCacheClear: (options) => ipcRenderer.invoke('media:cache:clear', options || {}), mediaCacheRetention: (options) => ipcRenderer.invoke('media:cache:retention', options || {}), // PS5 Autoloader autoloaderList: () => ipcRenderer.invoke('autoloader:list'), autoloaderRead: () => ipcRenderer.invoke('autoloader:read'), autoloaderSave: (content) => ipcRenderer.invoke('autoloader:save', { content }), autoloaderSaveLocal: (o) => ipcRenderer.invoke('autoloader:save-local', o), payloadManagerAutoloadRepairPlan: () => ipcRenderer.invoke('payload-manager:autoload-repair-plan'), payloadManagerAutoloadRepairApply: input => ipcRenderer.invoke('payload-manager:autoload-repair-apply', input), payloadManagerVerifiedStart: input => ipcRenderer.invoke('payload-manager:verified-start', input), payloadManagerAutoloadRollbackPlan: () => ipcRenderer.invoke('payload-manager:autoload-repair-rollback-plan'), payloadManagerAutoloadRollbackApply: input => ipcRenderer.invoke('payload-manager:autoload-repair-rollback', input), etaHenAutoloadPersistencePlan: () => ipcRenderer.invoke('etahen:autoload-persistence-plan'), etaHenAutoloadPersistenceApply: input => ipcRenderer.invoke('etahen:autoload-persistence-apply', input), etaHenDirectStart: input => ipcRenderer.invoke('etahen:direct-start', input), etaHenAutoloadPersistenceRollbackPlan: () => ipcRenderer.invoke('etahen:autoload-persistence-rollback-plan'), etaHenAutoloadPersistenceRollback: input => ipcRenderer.invoke('etahen:autoload-persistence-rollback', input), // Autoloader Snapshots snapshotTake: (label) => ipcRenderer.invoke('snapshot:autoloader:take', { label }), snapshotList: () => ipcRenderer.invoke('snapshot:autoloader:list'), snapshotRestore: (id, confirmation) => ipcRenderer.invoke('snapshot:autoloader:restore', { id, confirmation }), snapshotDelete: (id, confirmation) => ipcRenderer.invoke('snapshot:autoloader:delete', { id, confirmation }), snapshotExport: (id) => ipcRenderer.invoke('snapshot:autoloader:export', { id }), snapshotImport: (opts) => ipcRenderer.invoke('snapshot:autoloader:import', opts || {}), // Language / i18n langList: () => ipcRenderer.invoke('lang:list'), langLoad: (code) => ipcRenderer.invoke('lang:load', code), langExportBase: () => ipcRenderer.invoke('lang:export-base'), langImport: () => ipcRenderer.invoke('lang:import'), // Payload TCP sender (sends local payload to PS5 via raw TCP, e.g. bin-loader port 9021) payloadTcpSend: (o) => ipcRenderer.invoke('payload:tcp-send', o), // XAvatar PS5 management xavatarUploadToPs5: (buffer, filename) => ipcRenderer.invoke('xavatar:upload-to-ps5', { buffer, filename }), xavatarSaveFile: (buffer, filename) => ipcRenderer.invoke('xavatar:save-file', { buffer, filename }), // Fallback conversion channel (used when window.xavatarAPI is unavailable) xavatarConvertCanvas: (rgba, width, height, filename) => ipcRenderer.invoke('xavatar:convert-rgba', { rgba, width, height, filename }), ftpDownloadFile: (remotePath) => ipcRenderer.invoke('ftp:download-file', { remotePath }), ftpDeleteFile: (remotePath, confirmation) => ipcRenderer.invoke('ftp:delete-file', { remotePath, confirmation }), xavatarExtractPng: (buffer) => ipcRenderer.invoke('xavatar:extract-png', { buffer }), xavatarCompanion: () => ipcRenderer.invoke('xavatar:companion'), xavatarInstallCompanion: (confirmation) => ipcRenderer.invoke('xavatar:install-companion', { confirmation }), ps5ProfileInspect: () => ipcRenderer.invoke('ps5-profile:inspect'), ps5ProfileRuntimeStatus: () => ipcRenderer.invoke('ps5-profile:runtime-status'), ps5ProfileRuntimePreflight: () => ipcRenderer.invoke('ps5-profile:runtime-preflight'), ps5ProfileStartPayload: input => ipcRenderer.invoke('ps5-profile:start-payload', input), ps5P15A10: (action, input) => ipcRenderer.invoke(`ps5-p15-a10:${action}`, input), ps5P15R2Status: () => ipcRenderer.invoke('ps5-p15-r2:status'), ps5P15R2Preflight: () => ipcRenderer.invoke('ps5-p15-r2:preflight'), ps5P15R2Qualify: () => ipcRenderer.invoke('ps5-p15-r2:qualify'), ps5ProfileCurrentAvatar: input => ipcRenderer.invoke('ps5-profile:current-avatar', input), ps5ProfileListOperations: () => ipcRenderer.invoke('ps5-profile:list-operations'), ps5ProfilePrepareRename: input => ipcRenderer.invoke('ps5-profile:prepare-rename', input), ps5ProfilePrepareAvatar: input => ipcRenderer.invoke('ps5-profile:prepare-avatar', input), ps5ProfileApply: input => ipcRenderer.invoke('ps5-profile:apply', input), ps5ProfileRollback: input => ipcRenderer.invoke('ps5-profile:rollback', input), // ── Game Conversion (FFPKG / ExFAT) — Windows only ─────────────────────── convToolInfo: () => ipcRenderer.invoke('conv:tool-info'), convExfatToolInfo: () => ipcRenderer.invoke('conv:exfat-tool-info'), convToolPick: () => ipcRenderer.invoke('conv:tool-pick'), convExfatToolPick: () => ipcRenderer.invoke('conv:exfat-tool-pick'), convPickOutputDir: () => ipcRenderer.invoke('conv:pick-output-dir'), convPickTempDir: () => ipcRenderer.invoke('conv:pick-temp-dir'), convQueueAdd: (o) => ipcRenderer.invoke('conv:queue:add', o), convQueueAddBatch: (a) => ipcRenderer.invoke('conv:queue:add-batch', a), convQueueList: () => ipcRenderer.invoke('conv:queue:list'), convQueueIsPaused: () => ipcRenderer.invoke('conv:queue:is-paused'), convQueueStart: () => ipcRenderer.invoke('conv:queue:start'), convQueueCancel: (id) => ipcRenderer.invoke('conv:queue:cancel', { id }), convQueueClearDone: () => ipcRenderer.invoke('conv:queue:clear-done'), convQueueRetry: (id) => ipcRenderer.invoke('conv:queue:retry', { id }), convQueueCleanupTemp: () => ipcRenderer.invoke('conv:queue:cleanup-temp'), // GarlicSaves garlicStart: () => ipcRenderer.invoke('garlic:start'), // ELF Arsenal (PS5 web API on :6969) eaVersion: () => ipcRenderer.invoke('ea:version'), eaStart: () => ipcRenderer.invoke('ea:start'), eaEmbedUrl: () => ipcRenderer.invoke('ea:embed-url'), eaList: (path) => ipcRenderer.invoke('ea:list', { path }), eaStat: (path) => ipcRenderer.invoke('ea:stat', { path }), eaUsb: () => ipcRenderer.invoke('ea:usb'), eaMkdir: (path) => ipcRenderer.invoke('ea:mkdir', { path }), eaRename: (src, dst) => ipcRenderer.invoke('ea:rename', { src, dst }), eaDelete: (path, recursive) => ipcRenderer.invoke('ea:delete', { path, recursive }), eaCopy: (src, dst) => ipcRenderer.invoke('ea:copy', { src, dst }), eaMove: (src, dst) => ipcRenderer.invoke('ea:move', { src, dst }), eaJobStatus: () => ipcRenderer.invoke('ea:job-status'), eaJobCancel: () => ipcRenderer.invoke('ea:job-cancel'), eaLaunch: (path, args, daemon) => ipcRenderer.invoke('ea:launch', { path, args, daemon }), eaDownload: (path, filename) => ipcRenderer.invoke('ea:download', { path, filename }), eaUpload: (localPath, remotePath) => ipcRenderer.invoke('ea:upload', { localPath, remotePath }), // PFS Ripper (PFS/image library browser) pfsFoldersList: () => ipcRenderer.invoke('pfsripper:folders:list'), pfsFoldersAdd: () => ipcRenderer.invoke('pfsripper:folders:add'), pfsFoldersRemove: (folder) => ipcRenderer.invoke('pfsripper:folders:remove', { folder }), pfsScan: () => ipcRenderer.invoke('pfsripper:scan'), pfsExportCopy: (path) => ipcRenderer.invoke('pfsripper:export-copy', { path }), pfsExportFtp: (path, remoteDir) => ipcRenderer.invoke('pfsripper:export-ftp', { path, remoteDir }), pfsReveal: (path) => ipcRenderer.invoke('pfsripper:reveal', { path }), // Auto-Backpork Generator bpgenSdkPairs: () => ipcRenderer.invoke('bpgen:sdk-pairs'), bpgenCustomPairsGet: () => ipcRenderer.invoke('bpgen:custom-sdk-pairs-get'), bpgenCustomPairsSet: (pairs) => ipcRenderer.invoke('bpgen:custom-sdk-pairs-set', pairs), bpgenListMissing: (fw) => ipcRenderer.invoke('bpgen:list-missing', { firmwareLabel: fw || '' }), bpgenListAll: () => ipcRenderer.invoke('bpgen:list-all-with-local'), bpgenPickFolder: () => ipcRenderer.invoke('bpgen:pick-folder'), bpgenRun: (opts) => ipcRenderer.invoke('bpgen:run', opts), bpgenCancel: () => ipcRenderer.invoke('bpgen:cancel'), // Auto-Backpork Generator — Fakelib Manager bpgenFakelibScan: () => ipcRenderer.invoke('bpgen:fakelib-scan'), bpgenFakelibSetBasedir: (dir) => ipcRenderer.invoke('bpgen:fakelib-set-basedir', { dir: dir || null }), bpgenFakelibPickPup: () => ipcRenderer.invoke('bpgen:fakelib-pick-pup'), bpgenFakelibExtract: (opts) => ipcRenderer.invoke('bpgen:fakelib-extract', opts), // Web UI server control webuiStatus: () => ipcRenderer.invoke('webui:status'), webuiStart: () => ipcRenderer.invoke('webui:start'), webuiStop: () => ipcRenderer.invoke('webui:stop'), // Donation popup openDonate: () => ipcRenderer.send('donation:show'), // Events from main process on: (ch, fn) => ipcRenderer.on(ch, (_e, ...args) => fn(...args)), off: (ch) => ipcRenderer.removeAllListeners(ch), }); // xAvatar dedicated bridge. The reusable module's standalone preload is kept in // src/xavatarElectronModule for consumers, but the root app exposes the small // surface it actually needs directly so startup does not depend on chaining a // second preload script. contextBridge.exposeInMainWorld('xavatarAPI', { version: () => ipcRenderer.invoke('xavatar:version'), async convertFromCanvas(canvas, opts = {}) { const ctx = canvas.getContext('2d'); const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const rgba = new Uint8Array(imgData.data.buffer); const result = await ipcRenderer.invoke('xavatar:convert-rgba', { rgba, width: canvas.width, height: canvas.height, filename: opts.filename, }); if (!result || !result.ok) throw new Error(result?.error || 'xAvatar conversion failed'); return { buffer: result.buffer?.buffer || result.buffer, filename: result.filename }; }, download(buffer, filename) { const blob = new Blob([buffer], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = Object.assign(document.createElement('a'), { href: url, download: filename }); document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 10_000); }, });