From 129ffcf1a91004ee0bd14c4059e2166693f29bb6 Mon Sep 17 00:00:00 2001 From: ovosimpatico Date: Tue, 7 Jan 2025 09:22:50 -0300 Subject: [PATCH] Game Gear fixed Proxy BIOS, enhance config, allow per-platform decompression --- lib/emulatorConfig.js | 208 ++++++++++++++++++++++++++++++++------- server.js | 35 +++++++ views/pages/emulator.ejs | 97 +++++++++++++++--- 3 files changed, 291 insertions(+), 49 deletions(-) diff --git a/lib/emulatorConfig.js b/lib/emulatorConfig.js index 9588888..ab5b3c9 100644 --- a/lib/emulatorConfig.js +++ b/lib/emulatorConfig.js @@ -1,65 +1,203 @@ // See https://emulatorjs.org/docs/systems for available cores -const coreMap = { +const systemConfigs = { // Nintendo Systems - 'Nintendo Entertainment System': 'fceumm', - 'Super Nintendo Entertainment System': 'snes9x', - 'Nintendo 64': 'mupen64plus_next', - 'Nintendo DS': 'desmume2015', - 'Nintendo Game Boy': 'gambatte', - 'Nintendo Game Boy Color': 'gambatte', - 'Nintendo Game Boy Advance': 'mgba', + 'Nintendo Entertainment System': { + core: 'fceumm', + unpackRoms: true + }, + 'Super Nintendo Entertainment System': { + core: 'snes9x', + unpackRoms: true + }, + 'Nintendo 64': { + core: 'mupen64plus_next', + unpackRoms: true + }, + 'Nintendo DS': { + core: 'desmume2015', + unpackRoms: true + }, + 'Nintendo Game Boy': { + core: 'gambatte', + unpackRoms: true + }, + 'Nintendo Game Boy Color': { + core: 'gambatte', + unpackRoms: true + }, + 'Nintendo Game Boy Advance': { + core: 'mgba', + unpackRoms: true + }, // Sega Systems - 'Sega Master System': 'smsplus', - 'Sega Game Gear': 'genesis_plus_gx', // TODO: fix rom loading - 'Sega Mega Drive': 'genesis_plus_gx', - 'Sega CD': 'genesis_plus_gx', // TODO: add bios - 'Sega 32X': 'picodrive', // Known issue: https://github.com/EmulatorJS/EmulatorJS/issues/579 - 'Sega Saturn': 'yabause', + 'Sega Master System': { + core: 'smsplus', + unpackRoms: true + }, + 'Sega Game Gear': { + core: 'genesis_plus_gx', + unpackRoms: false + }, + 'Sega Mega Drive': { + core: 'genesis_plus_gx', + unpackRoms: true + }, + 'Sega CD': { // TODO: add bios + core: 'genesis_plus_gx', + unpackRoms: false, + bios: { + required: true, + files: { + 'bios_CD_U.bin': { + url: 'https://github.com/Abdess/retroarch_system/raw/refs/heads/libretro/Sega%20-%20Mega%20CD%20-%20Sega%20CD/bios_CD_U.bin', + md5: '2efd74e3232ff260e371b99f84024f7f', + region: 'US' + }, + 'bios_CD_E.bin': { + url: 'https://github.com/Abdess/retroarch_system/raw/refs/heads/libretro/Sega%20-%20Mega%20CD%20-%20Sega%20CD/bios_CD_E.bin', + md5: 'e66fa1dc5820d254611fdcdba0662372', + region: 'EU' + }, + 'bios_CD_J.bin': { + url: 'https://github.com/Abdess/retroarch_system/raw/refs/heads/libretro/Sega%20-%20Mega%20CD%20-%20Sega%20CD/bios_CD_J.bin', + md5: 'bdeb4c47da613946d422d97d98b21cda', + region: 'JP' + } + } + } + }, + 'Sega 32X': { // Known issue: https://github.com/EmulatorJS/EmulatorJS/issues/579 + core: 'picodrive', + unpackRoms: true + }, + 'Sega Saturn': { + core: 'yabause', + unpackRoms: true + }, // Atari Systems - 'Atari 2600': 'stella2014', - 'Atari 5200': 'a5200', - 'Atari 7800': 'prosystem', - 'Atari Jaguar': 'virtualjaguar', - 'Atari Lynx': 'handy', + 'Atari 2600': { + core: 'stella2014', + unpackRoms: true + }, + 'Atari 5200': { + core: 'a5200', + unpackRoms: true + }, + 'Atari 7800': { + core: 'prosystem', + unpackRoms: true + }, + 'Atari Jaguar': { + core: 'virtualjaguar', + unpackRoms: true + }, + 'Atari Lynx': { + core: 'handy', + unpackRoms: true + }, // Commodore Systems - 'Commodore 64': 'vice_x64sc', - 'Commodore 128': 'vice_x128', // Untested, Myrient has no ROMs for it - 'Commodore Amiga': 'puae', // TODO: fix rom loading - 'Commodore PET': 'vice_xpet', // Untested, Myrient has no ROMs for it - 'Commodore Plus-4': 'vice_xplus4', // TODO: fix rom loading - 'Commodore VIC-20': 'vice_xvic', // TODO: fix rom loading + 'Commodore 64': { + core: 'vice_x64sc', + unpackRoms: true + }, + 'Commodore 128': { // Untested, Myrient has no ROMs for it + core: 'vice_x128', + unpackRoms: true + }, + 'Commodore Amiga': { // TODO: fix rom loading + core: 'puae', + unpackRoms: true + }, + 'Commodore PET': { // Untested, Myrient has no ROMs for it + core: 'vice_xpet', + unpackRoms: true + }, + 'Commodore Plus-4': { // TODO: fix rom loading + core: 'vice_xplus4', + unpackRoms: true + }, + 'Commodore VIC-20': { // TODO: fix rom loading + core: 'vice_xvic', + unpackRoms: true + }, // Sony Systems - 'Sony PlayStation 1': 'pcsx_rearmed', // TODO: fix rom loading + 'Sony PlayStation 1': { // TODO: fix rom loading (doesn't seem to affect all games) + core: 'pcsx_rearmed', + unpackRoms: true + }, // Other Systems - 'Arcade': 'fbneo', // TODO: fix rom loading - 'ColecoVision': 'gearcoleco', // TODO: add bios - 'Panasonic 3DO': 'opera', // TODO: fix rom loading + 'Arcade': { // TODO: fix rom loading + core: 'fbneo', + unpackRoms: true + }, + 'ColecoVision': { // TODO: add bios + core: 'gearcoleco', + unpackRoms: false, + bios: { + required: true, + files: { + 'colecovision.rom': { + url: 'https://github.com/Abdess/retroarch_system/raw/refs/heads/libretro/Coleco%20-%20ColecoVision/colecovision.rom', + md5: '2c66f5911e5b42b8ebe113403548eee7', + region: 'Global' + } + } + } + }, + 'Panasonic 3DO': { // TODO: fix rom loading + core: 'opera', + unpackRoms: false + // bios: { + // required: true, + // files: { + // 'panafz1.bin': { + // url: 'https://files.catbox.moe/u5hy1c.bin', + // md5: 'f47264dd47fe30f73ab3c010015c155b', + // region: 'Global' + // } + // } + // } + } }; -const COMPATIBLE_SYSTEMS = Object.keys(coreMap); +const COMPATIBLE_SYSTEMS = Object.keys(systemConfigs); export function isEmulatorCompatible(category) { + console.log(`[EmulatorConfig] Checking compatibility for: ${category}`); + if (process.env.EMULATOR_ENABLED !== 'true') { + console.log('[EmulatorConfig] Emulator is disabled via environment variable'); return false; } - return COMPATIBLE_SYSTEMS.includes(category); + + const isCompatible = COMPATIBLE_SYSTEMS.includes(category); + console.log(`[EmulatorConfig] System compatibility result: ${isCompatible}`); + return isCompatible; } export function getEmulatorConfig(category) { - const core = coreMap[category] || 'unknown'; + console.log(`[EmulatorConfig] Configuring emulator for category: ${category}`); + + const systemConfig = systemConfigs[category]; + if (!systemConfig) { + console.warn(`[EmulatorConfig] No configuration found for category: ${category}`); + return null; + } - // Add system-specific settings const config = { - core, + core: systemConfig.core, system: category, - options: {} + unpackRoms: systemConfig.unpackRoms, + options: {}, + bios: systemConfig.bios || null }; + console.log(`[EmulatorConfig] Final configuration:`, config); return config; } diff --git a/server.js b/server.js index 70ff32a..3ce5d26 100644 --- a/server.js +++ b/server.js @@ -277,6 +277,41 @@ app.get("/proxy-rom/:id", async function (req, res) { } }); +app.get("/proxy-bios", async function (req, res) { + // Block access if emulator is disabled + if (process.env.EMULATOR_ENABLED !== 'true') { + res.status(403).send('Emulator feature is disabled'); + return; + } + + const biosUrl = req.query.url; + + // Validate that URL is from GitHub + if (!biosUrl || !biosUrl.startsWith('https://github.com')) { + res.status(403).send('Invalid BIOS URL - only GitHub URLs are allowed'); + return; + } + + try { + const response = await fetch(biosUrl); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const contentLength = response.headers.get('content-length'); + const contentType = response.headers.get('content-type'); + + res.setHeader('Content-Type', contentType || 'application/octet-stream'); + res.setHeader('Content-Length', contentLength); + + response.body.pipe(res); + } catch (error) { + console.error('Error proxying BIOS:', error); + res.status(500).send('Error fetching BIOS file'); + } +}); + server.listen(process.env.PORT, process.env.BIND_ADDRESS); server.on("listening", function () { console.log( diff --git a/views/pages/emulator.ejs b/views/pages/emulator.ejs index b838ca8..8b251d2 100644 --- a/views/pages/emulator.ejs +++ b/views/pages/emulator.ejs @@ -129,44 +129,104 @@ console.log(`%c${window.location.origin}/about`, 'font-size: 14px; color: #90CAF9;'); // Configure EmulatorJS + console.log('[Emulator] Starting emulator configuration'); + console.log('[Emulator] System:', '<%= emulatorConfig.system %>'); + console.log('[Emulator] Core:', '<%= emulatorConfig.core %>'); + window.EJS_player = '#game'; window.EJS_core = '<%= emulatorConfig.core %>'; window.EJS_gameUrl = '/proxy-rom/<%= romFile.id %>'; window.EJS_pathtodata = 'https://cdn.emulatorjs.org/stable/data/'; window.EJS_startOnLoaded = true; - window.EJS_biosUrl = undefined; - window.EJS_gameName = '<%= romFile.filename.replace(/\.[^/.]+$/, '') %>'; + window.EJS_gameName = '<%= romFile.filename.replace(/\.[^/.]+$/, "") %>'; window.EJS_backgroundBlur = true; window.EJS_defaultOptions = { - 'save-state-slot': 1, - 'save-state-location': 'local' + 'save-state-slot': 1, + 'save-state-location': 'local' + }; + + // BIOS configuration + window.EJS_biosUrl = <% if (emulatorConfig.bios) { %> + '/proxy-bios?url=' + encodeURIComponent(<%- JSON.stringify(Object.values(emulatorConfig.bios.files)[0].url) %>) + <% } else { %> + undefined + <% } %>; + + console.log('[Emulator] BIOS configuration:', window.EJS_biosUrl); + + // Required for Sega CD ?? + window.EJS_loadStateURL = window.location.href; + window.EJS_saveStateURL = window.location.href; + window.EJS_cheats = true; + + // Add error event listener for the emulator + window.EJS_onGameStart = () => { + console.log('[Emulator] Game started successfully'); + }; + + window.EJS_onLoadState = (state) => { + console.log('[Emulator] Load state:', state); + }; + + window.EJS_onSaveState = (state) => { + console.log('[Emulator] Save state:', state); + }; + + window.EJS_onLoadError = (error) => { + console.error('[Emulator] Load error:', error); }; - // Handle ROMs with progress async function loadRom() { try { + console.log('[Emulator] Starting ROM load process'); const progressContainer = document.getElementById('progress-container'); const progressBar = document.getElementById('download-progress'); const progressText = document.getElementById('progress-text'); progressContainer.style.display = 'flex'; - // Check if the file is compressed based on extension - const isCompressed = /\.(zip)$/i.test('<%= romFile.filename %>'); + const isCompressed = /\.(zip|7z)$/i.test('<%= romFile.filename %>'); + const shouldUnpack = <%= emulatorConfig.unpackRoms %>; + console.log(`[Emulator] ROM compression status: ${isCompressed ? 'compressed' : 'uncompressed'}`); + console.log(`[Emulator] Should unpack: ${shouldUnpack}`); - // Download phase progressText.textContent = 'Downloading ROM...'; + console.log('[Emulator] Initiating ROM download'); + const response = await fetch(EJS_gameUrl); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - if (!isCompressed) { - // For uncompressed ROMs, just return the URL directly + // If we're not unpacking, still show download progress but return direct URL + if (!isCompressed || !shouldUnpack) { + const contentLength = response.headers.get('content-length'); + const total = parseInt(contentLength, 10); + let loaded = 0; + + const reader = response.body.getReader(); + const chunks = []; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + chunks.push(value); + loaded += value.length; + + const percent = Math.round((loaded / total) * 100); + progressBar.style.width = percent + '%'; + progressText.textContent = `Downloading ROM: ${percent}%`; + } + + console.log('[Emulator] Using direct URL for ROM'); progressContainer.style.display = 'none'; - return EJS_gameUrl; + + // Create blob from chunks for direct loading + const blob = new Blob(chunks); + return URL.createObjectURL(blob); } - // For compressed files, continue with decompression + // For compressed files that need unpacking, continue with decompression const contentLength = response.headers.get('content-length'); const total = parseInt(contentLength, 10); let loaded = 0; @@ -186,35 +246,44 @@ progressText.textContent = `Downloading ROM: ${percent}%`; } - // Decompression phase - keep progress at 100% + // Decompression phase progressText.textContent = 'Decompressing the game...'; + console.log('[Emulator] Starting ZIP extraction'); const blob = new Blob(chunks); const zip = await JSZip.loadAsync(blob); const files = Object.keys(zip.files); + console.log('[Emulator] ZIP contents:', files); const romFile = files.find(f => !zip.files[f].dir); if (!romFile) { throw new Error('No ROM file found in ZIP archive'); } + console.log('[Emulator] Found ROM file in ZIP:', romFile); const romData = await zip.files[romFile].async('blob'); + console.log('[Emulator] ROM extraction complete'); progressContainer.style.display = 'none'; return URL.createObjectURL(romData); } catch (error) { - console.error('Error loading ROM:', error); + console.error('[Emulator] Error in loadRom:', error); throw error; } } loadRom() .then(romUrl => { + console.log('[Emulator] ROM loaded successfully, initializing EmulatorJS'); window.EJS_gameUrl = romUrl; const script = document.createElement('script'); script.src = `${window.EJS_pathtodata}loader.js`; + script.onerror = (error) => { + console.error('[Emulator] Failed to load EmulatorJS:', error); + }; document.body.appendChild(script); }) .catch(error => { + console.error('[Emulator] Fatal error:', error); const gameDiv = document.getElementById('game'); gameDiv.innerHTML = `
Error loading game: ${error.message}