245 lines
9.9 KiB
Plaintext
245 lines
9.9 KiB
Plaintext
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<style>
|
|
body {
|
|
min-height: 100vh;
|
|
}
|
|
.center-content {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
form label, form input {
|
|
display: block;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="center-content">
|
|
<h2>msGBA Emulator Online for GBA.</h2>
|
|
</div>
|
|
<div class="center-content">
|
|
<canvas id="emulator" width="240" height="160">
|
|
</canvas>
|
|
</div>
|
|
<div class="center-content" id="form-select-files">
|
|
<form>
|
|
<label for="rom">
|
|
Rom file
|
|
<input type="file" id="rom" name="rom"/>
|
|
</label>
|
|
<label for="savestate">
|
|
Savestate (A ss file from mgba...)
|
|
<input type="file" id="savestate" name="savestate"/>
|
|
</label>
|
|
<input type="button" value="Start emulation" id="start-emulation-button">
|
|
</form>
|
|
</div>
|
|
<script>
|
|
"use strict";
|
|
const MIN_WIDTH = 240;
|
|
const MIN_HEIGHT = 160;
|
|
const canvas = document.querySelector('#emulator');
|
|
const ctx = canvas.getContext('2d');
|
|
const emulator_dimensions = { width: MIN_WIDTH, height: MIN_HEIGHT };
|
|
const start_emulation_button = document.querySelector('#start-emulation-button');
|
|
const rom_file_selector = document.querySelector('#rom');
|
|
const savestate_file_selector = document.querySelector('#savestate');
|
|
const PACKET_ID_HELLO = 0n;
|
|
// Send is sent from server, so it is get here but whatever, it is the bad naming of the protocol.
|
|
const PACKET_ID_SEND_FRAME = 1n;
|
|
let printing_frame = false;
|
|
let is_little_endian = true;
|
|
(()=>{
|
|
let buf = new ArrayBuffer(4);
|
|
let buf8 = new Uint8ClampedArray(buf);
|
|
let data = new Uint32Array(buf);
|
|
data[0] = 0xdeadbeef;
|
|
if(buf8[0] === 0xde){
|
|
is_little_endian = false;
|
|
}
|
|
})();
|
|
console.log((is_little_endian ? 'Little endian' : 'Big endian') + " Detected");
|
|
|
|
function u64ToByteArrayBigEndian(input_number) {
|
|
const buffer = new ArrayBuffer(8);
|
|
const buffer8 = new Uint8Array(buffer);
|
|
const buffer64 = new BigUint64Array(buffer);
|
|
buffer64[0] = input_number;
|
|
if (is_little_endian) {
|
|
buffer8.reverse();
|
|
}
|
|
return buffer8;
|
|
}
|
|
|
|
function concatU8Array(array1, array2) {
|
|
const final_array = new Uint8Array(array1.length + array2.length);
|
|
final_array.set(array1);
|
|
final_array.set(array2, array1.length);
|
|
return final_array;
|
|
}
|
|
|
|
start_emulation_button.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if (rom_file_selector.files.length == 0) {
|
|
alert('There is no rom still');
|
|
return;
|
|
}
|
|
if (savestate_file_selector.files.length == 0) {
|
|
alert('There is no savestate still');
|
|
return;
|
|
}
|
|
const rom_file = rom_file_selector.files[0];
|
|
const savestate_file = savestate_file_selector.files[0];
|
|
rom_file.arrayBuffer().then((rom_buffer) => {
|
|
savestate_file.arrayBuffer().then((savestate_buffer) => {
|
|
const rom_array = new Uint8Array(rom_buffer);
|
|
const savestate_array = new Uint8Array(savestate_buffer);
|
|
const websocket = new WebSocket(`ws://localhost:3000/ws`);
|
|
websocket.binaryType = 'arraybuffer';
|
|
websocket.onclose = (message) => console.log('CLOSE', message);
|
|
websocket.onopen = () => {
|
|
console.log('Opened websocket.');
|
|
sendHello(websocket, rom_array, savestate_array);
|
|
};
|
|
websocket.addEventListener('message', onWebSocketPacket);
|
|
});
|
|
});
|
|
});
|
|
|
|
function onWebSocketPacket(event) {
|
|
const buffer = event.data;
|
|
let packet_u8 = new Uint8Array(buffer);
|
|
const id = byteArrayToU64BigEndian(packet_u8.slice(0, 8));
|
|
packet_u8 = packet_u8.slice(8, packet_u8.length);
|
|
const size = byteArrayToU64BigEndian(packet_u8.slice(0, 8));
|
|
const raw_data = packet_u8.slice(8, packet_u8.length);
|
|
packet_u8 = null;
|
|
switch (id) {
|
|
case PACKET_ID_SEND_FRAME:
|
|
handleSendFrame(raw_data);
|
|
break;
|
|
default:
|
|
console.log(`Received unknown packet ${id}`);
|
|
}
|
|
}
|
|
|
|
function handleSendFrame(raw_data) {
|
|
if (printing_frame) {
|
|
return;
|
|
}
|
|
printing_frame = true;
|
|
let data = raw_data;
|
|
const stride = byteArrayToU32BigEndian(data.slice(0, 4));
|
|
data = data.slice(4, data.length);
|
|
const output_buffer_size = byteArrayToU32BigEndian(data.slice(0, 8));
|
|
data = data.slice(8, data.length);
|
|
console.log(data.length / 4 / MIN_WIDTH);
|
|
const img_data = ctx.createImageData(MIN_WIDTH, MIN_HEIGHT);
|
|
const img_data_u8 = new Uint8Array(img_data.data.buffer);
|
|
for (let i = 0; i<data.length; i++) {
|
|
if (i % 4 == 3) {
|
|
img_data_u8[i] = 255;
|
|
continue;
|
|
}
|
|
img_data_u8[i] = data[i];
|
|
}
|
|
data = null;
|
|
createImageBitmap(img_data).then(drawBitmap);
|
|
}
|
|
|
|
function drawBitmap(bitmap) {
|
|
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
|
printing_frame = false;
|
|
}
|
|
|
|
function byteArrayToU32BigEndian(input_array) {
|
|
if (is_little_endian) {
|
|
input_array = input_array.reverse();
|
|
}
|
|
const buffer = input_array.buffer;
|
|
const output_u32_array = new Uint32Array(buffer);
|
|
return output_u32_array[0];
|
|
}
|
|
|
|
function byteArrayToU64BigEndian(input_array) {
|
|
if (is_little_endian) {
|
|
input_array = input_array.reverse();
|
|
}
|
|
const buffer = input_array.buffer;
|
|
const output_u64_array = new BigUint64Array(buffer);
|
|
return output_u64_array[0];
|
|
}
|
|
|
|
function sendHello(websocket, rom_array, savestate_array) {
|
|
console.log('Sending hello.');
|
|
const length_rom = BigInt(rom_array.length);
|
|
const length_savestate = BigInt(savestate_array.length);
|
|
const raw_data =
|
|
concatU8Array(
|
|
concatU8Array(
|
|
concatU8Array(u64ToByteArrayBigEndian(length_rom), rom_array),
|
|
u64ToByteArrayBigEndian(length_savestate)
|
|
),
|
|
savestate_array
|
|
);
|
|
sendPacket(websocket, PACKET_ID_HELLO, raw_data);
|
|
}
|
|
|
|
function sendPacket(websocket, id, raw_data) {
|
|
const packet_u8 = concatU8Array(
|
|
concatU8Array(u64ToByteArrayBigEndian(id), u64ToByteArrayBigEndian(BigInt(raw_data.length))),
|
|
raw_data
|
|
);
|
|
const packet_buffer = packet_u8.buffer;
|
|
console.log('Sending packet');
|
|
websocket.send(packet_buffer);
|
|
}
|
|
|
|
function buf2hex(buffer) { // buffer is an ArrayBuffer
|
|
return [...new Uint8Array(buffer)]
|
|
.map(x => x.toString(16).padStart(2, '0'))
|
|
.join('');
|
|
}
|
|
|
|
function resizeEmulator(canvas) {
|
|
const width = document.body.clientWidth;
|
|
const height = document.body.clientHeight * 0.75;
|
|
if (width < MIN_WIDTH || height < MIN_HEIGHT) {
|
|
alert('Size too small.');
|
|
return;
|
|
}
|
|
const ratio_width = Math.floor(width / MIN_WIDTH);
|
|
const ratio_height = Math.floor(height / MIN_HEIGHT);
|
|
if (ratio_width < ratio_height) {
|
|
emulator_dimensions.width = MIN_WIDTH * ratio_width;
|
|
emulator_dimensions.height = MIN_HEIGHT * ratio_width;
|
|
} else {
|
|
emulator_dimensions.height = MIN_HEIGHT * ratio_height;
|
|
emulator_dimensions.width = MIN_WIDTH * ratio_height;
|
|
}
|
|
canvas.width = emulator_dimensions.width;
|
|
canvas.height = emulator_dimensions.height;
|
|
fillBlack(ctx);
|
|
}
|
|
|
|
function fillBlack(ctx) {
|
|
ctx.beginPath();
|
|
ctx.rect(0, 0, emulator_dimensions.width, emulator_dimensions.height);
|
|
ctx.fillStyle = 'black';
|
|
ctx.fill();
|
|
}
|
|
|
|
|
|
function connectWebsocket(rom, savestate) {
|
|
}
|
|
|
|
window.addEventListener('resize', () => {
|
|
resizeEmulator(canvas);
|
|
});
|
|
resizeEmulator(canvas);
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|