emulator / index.html
pijou's picture
Add 1 files
d5a0194 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RetroVault - Classic Gaming Hub</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jsnes.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/gba.min.js"></script>
<style>
@font-face {
font-family: 'Press Start 2P';
src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
}
body {
font-family: 'Press Start 2P', cursive;
background-color: #0f0f1a;
color: #e0e0ff;
overflow-x: hidden;
}
.neon-text {
text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #ff00de, 0 0 20px #ff00de;
}
.neon-box {
box-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #0073e6, 0 0 20px #0073e6;
}
.pixel-border {
border-image: repeating-linear-gradient(90deg, #ff00de, #ff00de 10px, #0073e6 10px, #0073e6 20px) 5;
border-width: 4px;
border-style: solid;
}
.tab-active {
background: linear-gradient(to bottom, #ff00de, #0073e6);
color: white;
transform: translateY(-5px);
}
.scanlines {
position: relative;
}
.scanlines:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
to bottom,
transparent 50%,
rgba(0, 0, 0, 0.2) 51%
);
background-size: 100% 4px;
pointer-events: none;
z-index: 10;
}
.dark-mode {
background-color: #0f0f1a;
color: #e0e0ff;
}
.light-mode {
background-color: #f0f0ff;
color: #1a1a2e;
}
/* Pixel art buttons */
.btn-pixel {
position: relative;
display: inline-block;
padding: 10px 20px;
color: white;
background: #6a5acd;
text-decoration: none;
border-radius: 0;
border-bottom: 5px solid #483d8b;
transition: transform 0.1s, border-bottom 0.1s;
}
.btn-pixel:hover {
transform: translate(0, 2px);
border-bottom: 3px solid #483d8b;
}
.btn-pixel:active {
transform: translate(0, 5px);
border-bottom: 0px solid #483d8b;
}
/* CRT screen effect */
.crt-effect {
position: relative;
overflow: hidden;
}
.crt-effect:after {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(18, 16, 16, 0.1);
opacity: 0.2;
z-index: 2;
pointer-events: none;
}
.crt-effect:before {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
z-index: 2;
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
/* Emulator canvas styling */
#nesCanvas {
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
width: 100%;
height: 100%;
}
/* Loading spinner */
.loader {
border: 5px solid #f3f3f3;
border-top: 5px solid #ff00de;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 20px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Status messages */
.status-message {
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px;
border-radius: 5px;
text-align: center;
margin-top: 10px;
font-size: 0.8rem;
}
.status-success {
color: #00ff00;
}
.status-error {
color: #ff0000;
}
</style>
</head>
<body class="dark-mode">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="text-center mb-8">
<h1 class="text-5xl md:text-6xl font-bold mb-4 neon-text">RetroVault</h1>
<p class="text-xl md:text-2xl">Your classic gaming emporium</p>
<!-- Theme Toggle -->
<div class="flex justify-center mt-4">
<button id="themeToggle" class="btn-pixel px-6 py-2 rounded-full">
<i class="fas fa-moon"></i> Dark Mode
</button>
</div>
</header>
<!-- Main Dashboard -->
<div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 neon-box">
<!-- Console Tabs -->
<div class="flex mb-6 border-b-2 border-purple-500">
<button id="nesTab" class="tab-active px-6 py-3 font-bold rounded-t-lg mr-2 transition-all">
<i class="fas fa-gamepad mr-2"></i> NES
</button>
<button id="gbaTab" class="px-6 py-3 font-bold rounded-t-lg mr-2 transition-all hover:bg-purple-900 hover:text-white">
<i class="fas fa-mobile-alt mr-2"></i> GBA
</button>
<button id="controlsTab" class="px-6 py-3 font-bold rounded-t-lg transition-all hover:bg-purple-900 hover:text-white">
<i class="fas fa-keyboard mr-2"></i> Controls
</button>
</div>
<!-- NES Section -->
<div id="nesSection" class="console-section">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Emulator Container -->
<div class="md:col-span-2">
<div class="bg-black p-4 rounded-lg crt-effect scanlines">
<div id="nesEmulator" class="mx-auto" style="width: 100%; height: 400px;">
<canvas id="nesCanvas" width="256" height="240"></canvas>
</div>
<div id="nesStatus" class="status-message">Ready to load NES ROM</div>
</div>
<div class="flex flex-wrap justify-center mt-4 gap-2">
<button id="nesLoad" class="btn-pixel">
<i class="fas fa-folder-open mr-2"></i> Load ROM
</button>
<button id="nesSaveState" class="btn-pixel">
<i class="fas fa-save mr-2"></i> Save State
</button>
<button id="nesLoadState" class="btn-pixel">
<i class="fas fa-file-upload mr-2"></i> Load State
</button>
<button id="nesFullscreen" class="btn-pixel">
<i class="fas fa-expand mr-2"></i> Fullscreen
</button>
<button id="nesReset" class="btn-pixel">
<i class="fas fa-redo mr-2"></i> Reset
</button>
<button id="nesPause" class="btn-pixel">
<i class="fas fa-pause mr-2"></i> Pause
</button>
</div>
</div>
<!-- Game Info -->
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border">
<h3 class="text-xl font-bold mb-4 text-center neon-text">Game Info</h3>
<div id="nesGameInfo" class="text-sm">
<p class="mb-2"><span class="text-purple-400">No game loaded</span></p>
<p class="mb-2">Controls:</p>
<ul class="list-disc pl-5">
<li>Arrow Keys: D-Pad</li>
<li>Z: A Button</li>
<li>X: B Button</li>
<li>Enter: Start</li>
<li>Shift: Select</li>
</ul>
<p class="mt-4 text-xs text-gray-400">Upload your own NES ROMs to play</p>
<div class="mt-4">
<h4 class="text-lg font-bold mb-2 text-purple-400">Sample ROMs</h4>
<button id="loadSample1" class="btn-pixel text-xs px-3 py-1 mb-2">Super Mario Bros</button>
<button id="loadSample2" class="btn-pixel text-xs px-3 py-1 mb-2">Donkey Kong</button>
<button id="loadSample3" class="btn-pixel text-xs px-3 py-1">The Legend of Zelda</button>
</div>
</div>
</div>
</div>
</div>
<!-- GBA Section (Hidden by default) -->
<div id="gbaSection" class="console-section hidden">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Emulator Container -->
<div class="md:col-span-2">
<div class="bg-black p-4 rounded-lg crt-effect scanlines">
<div id="gbaEmulator" class="mx-auto" style="width: 100%; height: 400px;">
<div class="loader"></div>
<p class="text-center">GBA emulator loading...</p>
</div>
</div>
<div class="flex flex-wrap justify-center mt-4 gap-2">
<button id="gbaLoad" class="btn-pixel">
<i class="fas fa-folder-open mr-2"></i> Load ROM
</button>
<button id="gbaSaveState" class="btn-pixel">
<i class="fas fa-save mr-2"></i> Save State
</button>
<button id="gbaLoadState" class="btn-pixel">
<i class="fas fa-file-upload mr-2"></i> Load State
</button>
<button id="gbaFullscreen" class="btn-pixel">
<i class="fas fa-expand mr-2"></i> Fullscreen
</button>
<button id="gbaReset" class="btn-pixel">
<i class="fas fa-redo mr-2"></i> Reset
</button>
</div>
</div>
<!-- Game Info -->
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border">
<h3 class="text-xl font-bold mb-4 text-center neon-text">Game Info</h3>
<div id="gbaGameInfo" class="text-sm">
<p class="mb-2"><span class="text-purple-400">No game loaded</span></p>
<p class="mb-2">Controls:</p>
<ul class="list-disc pl-5">
<li>Arrow Keys: D-Pad</li>
<li>A: A Button</li>
<li>S: B Button</li>
<li>Enter: Start</li>
<li>Shift: Select</li>
<li>Q: L Button</li>
<li>W: R Button</li>
</ul>
<p class="mt-4 text-xs text-gray-400">Upload your own GBA ROMs to play</p>
</div>
</div>
</div>
</div>
<!-- Controls Section (Hidden by default) -->
<div id="controlsSection" class="console-section hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Key Mapping -->
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border">
<h3 class="text-xl font-bold mb-4 text-center neon-text">Keyboard Controls</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<h4 class="text-lg font-bold mb-2 text-purple-400">NES</h4>
<ul class="text-sm">
<li class="mb-1">Arrow Keys: D-Pad</li>
<li class="mb-1">Z: A Button</li>
<li class="mb-1">X: B Button</li>
<li class="mb-1">Enter: Start</li>
<li class="mb-1">Shift: Select</li>
</ul>
</div>
<div>
<h4 class="text-lg font-bold mb-2 text-purple-400">GBA</h4>
<ul class="text-sm">
<li class="mb-1">Arrow Keys: D-Pad</li>
<li class="mb-1">A: A Button</li>
<li class="mb-1">S: B Button</li>
<li class="mb-1">Enter: Start</li>
<li class="mb-1">Shift: Select</li>
<li class="mb-1">Q: L Button</li>
<li class="mb-1">W: R Button</li>
</ul>
</div>
</div>
</div>
<!-- Gamepad Info -->
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border">
<h3 class="text-xl font-bold mb-4 text-center neon-text">Gamepad Support</h3>
<div id="gamepadInfo" class="text-sm">
<p class="mb-4">Connect a gamepad and press any button to enable.</p>
<p class="mb-2">Supported gamepads:</p>
<ul class="list-disc pl-5">
<li class="mb-1">Xbox 360/One</li>
<li class="mb-1">PlayStation 3/4</li>
<li class="mb-1">Generic USB gamepads</li>
</ul>
<p class="mt-4 text-xs text-gray-400">Buttons will map automatically to standard layout</p>
</div>
</div>
</div>
<div class="mt-6 bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border">
<h3 class="text-xl font-bold mb-4 text-center neon-text">Customize Controls</h3>
<div class="text-center">
<button id="remapControls" class="btn-pixel px-6 py-2">
<i class="fas fa-keyboard mr-2"></i> Remap Keys
</button>
<p class="mt-2 text-xs text-gray-400">Coming soon: Custom key mapping for keyboard and gamepad</p>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-8 text-center text-sm text-gray-500">
<p>RetroVault - Play classic games in your browser</p>
<p class="mt-1">Note: You must provide your own legally obtained ROM files</p>
</footer>
</div>
<!-- Hidden file inputs -->
<input type="file" id="nesFileInput" accept=".nes" class="hidden">
<input type="file" id="gbaFileInput" accept=".gba" class="hidden">
<script>
// Theme Toggle
const themeToggle = document.getElementById('themeToggle');
const body = document.body;
themeToggle.addEventListener('click', () => {
if (body.classList.contains('dark-mode')) {
body.classList.remove('dark-mode');
body.classList.add('light-mode');
themeToggle.innerHTML = '<i class="fas fa-sun mr-2"></i> Light Mode';
} else {
body.classList.remove('light-mode');
body.classList.add('dark-mode');
themeToggle.innerHTML = '<i class="fas fa-moon mr-2"></i> Dark Mode';
}
});
// Tab Switching
const nesTab = document.getElementById('nesTab');
const gbaTab = document.getElementById('gbaTab');
const controlsTab = document.getElementById('controlsTab');
const nesSection = document.getElementById('nesSection');
const gbaSection = document.getElementById('gbaSection');
const controlsSection = document.getElementById('controlsSection');
function resetTabs() {
nesTab.classList.remove('tab-active');
gbaTab.classList.remove('tab-active');
controlsTab.classList.remove('tab-active');
nesTab.classList.add('hover:bg-purple-900', 'hover:text-white');
gbaTab.classList.add('hover:bg-purple-900', 'hover:text-white');
controlsTab.classList.add('hover:bg-purple-900', 'hover:text-white');
nesSection.classList.add('hidden');
gbaSection.classList.add('hidden');
controlsSection.classList.add('hidden');
}
nesTab.addEventListener('click', () => {
resetTabs();
nesTab.classList.add('tab-active');
nesTab.classList.remove('hover:bg-purple-900', 'hover:text-white');
nesSection.classList.remove('hidden');
});
gbaTab.addEventListener('click', () => {
resetTabs();
gbaTab.classList.add('tab-active');
gbaTab.classList.remove('hover:bg-purple-900', 'hover:text-white');
gbaSection.classList.remove('hidden');
});
controlsTab.addEventListener('click', () => {
resetTabs();
controlsTab.classList.add('tab-active');
controlsTab.classList.remove('hover:bg-purple-900', 'hover:text-white');
controlsSection.classList.remove('hidden');
});
// NES Emulator Setup
const nes = new jsnes.NES({
onFrame: function(frameBuffer) {
const canvas = document.getElementById('nesCanvas');
const context = canvas.getContext('2d');
const imageData = context.createImageData(canvas.width, canvas.height);
for (let i = 0; i < frameBuffer.length; i++) {
imageData.data[i * 4] = frameBuffer[i] >> 16 & 0xFF; // R
imageData.data[i * 4 + 1] = frameBuffer[i] >> 8 & 0xFF; // G
imageData.data[i * 4 + 2] = frameBuffer[i] & 0xFF; // B
imageData.data[i * 4 + 3] = 0xFF; // A
}
context.putImageData(imageData, 0, 0);
},
onAudioSample: function(left, right) {
// Audio handling would go here
}
});
// NES Controls
const keyMap = {
38: jsnes.Controller.BUTTON_UP, // Up arrow
40: jsnes.Controller.BUTTON_DOWN, // Down arrow
37: jsnes.Controller.BUTTON_LEFT, // Left arrow
39: jsnes.Controller.BUTTON_RIGHT, // Right arrow
90: jsnes.Controller.BUTTON_A, // Z (A button)
88: jsnes.Controller.BUTTON_B, // X (B button)
13: jsnes.Controller.BUTTON_START, // Enter (Start)
16: jsnes.Controller.BUTTON_SELECT // Shift (Select)
};
document.addEventListener('keydown', (e) => {
if (!nesSection.classList.contains('hidden')) {
const button = keyMap[e.keyCode];
if (button !== undefined) {
nes.buttonDown(1, button);
e.preventDefault();
}
}
});
document.addEventListener('keyup', (e) => {
if (!nesSection.classList.contains('hidden')) {
const button = keyMap[e.keyCode];
if (button !== undefined) {
nes.buttonUp(1, button);
e.preventDefault();
}
}
});
// NES File Handling
const nesFileInput = document.getElementById('nesFileInput');
const nesLoadBtn = document.getElementById('nesLoad');
const nesStatus = document.getElementById('nesStatus');
nesLoadBtn.addEventListener('click', () => {
nesFileInput.click();
});
nesFileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
// Show loading status
nesStatus.textContent = `Loading ${file.name}...`;
nesStatus.className = 'status-message';
const reader = new FileReader();
reader.onload = (e) => {
try {
const rom = new Uint8Array(e.target.result);
nes.loadROM(rom);
// Update game info
document.getElementById('nesGameInfo').innerHTML = `
<p class="mb-2"><span class="text-purple-400">${file.name}</span></p>
<p class="mb-2">Controls:</p>
<ul class="list-disc pl-5">
<li>Arrow Keys: D-Pad</li>
<li>Z: A Button</li>
<li>X: B Button</li>
<li>Enter: Start</li>
<li>Shift: Select</li>
</ul>
<p class="mt-4 text-xs text-gray-400">Press Save State to save your progress</p>
<div class="mt-4">
<h4 class="text-lg font-bold mb-2 text-purple-400">Sample ROMs</h4>
<button id="loadSample1" class="btn-pixel text-xs px-3 py-1 mb-2">Super Mario Bros</button>
<button id="loadSample2" class="btn-pixel text-xs px-3 py-1 mb-2">Donkey Kong</button>
<button id="loadSample3" class="btn-pixel text-xs px-3 py-1">The Legend of Zelda</button>
</div>
`;
// Update status
nesStatus.textContent = `Successfully loaded ${file.name}`;
nesStatus.className = 'status-message status-success';
// Start the emulation
if (!nesInterval) {
nesInterval = setInterval(() => {
nes.frame();
}, 1000 / 60); // Target 60 FPS
}
} catch (error) {
nesStatus.textContent = `Error loading ROM: ${error.message}`;
nesStatus.className = 'status-message status-error';
console.error('Error loading ROM:', error);
}
};
reader.onerror = () => {
nesStatus.textContent = 'Error reading file';
nesStatus.className = 'status-message status-error';
};
reader.readAsArrayBuffer(file);
});
// Sample ROMs (base64 encoded small demo ROMs)
const sampleRoms = {
mario: "BASE64_ENCODED_MARIO_ROM_DATA",
donkeykong: "BASE64_ENCODED_DONKEY_KONG_ROM_DATA",
zelda: "BASE64_ENCODED_ZELDA_ROM_DATA"
};
document.getElementById('loadSample1').addEventListener('click', () => {
// In a real implementation, you would load the sample ROM
nesStatus.textContent = "Sample ROM loading not implemented in this demo";
nesStatus.className = 'status-message status-error';
});
document.getElementById('loadSample2').addEventListener('click', () => {
nesStatus.textContent = "Sample ROM loading not implemented in this demo";
nesStatus.className = 'status-message status-error';
});
document.getElementById('loadSample3').addEventListener('click', () => {
nesStatus.textContent = "Sample ROM loading not implemented in this demo";
nesStatus.className = 'status-message status-error';
});
// NES Save/Load State
let nesState = null;
let nesInterval = null;
let isPaused = false;
document.getElementById('nesSaveState').addEventListener('click', () => {
try {
nesState = nes.toJSON();
nesStatus.textContent = "Game state saved!";
nesStatus.className = 'status-message status-success';
} catch (error) {
nesStatus.textContent = `Error saving state: ${error.message}`;
nesStatus.className = 'status-message status-error';
}
});
document.getElementById('nesLoadState').addEventListener('click', () => {
if (nesState) {
try {
nes.fromJSON(nesState);
nesStatus.textContent = "Game state loaded!";
nesStatus.className = 'status-message status-success';
} catch (error) {
nesStatus.textContent = `Error loading state: ${error.message}`;
nesStatus.className = 'status-message status-error';
}
} else {
nesStatus.textContent = "No saved state found!";
nesStatus.className = 'status-message status-error';
}
});
// NES Pause/Resume
document.getElementById('nesPause').addEventListener('click', () => {
if (isPaused) {
// Resume
nesInterval = setInterval(() => {
nes.frame();
}, 1000 / 60);
document.getElementById('nesPause').innerHTML = '<i class="fas fa-pause mr-2"></i> Pause';
nesStatus.textContent = "Game resumed";
nesStatus.className = 'status-message status-success';
} else {
// Pause
clearInterval(nesInterval);
nesInterval = null;
document.getElementById('nesPause').innerHTML = '<i class="fas fa-play mr-2"></i> Resume';
nesStatus.textContent = "Game paused";
nesStatus.className = 'status-message';
}
isPaused = !isPaused;
});
// NES Fullscreen
document.getElementById('nesFullscreen').addEventListener('click', () => {
const emulator = document.getElementById('nesEmulator');
if (emulator.requestFullscreen) {
emulator.requestFullscreen();
} else if (emulator.webkitRequestFullscreen) {
emulator.webkitRequestFullscreen();
} else if (emulator.msRequestFullscreen) {
emulator.msRequestFullscreen();
}
});
// NES Reset
document.getElementById('nesReset').addEventListener('click', () => {
if (nesInterval) {
clearInterval(nesInterval);
nesInterval = null;
}
nes.reset();
nesStatus.textContent = "Game reset";
nesStatus.className = 'status-message';
// Restart emulation
nesInterval = setInterval(() => {
nes.frame();
}, 1000 / 60);
});
// GBA Emulator
let gba = null;
let gbaRom = null;
// GBA File Handling
const gbaFileInput = document.getElementById('gbaFileInput');
const gbaLoadBtn = document.getElementById('gbaLoad');
gbaLoadBtn.addEventListener('click', () => {
gbaFileInput.click();
});
gbaFileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
const rom = new Uint8Array(e.target.result);
gbaRom = rom;
// Initialize GBA emulator
if (!gba) {
gba = new GameBoyAdvance();
// Set up video
const canvas = document.createElement('canvas');
canvas.width = 240;
canvas.height = 160;
const context = canvas.getContext('2d');
document.getElementById('gbaEmulator').innerHTML = '';
document.getElementById('gbaEmulator').appendChild(canvas);
gba.setCanvas(canvas);
// Set up audio (would need Web Audio API implementation)
// gba.setAudioDevice(...);
// Start emulation
gba.setInterval(() => {
gba.runStable();
}, 0);
}
// Load ROM
gba.loadRom(rom);
// Update game info
document.getElementById('gbaGameInfo').innerHTML = `
<p class="mb-2"><span class="text-purple-400">${file.name}</span></p>
<p class="mb-2">Controls:</p>
<ul class="list-disc pl-5">
<li>Arrow Keys: D-Pad</li>
<li>A: A Button</li>
<li>S: B Button</li>
<li>Enter: Start</li>
<li>Shift: Select</li>
<li>Q: L Button</li>
<li>W: R Button</li>
</ul>
<p class="mt-4 text-xs text-gray-400">Press Save State to save your progress</p>
`;
};
reader.readAsArrayBuffer(file);
});
// GBA Controls
document.addEventListener('keydown', (e) => {
if (!gba || gbaSection.classList.contains('hidden')) return;
// Map keys to GBA buttons
// This would need to be implemented based on GBA.js's input handling
// The following is a placeholder implementation
switch(e.keyCode) {
case 38: /* Up */ break;
case 40: /* Down */ break;
case 37: /* Left */ break;
case 39: /* Right */ break;
case 65: /* A (A Button) */ break;
case 83: /* S (B Button) */ break;
case 13: /* Enter (Start) */ break;
case 16: /* Shift (Select) */ break;
case 81: /* Q (L Button) */ break;
case 87: /* W (R Button) */ break;
}
});
// GBA Save/Load State
let gbaState = null;
document.getElementById('gbaSaveState').addEventListener('click', () => {
if (!gba) {
alert('No game loaded!');
return;
}
// This would need proper implementation with GBA.js
// gbaState = gba.saveState();
alert('Game state saved! (Placeholder - would save state in real implementation)');
});
document.getElementById('gbaLoadState').addEventListener('click', () => {
if (!gbaState) {
alert('No saved state found!');
return;
}
// This would need proper implementation with GBA.js
// gba.loadState(gbaState);
alert('Game state loaded! (Placeholder - would load state in real implementation)');
});
// GBA Fullscreen
document.getElementById('gbaFullscreen').addEventListener('click', () => {
const emulator = document.getElementById('gbaEmulator');
if (emulator.requestFullscreen) {
emulator.requestFullscreen();
} else if (emulator.webkitRequestFullscreen) {
emulator.webkitRequestFullscreen();
} else if (emulator.msRequestFullscreen) {
emulator.msRequestFullscreen();
}
});
// GBA Reset
document.getElementById('gbaReset').addEventListener('click', () => {
if (!gba || !gbaRom) {
alert('No game loaded!');
return;
}
gba.loadRom(gbaRom);
});
// Gamepad Support
window.addEventListener("gamepadconnected", (e) => {
document.getElementById('gamepadInfo').innerHTML = `
<p class="mb-2"><span class="text-green-400">Gamepad connected:</span> ${e.gamepad.id}</p>
<p class="mb-2">Standard mapping:</p>
<ul class="list-disc pl-5">
<li>D-Pad: D-Pad</li>
<li>A Button: A</li>
<li>B Button: B</li>
<li>Start: Start</li>
<li>Select: Select</li>
<li>Shoulder Buttons: L/R</li>
</ul>
<p class="mt-4 text-xs text-gray-400">Press buttons to control the game</p>
`;
// Start polling gamepad state
if (!gamepadPolling) {
gamepadPolling = true;
pollGamepads();
}
});
window.addEventListener("gamepaddisconnected", (e) => {
document.getElementById('gamepadInfo').innerHTML = `
<p class="mb-4">Connect a gamepad and press any button to enable.</p>
<p class="mb-2">Supported gamepads:</p>
<ul class="list-disc pl-5">
<li class="mb-1">Xbox 360/One</li>
<li class="mb-1">PlayStation 3/4</li>
<li class="mb-1">Generic USB gamepads</li>
</ul>
<p class="mt-4 text-xs text-gray-400">Buttons will map automatically to standard layout</p>
`;
});
let gamepadPolling = false;
let prevGamepadState = {};
function pollGamepads() {
if (!gamepadPolling) return;
const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
for (let i = 0; i < gamepads.length; i++) {
const gamepad = gamepads[i];
if (!gamepad) continue;
// Handle gamepad input for NES
if (!nesSection.classList.contains('hidden')) {
// D-pad
if (gamepad.axes[1] < -0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_UP);
else nes.buttonUp(1, jsnes.Controller.BUTTON_UP);
if (gamepad.axes[1] > 0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_DOWN);
else nes.buttonUp(1, jsnes.Controller.BUTTON_DOWN);
if (gamepad.axes[0] < -0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_LEFT);
else nes.buttonUp(1, jsnes.Controller.BUTTON_LEFT);
if (gamepad.axes[0] > 0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_RIGHT);
else nes.buttonUp(1, jsnes.Controller.BUTTON_RIGHT);
// Buttons (standard mapping)
if (gamepad.buttons[0] && gamepad.buttons[0].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_A);
else nes.buttonUp(1, jsnes.Controller.BUTTON_A);
if (gamepad.buttons[1] && gamepad.buttons[1].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_B);
else nes.buttonUp(1, jsnes.Controller.BUTTON_B);
if (gamepad.buttons[9] && gamepad.buttons[9].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_START);
else nes.buttonUp(1, jsnes.Controller.BUTTON_START);
if (gamepad.buttons[8] && gamepad.buttons[8].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_SELECT);
else nes.buttonUp(1, jsnes.Controller.BUTTON_SELECT);
}
// Handle gamepad input for GBA
// This would need to be implemented based on GBA.js's input handling
}
requestAnimationFrame(pollGamepads);
}
// Remap Controls Button
document.getElementById('remapControls').addEventListener('click', () => {
alert('Key remapping feature coming soon!');
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=pijou/emulator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>