@pkg-nec/emulatorjs 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +1 -0
- package/CHANGES.md +528 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +31 -0
- package/LICENSE +674 -0
- package/README.md +284 -0
- package/data/compression/README.md +11 -0
- package/data/compression/extract7z.js +1 -0
- package/data/compression/extractzip.js +1 -0
- package/data/compression/libunrar.js +519 -0
- package/data/compression/libunrar.wasm +0 -0
- package/data/cores/README.md +22 -0
- package/data/cores/package.json +65 -0
- package/data/emulator.css +1614 -0
- package/data/loader.js +170 -0
- package/data/localization/README.md +63 -0
- package/data/localization/af-FR.json +339 -0
- package/data/localization/ar-AR.json +301 -0
- package/data/localization/ben-BEN.json +301 -0
- package/data/localization/de-GER.json +301 -0
- package/data/localization/el-GR.json +301 -0
- package/data/localization/en-US.json +339 -0
- package/data/localization/es-ES.json +310 -0
- package/data/localization/fa-AF.json +310 -0
- package/data/localization/hi-HI.json +301 -0
- package/data/localization/it-IT.json +302 -0
- package/data/localization/ja-JA.json +301 -0
- package/data/localization/jv-JV.json +301 -0
- package/data/localization/ko-KO.json +336 -0
- package/data/localization/pt-BR.json +361 -0
- package/data/localization/retroarch.json +617 -0
- package/data/localization/ro-RO.json +310 -0
- package/data/localization/ru-RU.json +301 -0
- package/data/localization/tr-TR.json +310 -0
- package/data/localization/vi-VN.json +302 -0
- package/data/localization/zh-CN.json +351 -0
- package/data/src/GameManager.js +464 -0
- package/data/src/compression.js +142 -0
- package/data/src/emulator.js +6158 -0
- package/data/src/gamepad.js +167 -0
- package/data/src/nipplejs.js +1 -0
- package/data/src/shaders.js +236 -0
- package/data/src/socket.io.min.js +7 -0
- package/data/src/storage.js +125 -0
- package/data/version.json +5 -0
- package/docs/Logo-light.png +0 -0
- package/docs/Logo.png +0 -0
- package/docs/Logo.svg +4487 -0
- package/docs/contributors.json +46 -0
- package/docs/contributors.md +125 -0
- package/docs/favicon.ico +0 -0
- package/index.html +269 -0
- package/package.json +35 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
class EJS_GameManager {
|
|
2
|
+
constructor(Module, EJS) {
|
|
3
|
+
this.EJS = EJS;
|
|
4
|
+
this.Module = Module;
|
|
5
|
+
this.FS = this.Module.FS;
|
|
6
|
+
this.functions = {
|
|
7
|
+
restart: this.Module.cwrap("system_restart", "", []),
|
|
8
|
+
saveStateInfo: this.Module.cwrap("save_state_info", "string", []),
|
|
9
|
+
loadState: this.Module.cwrap("load_state", "number", ["string", "number"]),
|
|
10
|
+
screenshot: this.Module.cwrap("cmd_take_screenshot", "", []),
|
|
11
|
+
simulateInput: this.Module.cwrap("simulate_input", "null", ["number", "number", "number"]),
|
|
12
|
+
toggleMainLoop: this.Module.cwrap("toggleMainLoop", "null", ["number"]),
|
|
13
|
+
getCoreOptions: this.Module.cwrap("get_core_options", "string", []),
|
|
14
|
+
setVariable: this.Module.cwrap("ejs_set_variable", "null", ["string", "string"]),
|
|
15
|
+
setCheat: this.Module.cwrap("set_cheat", "null", ["number", "number", "string"]),
|
|
16
|
+
resetCheat: this.Module.cwrap("reset_cheat", "null", []),
|
|
17
|
+
toggleShader: this.Module.cwrap("shader_enable", "null", ["number"]),
|
|
18
|
+
getDiskCount: this.Module.cwrap("get_disk_count", "number", []),
|
|
19
|
+
getCurrentDisk: this.Module.cwrap("get_current_disk", "number", []),
|
|
20
|
+
setCurrentDisk: this.Module.cwrap("set_current_disk", "null", ["number"]),
|
|
21
|
+
getSaveFilePath: this.Module.cwrap("save_file_path", "string", []),
|
|
22
|
+
saveSaveFiles: this.Module.cwrap("cmd_savefiles", "", []),
|
|
23
|
+
supportsStates: this.Module.cwrap("supports_states", "number", []),
|
|
24
|
+
loadSaveFiles: this.Module.cwrap("refresh_save_files", "null", []),
|
|
25
|
+
toggleFastForward: this.Module.cwrap("toggle_fastforward", "null", ["number"]),
|
|
26
|
+
setFastForwardRatio: this.Module.cwrap("set_ff_ratio", "null", ["number"]),
|
|
27
|
+
toggleRewind: this.Module.cwrap("toggle_rewind", "null", ["number"]),
|
|
28
|
+
setRewindGranularity: this.Module.cwrap("set_rewind_granularity", "null", ["number"]),
|
|
29
|
+
toggleSlowMotion: this.Module.cwrap("toggle_slow_motion", "null", ["number"]),
|
|
30
|
+
setSlowMotionRatio: this.Module.cwrap("set_sm_ratio", "null", ["number"]),
|
|
31
|
+
getFrameNum: this.Module.cwrap("get_current_frame_count", "number", [""]),
|
|
32
|
+
setVSync: this.Module.cwrap("set_vsync", "null", ["number"]),
|
|
33
|
+
setVideoRoation: this.Module.cwrap("set_video_rotation", "null", ["number"]),
|
|
34
|
+
getVideoDimensions: this.Module.cwrap("get_video_dimensions", "number", ["string"]),
|
|
35
|
+
setKeyboardEnabled: this.Module.cwrap("ejs_set_keyboard_enabled", "null", ["number"])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.writeFile("/home/web_user/.config/retroarch/retroarch.cfg", this.getRetroArchCfg());
|
|
39
|
+
|
|
40
|
+
this.writeConfigFile();
|
|
41
|
+
this.initShaders();
|
|
42
|
+
this.setupPreLoadSettings();
|
|
43
|
+
|
|
44
|
+
this.EJS.on("exit", () => {
|
|
45
|
+
if (!this.EJS.failedToStart) {
|
|
46
|
+
this.saveSaveFiles();
|
|
47
|
+
this.functions.restart();
|
|
48
|
+
this.saveSaveFiles();
|
|
49
|
+
}
|
|
50
|
+
this.toggleMainLoop(0);
|
|
51
|
+
this.FS.unmount("/data/saves");
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
try {
|
|
54
|
+
this.Module.abort();
|
|
55
|
+
} catch(e) {
|
|
56
|
+
console.warn(e);
|
|
57
|
+
};
|
|
58
|
+
}, 1000);
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
setupPreLoadSettings() {
|
|
62
|
+
this.Module.callbacks.setupCoreSettingFile = (filePath) => {
|
|
63
|
+
if (this.EJS.debug) console.log("Setting up core settings with path:", filePath);
|
|
64
|
+
this.writeFile(filePath, this.EJS.getCoreSettings());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
mountFileSystems() {
|
|
68
|
+
return new Promise(async resolve => {
|
|
69
|
+
this.mkdir("/data");
|
|
70
|
+
this.mkdir("/data/saves");
|
|
71
|
+
this.FS.mount(this.FS.filesystems.IDBFS, { autoPersist: true }, "/data/saves");
|
|
72
|
+
this.FS.syncfs(true, resolve);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
writeConfigFile() {
|
|
76
|
+
if (!this.EJS.defaultCoreOpts.file || !this.EJS.defaultCoreOpts.settings) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let output = "";
|
|
80
|
+
for (const k in this.EJS.defaultCoreOpts.settings) {
|
|
81
|
+
output += k + ' = "' + this.EJS.defaultCoreOpts.settings[k] + '"\n';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.writeFile("/home/web_user/retroarch/userdata/config/" + this.EJS.defaultCoreOpts.file, output);
|
|
85
|
+
}
|
|
86
|
+
loadExternalFiles() {
|
|
87
|
+
return new Promise(async (resolve, reject) => {
|
|
88
|
+
if (this.EJS.config.externalFiles && this.EJS.config.externalFiles.constructor.name === "Object") {
|
|
89
|
+
for (const key in this.EJS.config.externalFiles) {
|
|
90
|
+
await new Promise(done => {
|
|
91
|
+
this.EJS.downloadFile(this.EJS.config.externalFiles[key], null, true, { responseType: "arraybuffer", method: "GET" }).then(async (res) => {
|
|
92
|
+
if (res === -1) {
|
|
93
|
+
if (this.EJS.debug) console.warn("Failed to fetch file from '" + this.EJS.config.externalFiles[key] + "'. Make sure the file exists.");
|
|
94
|
+
return done();
|
|
95
|
+
}
|
|
96
|
+
let path = key;
|
|
97
|
+
if (key.trim().endsWith("/")) {
|
|
98
|
+
const invalidCharacters = /[#<$+%>!`&*'|{}/\\?"=@:^\r\n]/ig;
|
|
99
|
+
let name = this.EJS.config.externalFiles[key].split("/").pop().split("#")[0].split("?")[0].replace(invalidCharacters, "").trim();
|
|
100
|
+
if (!name) return done();
|
|
101
|
+
const files = await this.EJS.checkCompression(new Uint8Array(res.data), this.EJS.localization("Decompress Game Assets"));
|
|
102
|
+
if (files["!!notCompressedData"]) {
|
|
103
|
+
path += name;
|
|
104
|
+
} else {
|
|
105
|
+
for (const k in files) {
|
|
106
|
+
this.writeFile(path + k, files[k]);
|
|
107
|
+
}
|
|
108
|
+
return done();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
this.writeFile(path, res.data);
|
|
113
|
+
} catch(e) {
|
|
114
|
+
if (this.EJS.debug) console.warn("Failed to write file to '" + path + "'. Make sure there are no conflicting files.");
|
|
115
|
+
}
|
|
116
|
+
done();
|
|
117
|
+
});
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
writeFile(path, data) {
|
|
125
|
+
const parts = path.split("/");
|
|
126
|
+
let current = "/";
|
|
127
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
128
|
+
if (!parts[i].trim()) continue;
|
|
129
|
+
current += parts[i] + "/";
|
|
130
|
+
this.mkdir(current);
|
|
131
|
+
}
|
|
132
|
+
this.FS.writeFile(path, data);
|
|
133
|
+
}
|
|
134
|
+
mkdir(path) {
|
|
135
|
+
try {
|
|
136
|
+
this.FS.mkdir(path);
|
|
137
|
+
} catch(e) {}
|
|
138
|
+
}
|
|
139
|
+
getRetroArchCfg() {
|
|
140
|
+
let cfg = "autosave_interval = 60\n" +
|
|
141
|
+
"screenshot_directory = \"/\"\n" +
|
|
142
|
+
"block_sram_overwrite = false\n" +
|
|
143
|
+
"video_gpu_screenshot = false\n" +
|
|
144
|
+
"audio_latency = 64\n" +
|
|
145
|
+
"video_top_portrait_viewport = true\n" +
|
|
146
|
+
"video_vsync = true\n" +
|
|
147
|
+
"video_smooth = false\n" +
|
|
148
|
+
"fastforward_ratio = 3.0\n" +
|
|
149
|
+
"slowmotion_ratio = 3.0\n" +
|
|
150
|
+
(this.EJS.rewindEnabled ? "rewind_enable = true\n" : "") +
|
|
151
|
+
(this.EJS.rewindEnabled ? "rewind_granularity = 6\n" : "") +
|
|
152
|
+
"savefile_directory = \"/data/saves\"\n";
|
|
153
|
+
|
|
154
|
+
if (this.EJS.retroarchOpts && Array.isArray(this.EJS.retroarchOpts)) {
|
|
155
|
+
this.EJS.retroarchOpts.forEach(option => {
|
|
156
|
+
let selected = this.EJS.preGetSetting(option.name);
|
|
157
|
+
console.log(selected);
|
|
158
|
+
if (!selected) {
|
|
159
|
+
selected = option.default;
|
|
160
|
+
}
|
|
161
|
+
const value = option.isString === false ? selected : '"' + selected + '"';
|
|
162
|
+
cfg += option.name + " = " + value + "\n"
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
return cfg;
|
|
166
|
+
}
|
|
167
|
+
initShaders() {
|
|
168
|
+
if (!this.EJS.config.shaders) return;
|
|
169
|
+
this.mkdir("/shader");
|
|
170
|
+
for (const shaderFileName in this.EJS.config.shaders) {
|
|
171
|
+
const shader = this.EJS.config.shaders[shaderFileName];
|
|
172
|
+
if (typeof shader === "string") {
|
|
173
|
+
this.FS.writeFile(`/shader/${shaderFileName}`, shader);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
clearEJSResetTimer() {
|
|
178
|
+
if (this.EJS.resetTimeout) {
|
|
179
|
+
clearTimeout(this.EJS.resetTimeout);
|
|
180
|
+
delete this.EJS.resetTimeout;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
restart() {
|
|
184
|
+
this.clearEJSResetTimer();
|
|
185
|
+
this.functions.restart();
|
|
186
|
+
}
|
|
187
|
+
getState() {
|
|
188
|
+
const state = this.functions.saveStateInfo().split("|");
|
|
189
|
+
if (state[2] !== "1") {
|
|
190
|
+
console.error(state[0]);
|
|
191
|
+
throw new Error(state[0]);
|
|
192
|
+
}
|
|
193
|
+
const size = parseInt(state[0]);
|
|
194
|
+
const dataStart = parseInt(state[1]);
|
|
195
|
+
const data = this.Module.HEAPU8.subarray(dataStart, dataStart + size);
|
|
196
|
+
return new Uint8Array(data);
|
|
197
|
+
}
|
|
198
|
+
loadState(state) {
|
|
199
|
+
try {
|
|
200
|
+
this.FS.unlink("game.state");
|
|
201
|
+
} catch(e) {}
|
|
202
|
+
this.FS.writeFile("/game.state", state);
|
|
203
|
+
this.clearEJSResetTimer();
|
|
204
|
+
this.functions.loadState("game.state", 0);
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
try {
|
|
207
|
+
this.FS.unlink("game.state");
|
|
208
|
+
} catch(e) {}
|
|
209
|
+
}, 5000)
|
|
210
|
+
}
|
|
211
|
+
screenshot() {
|
|
212
|
+
try {
|
|
213
|
+
this.FS.unlink("screenshot.png");
|
|
214
|
+
} catch(e) {}
|
|
215
|
+
this.functions.screenshot();
|
|
216
|
+
return new Promise(async resolve => {
|
|
217
|
+
while (1) {
|
|
218
|
+
try {
|
|
219
|
+
this.FS.stat("/screenshot.png");
|
|
220
|
+
return resolve(this.FS.readFile("/screenshot.png"));
|
|
221
|
+
} catch(e) {}
|
|
222
|
+
await new Promise(res => setTimeout(res, 50));
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
quickSave(slot) {
|
|
227
|
+
if (!slot) slot = 1;
|
|
228
|
+
let name = slot + "-quick.state";
|
|
229
|
+
try {
|
|
230
|
+
this.FS.unlink(name);
|
|
231
|
+
} catch(e) {}
|
|
232
|
+
try {
|
|
233
|
+
let data = this.getState();
|
|
234
|
+
this.FS.writeFile("/" + name, data);
|
|
235
|
+
} catch(e) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
quickLoad(slot) {
|
|
241
|
+
if (!slot) slot = 1;
|
|
242
|
+
(async () => {
|
|
243
|
+
let name = slot + "-quick.state";
|
|
244
|
+
this.clearEJSResetTimer();
|
|
245
|
+
this.functions.loadState(name, 0);
|
|
246
|
+
})();
|
|
247
|
+
}
|
|
248
|
+
simulateInput(player, index, value) {
|
|
249
|
+
if (this.EJS.isNetplay) {
|
|
250
|
+
this.EJS.netplay.simulateInput(player, index, value);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if ([24, 25, 26, 27, 28, 29].includes(index)) {
|
|
254
|
+
if (index === 24 && value === 1) {
|
|
255
|
+
const slot = this.EJS.settings["save-state-slot"] ? this.EJS.settings["save-state-slot"] : "1";
|
|
256
|
+
if (this.quickSave(slot)) {
|
|
257
|
+
this.EJS.displayMessage(this.EJS.localization("SAVED STATE TO SLOT") + " " + slot);
|
|
258
|
+
} else {
|
|
259
|
+
this.EJS.displayMessage(this.EJS.localization("FAILED TO SAVE STATE"));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (index === 25 && value === 1) {
|
|
263
|
+
const slot = this.EJS.settings["save-state-slot"] ? this.EJS.settings["save-state-slot"] : "1";
|
|
264
|
+
this.quickLoad(slot);
|
|
265
|
+
this.EJS.displayMessage(this.EJS.localization("LOADED STATE FROM SLOT") + " " + slot);
|
|
266
|
+
}
|
|
267
|
+
if (index === 26 && value === 1) {
|
|
268
|
+
let newSlot;
|
|
269
|
+
try {
|
|
270
|
+
newSlot = parseFloat(this.EJS.settings["save-state-slot"] ? this.EJS.settings["save-state-slot"] : "1") + 1;
|
|
271
|
+
} catch(e) {
|
|
272
|
+
newSlot = 1;
|
|
273
|
+
}
|
|
274
|
+
if (newSlot > 9) newSlot = 1;
|
|
275
|
+
this.EJS.displayMessage(this.EJS.localization("SET SAVE STATE SLOT TO") + " " + newSlot);
|
|
276
|
+
this.EJS.changeSettingOption("save-state-slot", newSlot.toString());
|
|
277
|
+
}
|
|
278
|
+
if (index === 27) {
|
|
279
|
+
this.functions.toggleFastForward(this.EJS.isFastForward ? !value : value);
|
|
280
|
+
}
|
|
281
|
+
if (index === 29) {
|
|
282
|
+
this.functions.toggleSlowMotion(this.EJS.isSlowMotion ? !value : value);
|
|
283
|
+
}
|
|
284
|
+
if (index === 28) {
|
|
285
|
+
if (this.EJS.rewindEnabled) {
|
|
286
|
+
this.functions.toggleRewind(value);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
this.functions.simulateInput(player, index, value);
|
|
292
|
+
}
|
|
293
|
+
getFileNames() {
|
|
294
|
+
if (this.EJS.getCore() === "picodrive") {
|
|
295
|
+
return ["bin", "gen", "smd", "md", "32x", "cue", "iso", "sms", "68k", "chd"];
|
|
296
|
+
} else {
|
|
297
|
+
return ["toc", "ccd", "exe", "pbp", "chd", "img", "bin", "iso"];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
createCueFile(fileNames) {
|
|
301
|
+
try {
|
|
302
|
+
if (fileNames.length > 1) {
|
|
303
|
+
fileNames = fileNames.filter((item) => {
|
|
304
|
+
return this.getFileNames().includes(item.split(".").pop().toLowerCase());
|
|
305
|
+
})
|
|
306
|
+
fileNames = fileNames.sort((a, b) => {
|
|
307
|
+
if (isNaN(a.charAt()) || isNaN(b.charAt())) throw new Error("Incorrect file name format");
|
|
308
|
+
return (parseInt(a.charAt()) > parseInt(b.charAt())) ? 1 : -1;
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
} catch(e) {
|
|
312
|
+
if (fileNames.length > 1) {
|
|
313
|
+
console.warn("Could not auto-create cue file(s).");
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
for (let i = 0; i < fileNames.length; i++) {
|
|
318
|
+
if (fileNames[i].split(".").pop().toLowerCase() === "ccd") {
|
|
319
|
+
console.warn("Did not auto-create cue file(s). Found a ccd.");
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (fileNames.length === 0) {
|
|
324
|
+
console.warn("Could not auto-create cue file(s).");
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
let baseFileName = fileNames[0].split("/").pop();
|
|
328
|
+
if (baseFileName.includes(".")) {
|
|
329
|
+
baseFileName = baseFileName.substring(0, baseFileName.length - baseFileName.split(".").pop().length - 1);
|
|
330
|
+
}
|
|
331
|
+
for (let i = 0; i < fileNames.length; i++) {
|
|
332
|
+
const contents = " FILE \"" + fileNames[i] + "\" BINARY\n TRACK 01 MODE1/2352\n INDEX 01 00:00:00";
|
|
333
|
+
this.FS.writeFile("/" + baseFileName + "-" + i + ".cue", contents);
|
|
334
|
+
}
|
|
335
|
+
if (fileNames.length > 1) {
|
|
336
|
+
let contents = "";
|
|
337
|
+
for (let i = 0; i < fileNames.length; i++) {
|
|
338
|
+
contents += "/" + baseFileName + "-" + i + ".cue\n";
|
|
339
|
+
}
|
|
340
|
+
this.FS.writeFile("/" + baseFileName + ".m3u", contents);
|
|
341
|
+
}
|
|
342
|
+
return (fileNames.length === 1) ? baseFileName + "-0.cue" : baseFileName + ".m3u";
|
|
343
|
+
}
|
|
344
|
+
loadPpssppAssets() {
|
|
345
|
+
return new Promise(resolve => {
|
|
346
|
+
this.EJS.downloadFile("cores/ppsspp-assets.zip", null, false, { responseType: "arraybuffer", method: "GET" }).then((res) => {
|
|
347
|
+
this.EJS.checkCompression(new Uint8Array(res.data), this.EJS.localization("Decompress Game Data")).then((pspassets) => {
|
|
348
|
+
if (pspassets === -1) {
|
|
349
|
+
this.EJS.textElem.innerText = this.localization("Network Error");
|
|
350
|
+
this.EJS.textElem.style.color = "red";
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.mkdir("/PPSSPP");
|
|
354
|
+
|
|
355
|
+
for (const file in pspassets) {
|
|
356
|
+
const data = pspassets[file];
|
|
357
|
+
const path = "/PPSSPP/" + file;
|
|
358
|
+
const paths = path.split("/");
|
|
359
|
+
let cp = "";
|
|
360
|
+
for (let i = 0; i < paths.length - 1; i++) {
|
|
361
|
+
if (paths[i] === "") continue;
|
|
362
|
+
cp += "/" + paths[i];
|
|
363
|
+
if (!this.FS.analyzePath(cp).exists) {
|
|
364
|
+
this.FS.mkdir(cp);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (!path.endsWith("/")) {
|
|
368
|
+
this.FS.writeFile(path, data);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
resolve();
|
|
372
|
+
})
|
|
373
|
+
});
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
setVSync(enabled) {
|
|
377
|
+
this.functions.setVSync(enabled);
|
|
378
|
+
}
|
|
379
|
+
toggleMainLoop(playing) {
|
|
380
|
+
this.functions.toggleMainLoop(playing);
|
|
381
|
+
}
|
|
382
|
+
getCoreOptions() {
|
|
383
|
+
return this.functions.getCoreOptions();
|
|
384
|
+
}
|
|
385
|
+
setVariable(option, value) {
|
|
386
|
+
this.functions.setVariable(option, value);
|
|
387
|
+
}
|
|
388
|
+
setCheat(index, enabled, code) {
|
|
389
|
+
this.functions.setCheat(index, enabled, code);
|
|
390
|
+
}
|
|
391
|
+
resetCheat() {
|
|
392
|
+
this.functions.resetCheat();
|
|
393
|
+
}
|
|
394
|
+
toggleShader(active) {
|
|
395
|
+
this.functions.toggleShader(active);
|
|
396
|
+
}
|
|
397
|
+
getDiskCount() {
|
|
398
|
+
return this.functions.getDiskCount();
|
|
399
|
+
}
|
|
400
|
+
getCurrentDisk() {
|
|
401
|
+
return this.functions.getCurrentDisk();
|
|
402
|
+
}
|
|
403
|
+
setCurrentDisk(disk) {
|
|
404
|
+
this.functions.setCurrentDisk(disk);
|
|
405
|
+
}
|
|
406
|
+
getSaveFilePath() {
|
|
407
|
+
return this.functions.getSaveFilePath();
|
|
408
|
+
}
|
|
409
|
+
saveSaveFiles() {
|
|
410
|
+
this.functions.saveSaveFiles();
|
|
411
|
+
this.EJS.callEvent("saveSaveFiles", this.getSaveFile(false));
|
|
412
|
+
//this.FS.syncfs(false, () => {});
|
|
413
|
+
}
|
|
414
|
+
supportsStates() {
|
|
415
|
+
return !!this.functions.supportsStates();
|
|
416
|
+
}
|
|
417
|
+
getSaveFile(save) {
|
|
418
|
+
if (save !== false) {
|
|
419
|
+
this.saveSaveFiles();
|
|
420
|
+
}
|
|
421
|
+
const exists = this.FS.analyzePath(this.getSaveFilePath()).exists;
|
|
422
|
+
return (exists ? this.FS.readFile(this.getSaveFilePath()) : null);
|
|
423
|
+
}
|
|
424
|
+
loadSaveFiles() {
|
|
425
|
+
this.clearEJSResetTimer();
|
|
426
|
+
this.functions.loadSaveFiles();
|
|
427
|
+
}
|
|
428
|
+
setFastForwardRatio(ratio) {
|
|
429
|
+
this.functions.setFastForwardRatio(ratio);
|
|
430
|
+
}
|
|
431
|
+
toggleFastForward(active) {
|
|
432
|
+
this.functions.toggleFastForward(active);
|
|
433
|
+
}
|
|
434
|
+
setSlowMotionRatio(ratio) {
|
|
435
|
+
this.functions.setSlowMotionRatio(ratio);
|
|
436
|
+
}
|
|
437
|
+
toggleSlowMotion(active) {
|
|
438
|
+
this.functions.toggleSlowMotion(active);
|
|
439
|
+
}
|
|
440
|
+
setRewindGranularity(value) {
|
|
441
|
+
this.functions.setRewindGranularity(value);
|
|
442
|
+
}
|
|
443
|
+
getFrameNum() {
|
|
444
|
+
return this.functions.getFrameNum();
|
|
445
|
+
}
|
|
446
|
+
setVideoRotation(rotation) {
|
|
447
|
+
this.functions.setVideoRoation(rotation);
|
|
448
|
+
}
|
|
449
|
+
getVideoDimensions(type) {
|
|
450
|
+
try {
|
|
451
|
+
return this.functions.getVideoDimensions(type);
|
|
452
|
+
} catch(e) {
|
|
453
|
+
console.warn(e);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
setKeyboardEnabled(enabled) {
|
|
457
|
+
this.functions.setKeyboardEnabled(enabled === true ? 1 : 0);
|
|
458
|
+
}
|
|
459
|
+
setAltKeyEnabled(enabled) {
|
|
460
|
+
this.functions.setKeyboardEnabled(enabled === true ? 3 : 2);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
window.EJS_GameManager = EJS_GameManager;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
class EJS_COMPRESSION {
|
|
2
|
+
constructor(EJS) {
|
|
3
|
+
this.EJS = EJS;
|
|
4
|
+
}
|
|
5
|
+
isCompressed(data) { //https://www.garykessler.net/library/file_sigs.html
|
|
6
|
+
//todo. Use hex instead of numbers
|
|
7
|
+
if ((data[0] === 80 && data[1] === 75) && ((data[2] === 3 && data[3] === 4) || (data[2] === 5 && data[3] === 6) || (data[2] === 7 && data[3] === 8))) {
|
|
8
|
+
return "zip";
|
|
9
|
+
} else if (data[0] === 55 && data[1] === 122 && data[2] === 188 && data[3] === 175 && data[4] === 39 && data[5] === 28) {
|
|
10
|
+
return "7z";
|
|
11
|
+
} else if ((data[0] === 82 && data[1] === 97 && data[2] === 114 && data[3] === 33 && data[4] === 26 && data[5] === 7) && ((data[6] === 0) || (data[6] === 1 && data[7] == 0))) {
|
|
12
|
+
return "rar";
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
decompress(data, updateMsg, fileCbFunc) {
|
|
17
|
+
const compressed = this.isCompressed(data.slice(0, 10));
|
|
18
|
+
if (compressed === null) {
|
|
19
|
+
if (typeof fileCbFunc === "function") {
|
|
20
|
+
fileCbFunc("!!notCompressedData", data);
|
|
21
|
+
}
|
|
22
|
+
return new Promise(resolve => resolve({ "!!notCompressedData": data }));
|
|
23
|
+
}
|
|
24
|
+
return this.decompressFile(compressed, data, updateMsg, fileCbFunc);
|
|
25
|
+
}
|
|
26
|
+
getWorkerFile(method) {
|
|
27
|
+
return new Promise(async (resolve, reject) => {
|
|
28
|
+
let path, obj;
|
|
29
|
+
if (method === "7z") {
|
|
30
|
+
path = "compression/extract7z.js";
|
|
31
|
+
obj = "sevenZip";
|
|
32
|
+
} else if (method === "zip") {
|
|
33
|
+
path = "compression/extractzip.js";
|
|
34
|
+
obj = "zip";
|
|
35
|
+
} else if (method === "rar") {
|
|
36
|
+
path = "compression/libunrar.js";
|
|
37
|
+
obj = "rar";
|
|
38
|
+
}
|
|
39
|
+
const res = await this.EJS.downloadFile(path, null, false, { responseType: "text", method: "GET" });
|
|
40
|
+
if (res === -1) {
|
|
41
|
+
this.EJS.startGameError(this.EJS.localization("Network Error"));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (method === "rar") {
|
|
45
|
+
const res2 = await this.EJS.downloadFile("compression/libunrar.wasm", null, false, { responseType: "arraybuffer", method: "GET" });
|
|
46
|
+
if (res2 === -1) {
|
|
47
|
+
this.EJS.startGameError(this.EJS.localization("Network Error"));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const path = URL.createObjectURL(new Blob([res2.data], { type: "application/wasm" }));
|
|
51
|
+
let script = `
|
|
52
|
+
let dataToPass = [];
|
|
53
|
+
Module = {
|
|
54
|
+
monitorRunDependencies: function(left) {
|
|
55
|
+
if (left == 0) {
|
|
56
|
+
setTimeout(function() {
|
|
57
|
+
unrar(dataToPass, null);
|
|
58
|
+
}, 100);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
onRuntimeInitialized: function() {},
|
|
62
|
+
locateFile: function(file) {
|
|
63
|
+
console.log("locateFile");
|
|
64
|
+
return "` + path + `";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
` + res.data + `
|
|
68
|
+
let unrar = function(data, password) {
|
|
69
|
+
let cb = function(fileName, fileSize, progress) {
|
|
70
|
+
postMessage({ "t": 4, "current": progress, "total": fileSize, "name": fileName });
|
|
71
|
+
};
|
|
72
|
+
let rarContent = readRARContent(data.map(function(d) {
|
|
73
|
+
return {
|
|
74
|
+
name: d.name,
|
|
75
|
+
content: new Uint8Array(d.content)
|
|
76
|
+
}
|
|
77
|
+
}), password, cb)
|
|
78
|
+
let rec = function(entry) {
|
|
79
|
+
if (!entry) return;
|
|
80
|
+
if (entry.type === "file") {
|
|
81
|
+
postMessage({ "t": 2, "file": entry.fullFileName, "size": entry.fileSize, "data": entry.fileContent });
|
|
82
|
+
} else if (entry.type === "dir") {
|
|
83
|
+
Object.keys(entry.ls).forEach(function(k) {
|
|
84
|
+
rec(entry.ls[k]);
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
throw "Unknown type";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
rec(rarContent);
|
|
91
|
+
postMessage({ "t": 1 });
|
|
92
|
+
return rarContent;
|
|
93
|
+
};
|
|
94
|
+
onmessage = function(data) {
|
|
95
|
+
dataToPass.push({ name: "test.rar", content: data.data });
|
|
96
|
+
};
|
|
97
|
+
`;
|
|
98
|
+
const blob = new Blob([script], {
|
|
99
|
+
type: "application/javascript"
|
|
100
|
+
})
|
|
101
|
+
resolve(blob);
|
|
102
|
+
} else {
|
|
103
|
+
const blob = new Blob([res.data], {
|
|
104
|
+
type: "application/javascript"
|
|
105
|
+
})
|
|
106
|
+
resolve(blob);
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
decompressFile(method, data, updateMsg, fileCbFunc) {
|
|
111
|
+
return new Promise(async callback => {
|
|
112
|
+
const file = await this.getWorkerFile(method);
|
|
113
|
+
const worker = new Worker(URL.createObjectURL(file));
|
|
114
|
+
const files = {};
|
|
115
|
+
worker.onmessage = (data) => {
|
|
116
|
+
if (!data.data) return;
|
|
117
|
+
//data.data.t/ 4=progress, 2 is file, 1 is zip done
|
|
118
|
+
if (data.data.t === 4) {
|
|
119
|
+
const pg = data.data;
|
|
120
|
+
const num = Math.floor(pg.current / pg.total * 100);
|
|
121
|
+
if (isNaN(num)) return;
|
|
122
|
+
const progress = " " + num.toString() + "%";
|
|
123
|
+
updateMsg(progress, true);
|
|
124
|
+
}
|
|
125
|
+
if (data.data.t === 2) {
|
|
126
|
+
if (typeof fileCbFunc === "function") {
|
|
127
|
+
fileCbFunc(data.data.file, data.data.data);
|
|
128
|
+
files[data.data.file] = true;
|
|
129
|
+
} else {
|
|
130
|
+
files[data.data.file] = data.data.data;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (data.data.t === 1) {
|
|
134
|
+
callback(files);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
worker.postMessage(data);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
window.EJS_COMPRESSION = EJS_COMPRESSION;
|