Roughly got save files working.

This commit is contained in:
2025-03-17 19:01:00 -05:00
parent 61d1859437
commit 3b6850f20a
4 changed files with 93 additions and 17 deletions

View File

@ -5,6 +5,7 @@
<title>Emulator Window</title> <title>Emulator Window</title>
<link href="/emulator.css" rel="stylesheet" type="text/css" /> <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> <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>
<script src="/jszip.min.js" type="text/javascript"></script>
</head> </head>
<body> <body>

View File

@ -3,6 +3,11 @@ class HomeplayEmulator {
scriptElement = null; scriptElement = null;
initInterval = null; initInterval = null;
oldGetRetroArchCfg = null; oldGetRetroArchCfg = null;
gameStarted = false;
saveUpdates = false;
saveInterval = null;
saveLock = false;
constructor() { constructor() {
// Set up the EJS stuff // Set up the EJS stuff
@ -50,6 +55,23 @@ class HomeplayEmulator {
this.initInterval = setInterval(() => { this.initInterval = setInterval(() => {
this.send({ message: 'iframe_loaded' }); this.send({ message: 'iframe_loaded' });
}, 1000); }, 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 => { send = data => {
@ -86,6 +108,11 @@ class HomeplayEmulator {
onEJSGameStart = () => { onEJSGameStart = () => {
console.log('EJS Game Start'); console.log('EJS Game Start');
setTimeout(() => {
window.EJS_emulator.gameManager.saveSaveFiles();
this.gameStarted = true;
}, 1000)
} }
onEJSGameEnd = () => { onEJSGameEnd = () => {
@ -127,7 +154,7 @@ class HomeplayEmulator {
} }
} }
md5File = async path => { getFileMD5 = async path => {
const buffer = window.EJS_emulator.gameManager.FS.readFile(path); const buffer = window.EJS_emulator.gameManager.FS.readFile(path);
const hash = await crypto.subtle.digest('SHA-256', buffer); const hash = await crypto.subtle.digest('SHA-256', buffer);
const hashArray = Array.from(new Uint8Array(hash)); const hashArray = Array.from(new Uint8Array(hash));
@ -155,21 +182,47 @@ class HomeplayEmulator {
// Return back as RA config string // Return back as RA config string
return Object.entries(raConfig).map(([k, v]) => `${k} = ${v}`).join('\n'); 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" })
})
}
} }
// 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(); window.homeplay = new HomeplayEmulator();

13
public/jszip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -27,7 +27,8 @@ type ReceiveEmulatorMessage = (
{ message: 'start' } | { message: 'start' } |
{ message: 'ready' } | { message: 'ready' } |
{ message: 'save_state' } | { message: 'save_state' } |
{ message: 'load_state' } { message: 'load_state' } |
{ message: 'save', data:string }
); );
export type EmulatorProps = { export type EmulatorProps = {
@ -68,6 +69,14 @@ export const Emulator:React.FC<EmulatorProps> = props => {
}); });
break; break;
case 'save':
// Download save data
const a = document.createElement('a');
a.href = `data:application/zip;base64,${msg.data}`;
a.download = `${props.gameId}.zip`;
a.click();
break;
case 'start': case 'start':
case 'ready': case 'ready':
case 'load_state': case 'load_state':