Full blown example.

This commit is contained in:
CSG-Dominic
2025-03-11 13:58:20 -05:00
parent ba27084fa1
commit 85f643ef10
5 changed files with 96 additions and 42 deletions

2
.gitignore vendored
View File

@ -41,3 +41,5 @@ yarn-error.log*
next-env.d.ts next-env.d.ts
*.gbc *.gbc
/data

View File

@ -66,7 +66,7 @@
}; };
const emulatorInit = data => { const emulatorInit = data => {
EJS_gameUrl = data.game; EJS_gameUrl = `/api/v1/rom?id=${data.gameId}`;
EJS_core = data.core; EJS_core = data.core;
EJS_gameName = data.gameName; EJS_gameName = data.gameName;
@ -88,34 +88,35 @@
} }
const { message } = data; const { message } = data;
switch(message) { switch(message) {
case 'init': case 'init':
emulatorInit(data); emulatorInit(data);
break; break;
case 'volume':
EJS_emulator.setVolume(data.volume);
break;
default: default:
console.error('Unknown message', message); console.error('Unknown message', message);
} }
}; };
EJS_ready = (...args) => { EJS_ready = () => {
send({ message: 'ready', data: args }); send({ message: 'ready' });
} }
EJS_onSaveState = (...args) => { EJS_onSaveState = () => {
send({ message: 'save', data: args }); send({ message: 'save' });
} }
EJS_onLoadState = (...args) => { EJS_onLoadState = () => {
send({ message: 'load', data: args }); send({ message: 'load' });
} }
EJS_onGameStart = (...args) => { EJS_onGameStart = () => {
send({ message: 'start', data: args }); send({ message: 'start' });
} }
</script> </script>
<!-- <script src="https://cdn.emulatorjs.org/stable/data/loader.js"></script> -->
</body> </body>
</html> </html>

View File

@ -9,35 +9,40 @@ type EmulatorCore = (
'gambatte' | 'mgba' 'gambatte' | 'mgba'
); );
const EMULATOR_SYSTEM_CORES:{ [key in EmulatorSystem]:EmulatorCore } = { type SendEmulatorMessageInit = {
'gb': 'gambatte', message:'init';
'gbc': 'gambatte', core:EmulatorCore;
'gba': 'mgba' system:EmulatorSystem;
gameId:string;
gameName:string;
} }
type SendEmulatorMessageVolume = {
message:'volume';
volume:number;
};
type SendEmulatorMessage = (
SendEmulatorMessageInit |
SendEmulatorMessageVolume
);
type ReceiveEmulatorMessage = (
{ message: 'start' } |
{ message: 'ready' } |
{ message: 'save' } |
{ message: 'load' }
);
export type EmulatorProps = { export type EmulatorProps = {
system:EmulatorSystem; system:EmulatorSystem;
}; };
type SendEmulatorMessageInit = { const EMULATOR_SYSTEM_CORES:{ [key in EmulatorSystem]:EmulatorCore } = {
message:'init'; 'gb': 'gambatte',
core:EmulatorCore; 'gbc': 'gambatte',
system:EmulatorSystem; 'gba': 'mgba'
game:string; };
gameName:string;
}
type SendEmulatorMessage = (
SendEmulatorMessageInit
);
type ReceiveEmulatorOnGameStart = {
message:'start'
}
type ReceiveEmulatorMessage = (
ReceiveEmulatorOnGameStart
);
export const Emulator:React.FC<EmulatorProps> = props => { export const Emulator:React.FC<EmulatorProps> = props => {
const iframeRef = useRef<HTMLIFrameElement>(null); const iframeRef = useRef<HTMLIFrameElement>(null);
@ -55,7 +60,7 @@ export const Emulator:React.FC<EmulatorProps> = props => {
message: 'init', message: 'init',
core: EMULATOR_SYSTEM_CORES[props.system], core: EMULATOR_SYSTEM_CORES[props.system],
system: props.system, system: props.system,
game: '/test.gbc', gameId: '0',
gameName: 'Pokemon - Crystal Version' gameName: 'Pokemon - Crystal Version'
}); });
@ -66,12 +71,12 @@ export const Emulator:React.FC<EmulatorProps> = props => {
} }
const msg = e.data as ReceiveEmulatorMessage; const msg = e.data as ReceiveEmulatorMessage;
console.log('recv', msg);
switch(msg.message) { switch(msg.message) {
case 'start': case 'start':
console.log('Game started!'); case 'ready':
break case 'load':
case 'save':
break;
default: default:
console.error('Invalid message received:', msg); console.error('Invalid message received:', msg);
break break

44
src/pages/api/v1/rom.ts Normal file
View File

@ -0,0 +1,44 @@
import { NextApiHandler } from "next";
import * as fs from 'fs';
import * as path from 'path';
const PATH_ROMS = path.resolve('.', 'data', 'games');
const handler:NextApiHandler = async (req, res) => {
if (req.method !== 'GET') {
res.setHeader('Allow', ['GET']);
res.status(405).end(`Method Not Allowed`);
return;
}
// ID query param
if(!req.query.id || typeof req.query.id !== 'string') {
res.status(404).end(`Not Found`);
return;
}
const { id } = req.query;
if(!id) {
res.status(404).end(`Not Found`);
return;
}
// Does rom exist?
const pathRom = path.resolve(PATH_ROMS, `${id}.bin`);
if(!fs.existsSync(pathRom)) {
res.status(404).end(`Not Found`);
return;
}
// Read rom
try {
const romStream = fs.createReadStream(pathRom);
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', `attachment; filename="${id}.bin"`);
romStream.pipe(res);
} catch (error) {
res.status(500).end(`Internal Server Error`);
}
};
export default handler;

View File

@ -26,7 +26,9 @@ export const Page:React.FC<PageProps> = ({ id }) => {
<h1>Playing Game ID: {id}</h1> <h1>Playing Game ID: {id}</h1>
<div className={styles.play__emulator}> <div className={styles.play__emulator}>
<Emulator system='gbc' /> <Emulator
system='gbc'
/>
</div> </div>
</div> </div>
); );