From 13a948b21a73acea9058f044ae50f31d57b67048 Mon Sep 17 00:00:00 2001 From: Sergiotarxz Date: Wed, 22 Mar 2023 13:53:16 +0100 Subject: [PATCH] Migrating the project to typescript. --- js-src/components/canvas-gba-emulator.jsx | 7 - js-src/components/canvas-gba-emulator.tsx | 11 ++ js-src/components/center-element.jsx | 13 -- js-src/components/center-element.tsx | 22 +++ ...select-files.jsx => form-select-files.tsx} | 12 +- js-src/components/{page.jsx => page.tsx} | 160 ++++++++++-------- js-src/{constants.js => constants.ts} | 5 + js-src/endian.js | 18 -- js-src/endian.ts | 40 +++++ js-src/index.jsx | 12 -- js-src/index.tsx | 18 ++ package.json | 4 + public/js/bundle.js | 142 ++++++++-------- tsconfig.json | 17 ++ webpack.config.js | 12 +- 15 files changed, 292 insertions(+), 201 deletions(-) delete mode 100644 js-src/components/canvas-gba-emulator.jsx create mode 100644 js-src/components/canvas-gba-emulator.tsx delete mode 100644 js-src/components/center-element.jsx create mode 100644 js-src/components/center-element.tsx rename js-src/components/{form-select-files.jsx => form-select-files.tsx} (63%) rename js-src/components/{page.jsx => page.tsx} (55%) rename js-src/{constants.js => constants.ts} (59%) delete mode 100644 js-src/endian.js create mode 100644 js-src/endian.ts delete mode 100644 js-src/index.jsx create mode 100644 js-src/index.tsx create mode 100644 tsconfig.json 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 ( - - ); -} 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/,