mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-20 06:12:57 +01:00
Emscripten: Reimplement save/load
This commit is contained in:
@@ -6,16 +6,136 @@
|
|||||||
*
|
*
|
||||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
var EmscriptenDeps = {
|
var EmscriptenDeps = {
|
||||||
ExportPersistentData: () =>
|
$SAVE: {
|
||||||
{
|
filehandle: null,
|
||||||
if (!window.JSZip)
|
|
||||||
{
|
readFileData: (isTrackDesign) => {
|
||||||
|
const filePath = isTrackDesign ? "/save.park.td6" : "/save.park";
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = FS.readFile(filePath);
|
||||||
|
FS.unlink(filePath);
|
||||||
|
} catch(e) {};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EmscriptenSaveGame: async (isTrackDesign, isAutosave, isSaveAs, type) => {
|
||||||
|
isTrackDesign = isTrackDesign === 1;
|
||||||
|
isAutosave = isAutosave === 1;
|
||||||
|
isSaveAs = isSaveAs === 1;
|
||||||
|
const data = SAVE.readFileData(isTrackDesign);
|
||||||
|
try {
|
||||||
|
if (window.showSaveFilePicker) {
|
||||||
|
if (SAVE.filehandle === null || isSaveAs) {
|
||||||
|
SAVE.filehandle = null;
|
||||||
|
const file = await showSaveFilePicker({
|
||||||
|
id: "OpenRCT2-" + type,
|
||||||
|
types: [
|
||||||
|
{
|
||||||
|
description: isTrackDesign ? "Track Design File" : "Park File",
|
||||||
|
accept: {
|
||||||
|
"application/octet-stream": [
|
||||||
|
isTrackDesign ? ".td6" : ".park"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
if (!file) throw new Error("Could not get a file as RW!");
|
||||||
|
SAVE.filehandle = file;
|
||||||
|
}
|
||||||
|
const writable = await SAVE.filehandle.createWritable();
|
||||||
|
await writable.write(data);
|
||||||
|
await writable.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
// Don't return - fallback to the old save dialog.
|
||||||
|
console.warn("Failed to use showSaveFilePicker with error message:", e);
|
||||||
|
|
||||||
|
// User requested abort -- return here.
|
||||||
|
if (e.name === "AbortError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isAutosave) {
|
||||||
|
const blob = new Blob([data]);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = "download.park";
|
||||||
|
a.click();
|
||||||
|
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EmscriptenResetAutosave: () => {
|
||||||
|
SAVE.filehandle = null;
|
||||||
|
},
|
||||||
|
EmscriptenLoadGame: async (type) => {
|
||||||
|
try {
|
||||||
|
if (window.showOpenFilePicker) {
|
||||||
|
SAVE.filehandle = null;
|
||||||
|
const [file] = await showOpenFilePicker({
|
||||||
|
id: "OpenRCT2-" + type,
|
||||||
|
types: [
|
||||||
|
{
|
||||||
|
description: "Park File",
|
||||||
|
accept: {
|
||||||
|
"application/octet-stream": [
|
||||||
|
".park"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Track Design File",
|
||||||
|
accept: {
|
||||||
|
"application/octet-stream": [
|
||||||
|
".td6"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
|
if (!file) throw new Error("Could not get a file as RW!");
|
||||||
|
SAVE.filehandle = file;
|
||||||
|
const fileInstance = await SAVE.filehandle.getFile();
|
||||||
|
const name = SAVE.filehandle.name;
|
||||||
|
const data = await fileInstance.arrayBuffer();
|
||||||
|
FS.writeFile(name, new Uint8Array(data));
|
||||||
|
_LoadGameCallback(stringToNewUTF8(name), type);
|
||||||
|
FS.unlink(name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
// Don't return - fallback to the old save dialog.
|
||||||
|
console.warn("Failed to use showOpenFilePicker with error message:", e);
|
||||||
|
|
||||||
|
// User requested abort -- return here.
|
||||||
|
if (e.name === "AbortError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.addEventListener("change", async () => {
|
||||||
|
const file = input.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const name = file.name;
|
||||||
|
const data = await file.arrayBuffer();
|
||||||
|
FS.writeFile(name, new Uint8Array(data));
|
||||||
|
_LoadGameCallback(stringToNewUTF8(name), type);
|
||||||
|
FS.unlink(name);
|
||||||
|
});
|
||||||
|
input.click();
|
||||||
|
},
|
||||||
|
ExportPersistentData: () => {
|
||||||
|
if (!window.JSZip) {
|
||||||
alert("JSZip library not found. Aborting");
|
alert("JSZip library not found. Aborting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const zipFolder = (folder) =>
|
const zipFolder = (folder) => {
|
||||||
{
|
|
||||||
let zip = new JSZip();
|
let zip = new JSZip();
|
||||||
const processFolder = (name) => {
|
const processFolder = (name) => {
|
||||||
let contents;
|
let contents;
|
||||||
@@ -50,10 +170,8 @@ var EmscriptenDeps = {
|
|||||||
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
ImportPersistentData: () =>
|
ImportPersistentData: () => {
|
||||||
{
|
if (!window.JSZip) {
|
||||||
if (!window.JSZip)
|
|
||||||
{
|
|
||||||
alert("JSZip library not found. Aborting");
|
alert("JSZip library not found. Aborting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -85,11 +203,11 @@ var EmscriptenDeps = {
|
|||||||
processFolder(dir);
|
processFolder(dir);
|
||||||
await new Promise(res => Module.FS.syncfs(false, res));
|
await new Promise(res => Module.FS.syncfs(false, res));
|
||||||
};
|
};
|
||||||
if (!confirm("Are you sure? This will wipe all current data.")) return;
|
|
||||||
alert("Select a zip file");
|
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
|
input.accept = ".zip";
|
||||||
input.addEventListener("change", async (e) => {
|
input.addEventListener("change", async (e) => {
|
||||||
|
if (!confirm("Are you sure? This will wipe all current data.")) return;
|
||||||
let zip = new JSZip();
|
let zip = new JSZip();
|
||||||
try {
|
try {
|
||||||
zip = await zip.loadAsync(e.target.files[0]);
|
zip = await zip.loadAsync(e.target.files[0]);
|
||||||
@@ -111,7 +229,42 @@ var EmscriptenDeps = {
|
|||||||
console.log("Database restored");
|
console.log("Database restored");
|
||||||
})
|
})
|
||||||
input.click();
|
input.click();
|
||||||
|
},
|
||||||
|
SelectFilesToUpload: () => {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.multiple = true;
|
||||||
|
input.accept = ".park, .sv4, .sv6";
|
||||||
|
input.addEventListener("change", async (e) => {
|
||||||
|
console.log(input);
|
||||||
|
if (!input.files || input.files.length === 0) return;
|
||||||
|
let success = 0;
|
||||||
|
for (let i=0; i<input.files.length; i++) {
|
||||||
|
const file = input.files[i];
|
||||||
|
const name = file.name;
|
||||||
|
const ab = await file.arrayBuffer();
|
||||||
|
const byteArray = new Uint8Array(ab);
|
||||||
|
const outputDirectory = "/persistent/save/uploads/";
|
||||||
|
try {
|
||||||
|
FS.mkdir(outputDirectory);
|
||||||
|
} catch(e) {};
|
||||||
|
try {
|
||||||
|
FS.writeFile(outputDirectory + name, byteArray);
|
||||||
|
success++;
|
||||||
|
} catch(e) {};
|
||||||
|
}
|
||||||
|
alert("Successfully uploaded " + success + " files");
|
||||||
|
});
|
||||||
|
input.click();
|
||||||
|
},
|
||||||
|
GetLocaleCurrencyCode: () => {
|
||||||
|
// There is no way to get this directly from javascript! Assume based off of user country
|
||||||
|
const localeToCurrencyMap = {AD:"EUR",AE:"AED",AF:"AFN",AG:"XCD",AI:"XCD",AL:"ALL",AM:"AMD",AN:"ANG",AO:"AOA",AQ:"USD",AR:"ARS",AS:"USD",AT:"EUR",AU:"AUD",AW:"AWG",AX:"EUR",AZ:"AZN",BA:"BAM",BB:"BBD",BD:"BDT",BE:"EUR",BF:"XOF",BG:"BGN",BH:"BHD",BI:"BIF",BJ:"XOF",BL:"EUR",BM:"BMD",BN:"BND",BO:"BOB",BQ:"USD",BR:"BRL",BS:"BSD",BT:"BTN",BV:"NOK",BW:"BWP",BY:"BYN",BZ:"BZD",CA:"CAD",CC:"AUD",CD:"CDF",CF:"XAF",CG:"XAF",CH:"CHF",CI:"XOF",CK:"NZD",CL:"CLP",CM:"XAF",CN:"CNY",CO:"COP",CR:"CRC",CU:"CUP",CV:"CVE",CW:"ANG",CX:"AUD",CY:"EUR",CZ:"CZK",DE:"EUR",DJ:"DJF",DK:"DKK",DM:"XCD",DO:"DOP",DZ:"DZD",EC:"USD",EE:"EUR",EG:"EGP",EH:"MAD",ER:"ERN",ES:"EUR",ET:"ETB",FI:"EUR",FJ:"FJD",FK:"FKP",FM:"USD",FO:"DKK",FR:"EUR",GA:"XAF",GB:"GBP",GD:"XCD",GE:"GEL",GF:"EUR",GG:"GBP",GH:"GHS",GI:"GIP",GL:"DKK",GM:"GMD",GN:"GNF",GP:"EUR",GQ:"XAF",GR:"EUR",GS:"FKP",GT:"GTQ",GU:"USD",GW:"XOF",GY:"GYD",HK:"HKD",HM:"AUD",HN:"HNL",HR:"EUR",HT:"HTG",HU:"HUF",ID:"IDR",IE:"EUR",IL:"ILS",IM:"GBP",IN:"INR",IO:"USD",IQ:"IQD",IR:"IRR",IS:"ISK",IT:"EUR",JE:"GBP",JM:"JMD",JO:"JOD",JP:"JPY",KE:"KES",KG:"KGS",KH:"KHR",KI:"AUD",KM:"KMF",KN:"XCD",KP:"KPW",KR:"KRW",KW:"KWD",KY:"KYD",KZ:"KZT",LA:"LAK",LB:"LBP",LC:"XCD",LI:"CHF",LK:"LKR",LR:"LRD",LS:"LSL",LT:"EUR",LU:"EUR",LV:"EUR",LY:"LYD",MA:"MAD",MC:"EUR",MD:"MDL",ME:"EUR",MF:"EUR",MG:"MGA",MH:"USD",MK:"MKD",ML:"XOF",MM:"MMK",MN:"MNT",MO:"MOP",MP:"USD",MQ:"EUR",MR:"MRU",MS:"XCD",MT:"EUR",MU:"MUR",MV:"MVR",MW:"MWK",MX:"MXN",MY:"MYR",MZ:"MZN",NA:"NAD",NC:"XPF",NE:"XOF",NF:"AUD",NG:"NGN",NI:"NIO",NL:"EUR",NO:"NOK",NP:"NPR",NR:"AUD",NU:"NZD",NZ:"NZD",OM:"OMR",PA:"PAB",PE:"PEN",PF:"XPF",PG:"PGK",PH:"PHP",PK:"PKR",PL:"PLN",PM:"EUR",PN:"NZD",PR:"USD",PS:"ILS",PT:"EUR",PW:"USD",PY:"PYG",QA:"QAR",RE:"EUR",RO:"RON",RS:"RSD",RU:"RUB",RW:"RWF",SA:"SAR",SB:"SBD",SC:"SCR",SD:"SDG",SE:"SEK",SG:"SGD",SH:"SHP",SI:"EUR",SJ:"NOK",SK:"EUR",SL:"SLE",SM:"EUR",SN:"XOF",SO:"SOS",SR:"SRD",SS:"SSP",ST:"STN",SV:"USD",SX:"ANG",SY:"SYP",SZ:"SZL",TC:"USD",TD:"XAF",TF:"EUR",TG:"XOF",TH:"THB",TJ:"TJS",TK:"NZD",TL:"USD",TM:"TMT",TN:"TND",TO:"TOP",TR:"TRY",TT:"TTD",TV:"AUD",TW:"TWD",TZ:"TZS",UA:"UAH",UG:"UGX",UM:"USD",US:"USD",UY:"UYU",UZ:"UZS",VA:"EUR",VC:"XCD",VE:"VED",VG:"USD",VI:"USD",VN:"VND",VU:"VUV",WF:"XPF",WS:"WST",XK:"EUR",YE:"YER",YT:"EUR",ZA:"ZAR",ZM:"ZMW",ZW:"ZWG"};
|
||||||
|
const country = Intl.DateTimeFormat().resolvedOptions().locale.split("-").pop();
|
||||||
|
const code = localeToCurrencyMap[country];
|
||||||
|
return stringToNewUTF8(code);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mergeInto(LibraryManager.library, EmscriptenDeps);
|
autoAddDeps(EmscriptenDeps, '$SAVE');
|
||||||
|
addToLibrary(EmscriptenDeps);
|
||||||
|
|||||||
@@ -52,33 +52,32 @@
|
|||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
})
|
})
|
||||||
|
|
||||||
window.Module = await window.OPENRCT2_WEB(
|
window.Module = await window.OPENRCT2_WEB({
|
||||||
|
noInitialRun: true,
|
||||||
|
arguments: [],
|
||||||
|
preRun: [],
|
||||||
|
postRun: [],
|
||||||
|
canvas: document.getElementById("canvas"),
|
||||||
|
print: function(msg)
|
||||||
{
|
{
|
||||||
noInitialRun: true,
|
console.log(msg);
|
||||||
arguments: [],
|
},
|
||||||
preRun: [],
|
printErr: function(msg)
|
||||||
postRun: [],
|
{
|
||||||
canvas: document.getElementById("canvas"),
|
console.log(msg);
|
||||||
print: function(msg)
|
},
|
||||||
|
totalDependencies: 0,
|
||||||
|
monitorRunDependencies: () => {},
|
||||||
|
locateFile: function(fileName)
|
||||||
|
{
|
||||||
|
if (assets !== null && fileName === "openrct2.wasm")
|
||||||
{
|
{
|
||||||
console.log(msg);
|
return assets.wasm;
|
||||||
},
|
|
||||||
printErr: function(msg)
|
|
||||||
{
|
|
||||||
console.log(msg);
|
|
||||||
},
|
|
||||||
totalDependencies: 0,
|
|
||||||
monitorRunDependencies: () => {},
|
|
||||||
locateFile: function(fileName)
|
|
||||||
{
|
|
||||||
if (assets !== null && fileName === "openrct2.wasm")
|
|
||||||
{
|
|
||||||
return assets.wasm;
|
|
||||||
}
|
|
||||||
console.log("loading", fileName);
|
|
||||||
return fileName;
|
|
||||||
}
|
}
|
||||||
});
|
console.log("loading", fileName);
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Module.FS.mkdir("/persistent");
|
Module.FS.mkdir("/persistent");
|
||||||
Module.FS.mount(Module.FS.filesystems.IDBFS, {autoPersist: true}, '/persistent');
|
Module.FS.mount(Module.FS.filesystems.IDBFS, {autoPersist: true}, '/persistent');
|
||||||
@@ -91,31 +90,23 @@
|
|||||||
|
|
||||||
await new Promise(res => Module.FS.syncfs(true, res));
|
await new Promise(res => Module.FS.syncfs(true, res));
|
||||||
|
|
||||||
let configExists = fileExists("/persistent/config.ini");
|
|
||||||
if (!configExists)
|
|
||||||
{
|
|
||||||
Module.FS.writeFile("/persistent/config.ini", `
|
|
||||||
[general]
|
|
||||||
game_path = "/RCT"
|
|
||||||
uncap_fps = true
|
|
||||||
window_scale = 1.750000
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const assetsOK = await updateAssets();
|
const assetsOK = await updateAssets();
|
||||||
if (!assetsOK)
|
if (!assetsOK)
|
||||||
{
|
{
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.FS.writeFile("/OpenRCT2/changelog.txt", `EMSCRIPTEN --- README
|
|
||||||
|
|
||||||
Since we're running in the web browser, we don't have direct access to the file system.
|
let changelog = "";
|
||||||
All save data is saved under the directory /persistent.
|
try {
|
||||||
|
const request = await fetch("https://api.github.com/repos/OpenRCT2/OpenRCT2/releases/latest");
|
||||||
|
const json = JSON.parse(await request.text());
|
||||||
|
changelog = json.body;
|
||||||
|
} catch(e) {
|
||||||
|
console.log("Failed to fetch changelog with error:", e);
|
||||||
|
}
|
||||||
|
|
||||||
ALWAYS be sure to save to /persistent/saves when saving a game! Otherwise it will be wiped!
|
Module.FS.writeFile("/OpenRCT2/changelog.txt", changelog);
|
||||||
|
|
||||||
You can import/export the /persistent folder in the options menu.`);
|
|
||||||
document.getElementById("loadingWebassembly").remove();
|
document.getElementById("loadingWebassembly").remove();
|
||||||
|
|
||||||
let filesFound = fileExists("/RCT/Data/ch.dat");
|
let filesFound = fileExists("/RCT/Data/ch.dat");
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ emcmake cmake ../ \
|
|||||||
-DLIBZIP_LIBRARIES="$LIBZIP_ROOT/build/lib/libzip.a" \
|
-DLIBZIP_LIBRARIES="$LIBZIP_ROOT/build/lib/libzip.a" \
|
||||||
-DZSTD_LIBRARIES="$ZSTD_ROOT/build/build/lib/libzstd.a" \
|
-DZSTD_LIBRARIES="$ZSTD_ROOT/build/build/lib/libzstd.a" \
|
||||||
-DEMSCRIPTEN_FLAGS="-s USE_SDL=2 -s USE_ZLIB=1 -s USE_BZIP2=1 -s USE_LIBPNG=1 -pthread -O3" \
|
-DEMSCRIPTEN_FLAGS="-s USE_SDL=2 -s USE_ZLIB=1 -s USE_BZIP2=1 -s USE_LIBPNG=1 -pthread -O3" \
|
||||||
-DEMSCRIPTEN_LDFLAGS="-Wno-pthreads-mem-growth -s SAFE_HEAP=0 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4GB -s INITIAL_MEMORY=2GB -s STACK_SIZE=8388608 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s PTHREAD_POOL_SIZE=120 -pthread -s EXPORTED_RUNTIME_METHODS=ccall,FS,callMain,UTF8ToString,stringToNewUTF8 -lidbfs.js --use-preload-plugins -s MODULARIZE=1 -s 'EXPORT_NAME=\"OPENRCT2_WEB\"'"
|
-DEMSCRIPTEN_EXPORTED_FUNCTIONS="_GetVersion,_main" \
|
||||||
|
-DEMSCRIPTEN_LDFLAGS="-Wno-pthreads-mem-growth -s SAFE_HEAP=0 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4GB -s INITIAL_MEMORY=2GB -s STACK_SIZE=8388608 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s PTHREAD_POOL_SIZE=120 -pthread -s EXPORTED_RUNTIME_METHODS=ccall,FS,callMain,UTF8ToString,stringToNewUTF8 -lidbfs.js --use-preload-plugins -s MODULARIZE=1 -s 'EXPORT_NAME=\"OPENRCT2_WEB\"' -Wl,--export-if-defined=LoadGameCallback"
|
||||||
|
|
||||||
emmake ninja
|
emmake ninja || exit 1
|
||||||
|
|
||||||
rm -rf www/
|
rm -rf www/
|
||||||
mkdir -p www/
|
mkdir -p www/
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ if (EMSCRIPTEN)
|
|||||||
endif ()
|
endif ()
|
||||||
set(SHARED_FLAGS "-fexceptions")
|
set(SHARED_FLAGS "-fexceptions")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USE_FLAGS} ${SHARED_FLAGS}")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USE_FLAGS} ${SHARED_FLAGS}")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMSCRIPTEN_LDFLAGS} --bind ${SHARED_FLAGS} -s EXPORTED_FUNCTIONS=_GetVersion,_main --js-library ${ROOT_DIR}/emscripten/deps.js")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMSCRIPTEN_LDFLAGS} --bind ${SHARED_FLAGS} -sEXPORTED_FUNCTIONS=${EMSCRIPTEN_EXPORTED_FUNCTIONS} --js-library ${ROOT_DIR}/emscripten/deps.js")
|
||||||
find_package(SpeexDSP REQUIRED)
|
find_package(SpeexDSP REQUIRED)
|
||||||
elseif (MSVC)
|
elseif (MSVC)
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
|
|||||||
@@ -28,6 +28,13 @@
|
|||||||
#include <openrct2/ui/WindowManager.h>
|
#include <openrct2/ui/WindowManager.h>
|
||||||
#include <openrct2/windows/Intent.h>
|
#include <openrct2/windows/Intent.h>
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
extern "C" {
|
||||||
|
extern void EmscriptenLoadGame(LoadSaveType type);
|
||||||
|
extern void EmscriptenSaveGame(bool isTrackDesign, bool isAutosave, bool saveAs, LoadSaveType type);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace OpenRCT2::Ui::FileBrowser
|
namespace OpenRCT2::Ui::FileBrowser
|
||||||
{
|
{
|
||||||
static LoadSaveCallback _loadSaveCallback;
|
static LoadSaveCallback _loadSaveCallback;
|
||||||
@@ -37,6 +44,19 @@ namespace OpenRCT2::Ui::FileBrowser
|
|||||||
{
|
{
|
||||||
RegisterCallback(callback);
|
RegisterCallback(callback);
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
if (action == LoadSaveAction::save)
|
||||||
|
{
|
||||||
|
Select("/save.park", action, type, trackDesign);
|
||||||
|
EmscriptenSaveGame(type == LoadSaveType::track, false, true, type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmscriptenLoadGame(type);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
auto hasFilePicker = OpenRCT2::GetContext()->GetUiContext().HasFilePicker();
|
auto hasFilePicker = OpenRCT2::GetContext()->GetUiContext().HasFilePicker();
|
||||||
auto& config = Config::Get().general;
|
auto& config = Config::Get().general;
|
||||||
|
|
||||||
@@ -526,3 +546,10 @@ namespace OpenRCT2::Ui::FileBrowser
|
|||||||
return ContextOpenCommonFileDialog(desc);
|
return ContextOpenCommonFileDialog(desc);
|
||||||
}
|
}
|
||||||
} // namespace OpenRCT2::Ui::FileBrowser
|
} // namespace OpenRCT2::Ui::FileBrowser
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
extern "C" void LoadGameCallback(const char* path, LoadSaveType action)
|
||||||
|
{
|
||||||
|
OpenRCT2::Ui::FileBrowser::Select(path, LoadSaveAction::load, action, nullptr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -66,3 +66,7 @@ namespace OpenRCT2::Ui::FileBrowser
|
|||||||
WindowBase* OpenPreferred(
|
WindowBase* OpenPreferred(
|
||||||
LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, TrackDesign* trackDesign);
|
LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, TrackDesign* trackDesign);
|
||||||
} // namespace OpenRCT2::Ui::FileBrowser
|
} // namespace OpenRCT2::Ui::FileBrowser
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
extern "C" void LoadGameCallback(const char* path, LoadSaveType action);
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -48,6 +48,13 @@
|
|||||||
#include <openrct2/world/Scenery.h>
|
#include <openrct2/world/Scenery.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
extern "C" {
|
||||||
|
extern void EmscriptenLoadGame();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace OpenRCT2::Numerics;
|
using namespace OpenRCT2::Numerics;
|
||||||
|
|
||||||
namespace OpenRCT2::Ui::Windows
|
namespace OpenRCT2::Ui::Windows
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ target_link_libraries(libopenrct2 Threads::Threads)
|
|||||||
|
|
||||||
# For some reason, these flags break the check for pthreads. Add them after.
|
# For some reason, these flags break the check for pthreads. Add them after.
|
||||||
if (EMSCRIPTEN)
|
if (EMSCRIPTEN)
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS=_GetVersion,_main --js-library ${ROOT_DIR}/emscripten/deps.js")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS=${EMSCRIPTEN_EXPORTED_FUNCTIONS} --js-library ${ROOT_DIR}/emscripten/deps.js")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT MINGW AND NOT MSVC AND NOT EMSCRIPTEN)
|
if (NOT MINGW AND NOT MSVC AND NOT EMSCRIPTEN)
|
||||||
|
|||||||
@@ -76,6 +76,13 @@
|
|||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
extern "C" {
|
||||||
|
extern void EmscriptenSaveGame(bool isTrackDesign, bool isAutosave, bool saveAs, LoadSaveType type);
|
||||||
|
extern void EmscriptenResetAutosave();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
using namespace OpenRCT2;
|
||||||
|
|
||||||
uint16_t gCurrentDeltaTime;
|
uint16_t gCurrentDeltaTime;
|
||||||
@@ -455,8 +462,14 @@ void SaveGame()
|
|||||||
{
|
{
|
||||||
if (!gFirstTimeSaving && !gIsAutosaveLoaded)
|
if (!gFirstTimeSaving && !gIsAutosaveLoaded)
|
||||||
{
|
{
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
const auto savePath = Path::WithExtension(gScenarioSavePath, ".park");
|
const auto savePath = Path::WithExtension(gScenarioSavePath, ".park");
|
||||||
SaveGameWithName(savePath);
|
SaveGameWithName(savePath);
|
||||||
|
#else
|
||||||
|
const auto savePath = Path::WithExtension("save", ".park");
|
||||||
|
SaveGameWithName(savePath);
|
||||||
|
EmscriptenSaveGame(false, false, false, LoadSaveType::park);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -512,6 +525,7 @@ void SaveGameAs()
|
|||||||
ContextOpenIntent(intent.get());
|
ContextOpenIntent(intent.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
static void LimitAutosaveCount(const size_t numberOfFilesToKeep, bool processLandscapeFolder)
|
static void LimitAutosaveCount(const size_t numberOfFilesToKeep, bool processLandscapeFolder)
|
||||||
{
|
{
|
||||||
size_t autosavesCount = 0;
|
size_t autosavesCount = 0;
|
||||||
@@ -613,6 +627,14 @@ void GameAutosave()
|
|||||||
if (!ScenarioSave(gameState, path, saveFlags))
|
if (!ScenarioSave(gameState, path, saveFlags))
|
||||||
Console::Error::WriteLine("Could not autosave the scenario. Is the save folder writeable?");
|
Console::Error::WriteLine("Could not autosave the scenario. Is the save folder writeable?");
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
void GameAutosave()
|
||||||
|
{
|
||||||
|
const auto savePath = Path::WithExtension("save", ".park");
|
||||||
|
SaveGameWithName(savePath);
|
||||||
|
EmscriptenSaveGame(false, true, false, LoadSaveType::park);
|
||||||
|
}
|
||||||
|
#endif // __EMSCRIPTEN__
|
||||||
|
|
||||||
static void GameLoadOrQuitNoSavePromptCallback(ModalResult result, const utf8* path)
|
static void GameLoadOrQuitNoSavePromptCallback(ModalResult result, const utf8* path)
|
||||||
{
|
{
|
||||||
@@ -643,6 +665,9 @@ static void NewGameWindowCallback(const utf8* path)
|
|||||||
GetContext()->LoadParkFromFile(path, false, true);
|
GetContext()->LoadParkFromFile(path, false, true);
|
||||||
GameLoadScripts();
|
GameLoadScripts();
|
||||||
GameNotifyMapChanged();
|
GameNotifyMapChanged();
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
EmscriptenResetAutosave();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -685,6 +710,9 @@ void GameLoadOrQuitNoSavePrompt()
|
|||||||
gFirstTimeSaving = true;
|
gFirstTimeSaving = true;
|
||||||
GameNotifyMapChange();
|
GameNotifyMapChange();
|
||||||
GameUnloadScripts();
|
GameUnloadScripts();
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
EmscriptenResetAutosave();
|
||||||
|
#endif
|
||||||
|
|
||||||
auto* context = OpenRCT2::GetContext();
|
auto* context = OpenRCT2::GetContext();
|
||||||
context->SetActiveScene(context->GetTitleScene());
|
context->SetActiveScene(context->GetTitleScene());
|
||||||
|
|||||||
138
src/openrct2/platform/Platform.Emscripten.cpp
Normal file
138
src/openrct2/platform/Platform.Emscripten.cpp
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Copyright (c) 2014-2025 OpenRCT2 developers
|
||||||
|
*
|
||||||
|
* For a complete list of all authors, please refer to contributors.md
|
||||||
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||||
|
*
|
||||||
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
|
#include "../Diagnostic.h"
|
||||||
|
#include "../GameState.h"
|
||||||
|
#include "../core/Compression.h"
|
||||||
|
#include "../core/File.h"
|
||||||
|
#include "../core/Guard.hpp"
|
||||||
|
#include "../core/MemoryStream.h"
|
||||||
|
#include "../localisation/Language.h"
|
||||||
|
#include "../park/ParkFile.h"
|
||||||
|
|
||||||
|
#include <emscripten.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
extern char* GetLocaleCurrencyCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace OpenRCT2::Platform
|
||||||
|
{
|
||||||
|
std::string GetFolderPath(SpecialFolder folder)
|
||||||
|
{
|
||||||
|
switch (folder)
|
||||||
|
{
|
||||||
|
case SpecialFolder::userCache:
|
||||||
|
case SpecialFolder::userConfig:
|
||||||
|
case SpecialFolder::userData:
|
||||||
|
case SpecialFolder::userHome:
|
||||||
|
return "/persistent";
|
||||||
|
default:
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetDocsPath()
|
||||||
|
{
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetInstallPath()
|
||||||
|
{
|
||||||
|
return "/OpenRCT2";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetCurrentExecutablePath()
|
||||||
|
{
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
u8string StrDecompToPrecomp(u8string_view input)
|
||||||
|
{
|
||||||
|
return u8string(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleSpecialCommandLineArgument(const char* argument)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t GetLocaleLanguage()
|
||||||
|
{
|
||||||
|
auto locale = reinterpret_cast<char*>(EM_ASM_PTR({
|
||||||
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||||
|
return stringToNewUTF8(locale);
|
||||||
|
}));
|
||||||
|
auto languageId = LanguageGetIDFromLocale(locale);
|
||||||
|
free(locale);
|
||||||
|
return languageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrencyType GetLocaleCurrency()
|
||||||
|
{
|
||||||
|
auto localeCurrencyCode = GetLocaleCurrencyCode();
|
||||||
|
auto currency = Platform::GetCurrencyValue(localeCurrencyCode);
|
||||||
|
free(localeCurrencyCode);
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeasurementFormat GetLocaleMeasurementFormat()
|
||||||
|
{
|
||||||
|
int isImperial = EM_ASM_INT({
|
||||||
|
// There is no way to get this directly from javascript! Assume based off of user country
|
||||||
|
const country = Intl.DateTimeFormat().resolvedOptions().locale.split("-").pop();
|
||||||
|
return ([ "LR", "MM", "US" ]).includes(country) ? 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return isImperial == 1 ? MeasurementFormat::Imperial : MeasurementFormat::Metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetSteamPath()
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
u8string GetRCT1SteamDir()
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
u8string GetRCT2SteamDir()
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
u8string GetRCTClassicSteamDir()
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_TTF
|
||||||
|
std::string GetFontPath(const TTFFontDescriptor& font)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<std::string_view> GetSearchablePathsRCT1()
|
||||||
|
{
|
||||||
|
return { "/RCT" };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string_view> GetSearchablePathsRCT2()
|
||||||
|
{
|
||||||
|
return { "/RCT" };
|
||||||
|
}
|
||||||
|
} // namespace OpenRCT2::Platform
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
#if defined(__unix__) && !defined(__ANDROID__) && !defined(__APPLE__)
|
#if defined(__unix__) && !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
|
||||||
|
|
||||||
#include "../Diagnostic.h"
|
#include "../Diagnostic.h"
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ namespace OpenRCT2::Platform
|
|||||||
{
|
{
|
||||||
LOG_FATAL("failed to get process path");
|
LOG_FATAL("failed to get process path");
|
||||||
}
|
}
|
||||||
#elif defined(__OpenBSD__) || defined(__EMSCRIPTEN__)
|
#elif defined(__OpenBSD__)
|
||||||
// There is no way to get the path name of a running executable.
|
// There is no way to get the path name of a running executable.
|
||||||
// If you are not using the port or package, you may have to change this line!
|
// If you are not using the port or package, you may have to change this line!
|
||||||
strlcpy(exePath, "/usr/local/bin/", sizeof(exePath));
|
strlcpy(exePath, "/usr/local/bin/", sizeof(exePath));
|
||||||
|
|||||||
Reference in New Issue
Block a user