mirror of
https://github.com/EmulatorJS/EmulatorJS.git
synced 2024-09-20 02:46:23 +00:00
378 lines
16 KiB
JavaScript
378 lines
16 KiB
JavaScript
class EJS_GameManager {
|
|
constructor(Module, EJS) {
|
|
this.EJS = EJS;
|
|
this.Module = Module;
|
|
this.FS = this.Module.FS;
|
|
this.functions = {
|
|
restart: this.Module.cwrap('system_restart', '', []),
|
|
saveStateInfo: this.Module.cwrap('save_state_info', 'null', []),
|
|
loadState: this.Module.cwrap('load_state', 'number', ['string', 'number']),
|
|
screenshot: this.Module.cwrap('cmd_take_screenshot', '', []),
|
|
simulateInput: this.Module.cwrap('simulate_input', 'null', ['number', 'number', 'number']),
|
|
toggleMainLoop: this.Module.cwrap('toggleMainLoop', 'null', ['number']),
|
|
getCoreOptions: this.Module.cwrap('get_core_options', 'string', []),
|
|
setVariable: this.Module.cwrap('ejs_set_variable', 'null', ['string', 'string']),
|
|
setCheat: this.Module.cwrap('set_cheat', 'null', ['number', 'number', 'string']),
|
|
resetCheat: this.Module.cwrap('reset_cheat', 'null', []),
|
|
toggleShader: this.Module.cwrap('shader_enable', 'null', ['number']),
|
|
getDiskCount: this.Module.cwrap('get_disk_count', 'number', []),
|
|
getCurrentDisk: this.Module.cwrap('get_current_disk', 'number', []),
|
|
setCurrentDisk: this.Module.cwrap('set_current_disk', 'null', ['number']),
|
|
getSaveFilePath: this.Module.cwrap('save_file_path', 'string', []),
|
|
saveSaveFiles: this.Module.cwrap('cmd_savefiles', '', []),
|
|
supportsStates: this.Module.cwrap('supports_states', 'number', []),
|
|
loadSaveFiles: this.Module.cwrap('refresh_save_files', 'null', []),
|
|
toggleFastForward: this.Module.cwrap('toggle_fastforward', 'null', ['number']),
|
|
setFastForwardRatio: this.Module.cwrap('set_ff_ratio', 'null', ['number']),
|
|
toggleRewind: this.Module.cwrap('toggle_rewind', 'null', ['number']),
|
|
setRewindGranularity: this.Module.cwrap('set_rewind_granularity', 'null', ['number']),
|
|
toggleSlowMotion: this.Module.cwrap('toggle_slow_motion', 'null', ['number']),
|
|
setSlowMotionRatio: this.Module.cwrap('set_sm_ratio', 'null', ['number']),
|
|
getFrameNum: this.Module.cwrap('get_current_frame_count', 'number', ['']),
|
|
setVSync: this.Module.cwrap('set_vsync', 'null', ['number'])
|
|
}
|
|
this.writeFile("/home/web_user/retroarch/userdata/config/Beetle PSX HW/Beetle PSX HW.opt", 'beetle_psx_hw_renderer = "software"\n');
|
|
this.writeFile("/home/web_user/retroarch/userdata/config/MAME 2003 (0.78)/MAME 2003 (0.78).opt", 'mame2003_skip_disclaimer = "enabled"\nmame2003_skip_warnings = "enabled"\n');
|
|
|
|
this.mkdir("/data");
|
|
this.mkdir("/data/saves");
|
|
|
|
this.writeFile("/home/web_user/retroarch/userdata/retroarch.cfg", this.getRetroArchCfg());
|
|
|
|
this.FS.mount(IDBFS, {}, '/data/saves');
|
|
this.FS.syncfs(true, () => {});
|
|
|
|
this.initShaders();
|
|
|
|
this.EJS.addEventListener(window, "beforeunload", () => {
|
|
this.saveSaveFiles();
|
|
this.FS.syncfs(() => {});
|
|
})
|
|
}
|
|
loadExternalFiles() {
|
|
return new Promise(async (resolve, reject) => {
|
|
if (this.EJS.config.externalFiles && this.EJS.config.externalFiles.constructor.name === 'Object') {
|
|
for (const key in this.EJS.config.externalFiles) {
|
|
await new Promise(done => {
|
|
this.EJS.downloadFile(this.EJS.config.externalFiles[key], async (res) => {
|
|
if (res === -1) {
|
|
if (this.EJS.debug) console.warn("Failed to fetch file from '" + this.EJS.config.externalFiles[key] + "'. Make sure the file exists.");
|
|
return done();
|
|
}
|
|
let path = key;
|
|
if (key.trim().endsWith("/")) {
|
|
const invalidCharacters = /[#<$+%>!`&*'|{}/\\?"=@:^\r\n]/ig;
|
|
let name = this.EJS.config.externalFiles[key].split("/").pop().split("#")[0].split("?")[0].replace(invalidCharacters, "").trim();
|
|
if (!name) return done();
|
|
const files = await this.EJS.checkCompression(new Uint8Array(res.data), this.EJS.localization("Decompress Game Assets"));
|
|
if (files["!!notCompressedData"]) {
|
|
path += name;
|
|
} else {
|
|
for (const k in files) {
|
|
this.writeFile(path+k, files[k]);
|
|
}
|
|
return done();
|
|
}
|
|
}
|
|
try {
|
|
this.writeFile(path, res.data);
|
|
} catch(e) {
|
|
if (this.EJS.debug) console.warn("Failed to write file to '" + path + "'. Make sure there are no conflicting files.");
|
|
}
|
|
done();
|
|
}, null, true, {responseType: "arraybuffer", method: "GET"});
|
|
})
|
|
}
|
|
}
|
|
resolve();
|
|
});
|
|
}
|
|
writeFile(path, data) {
|
|
const parts = path.split("/");
|
|
let current = "/";
|
|
for (let i=0; i<parts.length-1; i++) {
|
|
if (!parts[i].trim()) continue;
|
|
current += parts[i] + "/";
|
|
this.mkdir(current);
|
|
}
|
|
this.FS.writeFile(path, data);
|
|
}
|
|
mkdir(path) {
|
|
try {
|
|
this.FS.mkdir(path);
|
|
} catch(e) {}
|
|
}
|
|
getRetroArchCfg() {
|
|
return "autosave_interval = 60\n" +
|
|
"screenshot_directory = \"/\"\n" +
|
|
"block_sram_overwrite = false\n" +
|
|
"video_gpu_screenshot = false\n" +
|
|
"audio_latency = 64\n" +
|
|
"video_top_portrait_viewport = true\n" +
|
|
"video_vsync = true\n" +
|
|
"video_smooth = false\n" +
|
|
"fastforward_ratio = 3.0\n" +
|
|
"slowmotion_ratio = 3.0\n" +
|
|
(this.EJS.rewindEnabled ? "rewind_enable = true\n" : "") +
|
|
(this.EJS.rewindEnabled ? "rewind_granularity = 6\n" : "") +
|
|
"savefile_directory = \"/data/saves\"\n";
|
|
}
|
|
initShaders() {
|
|
if (!window.EJS_SHADERS) return;
|
|
this.mkdir("/shader");
|
|
for (const shader in window.EJS_SHADERS) {
|
|
this.FS.writeFile('/shader/'+shader, window.EJS_SHADERS[shader]);
|
|
}
|
|
}
|
|
clearEJSResetTimer() {
|
|
if (this.EJS.resetTimeout) {
|
|
clearTimeout(this.EJS.resetTimeout);
|
|
delete this.EJS.resetTimeout;
|
|
}
|
|
}
|
|
restart() {
|
|
this.clearEJSResetTimer();
|
|
this.functions.restart();
|
|
}
|
|
getState() {
|
|
this.functions.saveStateInfo();
|
|
return this.FS.readFile("/current.state");
|
|
}
|
|
loadState(state) {
|
|
try {
|
|
this.FS.unlink('game.state');
|
|
} catch(e){}
|
|
this.FS.writeFile('/game.state', state);
|
|
this.clearEJSResetTimer();
|
|
this.functions.loadState("game.state", 0);
|
|
setTimeout(() => {
|
|
try {
|
|
this.FS.unlink('game.state');
|
|
} catch(e){}
|
|
}, 5000)
|
|
}
|
|
screenshot() {
|
|
this.functions.screenshot();
|
|
return new Promise(async resolve => {
|
|
while (1) {
|
|
try {
|
|
FS.stat("/screenshot.png");
|
|
return resolve(this.FS.readFile("/screenshot.png"));
|
|
} catch(e) {}
|
|
|
|
await new Promise(res => setTimeout(res, 50));
|
|
}
|
|
})
|
|
}
|
|
quickSave(slot) {
|
|
if (!slot) slot = 1;
|
|
(async () => {
|
|
let name = slot + '-quick.state';
|
|
try {
|
|
this.FS.unlink(name);
|
|
} catch (e) {}
|
|
let data = await this.getState();
|
|
this.FS.writeFile('/'+name, data);
|
|
})();
|
|
}
|
|
quickLoad(slot) {
|
|
if (!slot) slot = 1;
|
|
(async () => {
|
|
let name = slot + '-quick.state';
|
|
this.clearEJSResetTimer();
|
|
this.functions.loadState(name, 0);
|
|
})();
|
|
}
|
|
simulateInput(player, index, value) {
|
|
if (this.EJS.isNetplay) {
|
|
this.EJS.netplay.simulateInput(player, index, value);
|
|
return;
|
|
}
|
|
if ([24, 25, 26, 27, 28, 29].includes(index)) {
|
|
if (index === 24 && value === 1) {
|
|
const slot = this.EJS.settings['save-state-slot'] ? this.EJS.settings['save-state-slot'] : "1";
|
|
this.quickSave(slot);
|
|
this.EJS.displayMessage(this.EJS.localization("SAVED STATE TO SLOT")+" "+slot);
|
|
}
|
|
if (index === 25 && value === 1) {
|
|
const slot = this.EJS.settings['save-state-slot'] ? this.EJS.settings['save-state-slot'] : "1";
|
|
this.quickLoad(slot);
|
|
this.EJS.displayMessage(this.EJS.localization("LOADED STATE FROM SLOT")+" "+slot);
|
|
}
|
|
if (index === 26 && value === 1) {
|
|
let newSlot;
|
|
try {
|
|
newSlot = parseFloat(this.EJS.settings['save-state-slot'] ? this.EJS.settings['save-state-slot'] : "1") + 1;
|
|
} catch(e) {
|
|
newSlot = 1;
|
|
}
|
|
if (newSlot > 9) newSlot = 1;
|
|
this.EJS.displayMessage(this.EJS.localization("SET SAVE STATE SLOT TO")+" "+newSlot);
|
|
this.EJS.changeSettingOption('save-state-slot', newSlot.toString());
|
|
}
|
|
if (index === 27) {
|
|
this.functions.toggleFastForward(this.EJS.isFastForward ? !value : value);
|
|
}
|
|
if (index === 29) {
|
|
this.functions.toggleSlowMotion(this.EJS.isSlowMotion ? !value : value);
|
|
}
|
|
if (index === 28) {
|
|
if (this.EJS.rewindEnabled) {
|
|
this.functions.toggleRewind(value);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
this.functions.simulateInput(player, index, value);
|
|
}
|
|
getFileNames() {
|
|
if (this.EJS.getCore() === "picodrive") {
|
|
return ["bin", "gen", "smd", "md", "32x", "cue", "iso", "sms", "68k", "chd"];
|
|
} else {
|
|
return ["toc", "ccd", "exe", "pbp", "chd", "img", "bin", "iso"];
|
|
}
|
|
}
|
|
createCueFile(fileNames) {
|
|
try {
|
|
if (fileNames.length > 1) {
|
|
fileNames = fileNames.filter((item) => {
|
|
return this.getFileNames().includes(item.split(".").pop().toLowerCase());
|
|
})
|
|
fileNames = fileNames.sort((a, b) => {
|
|
if (isNaN(a.charAt()) || isNaN(b.charAt())) throw new Error("Incorrect file name format");
|
|
return (parseInt(a.charAt()) > parseInt(b.charAt())) ? 1 : -1;
|
|
})
|
|
}
|
|
} catch(e) {
|
|
if (fileNames.length > 1) {
|
|
console.warn("Could not auto-create cue file(s).");
|
|
return null;
|
|
}
|
|
}
|
|
for (let i=0; i<fileNames.length; i++) {
|
|
if (fileNames[i].split(".").pop().toLowerCase() === "ccd") {
|
|
console.warn("Did not auto-create cue file(s). Found a ccd.");
|
|
return null;
|
|
}
|
|
}
|
|
if (fileNames.length === 0) {
|
|
console.warn("Could not auto-create cue file(s).");
|
|
return null;
|
|
}
|
|
let baseFileName = fileNames[0].split("/").pop();
|
|
if (baseFileName.includes(".")) {
|
|
baseFileName = baseFileName.substring(0, baseFileName.length - baseFileName.split(".").pop().length - 1);
|
|
}
|
|
for (let i=0; i<fileNames.length; i++) {
|
|
const contents = " FILE \""+fileNames[i]+"\" BINARY\n TRACK 01 MODE1/2352\n INDEX 01 00:00:00";
|
|
FS.writeFile("/"+baseFileName+"-"+i+".cue", contents);
|
|
}
|
|
if (fileNames.length > 1) {
|
|
let contents = "";
|
|
for (let i=0; i<fileNames.length; i++) {
|
|
contents += "/"+baseFileName+"-"+i+".cue\n";
|
|
}
|
|
FS.writeFile("/"+baseFileName+".m3u", contents);
|
|
}
|
|
return (fileNames.length === 1) ? baseFileName+"-0.cue" : baseFileName+".m3u";
|
|
}
|
|
loadPpssppAssets() {
|
|
return new Promise(resolve => {
|
|
this.EJS.downloadFile('cores/ppsspp-assets.zip', (res) => {
|
|
this.EJS.checkCompression(new Uint8Array(res.data), this.EJS.localization("Decompress Game Data")).then((pspassets) => {
|
|
if (pspassets === -1) {
|
|
this.EJS.textElem.innerText = this.localization('Network Error');
|
|
this.EJS.textElem.style.color = "red";
|
|
return;
|
|
}
|
|
this.mkdir("/PPSSPP");
|
|
|
|
for (const file in pspassets) {
|
|
const data = pspassets[file];
|
|
const path = "/PPSSPP/"+file;
|
|
const paths = path.split("/");
|
|
let cp = "";
|
|
for (let i=0; i<paths.length-1; i++) {
|
|
if (paths[i] === "") continue;
|
|
cp += "/"+paths[i];
|
|
if (!FS.analyzePath(cp).exists) {
|
|
FS.mkdir(cp);
|
|
}
|
|
}
|
|
this.FS.writeFile(path, data);
|
|
}
|
|
resolve();
|
|
})
|
|
}, null, false, {responseType: "arraybuffer", method: "GET"});
|
|
})
|
|
}
|
|
setVSync(enabled) {
|
|
this.functions.setVSync(enabled);
|
|
}
|
|
toggleMainLoop(playing) {
|
|
this.functions.toggleMainLoop(playing);
|
|
}
|
|
getCoreOptions() {
|
|
return this.functions.getCoreOptions();
|
|
}
|
|
setVariable(option, value) {
|
|
this.functions.setVariable(option, value);
|
|
}
|
|
setCheat(index, enabled, code) {
|
|
this.functions.setCheat(index, enabled, code);
|
|
}
|
|
resetCheat() {
|
|
this.functions.resetCheat();
|
|
}
|
|
toggleShader(active) {
|
|
this.functions.toggleShader(active);
|
|
}
|
|
getDiskCount() {
|
|
return this.functions.getDiskCount();
|
|
}
|
|
getCurrentDisk() {
|
|
return this.functions.getCurrentDisk();
|
|
}
|
|
setCurrentDisk(disk) {
|
|
this.functions.setCurrentDisk(disk);
|
|
}
|
|
getSaveFilePath() {
|
|
return this.functions.getSaveFilePath();
|
|
}
|
|
saveSaveFiles() {
|
|
this.functions.saveSaveFiles();
|
|
this.FS.syncfs(false, () => {});
|
|
}
|
|
supportsStates() {
|
|
return !!this.functions.supportsStates();
|
|
}
|
|
getSaveFile() {
|
|
this.saveSaveFiles();
|
|
const exists = FS.analyzePath(this.getSaveFilePath()).exists;
|
|
return (exists ? FS.readFile(this.getSaveFilePath()) : null);
|
|
}
|
|
loadSaveFiles() {
|
|
this.clearEJSResetTimer();
|
|
this.functions.loadSaveFiles();
|
|
}
|
|
setFastForwardRatio(ratio) {
|
|
this.functions.setFastForwardRatio(ratio);
|
|
}
|
|
toggleFastForward(active) {
|
|
this.functions.toggleFastForward(active);
|
|
}
|
|
setSlowMotionRatio(ratio) {
|
|
this.functions.setSlowMotionRatio(ratio);
|
|
}
|
|
toggleSlowMotion(active) {
|
|
this.functions.toggleSlowMotion(active);
|
|
}
|
|
setRewindGranularity(value) {
|
|
this.functions.setRewindGranularity(value);
|
|
}
|
|
getFrameNum() {
|
|
return this.functions.getFrameNum();
|
|
}
|
|
}
|
|
|
|
window.EJS_GameManager = EJS_GameManager;
|