File size: 6,705 Bytes
89610f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GBA.js Mobile - Fixed Loader</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
  body { margin:0; background:#000; font-family:Arial; color:#fff; overflow:hidden; }
  #screen { display:block; width:100%; height:auto; image-rendering:pixelated; }
  #info { position:absolute; top:10px; left:10px; background:rgba(0,0,0,0.6); padding:10px; border-radius:8px; z-index:30; pointer-events:auto; }
  #touchControls { position:absolute; inset:0; pointer-events:none; z-index:20; padding:10px; display:none; box-sizing:border-box; }
  .control-row { display:flex; justify-content:space-between; align-items:flex-end; height:100%; pointer-events:none; }
  button { background:rgba(80,80,80,0.7); color:white; font-weight:bold; border:none; border-radius:10px; box-shadow:0 3px 8px #000; touch-action:manipulation; -webkit-tap-highlight-color:transparent; }
  #dpad { display:grid; grid-template-columns:repeat(3,1fr); grid-template-rows:repeat(3,1fr); gap:6px; width:35%; max-width:160px; aspect-ratio:1/1; pointer-events:auto; }
  #dpad button { font-size:clamp(1.6rem,8vw,2.4rem); }
  .face-btn { width:clamp(60px,17vw,85px); height:clamp(60px,17vw,85px); border-radius:50%; font-size:clamp(1.8rem,7vw,2.4rem); pointer-events:auto; }
  #a { background:rgba(220,40,40,0.8); }
  #b { background:rgba(40,100,220,0.8); }
  .shoulder { width:clamp(50px,14vw,70px); height:clamp(32px,8vw,40px); font-size:clamp(1rem,4vw,1.3rem); background:rgba(100,100,100,0.8); }
  .action { width:clamp(65px,17vw,85px); height:clamp(36px,9vw,44px); font-size:clamp(0.95rem,3.8vw,1.2rem); background:rgba(40,140,40,0.8); }
  @media (max-width:360px) { #dpad {max-width:130px;} .face-btn {width:55px;height:55px;} }
</style>
</head>
<body>

<canvas id="screen" width="240" height="160"></canvas>

<div id="info">
  <button onclick="document.getElementById('romfile').click()">Load .gba ROM</button>
  <input id="romfile" type="file" accept=".gba" style="display:none;">
  <p id="status">Waiting for ROM...</p>
</div>

<div id="touchControls">
  <div class="control-row">
    <!-- D-Pad left -->
    <div id="dpad">
      <div></div><button id="up">โ†‘</button><div></div>
      <button id="left">โ†</button><div></div><button id="right">โ†’</button>
      <div></div><button id="down">โ†“</button><div></div>
    </div>

    <!-- Right side controls -->
    <div style="display:flex; flex-direction:column; align-items:flex-end; gap:8px; width:60%; pointer-events:auto;">
      <div style="display:flex; gap:10px;">
        <button id="l" class="shoulder">L</button>
        <button id="r" class="shoulder">R</button>
      </div>
      <div style="display:flex; gap:10px;">
        <button id="select" class="action">Select</button>
        <button id="start" class="action">Start</button>
      </div>
      <div style="display:flex; gap:18px;">
        <button id="b" class="face-btn">B</button>
        <button id="a" class="face-btn">A</button>
      </div>
    </div>
  </div>
</div>

<!-- Load GBA.js from reliable CDN mirror -->
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/util.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/core.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/arm.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/thumb.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/mmu.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/io.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/audio.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/video.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/video/software.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/irq.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/keypad.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gbajs/gbajs@master/js/gba.js"></script>

<script>
let gba;
const canvas = document.getElementById('screen');
const status = document.getElementById('status');

try {
  gba = new GameBoyAdvance();
  gba.keypad.eatInput = true;
  gba.setCanvas(canvas);
  gba.logLevel = 0; // quiet unless error
  console.log("GBA core initialized");
} catch(e) {
  status.textContent = "Emulator init failed: " + e.message;
  alert("Cannot start emulator: " + e);
}

if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
  document.getElementById('touchControls').style.display = 'block';
  canvas.style.width = '100%';
  canvas.style.height = 'auto';
}

// Key simulation
function press(code)   { window.dispatchEvent(new KeyboardEvent('keydown', {keyCode:code, bubbles:true})); }
function release(code) { window.dispatchEvent(new KeyboardEvent('keyup',   {keyCode:code, bubbles:true})); }

const keys = {
  up:38, down:40, left:37, right:39,
  a:88, b:90, l:65, r:83,
  start:13, select:16
};

Object.entries(keys).forEach(([id, code]) => {
  const btn = document.getElementById(id);
  if (btn) {
    btn.addEventListener('touchstart', e => { e.preventDefault(); press(code); });
    btn.addEventListener('touchend',   e => { e.preventDefault(); release(code); });
    btn.addEventListener('touchcancel', e => { e.preventDefault(); release(code); });
  }
});

// Load ROM
document.getElementById('romfile').addEventListener('change', function(e) {
  const file = e.target.files[0];
  if (!file) return;

  status.textContent = "Reading file... (" + (file.size / 1024 / 1024).toFixed(1) + " MB)";

  const reader = new FileReader();
  reader.onload = function() {
    try {
      const buffer = reader.result;
      if (buffer.byteLength < 0x4000) {
        throw new Error("File too small โ€“ probably not a GBA ROM");
      }

      gba.loadRom(buffer, (success) => {
        if (success) {
          status.textContent = "ROM loaded! Starting...";
          gba.runStable();
          setTimeout(() => { document.getElementById('info').style.display = 'none'; }, 1500);
        } else {
          throw new Error("loadRom returned false");
        }
      });
    } catch (err) {
      status.textContent = "Load failed: " + err.message;
      alert("Error loading ROM:\n" + err.message + "\n\nTry a different .gba file (clean dump, not zipped).");
      console.error(err);
    }
  };
  reader.onerror = () => {
    status.textContent = "File read error";
    alert("Cannot read the file โ€“ maybe corrupted or browser blocked it.");
  };
  reader.readAsArrayBuffer(file);
});
</script>
</body>
</html>