228 lines
5.8 KiB
JavaScript
228 lines
5.8 KiB
JavaScript
// Init EJS stuff
|
|
class HomeplayEmulator {
|
|
scriptElement = null;
|
|
initInterval = null;
|
|
oldGetRetroArchCfg = null;
|
|
gameStarted = false;
|
|
|
|
saveUpdates = false;
|
|
saveInterval = null;
|
|
saveLock = false;
|
|
|
|
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);
|
|
|
|
// Check for save updates
|
|
this.saveInterval = setInterval(async () => {
|
|
if(this.saveLock) return;
|
|
this.saveLock = true;
|
|
try {
|
|
this.saveUpdates = await this.hasSaveUpdates();
|
|
if(this.saveUpdates) {
|
|
console.log('Saving');
|
|
await this.save();
|
|
}
|
|
} catch(e) {
|
|
console.error('Error checking for save updates', e);
|
|
} finally {
|
|
this.saveLock = false;
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
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');
|
|
|
|
setTimeout(() => {
|
|
window.EJS_emulator.gameManager.saveSaveFiles();
|
|
this.gameStarted = true;
|
|
}, 1000)
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
getFileMD5 = 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');
|
|
}
|
|
|
|
getSaveFiles = () => {
|
|
return [ 'rom.srm' ];
|
|
}
|
|
|
|
hasSaveUpdates = async () => {
|
|
if(!window.EJS_emulator) return false;
|
|
if(!window.EJS_emulator.gameManager) return false;
|
|
if(!this.gameStarted) return false;
|
|
|
|
const saveFiles = this.getSaveFiles();
|
|
const hashes = await Promise.all(saveFiles.map(f => this.getFileMD5(`/homeplay/saves/${f}`)));
|
|
|
|
window.EJS_emulator.gameManager.saveSaveFiles();
|
|
|
|
const newHashes = await Promise.all(saveFiles.map(f => this.getFileMD5(`/homeplay/saves/${f}`)));
|
|
const changedFiles = saveFiles.filter((f, i) => hashes[i] !== newHashes[i]);
|
|
|
|
return !!changedFiles.length;
|
|
}
|
|
|
|
save = async () => {
|
|
// Check if updates
|
|
if(!this.hasSaveUpdates) return;
|
|
|
|
// Get save file contents
|
|
const saveFiles = this.getSaveFiles();
|
|
|
|
// Now we need to create a zip archive. EJS provides a nice ZIP library.
|
|
const zip = new JSZip();
|
|
|
|
await Promise.all(saveFiles.map(async f => {
|
|
const data = window.EJS_emulator.gameManager.FS.readFile(`/homeplay/saves/${f}`);
|
|
zip.file(f, data);
|
|
}));
|
|
|
|
this.send({
|
|
message: 'save',
|
|
data: await zip.generateAsync({ type:"base64" })
|
|
})
|
|
}
|
|
}
|
|
|
|
window.homeplay = new HomeplayEmulator(); |