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.',
|
||||
requires => {
|
||||
'Mojolicious' => 0,
|
||||
'Moo' => 0,
|
||||
'Types::Standard' => 0,
|
||||
},
|
||||
);
|
||||
$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;
|
10
lib/MSGBA/Web/Controller/Static.pm
Normal file
10
lib/MSGBA/Web/Controller/Static.pm
Normal file
@ -0,0 +1,10 @@
|
||||
package MSGBA::Web::Controller::Static;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||
|
||||
1;
|
110
lib/MSGBA/Web/Controller/WS.pm
Normal file
110
lib/MSGBA/Web/Controller/WS.pm
Normal file
@ -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;
|
2
msgba-web.yml
Normal file
2
msgba-web.yml
Normal file
@ -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;
|
||||
|
||||
# Start command line interface for application
|
||||
Mojolicious::Commands->start_app('msgba-web');
|
||||
Mojolicious::Commands->start_app('MSGBA::Web');
|
||||
|
170
templates/static/index.html.ep
Normal file
170
templates/static/index.html.ep
Normal file
@ -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…
x
Reference in New Issue
Block a user