import * as React from 'react'; import CenterElement from '@msgba/components/center-element' import CanvasGBAEmulator from '@msgba/components/canvas-gba-emulator' import OverlayControls from '@msgba/components/overlay-controls' import OverlayMenu from '@msgba/components/overlay-menu' import OverlaySelectFiles from '@msgba/components/overlay-select-files' import { sendHello, handleSendFrame } from '@msgba/packet' import Endian from '@msgba/endian' import { MIN_WIDTH_GBA, MIN_HEIGHT_GBA, MIN_WIDTH_GBC, MIN_HEIGHT_GBC, PACKET_ID_SEND_FRAME } from '@msgba/constants' export interface handleClickStartEmulationButtonObjectArgs { e: React.MouseEvent inputRom: HTMLInputElement | null inputSaveState: HTMLInputElement | null canvas: React.RefObject setEmulationStarted: (c: boolean) => void setHiddenMenu: (c: boolean) => void setHiddenFormSelectFiles: (c: boolean) => void setWebSocket: (c: WebSocket | null) => void isGBC: boolean setIsGBC: (c: boolean) => void controlsRef: React.RefObject } function handleClickStartEmulationButton ({ e, inputRom, inputSaveState, canvas, setEmulationStarted, setHiddenMenu, setHiddenFormSelectFiles, setWebSocket, isGBC, setIsGBC, controlsRef }: handleClickStartEmulationButtonObjectArgs): void { e.preventDefault() if (canvas.current == null) { alert('Canvas does not exists?') return } const ctx = canvas.current.getContext('2d') 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 } if (inputSaveState.files.length === 0) { alert('There is no savestate still') return } const romFile = inputRom.files[0] const savestateFile = inputSaveState.files[0] romFile.arrayBuffer().then((romBuffer) => { savestateFile.arrayBuffer().then((savestateBuffer) => { const romArray = new Uint8Array(romBuffer) const savestateArray = new Uint8Array(savestateBuffer) const webSocket = new WebSocket(`ws://${window.location.host}/ws`) setWebSocket(webSocket) webSocket.binaryType = 'arraybuffer' webSocket.onclose = (message) => { setEmulationStarted(false) console.log('Closing websocket.') setWebSocket(null) } webSocket.onopen = () => { console.log('Opened websocket.') setEmulationStarted(true) sendHello(webSocket, romArray, savestateArray) setHiddenMenu(true) setHiddenFormSelectFiles(true) } webSocket.addEventListener('message', (event) => { onWebSocketPacket(event, canvas.current, ctx, isGBC, setIsGBC) }) }).catch((c: string) => { console.log('Unable to convert file to array_buffer') }) }).catch((c: string) => { console.log('Unable to convert file to array_buffer') }) } function onWebSocketPacket (event: MessageEvent, canvas: HTMLCanvasElement | null, ctx: CanvasRenderingContext2D, isGBC: boolean, setIsGBC: (c: boolean) => void): void { const buffer = event.data let packetU8: Uint8Array | null = new Uint8Array(buffer) const id = Endian.byteArrayToU64BigEndian(packetU8.slice(0, 8)) packetU8 = packetU8.slice(8, packetU8.length) const size = Endian.byteArrayToU64BigEndian(packetU8.slice(0, 8)) const rawData = packetU8.slice(8, Number(size)) packetU8 = null switch (id) { case PACKET_ID_SEND_FRAME: handleSendFrame(rawData, canvas, setIsGBC) break default: console.log(`Received unknown packet ${id}`) } } export default function Page (): JSX.Element { const [isGBC, setIsGBC] = React.useState(false) const screenDimensions = useScreenDimensions(isGBC) const emulatorDimensions = calculateSizeEmulator(screenDimensions, isGBC) const canvasRef = React.useRef(null) function resizeCanvas (): void { const canvas = canvasRef.current 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(true) React.useEffect(resizeCanvas, [emulatorDimensions]) const refInputRom = React.useRef(null) const refInputSaveState = React.useRef(null) const [emulationStarted, setEmulationStarted] = React.useState(false) const [hiddenMenu, setHiddenMenu] = React.useState(true) const [webSocket, setWebSocket] = React.useState(null) const controlsRef = React.useRef(null) React.useEffect(() => { console.log('Focusing the main screen') setTimeout(() => { if (!hiddenFormSelectFiles) { if (refInputRom.current != null) { refInputRom.current.focus() } return } if (!hiddenMenu) { if (firstMenuElement.current != null) { firstMenuElement.current.focus() } return } if (controlsRef.current != null && hiddenMenu) { controlsRef.current.focus() } }, 100) }, [hiddenMenu, hiddenFormSelectFiles]) const onStartEmulation = (e: React.MouseEvent): void => { handleClickStartEmulationButton({ e, setEmulationStarted, inputRom: refInputRom.current, inputSaveState: refInputSaveState.current, canvas: canvasRef, setHiddenMenu, setHiddenFormSelectFiles, setWebSocket, isGBC, setIsGBC, controlsRef }) } const firstMenuElement = React.useRef(null) const screenRef = React.useRef(null) const [isFullscreen, setIsFullscreen] = React.useState(false) return (
) } function getScreenDimensions (): EmulatorDimensions { return { width: document.body.clientWidth, height: document.body.clientHeight } } function useScreenDimensions (isGBC: boolean): EmulatorDimensions { const [screenDimensions, setScreenDimensions] = React.useState(getScreenDimensions()) React.useEffect(() => { function onResize (): void { setScreenDimensions(getScreenDimensions()) } window.addEventListener('resize', onResize) return () => { window.removeEventListener('resize', onResize) } }, [isGBC]) return screenDimensions } function fillBlack (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): void { ctx.beginPath() ctx.rect(0, 0, canvas.width, canvas.height) ctx.fillStyle = '#0E0E10' ctx.fill() } export interface EmulatorDimensions { width?: number height?: number }; function calculateSizeEmulator (screenDimensions: EmulatorDimensions, isGBC: boolean): EmulatorDimensions { if (screenDimensions.width === undefined || screenDimensions.height === undefined) { console.error(screenDimensions, 'screenDimensions has undefined fields') return {} } const width = screenDimensions.width const height = screenDimensions.height const emulatorDimensions: EmulatorDimensions = {} const minWidth = !isGBC ? MIN_WIDTH_GBA : MIN_WIDTH_GBC const minHeight = !isGBC ? MIN_HEIGHT_GBA : MIN_HEIGHT_GBC if (width < minWidth || height < minHeight) { return { width: minWidth, height: minHeight } } const ratioWidth = width / minWidth const ratioHeight = height / minHeight if (ratioWidth < ratioHeight) { emulatorDimensions.width = minWidth * ratioWidth emulatorDimensions.height = minHeight * ratioWidth } else { emulatorDimensions.height = minHeight * ratioHeight emulatorDimensions.width = minWidth * ratioHeight } return emulatorDimensions }