| | <html> |
| | <head> |
| | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
| | <title>GBA.js Mobile</title> |
| | <link rel="stylesheet" href="resources/main.css"> |
| | <style> |
| | |
| | #touch-controls { |
| | display: none; |
| | position: fixed; |
| | bottom: 0; |
| | left: 0; |
| | width: 100%; |
| | height: 260px; |
| | user-select: none; |
| | -webkit-user-select: none; |
| | touch-action: none; |
| | z-index: 99999; |
| | } |
| | |
| | .t-btn { |
| | position: absolute; |
| | background: rgba(255, 255, 255, 0.2); |
| | border: 2px solid rgba(255, 255, 255, 0.4); |
| | border-radius: 50%; |
| | color: white; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | font-weight: bold; |
| | font-family: sans-serif; |
| | box-shadow: 0 4px 6px rgba(0,0,0,0.3); |
| | touch-action: none; |
| | } |
| | |
| | .t-btn:active { background: rgba(255, 255, 255, 0.5); transform: scale(0.9); } |
| | |
| | |
| | #t-up { bottom: 160px; left: 70px; width: 60px; height: 60px; } |
| | #t-down { bottom: 40px; left: 70px; width: 60px; height: 60px; } |
| | #t-left { bottom: 100px; left: 10px; width: 60px; height: 60px; } |
| | #t-right { bottom: 100px; left: 130px; width: 60px; height: 60px; } |
| | |
| | |
| | #t-a { bottom: 110px; right: 20px; width: 80px; height: 80px; background: rgba(255, 0, 0, 0.3); } |
| | #t-b { bottom: 60px; right: 110px; width: 80px; height: 80px; background: rgba(255, 255, 0, 0.3); } |
| | |
| | |
| | .menu-btn { width: 75px; height: 35px; border-radius: 5px; font-size: 12px; bottom: 60px; } |
| | #t-select { left: 50%; margin-left: -85px; } |
| | #t-start { left: 50%; margin-left: 10px; } |
| | |
| | @media (pointer: coarse) { |
| | #touch-controls { display: block; } |
| | #controls { padding-bottom: 260px; } |
| | #screen { width: 100vw !important; height: auto !important; max-width: 480px; } |
| | } |
| | </style> |
| |
|
| | <script src="js/util.js"></script> |
| | <script src="js/core.js"></script> |
| | <script src="js/arm.js"></script> |
| | <script src="js/thumb.js"></script> |
| | <script src="js/mmu.js"></script> |
| | <script src="js/io.js"></script> |
| | <script src="js/audio.js"></script> |
| | <script src="js/video.js"></script> |
| | <script src="js/video/proxy.js"></script> |
| | <script src="js/video/software.js"></script> |
| | <script src="js/irq.js"></script> |
| | <script src="js/keypad.js"></script> |
| | <script src="js/sio.js"></script> |
| | <script src="js/savedata.js"></script> |
| | <script src="js/gpio.js"></script> |
| | <script src="js/gba.js"></script> |
| | <script src="resources/xhr.js"></script> |
| |
|
| | <script> |
| | var gba; |
| | var runCommands = []; |
| | |
| | try { |
| | gba = new GameBoyAdvance(); |
| | gba.keypad.eatInput = true; |
| | } catch (exception) { |
| | gba = null; |
| | } |
| | |
| | window.onload = function() { |
| | if (gba && FileReader) { |
| | var canvas = document.getElementById('screen'); |
| | gba.setCanvas(canvas); |
| | loadRom('resources/bios.bin', function(bios) { |
| | gba.setBios(bios); |
| | }); |
| | |
| | |
| | initTouchGamepad(); |
| | } |
| | } |
| | |
| | function initTouchGamepad() { |
| | |
| | const map = { |
| | 't-a': 0, 't-b': 1, 't-select': 2, 't-start': 3, |
| | 't-right': 4, 't-left': 5, 't-up': 6, 't-down': 7 |
| | }; |
| | |
| | Object.keys(map).forEach(id => { |
| | const el = document.getElementById(id); |
| | if (!el) return; |
| | |
| | const keyCode = map[id]; |
| | |
| | const press = (e) => { |
| | e.preventDefault(); |
| | |
| | if (gba.audio.context && gba.audio.context.state === 'suspended') { |
| | gba.audio.context.resume(); |
| | } |
| | gba.keypad.keydown(keyCode); |
| | }; |
| | |
| | const release = (e) => { |
| | e.preventDefault(); |
| | gba.keypad.keyup(keyCode); |
| | }; |
| | |
| | el.addEventListener('touchstart', press, {passive: false}); |
| | el.addEventListener('touchend', release, {passive: false}); |
| | el.addEventListener('mousedown', press); |
| | el.addEventListener('mouseup', release); |
| | }); |
| | } |
| | |
| | function run(file) { |
| | gba.loadRomFromFile(file, function(result) { |
| | if (result) { |
| | for (var i = 0; i < runCommands.length; ++i) { runCommands[i](); } |
| | runCommands = []; |
| | document.getElementById('preload').style.display = 'none'; |
| | document.getElementById('ingame').classList.remove('hidden'); |
| | gba.runStable(); |
| | } |
| | }); |
| | } |
| | |
| | function reset() { |
| | gba.pause(); |
| | gba.reset(); |
| | gba.runStable(); |
| | } |
| | |
| | function togglePause() { |
| | var e = document.getElementById('pause'); |
| | if (gba.paused) { gba.runStable(); e.textContent = "PAUSE"; } |
| | else { gba.pause(); e.textContent = "UNPAUSE"; } |
| | } |
| | </script> |
| | </head> |
| | <body> |
| | <canvas id="screen" width="480" height="320"></canvas> |
| |
|
| | <div id="touch-controls"> |
| | <div class="t-btn" id="t-up">▲</div> |
| | <div class="t-btn" id="t-down">▼</div> |
| | <div class="t-btn" id="t-left">◀</div> |
| | <div class="t-btn" id="t-right">▶</div> |
| |
|
| | <div class="t-btn" id="t-a">A</div> |
| | <div class="t-btn" id="t-b">B</div> |
| |
|
| | <div class="t-btn menu-btn" id="t-select">SELECT</div> |
| | <div class="t-btn menu-btn" id="t-start">START</div> |
| | </div> |
| |
|
| | <section id="controls"> |
| | <div id="preload"> |
| | <button class="bigbutton" id="select" onclick="document.getElementById('loader').click()">SELECT ROM</button> |
| | <input id="loader" type="file" accept=".gba" onchange="run(this.files[0]);" style="display:none;"> |
| | </div> |
| | <div id="ingame" class="hidden"> |
| | <button id="pause" class="bigbutton" onclick="togglePause()">PAUSE</button> |
| | <button class="bigbutton" onclick="reset()">RESET</button> |
| | <button onclick="gba.downloadSavedata()">SAVE</button> |
| | </div> |
| | </section> |
| | </body> |
| | </html> |
| |
|