diff --git a/js-src/components/canvas-gba-emulator.jsx b/js-src/components/canvas-gba-emulator.jsx
deleted file mode 100644
index 8e03378..0000000
--- a/js-src/components/canvas-gba-emulator.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from 'react';
-
-import {MIN_WIDTH, MIN_HEIGHT} from '/constants';
-
-export default function CanvasGBAEmulator(props) {
- return ();
-}
diff --git a/js-src/components/canvas-gba-emulator.tsx b/js-src/components/canvas-gba-emulator.tsx
new file mode 100644
index 0000000..a65ab63
--- /dev/null
+++ b/js-src/components/canvas-gba-emulator.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+
+import {MIN_WIDTH, MIN_HEIGHT} from '/constants';
+
+export interface CanvasGBAEmulatorProps {
+ canvasRef: React.RefObject
+}
+
+export default function CanvasGBAEmulator(props: CanvasGBAEmulatorProps) {
+ return ();
+}
diff --git a/js-src/components/center-element.jsx b/js-src/components/center-element.jsx
deleted file mode 100644
index 89c242f..0000000
--- a/js-src/components/center-element.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-
-export default function CenterElement(props) {
- let hidden = props.hidden;
- if (hidden == null) {
- hidden = false;
- }
- return (
- {props.children}
- );
-}
diff --git a/js-src/components/center-element.tsx b/js-src/components/center-element.tsx
new file mode 100644
index 0000000..4a41d0d
--- /dev/null
+++ b/js-src/components/center-element.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+
+export interface CenterElementProps {
+ hidden?: boolean | undefined,
+ children?: React.ReactNode,
+}
+
+type IHash = {
+ [id: string]: string;
+}
+
+export default function CenterElement(props: CenterElementProps) {
+ const styles: IHash = {};
+ let hidden = props.hidden;
+ if (hidden == null) {
+ hidden = false;
+ }
+ styles["display"] = hidden ? 'none' : '';
+ return (
+ {props.children}
+ );
+}
diff --git a/js-src/components/form-select-files.jsx b/js-src/components/form-select-files.tsx
similarity index 63%
rename from js-src/components/form-select-files.jsx
rename to js-src/components/form-select-files.tsx
index 09b00d0..c1cbc8e 100644
--- a/js-src/components/form-select-files.jsx
+++ b/js-src/components/form-select-files.tsx
@@ -1,6 +1,12 @@
-import React from 'react';
+import * as React from 'react';
-export default function FormSelectFiles(props) {
+export interface FormSelectFilesProps {
+ refInputRom: React.RefObject;
+ refInputSaveState: React.RefObject;
+ onStartEmulation: React.MouseEventHandler;
+}
+
+export default function FormSelectFiles(props: FormSelectFilesProps) {
const inputRom = props.refInputRom ? props.refInputRom : React.useRef(null);
const inputSaveState = props.refInputSaveState ? props.refInputSaveState : React.useRef(null);
const onStartEmulation = props.onStartEmulation;
@@ -15,7 +21,7 @@ export default function FormSelectFiles(props) {
Savestate (A ss file from mgba...)
-
+
);
}
diff --git a/js-src/components/page.jsx b/js-src/components/page.tsx
similarity index 55%
rename from js-src/components/page.jsx
rename to js-src/components/page.tsx
index c02a836..9224014 100644
--- a/js-src/components/page.jsx
+++ b/js-src/components/page.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import * as React from 'react';
import CenterElement from '/components/center-element';
import FormSelectFiles from '/components/form-select-files';
@@ -8,9 +8,32 @@ import Endian from '/endian';
import {MIN_WIDTH, MIN_HEIGHT, PACKET_ID_HELLO, PACKET_ID_SEND_FRAME} from '/constants';
-function handleClickStartEmulationButton({e, inputRom, inputSaveState, setHiddenFormSelectFiles, canvas}) {
+type setBooleanCallback = (c: boolean) => boolean;
+
+export interface handleClickStartEmulationButtonObjectArgs {
+ e: React.MouseEvent;
+ inputRom: HTMLInputElement | null;
+ inputSaveState: HTMLInputElement | null;
+ setHiddenFormSelectFiles: (c: setBooleanCallback) => void;
+ canvas: HTMLCanvasElement | null;
+ printingFrame: boolean;
+ setPrintingFrame: (c: setBooleanCallback) => void;
+};
+
+function handleClickStartEmulationButton({e, inputRom, inputSaveState, setHiddenFormSelectFiles, canvas, printingFrame, setPrintingFrame}: handleClickStartEmulationButtonObjectArgs) {
+ if (canvas == null) {
+ alert('Canvas does not exists?');
+ return;
+ }
const ctx = canvas.getContext('2d')
- e.preventDefault();
+ if (ctx == null) {
+ alert('Unable to create canvas context, doing nothing');
+ return;
+ }
+ if (inputRom == null || inputSaveState == null || inputRom.files == null || inputSaveState.files == null) {
+ alert('Unable to read the files ');
+ return;
+ }
if (inputRom.files.length == 0) {
alert('There is no rom still');
return;
@@ -23,42 +46,37 @@ function handleClickStartEmulationButton({e, inputRom, inputSaveState, setHidden
const savestate_file = inputSaveState.files[0];
rom_file.arrayBuffer().then((rom_buffer) => {
savestate_file.arrayBuffer().then((savestate_buffer) => {
+ setHiddenFormSelectFiles((c: boolean) => true);
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 = () => {
+ websocket.onclose = (message) => {
setHiddenFormSelectFiles(c => false);
+ console.log('Closing websocket.');
+ }
+ websocket.onopen = () => {
console.log('Opened websocket.');
sendHello(websocket, rom_array, savestate_array);
};
- websocket.addEventListener('message', (event) => onWebSocketPacket(event, canvas, ctx));
+ setPrintingFrame(c => false);
+ websocket.addEventListener('message', (event) => {
+ onWebSocketPacket(event, canvas, ctx, printingFrame, setPrintingFrame)
+ });
});
});
}
-function concatU8Array(array1, array2) {
+function concatU8Array(array1: Uint8Array, array2: Uint8Array) {
const final_array = new Uint8Array(array1.length + array2.length);
final_array.set(array1);
final_array.set(array2, array1.length);
return final_array;
}
-function u64ToByteArrayBigEndian(input_number) {
- const buffer = new ArrayBuffer(8);
- const buffer8 = new Uint8Array(buffer);
- const buffer64 = new BigUint64Array(buffer);
- buffer64[0] = input_number;
- if (Endian.isLittleEndian()) {
- buffer8.reverse();
- }
- return buffer8;
-}
-
-function sendPacket(websocket, id, raw_data) {
+function sendPacket(websocket: WebSocket, id: bigint, raw_data: Uint8Array) {
const packet_u8 = concatU8Array(
- concatU8Array(u64ToByteArrayBigEndian(id), u64ToByteArrayBigEndian(BigInt(raw_data.length))),
+ concatU8Array(Endian.u64ToByteArrayBigEndian(id), Endian.u64ToByteArrayBigEndian(BigInt(raw_data.length))),
raw_data
);
const packet_buffer = packet_u8.buffer;
@@ -66,50 +84,48 @@ function sendPacket(websocket, id, raw_data) {
websocket.send(packet_buffer);
}
- function sendHello(websocket, rom_array, savestate_array) {
+ function sendHello(websocket: WebSocket, rom_array: Uint8Array, savestate_array: Uint8Array) {
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)
+ concatU8Array(Endian.u64ToByteArrayBigEndian(length_rom), rom_array),
+ Endian.u64ToByteArrayBigEndian(length_savestate)
),
savestate_array
);
sendPacket(websocket, PACKET_ID_HELLO, raw_data);
}
-function onWebSocketPacket(event, canvas, ctx) {
+function onWebSocketPacket(event: MessageEvent, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: setBooleanCallback) => void) {
const buffer = event.data;
- let packet_u8 = new Uint8Array(buffer);
- const id = byteArrayToU64BigEndian(packet_u8.slice(0, 8));
+ let packet_u8: Uint8Array | null = new Uint8Array(buffer);
+ const id = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
packet_u8 = packet_u8.slice(8, packet_u8.length);
- const size = byteArrayToU64BigEndian(packet_u8.slice(0, 8));
+ const size = Endian.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, canvas, ctx);
+ handleSendFrame(raw_data, canvas, ctx, printingFrame, setPrintingFrame);
break;
default:
console.log(`Received unknown packet ${id}`);
}
}
-let printing_frame = false;
-function handleSendFrame(raw_data, canvas, ctx) {
- if (printing_frame) {
+function handleSendFrame(raw_data: Uint8Array, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: setBooleanCallback) => void) {
+ if (printingFrame) {
return;
}
- printing_frame = true;
- let data = raw_data;
- const stride = byteArrayToU32BigEndian(data.slice(0, 4));
+ setPrintingFrame(c => true);
+ let data: Uint8Array | null = raw_data;
+ const stride = Endian.byteArrayToU32BigEndian(data.slice(0, 4));
data = data.slice(4, data.length);
- const output_buffer_size = byteArrayToU32BigEndian(data.slice(0, 8));
+ const output_buffer_size = Endian.byteArrayToU64BigEndian(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 drawBitmap(bitmap, canvas, ctx));
+ createImageBitmap(img_data).then((bitmap) => drawBitmap(bitmap, canvas, ctx, printingFrame));
}
-function byteArrayToU32BigEndian(input_array) {
- if (Endian.isLittleEndian()) {
- 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 (Endian.isLittleEndian()) {
- input_array = input_array.reverse();
- }
- const buffer = input_array.buffer;
- const output_u64_array = new BigUint64Array(buffer);
- return output_u64_array[0];
-}
-
-function drawBitmap(bitmap, canvas, ctx) {
+function drawBitmap(bitmap: ImageBitmap, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean) {
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
- printing_frame = false;
+ printingFrame = false;
}
export default function Page() {
const screenDimensions = useScreenDimensions();
const emulatorDimensions = calculateSizeEmulator(screenDimensions);
- const canvasRef = React.useRef(null);
- function resizeCanvas(node) {
+ const canvasRef = React.useRef(null);
+ function resizeCanvas() {
const canvas = canvasRef.current;
- if (canvas) {
- canvas.width = emulatorDimensions.width;
- canvas.height = emulatorDimensions.height;
- const ctx = canvas.getContext('2d')
- fillBlack(canvas, ctx);
+ if (canvas == null) {
+ return;
}
+ if (emulatorDimensions.width === undefined || emulatorDimensions.height === undefined) {
+ return;
+ }
+ canvas.width = emulatorDimensions.width;
+ canvas.height = emulatorDimensions.height;
+ const ctx = canvas.getContext('2d')
+ if (ctx == null) {
+ return;
+ }
+ fillBlack(canvas, ctx);
};
- const [hiddenFormSelectFiles, setHiddenFormSelectFiles] = React.useState(false);
+ const [hiddenFormSelectFiles, setHiddenFormSelectFiles] = React.useState(false);
+ const [printingFrame, setPrintingFrame] = React.useState(false);
React.useEffect(resizeCanvas, [emulatorDimensions]);
- const refInputRom = React.useRef(null);
- const refInputSaveState = React.useRef(null);
- const onStartEmulation = (e) => {
+ const refInputRom = React.useRef(null);
+ const refInputSaveState = React.useRef(null);
+ const onStartEmulation = (e: React.MouseEvent) => {
handleClickStartEmulationButton({
e: e,
setHiddenFormSelectFiles: setHiddenFormSelectFiles,
inputRom: refInputRom.current,
inputSaveState: refInputSaveState.current,
canvas: canvasRef.current,
+ setPrintingFrame: setPrintingFrame,
+ printingFrame: printingFrame,
});
};
return (
@@ -198,7 +206,7 @@ function getScreenDimensions() {
}
function useScreenDimensions() {
- const [screenDimensions, setScreenDimensions] = React.useState(getScreenDimensions());
+ const [screenDimensions, setScreenDimensions] = React.useState(getScreenDimensions());
React.useEffect(() => {
function onResize() {
@@ -215,17 +223,25 @@ function useScreenDimensions() {
return screenDimensions;
}
-function fillBlack(canvas, ctx) {
+function fillBlack(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.fill();
}
-function calculateSizeEmulator(screenDimensions) {
+export interface EmulatorDimensions {
+ width?: number;
+ height?: number;
+};
+function calculateSizeEmulator(screenDimensions: EmulatorDimensions): EmulatorDimensions {
+ if (screenDimensions.width === undefined || screenDimensions.height === undefined) {
+ console.error(screenDimensions, 'screenDimensions has undefined fields');
+ return {};
+ }
const width = screenDimensions.width;
const height = screenDimensions.height * 0.75;
- const emulatorDimensions = {};
+ const emulatorDimensions: EmulatorDimensions = {};
if (width < MIN_WIDTH || height < MIN_HEIGHT) {
return {
width: MIN_WIDTH,
diff --git a/js-src/constants.js b/js-src/constants.ts
similarity index 59%
rename from js-src/constants.js
rename to js-src/constants.ts
index 61435fe..1736c0f 100644
--- a/js-src/constants.js
+++ b/js-src/constants.ts
@@ -3,6 +3,11 @@ export const MIN_HEIGHT = 160;
export const PACKET_ID_HELLO = 0n;
export const PACKET_ID_SEND_FRAME = 1n;
export default class Constants {
+ public static MIN_WIDTH: number = MIN_WIDTH;
+ public static MIN_HEIGHT: number = MIN_HEIGHT;
+ public static PACKET_ID_HELLO: bigint = PACKET_ID_HELLO;
+ public static PACKET_ID_SEND_FRAME: bigint = PACKET_ID_SEND_FRAME;
+
};
Constants.MIN_WIDTH = MIN_WIDTH;
Constants.MIN_HEIGHT = MIN_HEIGHT;
diff --git a/js-src/endian.js b/js-src/endian.js
deleted file mode 100644
index 510fd60..0000000
--- a/js-src/endian.js
+++ /dev/null
@@ -1,18 +0,0 @@
-"use strict";
-
-let littleEndian = true;
-(()=>{
- let buf = new ArrayBuffer(4);
- let buf8 = new Uint8ClampedArray(buf);
- let data = new Uint32Array(buf);
- data[0] = 0xdeadbeef;
- if(buf8[0] === 0xde){
- littleEndian = false;
- }
-})()
-
-export default class Endian {
- static isLittleEndian() {
- return littleEndian;
- }
-}
diff --git a/js-src/endian.ts b/js-src/endian.ts
new file mode 100644
index 0000000..576c6bd
--- /dev/null
+++ b/js-src/endian.ts
@@ -0,0 +1,40 @@
+"use strict";
+
+let littleEndian = true; (()=>{ let buf = new ArrayBuffer(4); let buf8 = new
+Uint8ClampedArray(buf); let data = new Uint32Array(buf); data[0] = 0xdeadbeef;
+if(buf8[0] === 0xde){ littleEndian = false; } })()
+
+export default class Endian {
+ static isLittleEndian() {
+ return littleEndian;
+ }
+
+ static byteArrayToU32BigEndian(inputArray: Uint8Array) {
+ if (Endian.isLittleEndian()) {
+ inputArray = inputArray.reverse();
+ }
+ const buffer = inputArray.buffer;
+ const outputU32Array = new Uint32Array(buffer);
+ return outputU32Array[0];
+ }
+
+ static byteArrayToU64BigEndian(inputArray: Uint8Array) {
+ if (Endian.isLittleEndian()) {
+ inputArray = inputArray.reverse();
+ }
+ const buffer = inputArray.buffer;
+ const outputU64Array = new BigUint64Array(buffer);
+ return outputU64Array[0];
+ }
+
+ static u64ToByteArrayBigEndian(inputNumber: bigint) {
+ const buffer = new ArrayBuffer(8);
+ const buffer8 = new Uint8Array(buffer);
+ const buffer64 = new BigUint64Array(buffer);
+ buffer64[0] = inputNumber;
+ if (Endian.isLittleEndian()) {
+ buffer8.reverse();
+ }
+ return buffer8;
+ }
+}
diff --git a/js-src/index.jsx b/js-src/index.jsx
deleted file mode 100644
index 90f0a33..0000000
--- a/js-src/index.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-"use strict";
-import React from 'react';
-import ReactDOMClient from 'react-dom/client';
-
-import Endian from '/endian';
-import Page from '/components/page';
-
-const body = document.querySelector('body');
-const app = document.createElement('div');
-body.appendChild(app);
-const root = ReactDOMClient.createRoot(app);
-root.render();
diff --git a/js-src/index.tsx b/js-src/index.tsx
new file mode 100644
index 0000000..cb4d83b
--- /dev/null
+++ b/js-src/index.tsx
@@ -0,0 +1,18 @@
+"use strict";
+import * as React from 'react';
+import * as ReactDOMClient from 'react-dom/client';
+
+import Endian from '/endian';
+import Page from '/components/page';
+
+const body = document.querySelector('body');
+if (body != null) {
+ fillBody(body);
+}
+
+function fillBody(body: HTMLElement) {
+ const app = document.createElement('div');
+ body.appendChild(app);
+ const root = ReactDOMClient.createRoot(app);
+ root.render();
+}
diff --git a/package.json b/package.json
index d005f80..2c215ad 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,12 @@
"license": "MIT",
"devDependencies": {
"@babel/preset-react": "^7.18.6",
+ "@types/react": "^18.0.28",
+ "@types/react-dom": "^18.0.11",
"babel-loader": "^9.1.2",
"file-loader": "^6.2.0",
+ "ts-loader": "^9.4.2",
+ "typescript": "^5.0.2",
"url-loader": "^4.1.1",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.2"
diff --git a/public/js/bundle.js b/public/js/bundle.js
index e03a0ba..7b35e7c 100644
--- a/public/js/bundle.js
+++ b/public/js/bundle.js
@@ -10,76 +10,6 @@
/******/ "use strict";
/******/ var __webpack_modules__ = ({
-/***/ "./js-src/components/canvas-gba-emulator.jsx":
-/*!***************************************************!*\
- !*** ./js-src/components/canvas-gba-emulator.jsx ***!
- \***************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ CanvasGBAEmulator)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../constants */ \"./js-src/constants.js\");\n\n\nfunction CanvasGBAEmulator(props) {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"canvas\", {\n ref: props.canvasRef,\n width: _constants__WEBPACK_IMPORTED_MODULE_1__.MIN_WIDTH,\n height: _constants__WEBPACK_IMPORTED_MODULE_1__.MIN_HEIGHT\n });\n}\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/canvas-gba-emulator.jsx?");
-
-/***/ }),
-
-/***/ "./js-src/components/center-element.jsx":
-/*!**********************************************!*\
- !*** ./js-src/components/center-element.jsx ***!
- \**********************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ CenterElement)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\nfunction CenterElement(props) {\n let hidden = props.hidden;\n if (hidden == null) {\n hidden = false;\n }\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", {\n style: {\n display: hidden ? 'none' : ''\n },\n className: \"center-content\"\n }, props.children);\n}\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/center-element.jsx?");
-
-/***/ }),
-
-/***/ "./js-src/components/form-select-files.jsx":
-/*!*************************************************!*\
- !*** ./js-src/components/form-select-files.jsx ***!
- \*************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ FormSelectFiles)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\nfunction FormSelectFiles(props) {\n const inputRom = props.refInputRom ? props.refInputRom : react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);\n const inputSaveState = props.refInputSaveState ? props.refInputSaveState : react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);\n const onStartEmulation = props.onStartEmulation;\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"form\", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"label\", {\n htmlFor: \"rom\"\n }, \"Rom file\", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"input\", {\n type: \"file\",\n ref: inputRom,\n name: \"rom\"\n })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"label\", {\n htmlFor: \"savestate\"\n }, \"Savestate (A ss file from mgba...)\", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"input\", {\n type: \"file\",\n ref: inputSaveState,\n name: \"savestate\"\n })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"input\", {\n type: \"button\",\n value: \"Start emulation\",\n onClick: onStartEmulation,\n ref: props.startEmulationButton\n }));\n}\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/form-select-files.jsx?");
-
-/***/ }),
-
-/***/ "./js-src/components/page.jsx":
-/*!************************************!*\
- !*** ./js-src/components/page.jsx ***!
- \************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Page)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _components_center_element__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../components/center-element */ \"./js-src/components/center-element.jsx\");\n/* harmony import */ var _components_form_select_files__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../components/form-select-files */ \"./js-src/components/form-select-files.jsx\");\n/* harmony import */ var _components_canvas_gba_emulator__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../components/canvas-gba-emulator */ \"./js-src/components/canvas-gba-emulator.jsx\");\n/* harmony import */ var _endian__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../endian */ \"./js-src/endian.js\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../constants */ \"./js-src/constants.js\");\n\n\n\n\n\n\nfunction handleClickStartEmulationButton({\n e,\n inputRom,\n inputSaveState,\n setHiddenFormSelectFiles,\n canvas\n}) {\n const ctx = canvas.getContext('2d');\n e.preventDefault();\n if (inputRom.files.length == 0) {\n alert('There is no rom still');\n return;\n }\n if (inputSaveState.files.length == 0) {\n alert('There is no savestate still');\n return;\n }\n const rom_file = inputRom.files[0];\n const savestate_file = inputSaveState.files[0];\n rom_file.arrayBuffer().then(rom_buffer => {\n savestate_file.arrayBuffer().then(savestate_buffer => {\n const rom_array = new Uint8Array(rom_buffer);\n const savestate_array = new Uint8Array(savestate_buffer);\n const websocket = new WebSocket(`ws://localhost:3000/ws`);\n websocket.binaryType = 'arraybuffer';\n websocket.onclose = message => console.log('CLOSE', message);\n websocket.onopen = () => {\n setHiddenFormSelectFiles(c => false);\n console.log('Opened websocket.');\n sendHello(websocket, rom_array, savestate_array);\n };\n websocket.addEventListener('message', event => onWebSocketPacket(event, canvas, ctx));\n });\n });\n}\nfunction concatU8Array(array1, array2) {\n const final_array = new Uint8Array(array1.length + array2.length);\n final_array.set(array1);\n final_array.set(array2, array1.length);\n return final_array;\n}\nfunction u64ToByteArrayBigEndian(input_number) {\n const buffer = new ArrayBuffer(8);\n const buffer8 = new Uint8Array(buffer);\n const buffer64 = new BigUint64Array(buffer);\n buffer64[0] = input_number;\n if (_endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].isLittleEndian()) {\n buffer8.reverse();\n }\n return buffer8;\n}\nfunction sendPacket(websocket, id, raw_data) {\n const packet_u8 = concatU8Array(concatU8Array(u64ToByteArrayBigEndian(id), u64ToByteArrayBigEndian(BigInt(raw_data.length))), raw_data);\n const packet_buffer = packet_u8.buffer;\n console.log('Sending packet');\n websocket.send(packet_buffer);\n}\nfunction sendHello(websocket, rom_array, savestate_array) {\n console.log('Sending hello.');\n const length_rom = BigInt(rom_array.length);\n const length_savestate = BigInt(savestate_array.length);\n const raw_data = concatU8Array(concatU8Array(concatU8Array(u64ToByteArrayBigEndian(length_rom), rom_array), u64ToByteArrayBigEndian(length_savestate)), savestate_array);\n sendPacket(websocket, _constants__WEBPACK_IMPORTED_MODULE_5__.PACKET_ID_HELLO, raw_data);\n}\nfunction onWebSocketPacket(event, canvas, ctx) {\n const buffer = event.data;\n let packet_u8 = new Uint8Array(buffer);\n const id = byteArrayToU64BigEndian(packet_u8.slice(0, 8));\n packet_u8 = packet_u8.slice(8, packet_u8.length);\n const size = byteArrayToU64BigEndian(packet_u8.slice(0, 8));\n const raw_data = packet_u8.slice(8, packet_u8.length);\n packet_u8 = null;\n switch (id) {\n case _constants__WEBPACK_IMPORTED_MODULE_5__.PACKET_ID_SEND_FRAME:\n handleSendFrame(raw_data, canvas, ctx);\n break;\n default:\n console.log(`Received unknown packet ${id}`);\n }\n}\nlet printing_frame = false;\nfunction handleSendFrame(raw_data, canvas, ctx) {\n if (printing_frame) {\n return;\n }\n printing_frame = true;\n let data = raw_data;\n const stride = byteArrayToU32BigEndian(data.slice(0, 4));\n data = data.slice(4, data.length);\n const output_buffer_size = byteArrayToU32BigEndian(data.slice(0, 8));\n data = data.slice(8, data.length);\n console.log(data.length / 4 / _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH);\n const img_data = ctx.createImageData(_constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH, _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT);\n const img_data_u8 = new Uint8Array(img_data.data.buffer);\n for (let i = 0; i < data.length; i++) {\n if (i % 4 == 3) {\n img_data_u8[i] = 255;\n continue;\n }\n img_data_u8[i] = data[i];\n }\n data = null;\n createImageBitmap(img_data).then(bitmap => drawBitmap(bitmap, canvas, ctx));\n}\nfunction byteArrayToU32BigEndian(input_array) {\n if (_endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].isLittleEndian()) {\n input_array = input_array.reverse();\n }\n const buffer = input_array.buffer;\n const output_u32_array = new Uint32Array(buffer);\n return output_u32_array[0];\n}\nfunction byteArrayToU64BigEndian(input_array) {\n if (_endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].isLittleEndian()) {\n input_array = input_array.reverse();\n }\n const buffer = input_array.buffer;\n const output_u64_array = new BigUint64Array(buffer);\n return output_u64_array[0];\n}\nfunction drawBitmap(bitmap, canvas, ctx) {\n ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);\n printing_frame = false;\n}\nfunction Page() {\n const screenDimensions = useScreenDimensions();\n const emulatorDimensions = calculateSizeEmulator(screenDimensions);\n const canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);\n function resizeCanvas(node) {\n const canvas = canvasRef.current;\n if (canvas) {\n canvas.width = emulatorDimensions.width;\n canvas.height = emulatorDimensions.height;\n const ctx = canvas.getContext('2d');\n fillBlack(canvas, ctx);\n }\n }\n ;\n const [hiddenFormSelectFiles, setHiddenFormSelectFiles] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);\n react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(resizeCanvas, [emulatorDimensions]);\n const refInputRom = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);\n const refInputSaveState = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);\n const onStartEmulation = e => {\n handleClickStartEmulationButton({\n e: e,\n setHiddenFormSelectFiles: setHiddenFormSelectFiles,\n inputRom: refInputRom.current,\n inputSaveState: refInputSaveState.current,\n canvas: canvasRef.current\n });\n };\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_center_element__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"h2\", null, \"msGBA Online Emulator for GBA.\")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_center_element__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_canvas_gba_emulator__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n canvasRef: canvasRef\n })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_center_element__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n hidden: hiddenFormSelectFiles\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_form_select_files__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n refInputRom: refInputRom,\n refInputSaveState: refInputSaveState,\n onStartEmulation: onStartEmulation\n })));\n}\nfunction getScreenDimensions() {\n return {\n width: document.body.clientWidth,\n height: document.body.clientHeight\n };\n}\nfunction useScreenDimensions() {\n const [screenDimensions, setScreenDimensions] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(getScreenDimensions());\n react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {\n function onResize() {\n setScreenDimensions(getScreenDimensions());\n }\n window.addEventListener(\"resize\", onResize);\n return () => {\n window.removeEventListener(\"resize\", onResize);\n };\n }, []);\n return screenDimensions;\n}\nfunction fillBlack(canvas, ctx) {\n ctx.beginPath();\n ctx.rect(0, 0, canvas.width, canvas.height);\n ctx.fillStyle = 'black';\n ctx.fill();\n}\nfunction calculateSizeEmulator(screenDimensions) {\n const width = screenDimensions.width;\n const height = screenDimensions.height * 0.75;\n const emulatorDimensions = {};\n if (width < _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH || height < _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT) {\n return {\n width: _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH,\n height: _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT\n };\n }\n const ratioWidth = Math.floor(width / _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH);\n const ratioHeight = Math.floor(height / _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT);\n if (ratioWidth < ratioHeight) {\n emulatorDimensions.width = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH * ratioWidth;\n emulatorDimensions.height = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT * ratioWidth;\n } else {\n emulatorDimensions.height = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT * ratioHeight;\n emulatorDimensions.width = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH * ratioHeight;\n }\n return emulatorDimensions;\n}\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/page.jsx?");
-
-/***/ }),
-
-/***/ "./js-src/constants.js":
-/*!*****************************!*\
- !*** ./js-src/constants.js ***!
- \*****************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"MIN_HEIGHT\": () => (/* binding */ MIN_HEIGHT),\n/* harmony export */ \"MIN_WIDTH\": () => (/* binding */ MIN_WIDTH),\n/* harmony export */ \"PACKET_ID_HELLO\": () => (/* binding */ PACKET_ID_HELLO),\n/* harmony export */ \"PACKET_ID_SEND_FRAME\": () => (/* binding */ PACKET_ID_SEND_FRAME),\n/* harmony export */ \"default\": () => (/* binding */ Constants)\n/* harmony export */ });\nconst MIN_WIDTH = 240;\nconst MIN_HEIGHT = 160;\nconst PACKET_ID_HELLO = 0n;\nconst PACKET_ID_SEND_FRAME = 1n;\nclass Constants {}\n;\nConstants.MIN_WIDTH = MIN_WIDTH;\nConstants.MIN_HEIGHT = MIN_HEIGHT;\nConstants.PACKET_ID_HELLO = PACKET_ID_HELLO;\nConstants.PACKET_ID_SEND_FRAME = PACKET_ID_SEND_FRAME;\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/constants.js?");
-
-/***/ }),
-
-/***/ "./js-src/endian.js":
-/*!**************************!*\
- !*** ./js-src/endian.js ***!
- \**************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Endian)\n/* harmony export */ });\n\n\nlet littleEndian = true;\n(() => {\n let buf = new ArrayBuffer(4);\n let buf8 = new Uint8ClampedArray(buf);\n let data = new Uint32Array(buf);\n data[0] = 0xdeadbeef;\n if (buf8[0] === 0xde) {\n littleEndian = false;\n }\n})();\nclass Endian {\n static isLittleEndian() {\n return littleEndian;\n }\n}\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/endian.js?");
-
-/***/ }),
-
-/***/ "./js-src/index.jsx":
-/*!**************************!*\
- !*** ./js-src/index.jsx ***!
- \**************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_dom_client__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom/client */ \"./node_modules/react-dom/client.js\");\n/* harmony import */ var _endian__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../endian */ \"./js-src/endian.js\");\n/* harmony import */ var _components_page__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../components/page */ \"./js-src/components/page.jsx\");\n\n\n\n\n\n\nconst body = document.querySelector('body');\nconst app = document.createElement('div');\nbody.appendChild(app);\nconst root = react_dom_client__WEBPACK_IMPORTED_MODULE_1__.createRoot(app);\nroot.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_page__WEBPACK_IMPORTED_MODULE_3__[\"default\"], null));\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/index.jsx?");
-
-/***/ }),
-
/***/ "./node_modules/react-dom/cjs/react-dom.development.js":
/*!*************************************************************!*\
!*** ./node_modules/react-dom/cjs/react-dom.development.js ***!
@@ -148,6 +78,76 @@ eval("/**\n * @license React\n * scheduler.development.js\n *\n * Copyright (c)
eval("\n\nif (false) {} else {\n module.exports = __webpack_require__(/*! ./cjs/scheduler.development.js */ \"./node_modules/scheduler/cjs/scheduler.development.js\");\n}\n\n\n//# sourceURL=webpack://MSGBA-Web/./node_modules/scheduler/index.js?");
+/***/ }),
+
+/***/ "./js-src/components/canvas-gba-emulator.tsx":
+/*!***************************************************!*\
+ !*** ./js-src/components/canvas-gba-emulator.tsx ***!
+ \***************************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ CanvasGBAEmulator)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../constants */ \"./js-src/constants.ts\");\n\n\nfunction CanvasGBAEmulator(props) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"canvas\", { ref: props.canvasRef, width: _constants__WEBPACK_IMPORTED_MODULE_1__.MIN_WIDTH, height: _constants__WEBPACK_IMPORTED_MODULE_1__.MIN_HEIGHT }));\n}\n\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/canvas-gba-emulator.tsx?");
+
+/***/ }),
+
+/***/ "./js-src/components/center-element.tsx":
+/*!**********************************************!*\
+ !*** ./js-src/components/center-element.tsx ***!
+ \**********************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ CenterElement)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\nfunction CenterElement(props) {\n const styles = {};\n let hidden = props.hidden;\n if (hidden == null) {\n hidden = false;\n }\n styles[\"display\"] = hidden ? 'none' : '';\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { style: styles, className: \"center-content\" }, props.children));\n}\n\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/center-element.tsx?");
+
+/***/ }),
+
+/***/ "./js-src/components/form-select-files.tsx":
+/*!*************************************************!*\
+ !*** ./js-src/components/form-select-files.tsx ***!
+ \*************************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ FormSelectFiles)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\nfunction FormSelectFiles(props) {\n const inputRom = props.refInputRom ? props.refInputRom : react__WEBPACK_IMPORTED_MODULE_0__.useRef(null);\n const inputSaveState = props.refInputSaveState ? props.refInputSaveState : react__WEBPACK_IMPORTED_MODULE_0__.useRef(null);\n const onStartEmulation = props.onStartEmulation;\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"form\", null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", { htmlFor: \"rom\" },\n \"Rom file\",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"input\", { type: \"file\", ref: inputRom, name: \"rom\" })),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", { htmlFor: \"savestate\" },\n \"Savestate (A ss file from mgba...)\",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"input\", { type: \"file\", ref: inputSaveState, name: \"savestate\" })),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"input\", { type: \"button\", value: \"Start emulation\", onClick: onStartEmulation })));\n}\n\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/form-select-files.tsx?");
+
+/***/ }),
+
+/***/ "./js-src/components/page.tsx":
+/*!************************************!*\
+ !*** ./js-src/components/page.tsx ***!
+ \************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Page)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _components_center_element__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../components/center-element */ \"./js-src/components/center-element.tsx\");\n/* harmony import */ var _components_form_select_files__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../components/form-select-files */ \"./js-src/components/form-select-files.tsx\");\n/* harmony import */ var _components_canvas_gba_emulator__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../components/canvas-gba-emulator */ \"./js-src/components/canvas-gba-emulator.tsx\");\n/* harmony import */ var _endian__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../endian */ \"./js-src/endian.ts\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../constants */ \"./js-src/constants.ts\");\n\n\n\n\n\n\n;\nfunction handleClickStartEmulationButton({ e, inputRom, inputSaveState, setHiddenFormSelectFiles, canvas, printingFrame, setPrintingFrame }) {\n if (canvas == null) {\n alert('Canvas does not exists?');\n return;\n }\n const ctx = canvas.getContext('2d');\n if (ctx == null) {\n alert('Unable to create canvas context, doing nothing');\n return;\n }\n if (inputRom == null || inputSaveState == null || inputRom.files == null || inputSaveState.files == null) {\n alert('Unable to read the files ');\n return;\n }\n if (inputRom.files.length == 0) {\n alert('There is no rom still');\n return;\n }\n if (inputSaveState.files.length == 0) {\n alert('There is no savestate still');\n return;\n }\n const rom_file = inputRom.files[0];\n const savestate_file = inputSaveState.files[0];\n rom_file.arrayBuffer().then((rom_buffer) => {\n savestate_file.arrayBuffer().then((savestate_buffer) => {\n setHiddenFormSelectFiles((c) => true);\n const rom_array = new Uint8Array(rom_buffer);\n const savestate_array = new Uint8Array(savestate_buffer);\n const websocket = new WebSocket(`ws://localhost:3000/ws`);\n websocket.binaryType = 'arraybuffer';\n websocket.onclose = (message) => {\n setHiddenFormSelectFiles(c => false);\n console.log('Closing websocket.');\n };\n websocket.onopen = () => {\n console.log('Opened websocket.');\n sendHello(websocket, rom_array, savestate_array);\n };\n setPrintingFrame(c => false);\n websocket.addEventListener('message', (event) => {\n onWebSocketPacket(event, canvas, ctx, printingFrame, setPrintingFrame);\n });\n });\n });\n}\nfunction concatU8Array(array1, array2) {\n const final_array = new Uint8Array(array1.length + array2.length);\n final_array.set(array1);\n final_array.set(array2, array1.length);\n return final_array;\n}\nfunction sendPacket(websocket, id, raw_data) {\n const packet_u8 = concatU8Array(concatU8Array(_endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].u64ToByteArrayBigEndian(id), _endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].u64ToByteArrayBigEndian(BigInt(raw_data.length))), raw_data);\n const packet_buffer = packet_u8.buffer;\n console.log('Sending packet');\n websocket.send(packet_buffer);\n}\nfunction sendHello(websocket, rom_array, savestate_array) {\n console.log('Sending hello.');\n const length_rom = BigInt(rom_array.length);\n const length_savestate = BigInt(savestate_array.length);\n const raw_data = concatU8Array(concatU8Array(concatU8Array(_endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].u64ToByteArrayBigEndian(length_rom), rom_array), _endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].u64ToByteArrayBigEndian(length_savestate)), savestate_array);\n sendPacket(websocket, _constants__WEBPACK_IMPORTED_MODULE_5__.PACKET_ID_HELLO, raw_data);\n}\nfunction onWebSocketPacket(event, canvas, ctx, printingFrame, setPrintingFrame) {\n const buffer = event.data;\n let packet_u8 = new Uint8Array(buffer);\n const id = _endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].byteArrayToU64BigEndian(packet_u8.slice(0, 8));\n packet_u8 = packet_u8.slice(8, packet_u8.length);\n const size = _endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].byteArrayToU64BigEndian(packet_u8.slice(0, 8));\n const raw_data = packet_u8.slice(8, packet_u8.length);\n packet_u8 = null;\n switch (id) {\n case _constants__WEBPACK_IMPORTED_MODULE_5__.PACKET_ID_SEND_FRAME:\n handleSendFrame(raw_data, canvas, ctx, printingFrame, setPrintingFrame);\n break;\n default:\n console.log(`Received unknown packet ${id}`);\n }\n}\nfunction handleSendFrame(raw_data, canvas, ctx, printingFrame, setPrintingFrame) {\n if (printingFrame) {\n return;\n }\n setPrintingFrame(c => true);\n let data = raw_data;\n const stride = _endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].byteArrayToU32BigEndian(data.slice(0, 4));\n data = data.slice(4, data.length);\n const output_buffer_size = _endian__WEBPACK_IMPORTED_MODULE_4__[\"default\"].byteArrayToU64BigEndian(data.slice(0, 8));\n data = data.slice(8, data.length);\n const img_data = ctx.createImageData(_constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH, _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT);\n const img_data_u8 = new Uint8Array(img_data.data.buffer);\n for (let i = 0; i < data.length; i++) {\n if (i % 4 == 3) {\n img_data_u8[i] = 255;\n continue;\n }\n img_data_u8[i] = data[i];\n }\n data = null;\n createImageBitmap(img_data).then((bitmap) => drawBitmap(bitmap, canvas, ctx, printingFrame));\n}\nfunction drawBitmap(bitmap, canvas, ctx, printingFrame) {\n ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);\n printingFrame = false;\n}\nfunction Page() {\n const screenDimensions = useScreenDimensions();\n const emulatorDimensions = calculateSizeEmulator(screenDimensions);\n const canvasRef = react__WEBPACK_IMPORTED_MODULE_0__.useRef(null);\n function resizeCanvas() {\n const canvas = canvasRef.current;\n if (canvas == null) {\n return;\n }\n if (emulatorDimensions.width === undefined || emulatorDimensions.height === undefined) {\n return;\n }\n canvas.width = emulatorDimensions.width;\n canvas.height = emulatorDimensions.height;\n const ctx = canvas.getContext('2d');\n if (ctx == null) {\n return;\n }\n fillBlack(canvas, ctx);\n }\n ;\n const [hiddenFormSelectFiles, setHiddenFormSelectFiles] = react__WEBPACK_IMPORTED_MODULE_0__.useState(false);\n const [printingFrame, setPrintingFrame] = react__WEBPACK_IMPORTED_MODULE_0__.useState(false);\n react__WEBPACK_IMPORTED_MODULE_0__.useEffect(resizeCanvas, [emulatorDimensions]);\n const refInputRom = react__WEBPACK_IMPORTED_MODULE_0__.useRef(null);\n const refInputSaveState = react__WEBPACK_IMPORTED_MODULE_0__.useRef(null);\n const onStartEmulation = (e) => {\n handleClickStartEmulationButton({\n e: e,\n setHiddenFormSelectFiles: setHiddenFormSelectFiles,\n inputRom: refInputRom.current,\n inputSaveState: refInputSaveState.current,\n canvas: canvasRef.current,\n setPrintingFrame: setPrintingFrame,\n printingFrame: printingFrame,\n });\n };\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_components_center_element__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"h2\", null, \"msGBA Online Emulator for GBA.\")),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_components_center_element__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_components_canvas_gba_emulator__WEBPACK_IMPORTED_MODULE_3__[\"default\"], { canvasRef: canvasRef })),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_components_center_element__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { hidden: hiddenFormSelectFiles },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_components_form_select_files__WEBPACK_IMPORTED_MODULE_2__[\"default\"], { refInputRom: refInputRom, refInputSaveState: refInputSaveState, onStartEmulation: onStartEmulation }))));\n}\nfunction getScreenDimensions() {\n return {\n width: document.body.clientWidth,\n height: document.body.clientHeight\n };\n}\nfunction useScreenDimensions() {\n const [screenDimensions, setScreenDimensions] = react__WEBPACK_IMPORTED_MODULE_0__.useState(getScreenDimensions());\n react__WEBPACK_IMPORTED_MODULE_0__.useEffect(() => {\n function onResize() {\n setScreenDimensions(getScreenDimensions());\n }\n window.addEventListener(\"resize\", onResize);\n return () => {\n window.removeEventListener(\"resize\", onResize);\n };\n }, []);\n return screenDimensions;\n}\nfunction fillBlack(canvas, ctx) {\n ctx.beginPath();\n ctx.rect(0, 0, canvas.width, canvas.height);\n ctx.fillStyle = 'black';\n ctx.fill();\n}\n;\nfunction calculateSizeEmulator(screenDimensions) {\n if (screenDimensions.width === undefined || screenDimensions.height === undefined) {\n console.error(screenDimensions, 'screenDimensions has undefined fields');\n return {};\n }\n const width = screenDimensions.width;\n const height = screenDimensions.height * 0.75;\n const emulatorDimensions = {};\n if (width < _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH || height < _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT) {\n return {\n width: _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH,\n height: _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT,\n };\n }\n const ratioWidth = Math.floor(width / _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH);\n const ratioHeight = Math.floor(height / _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT);\n if (ratioWidth < ratioHeight) {\n emulatorDimensions.width = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH * ratioWidth;\n emulatorDimensions.height = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT * ratioWidth;\n }\n else {\n emulatorDimensions.height = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_HEIGHT * ratioHeight;\n emulatorDimensions.width = _constants__WEBPACK_IMPORTED_MODULE_5__.MIN_WIDTH * ratioHeight;\n }\n return emulatorDimensions;\n}\n\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/components/page.tsx?");
+
+/***/ }),
+
+/***/ "./js-src/constants.ts":
+/*!*****************************!*\
+ !*** ./js-src/constants.ts ***!
+ \*****************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"MIN_HEIGHT\": () => (/* binding */ MIN_HEIGHT),\n/* harmony export */ \"MIN_WIDTH\": () => (/* binding */ MIN_WIDTH),\n/* harmony export */ \"PACKET_ID_HELLO\": () => (/* binding */ PACKET_ID_HELLO),\n/* harmony export */ \"PACKET_ID_SEND_FRAME\": () => (/* binding */ PACKET_ID_SEND_FRAME),\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nconst MIN_WIDTH = 240;\nconst MIN_HEIGHT = 160;\nconst PACKET_ID_HELLO = 0n;\nconst PACKET_ID_SEND_FRAME = 1n;\nclass Constants {\n}\nConstants.MIN_WIDTH = MIN_WIDTH;\nConstants.MIN_HEIGHT = MIN_HEIGHT;\nConstants.PACKET_ID_HELLO = PACKET_ID_HELLO;\nConstants.PACKET_ID_SEND_FRAME = PACKET_ID_SEND_FRAME;\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Constants);\n;\nConstants.MIN_WIDTH = MIN_WIDTH;\nConstants.MIN_HEIGHT = MIN_HEIGHT;\nConstants.PACKET_ID_HELLO = PACKET_ID_HELLO;\nConstants.PACKET_ID_SEND_FRAME = PACKET_ID_SEND_FRAME;\n\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/constants.ts?");
+
+/***/ }),
+
+/***/ "./js-src/endian.ts":
+/*!**************************!*\
+ !*** ./js-src/endian.ts ***!
+ \**************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Endian)\n/* harmony export */ });\n\nlet littleEndian = true;\n(() => {\n let buf = new ArrayBuffer(4);\n let buf8 = new Uint8ClampedArray(buf);\n let data = new Uint32Array(buf);\n data[0] = 0xdeadbeef;\n if (buf8[0] === 0xde) {\n littleEndian = false;\n }\n})();\nclass Endian {\n static isLittleEndian() {\n return littleEndian;\n }\n static byteArrayToU32BigEndian(inputArray) {\n if (Endian.isLittleEndian()) {\n inputArray = inputArray.reverse();\n }\n const buffer = inputArray.buffer;\n const outputU32Array = new Uint32Array(buffer);\n return outputU32Array[0];\n }\n static byteArrayToU64BigEndian(inputArray) {\n if (Endian.isLittleEndian()) {\n inputArray = inputArray.reverse();\n }\n const buffer = inputArray.buffer;\n const outputU64Array = new BigUint64Array(buffer);\n return outputU64Array[0];\n }\n static u64ToByteArrayBigEndian(inputNumber) {\n const buffer = new ArrayBuffer(8);\n const buffer8 = new Uint8Array(buffer);\n const buffer64 = new BigUint64Array(buffer);\n buffer64[0] = inputNumber;\n if (Endian.isLittleEndian()) {\n buffer8.reverse();\n }\n return buffer8;\n }\n}\n\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/endian.ts?");
+
+/***/ }),
+
+/***/ "./js-src/index.tsx":
+/*!**************************!*\
+ !*** ./js-src/index.tsx ***!
+ \**************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_dom_client__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom/client */ \"./node_modules/react-dom/client.js\");\n/* harmony import */ var _components_page__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../components/page */ \"./js-src/components/page.tsx\");\n\n\n\n\nconst body = document.querySelector('body');\nif (body != null) {\n fillBody(body);\n}\nfunction fillBody(body) {\n const app = document.createElement('div');\n body.appendChild(app);\n const root = react_dom_client__WEBPACK_IMPORTED_MODULE_1__.createRoot(app);\n root.render(react__WEBPACK_IMPORTED_MODULE_0__.createElement(_components_page__WEBPACK_IMPORTED_MODULE_2__[\"default\"], null));\n}\n\n\n//# sourceURL=webpack://MSGBA-Web/./js-src/index.tsx?");
+
/***/ })
/******/ });
@@ -234,7 +234,7 @@ eval("\n\nif (false) {} else {\n module.exports = __webpack_require__(/*! ./cjs
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
-/******/ var __webpack_exports__ = __webpack_require__("./js-src/index.jsx");
+/******/ var __webpack_exports__ = __webpack_require__("./js-src/index.tsx");
/******/
/******/ })()
;
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..1cb9716
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "outDir": "./public/js/",
+ "noImplicitAny": true,
+ "module": "es2020",
+ "target": "es2020",
+ "jsx": "react",
+ "allowJs": true,
+ "moduleResolution": "node",
+ "strictNullChecks": true,
+ "baseUrl": ".",
+ "paths": {
+ "*": ["js-src/*"]
+ }
+ },
+ "include": ["js-src/*.ts", "js-src/*/*.ts" ]
+}
diff --git a/webpack.config.js b/webpack.config.js
index 077e310..94a54bb 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,23 +1,25 @@
const path = require('path');
module.exports = {
- entry: './js-src/index.jsx',
+ entry: './js-src/index.tsx',
mode: 'development',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public/js/'),
},
resolve: {
- extensions: [
- '.js',
- '.jsx',
- ],
+ extensions: [ '.js', '.jsx','.ts', '.tsx' ],
roots: [
path.resolve(__dirname, 'js-src/')
]
},
module: {
rules: [
+ {
+ test: /\.tsx?$/,
+ use: 'ts-loader',
+ exclude: /node_modules/,
+ },
{
test: /\.jpe?g|png$/,
exclude: /node_modules/,