Adding version with the byte websocket bug to report to mojo.
This commit is contained in:
parent
876f3506e6
commit
6bcde9fa56
2
Build.PL
2
Build.PL
|
@ -10,6 +10,8 @@ my $build = Module::Build->new(
|
||||||
dist_abstract => 'The emulator webpage.',
|
dist_abstract => 'The emulator webpage.',
|
||||||
requires => {
|
requires => {
|
||||||
'Mojolicious' => 0,
|
'Mojolicious' => 0,
|
||||||
|
'Moo' => 0,
|
||||||
|
'Types::Standard' => 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
$build->create_build_script;
|
$build->create_build_script;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package MSGBA::Web;
|
||||||
|
use Mojo::Base 'Mojolicious', -signatures;
|
||||||
|
|
||||||
|
# This method will run once at server start
|
||||||
|
sub startup ($self) {
|
||||||
|
|
||||||
|
# Load configuration from config file
|
||||||
|
my $config = $self->plugin('NotYAMLConfig' => { file => './msgba-web.yml' });
|
||||||
|
|
||||||
|
# Configure the application
|
||||||
|
$self->secrets($config->{secrets});
|
||||||
|
|
||||||
|
# Router
|
||||||
|
my $r = $self->routes;
|
||||||
|
|
||||||
|
# Normal route to controller
|
||||||
|
$r->get('/')->to('Static#index');
|
||||||
|
$r->websocket('/ws')->to('WS#proxy');
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
|
@ -0,0 +1,10 @@
|
||||||
|
package MSGBA::Web::Controller::Static;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
|
||||||
|
1;
|
|
@ -0,0 +1,110 @@
|
||||||
|
package MSGBA::Web::Controller::WS;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
|
||||||
|
sub proxy {
|
||||||
|
my $self = shift;
|
||||||
|
my $conn = MSGBA::Web::Controller::WS::Connection->new(ws => $self, config => $self->config);
|
||||||
|
return $conn->handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
package MSGBA::Web::Controller::WS::Connection {
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Moo;
|
||||||
|
use IO::Socket::UNIX;
|
||||||
|
|
||||||
|
|
||||||
|
has config => (
|
||||||
|
is => 'ro',
|
||||||
|
required => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
has ws => (
|
||||||
|
is => 'ro',
|
||||||
|
required => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
has msgba_connection => (
|
||||||
|
is => 'rw',
|
||||||
|
required => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
sub handle {
|
||||||
|
my $self = shift;
|
||||||
|
my $ws = $self->ws;
|
||||||
|
$self->_build_msgba_connection;
|
||||||
|
$ws->on('message', sub {
|
||||||
|
my ($ws, $bytes) = @_;
|
||||||
|
if (!$bytes) {
|
||||||
|
warn "Received empty message";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
say "Received message";
|
||||||
|
say unpack 'H*', $bytes;
|
||||||
|
open my $fh, '<', \$bytes;
|
||||||
|
read $fh, my $id, 8;
|
||||||
|
read $fh, my $size, 8;
|
||||||
|
$size = unpack 'Q>', $size;
|
||||||
|
close $fh;
|
||||||
|
$self->msgba_connection->print($bytes);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
#while (my $packet = $self->read_packet) {
|
||||||
|
# $self->handle_packet($packet);
|
||||||
|
#}
|
||||||
|
#close $self->msgba_connection;
|
||||||
|
#$ws->closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_packet {
|
||||||
|
my $self = shift;
|
||||||
|
my $packet = shift;
|
||||||
|
my $ws = $self->ws;
|
||||||
|
my ($id, $size, $raw_data) = $packet->@{qw/id size raw_data/};
|
||||||
|
$ws->send({binary => "${id}${size}${raw_data}"});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub read_packet {
|
||||||
|
my $self = shift;
|
||||||
|
my $fh = $self->msgba_connection;
|
||||||
|
my $ws = $self->ws;
|
||||||
|
my ($id, $size, $raw_data);
|
||||||
|
if ((read $fh, $id, 8) != 8) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((read $fh, $size, 8) != 8) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $size_num = unpack 'Q>', $size;
|
||||||
|
if ((read $fh, $raw_data, $size_num) != $size_num) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id => $id,
|
||||||
|
size => $size,
|
||||||
|
raw_data => $raw_data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _build_msgba_connection {
|
||||||
|
my $self = shift;
|
||||||
|
my $config = $self->config;
|
||||||
|
my $msgba_connection = IO::Socket::UNIX->new(
|
||||||
|
Type => SOCK_STREAM(),
|
||||||
|
Peer => $config->{domain_socket},
|
||||||
|
) or die "@{[$config->{domain_socket}]}: $!";
|
||||||
|
$self->msgba_connection($msgba_connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
1;
|
|
@ -1,20 +0,0 @@
|
||||||
package msgba-web;
|
|
||||||
use Mojo::Base 'Mojolicious', -signatures;
|
|
||||||
|
|
||||||
# This method will run once at server start
|
|
||||||
sub startup ($self) {
|
|
||||||
|
|
||||||
# Load configuration from config file
|
|
||||||
my $config = $self->plugin('NotYAMLConfig');
|
|
||||||
|
|
||||||
# Configure the application
|
|
||||||
$self->secrets($config->{secrets});
|
|
||||||
|
|
||||||
# Router
|
|
||||||
my $r = $self->routes;
|
|
||||||
|
|
||||||
# Normal route to controller
|
|
||||||
$r->get('/')->to('Example#welcome');
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
|
|
@ -1,11 +0,0 @@
|
||||||
package msgba-web::Controller::Example;
|
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
|
||||||
|
|
||||||
# This action will render a template
|
|
||||||
sub welcome ($self) {
|
|
||||||
|
|
||||||
# Render template "example/welcome.html.ep" with message
|
|
||||||
$self->render(msg => 'Welcome to the Mojolicious real-time web framework!');
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
domain_socket: "/home/sergio/msgba/msgba.sock"
|
|
@ -8,4 +8,4 @@ use lib curfile->dirname->sibling('lib')->to_string;
|
||||||
use Mojolicious::Commands;
|
use Mojolicious::Commands;
|
||||||
|
|
||||||
# Start command line interface for application
|
# Start command line interface for application
|
||||||
Mojolicious::Commands->start_app('msgba-web');
|
Mojolicious::Commands->start_app('MSGBA::Web');
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
<!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 websocket = null;
|
||||||
|
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', () => {
|
||||||
|
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);
|
||||||
|
websocket = new WebSocket(`ws://${location.host}/ws`);
|
||||||
|
websocket.addEventListener('open', () => {
|
||||||
|
console.log('Opened websocket.');
|
||||||
|
sendHello(websocket, rom_array, savestate_array);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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(BigInt(id)), u64ToByteArrayBigEndian(BigInt(raw_data.length))),
|
||||||
|
raw_data
|
||||||
|
);
|
||||||
|
const packet_blob = packet_u8.buffer;
|
||||||
|
console.log('Sending packet');
|
||||||
|
websocket.send(packet_blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeEmulator(canvas) {
|
||||||
|
const width = document.body.clientWidth;
|
||||||
|
const height = document.body.clientHeight;
|
||||||
|
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>
|
Loading…
Reference in New Issue