diff --git a/public/emulator.css b/public/emulator.css new file mode 100644 index 0000000..bf67f3f --- /dev/null +++ b/public/emulator.css @@ -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; +} \ No newline at end of file diff --git a/public/emulator.html b/public/emulator.html index 221b75e..0f1ff00 100644 --- a/public/emulator.html +++ b/public/emulator.html @@ -2,184 +2,14 @@ - Test - - + Emulator Window +
- - + \ No newline at end of file diff --git a/public/emulator.js b/public/emulator.js new file mode 100644 index 0000000..12338a5 --- /dev/null +++ b/public/emulator.js @@ -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(); \ No newline at end of file diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 795e129..6518309 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -1,4 +1,3 @@ -import { LanguageProvider } from '@/providers/LanguageProvider'; import { Html, Head, Main, NextScript, DocumentProps } from 'next/document'; const RootDocument:React.FC = props => {