Cleaned up the Emulator JS

This commit is contained in:
2025-03-17 18:22:08 -05:00
parent 4be5dadb24
commit 61d1859437
4 changed files with 197 additions and 174 deletions

19
public/emulator.css Normal file
View File

@ -0,0 +1,19 @@
body,html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background: transparent;
}
.game {
width: 100%;
height: 100%;
max-width: 100%;
display: block;
margin: 0 auto;
}
.ejs_menu_bar {
display: none !important;
}

View File

@ -2,184 +2,14 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test</title>
<style type="text/css">
body,html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background: transparent;
}
.game {
width: 100%;
height: 100%;
max-width: 100%;
display: block;
margin: 0 auto;
}
.ejs_menu_bar {
display: none !important;
}
</style>
<title>Emulator Window</title>
<link href="/emulator.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js" integrity="sha512-a+SUDuwNzXDvz4XrIcXHuCf089/iJAoN4lmrXJg18XnduKK6YlDHNRalv4yd1N40OKI80tFidF+rqTFKGPoWFQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<div class="game" id="game">
</div>
<script type="text/javascript" language="javascript">
let scriptElement = null;
let initInterval = setInterval(() => {
send({ message: 'iframe_loaded' });
}, 500);
EJS_player = "#game";
EJS_pathtodata = "https://cdn.emulatorjs.org/stable/data/";
EJS_language = "en-US";
EJS_volume = 0;
EJS_startOnLoaded = true;
EJS_backgroundColor = '#000';
EJS_Buttons = {
playPause: false,
restart: false,
mute: false,
settings: false,
fullscreen: false,
saveState: false,
loadState: false,
screenRecord: false,
gamepad: false,
cheat: false,
volume: false,
saveSavFiles: false,
loadSavFiles: false,
quickSave: false,
quickLoad: false,
screenshot: false,
cacheManager: false,
exitEmulation: false
}
EJS_defaultOptions = {
};
const emulatorInit = data => {
EJS_gameUrl = `/api/v1/rom?id=${data.gameId}`;
EJS_core = data.core;
EJS_gameName = data.gameName;
EJS_externalFiles = {
'/homeplay/saves/': `/api/v1/save?id=${data.gameId}`
}
scriptElement = document.createElement('script');
scriptElement.src = 'https://cdn.emulatorjs.org/stable/data/loader.js';
document.body.appendChild(scriptElement);
clearInterval(initInterval);
}
const md5File = async path => {
const buffer = EJS_emulator.gameManager.FS.readFile(path);
const hash = await crypto.subtle.digest('SHA-256', buffer);
const hashArray = Array.from(new Uint8Array(hash));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
const emulatorSave = async () => {
// First, we get the currently saved MD5 hash of all files in the save
// directory
const contents = EJS_emulator.gameManager.FS.readdir('/homeplay/saves').filter(f => !f.startsWith('.'));
const hashes = await Promise.all(contents.map(f => md5File(`/homeplay/saves/${f}`)));
EJS_emulator.gameManager.saveSaveFiles();
const newHashes = await Promise.all(contents.map(f => md5File(`/homeplay/saves/${f}`)));
const changedFiles = contents.filter((f, i) => hashes[i] !== newHashes[i]);
console.log('Changed files', changedFiles);
send({
message: ''
})
}
const send = data => {
window.parent.postMessage(data, '*');
}
window.onmessage = (e) => {
const { data } = e;
if(!data) {
console.error('No data');
return;
}
const { message } = data;
switch(message) {
case 'init':
emulatorInit(data);
break;
case 'volume':
EJS_emulator.setVolume(data.volume);
break;
case 'save':
emulatorSave();
break;
default:
console.error('Unknown message', message);
}
};
EJS_ready = () => {
// Modify the EJS stuff
const oldGetRetroArchCfg = window.EJS_GameManager.prototype.getRetroArchCfg;
const oldMountFileSystems = window.EJS_GameManager.prototype.mountFileSystems;
EJS_GameManager.prototype.getRetroArchCfg = function() {
const ejsConfig = oldGetRetroArchCfg.call(this);
// Parse back into key value pair
const raConfig = ejsConfig.split('\n').reduce((acc, line) => {
if(!line.length) return acc;
const [key, value] = line.split('=');
acc[key.trim()] = value.trim();
return acc;
}, {});
// Add new options
raConfig['savefiles_in_content_dir'] = false;
raConfig['sort_savefiles_by_content_enable'] = false;
raConfig['sort_savefiles_enable'] = false;
raConfig['savefile_directory'] = '/homeplay/saves';
// Return back as RA config string
return Object.entries(raConfig).map(([k, v]) => `${k} = ${v}`).join('\n');
}
send({ message: 'ready' });
}
EJS_onSaveState = () => {
send({ message: 'save_state' });
}
EJS_onLoadState = () => {
send({ message: 'load_state' });
}
EJS_onGameStart = () => {
send({ message: 'start' });
}
</script>
<script src="/emulator.js" type="text/javascript"></script>
</body>
</html>

175
public/emulator.js Normal file
View File

@ -0,0 +1,175 @@
// Init EJS stuff
class HomeplayEmulator {
scriptElement = null;
initInterval = null;
oldGetRetroArchCfg = null;
constructor() {
// Set up the EJS stuff
window.EJS_player = "#game";
window.EJS_pathtodata = "https://cdn.emulatorjs.org/stable/data/";
window.EJS_language = "en-US";
window.EJS_volume = 0;
window.EJS_startOnLoaded = true;
window.EJS_backgroundColor = '#000';
window.EJS_Buttons = {
playPause: false,
restart: false,
mute: false,
settings: false,
fullscreen: false,
saveState: false,
loadState: false,
screenRecord: false,
gamepad: false,
cheat: false,
volume: false,
saveSavFiles: false,
loadSavFiles: false,
quickSave: false,
quickLoad: false,
screenshot: false,
cacheManager: false,
exitEmulation: false
}
window.EJS_defaultOptions = {
};
// Bind events
window.EJS_ready = this.onEJSReady;
window.EJS_onGameStart = this.onEJSGameStart;
window.EJS_onGameEnd = this.onEJSGameEnd;
window.EJS_onSaveState = this.onEJSSaveState;
window.EJS_onLoadState = this.onEJSLoadState;
window.onmessage = this.onMessage;
// Begin the init interval
this.initInterval = setInterval(() => {
this.send({ message: 'iframe_loaded' });
}, 1000);
}
send = data => {
window.parent.postMessage(data, '*');
}
init = data => {
window.EJS_gameUrl = `/api/v1/rom?id=${data.gameId}`;
window.EJS_core = data.core;
window.EJS_gameName = data.gameName;
window.EJS_externalFiles = {
'/homeplay/saves/': `/api/v1/save?id=${data.gameId}`
}
this.scriptElement = document.createElement('script');
this.scriptElement.src = 'https://cdn.emulatorjs.org/stable/data/loader.js';
document.body.appendChild(this.scriptElement);
clearInterval(this.initInterval);
}
onEJSReady = () => {
// Modify the EJS stuff
this.oldGetRetroArchCfg = window.EJS_GameManager.prototype.getRetroArchCfg;
const raConfig = (...a) => this.getRetroArchCfg(...a);
window.EJS_GameManager.prototype.getRetroArchCfg = function() {
return raConfig(this);
}
this.send({ message: 'ready' });
console.log('EJS Ready');
}
onEJSGameStart = () => {
console.log('EJS Game Start');
}
onEJSGameEnd = () => {
console.log('EJS Game End');
}
onEJSSaveState = () => {
console.log('EJS Save State');
}
onEJSLoadState = () => {
console.log('EJS Load State');;
}
onMessage = e => {
const { data } = e;
if(!data) {
console.error('No data');
return;
}
const { message } = data;
switch(message) {
case 'init':
this.init(data);
break;
case 'volume':
window.EJS_volume = data.volume;
break;
case 'save':
window.EJS_emulator.saveSaveFiles();
break;
default:
console.error('Unknown message', message);
}
}
md5File = async path => {
const buffer = window.EJS_emulator.gameManager.FS.readFile(path);
const hash = await crypto.subtle.digest('SHA-256', buffer);
const hashArray = Array.from(new Uint8Array(hash));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
getRetroArchCfg = (gameManager) => {
const ejsConfig = this.oldGetRetroArchCfg.call(gameManager);
// Parse back into key value pair
const raConfig = ejsConfig.split('\n').reduce((acc, line) => {
if(!line.length) return acc;
const [key, value] = line.split('=');
acc[key.trim()] = value.trim();
return acc;
}, {});
// Add new options
raConfig['savefiles_in_content_dir'] = false;
raConfig['sort_savefiles_by_content_enable'] = false;
raConfig['sort_savefiles_enable'] = false;
raConfig['savefile_directory'] = '/homeplay/saves';
// Return back as RA config string
return Object.entries(raConfig).map(([k, v]) => `${k} = ${v}`).join('\n');
}
}
// const emulatorSave = async () => {
// // First, we get the currently saved MD5 hash of all files in the save
// // directory
// const contents = EJS_emulator.gameManager.FS.readdir('/homeplay/saves').filter(f => !f.startsWith('.'));
// const hashes = await Promise.all(contents.map(f => md5File(`/homeplay/saves/${f}`)));
// EJS_emulator.gameManager.saveSaveFiles();
// const newHashes = await Promise.all(contents.map(f => md5File(`/homeplay/saves/${f}`)));
// const changedFiles = contents.filter((f, i) => hashes[i] !== newHashes[i]);
// console.log('Changed files', changedFiles);
// send({
// message: ''
// })
// }
window.homeplay = new HomeplayEmulator();

View File

@ -1,4 +1,3 @@
import { LanguageProvider } from '@/providers/LanguageProvider';
import { Html, Head, Main, NextScript, DocumentProps } from 'next/document';
const RootDocument:React.FC<DocumentProps> = props => {