From 1eff246b1a4a6a058d67c8578dcfebcc8fec87bd Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Thu, 7 Aug 2025 08:34:26 -0500 Subject: [PATCH] Emscripten: Reimplement save/load --- emscripten/deps.js | 179 ++++++++++++++++-- emscripten/static/index.js | 75 ++++---- scripts/build-emscripten | 5 +- src/openrct2-ui/CMakeLists.txt | 2 +- src/openrct2-ui/interface/FileBrowser.cpp | 27 +++ src/openrct2-ui/interface/FileBrowser.h | 4 + src/openrct2-ui/windows/TopToolbar.cpp | 7 + src/openrct2/CMakeLists.txt | 2 +- src/openrct2/Game.cpp | 28 +++ src/openrct2/platform/Platform.Emscripten.cpp | 138 ++++++++++++++ src/openrct2/platform/Platform.Linux.cpp | 4 +- 11 files changed, 410 insertions(+), 61 deletions(-) create mode 100644 src/openrct2/platform/Platform.Emscripten.cpp diff --git a/emscripten/deps.js b/emscripten/deps.js index 2967b9e6a5..38dc592c67 100644 --- a/emscripten/deps.js +++ b/emscripten/deps.js @@ -6,16 +6,136 @@ * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ + var EmscriptenDeps = { - ExportPersistentData: () => - { - if (!window.JSZip) - { + $SAVE: { + filehandle: null, + + 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"); return; } - const zipFolder = (folder) => - { + const zipFolder = (folder) => { let zip = new JSZip(); const processFolder = (name) => { let contents; @@ -50,10 +170,8 @@ var EmscriptenDeps = { setTimeout(() => URL.revokeObjectURL(a.href), 1000); }) }, - ImportPersistentData: () => - { - if (!window.JSZip) - { + ImportPersistentData: () => { + if (!window.JSZip) { alert("JSZip library not found. Aborting"); return; } @@ -85,11 +203,11 @@ var EmscriptenDeps = { processFolder(dir); 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"); input.type = "file"; + input.accept = ".zip"; input.addEventListener("change", async (e) => { + if (!confirm("Are you sure? This will wipe all current data.")) return; let zip = new JSZip(); try { zip = await zip.loadAsync(e.target.files[0]); @@ -111,7 +229,42 @@ var EmscriptenDeps = { console.log("Database restored"); }) 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 { + // 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); diff --git a/emscripten/static/index.js b/emscripten/static/index.js index 61b320079d..feb0f09395 100644 --- a/emscripten/static/index.js +++ b/emscripten/static/index.js @@ -52,33 +52,32 @@ 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, - arguments: [], - preRun: [], - postRun: [], - canvas: document.getElementById("canvas"), - print: function(msg) + console.log(msg); + }, + printErr: function(msg) + { + console.log(msg); + }, + totalDependencies: 0, + monitorRunDependencies: () => {}, + locateFile: function(fileName) + { + if (assets !== null && fileName === "openrct2.wasm") { - console.log(msg); - }, - 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; + return assets.wasm; } - }); + console.log("loading", fileName); + return fileName; + } + }); Module.FS.mkdir("/persistent"); Module.FS.mount(Module.FS.filesystems.IDBFS, {autoPersist: true}, '/persistent'); @@ -91,31 +90,23 @@ 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(); 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. -All save data is saved under the directory /persistent. + let changelog = ""; + 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(); let filesFound = fileExists("/RCT/Data/ch.dat"); diff --git a/scripts/build-emscripten b/scripts/build-emscripten index f6f516205d..1a96259468 100755 --- a/scripts/build-emscripten +++ b/scripts/build-emscripten @@ -28,9 +28,10 @@ emcmake cmake ../ \ -DLIBZIP_LIBRARIES="$LIBZIP_ROOT/build/lib/libzip.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_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/ mkdir -p www/ diff --git a/src/openrct2-ui/CMakeLists.txt b/src/openrct2-ui/CMakeLists.txt index ac0809deb1..cd3e3ccc7f 100644 --- a/src/openrct2-ui/CMakeLists.txt +++ b/src/openrct2-ui/CMakeLists.txt @@ -12,7 +12,7 @@ if (EMSCRIPTEN) endif () set(SHARED_FLAGS "-fexceptions") 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) elseif (MSVC) find_package(SDL2 REQUIRED) diff --git a/src/openrct2-ui/interface/FileBrowser.cpp b/src/openrct2-ui/interface/FileBrowser.cpp index 3d6220d3b0..693687e803 100644 --- a/src/openrct2-ui/interface/FileBrowser.cpp +++ b/src/openrct2-ui/interface/FileBrowser.cpp @@ -28,6 +28,13 @@ #include #include +#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 { static LoadSaveCallback _loadSaveCallback; @@ -37,6 +44,19 @@ namespace OpenRCT2::Ui::FileBrowser { 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& config = Config::Get().general; @@ -526,3 +546,10 @@ namespace OpenRCT2::Ui::FileBrowser return ContextOpenCommonFileDialog(desc); } } // 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 diff --git a/src/openrct2-ui/interface/FileBrowser.h b/src/openrct2-ui/interface/FileBrowser.h index 7593135f61..e2d8501068 100644 --- a/src/openrct2-ui/interface/FileBrowser.h +++ b/src/openrct2-ui/interface/FileBrowser.h @@ -66,3 +66,7 @@ namespace OpenRCT2::Ui::FileBrowser WindowBase* OpenPreferred( LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, TrackDesign* trackDesign); } // namespace OpenRCT2::Ui::FileBrowser + +#ifdef __EMSCRIPTEN__ +extern "C" void LoadGameCallback(const char* path, LoadSaveType action); +#endif diff --git a/src/openrct2-ui/windows/TopToolbar.cpp b/src/openrct2-ui/windows/TopToolbar.cpp index e4dc752108..9ac6c50ab1 100644 --- a/src/openrct2-ui/windows/TopToolbar.cpp +++ b/src/openrct2-ui/windows/TopToolbar.cpp @@ -48,6 +48,13 @@ #include #include +#ifdef __EMSCRIPTEN__ + #include +extern "C" { +extern void EmscriptenLoadGame(); +} +#endif + using namespace OpenRCT2::Numerics; namespace OpenRCT2::Ui::Windows diff --git a/src/openrct2/CMakeLists.txt b/src/openrct2/CMakeLists.txt index 6d4de005ec..5a61c88d2e 100644 --- a/src/openrct2/CMakeLists.txt +++ b/src/openrct2/CMakeLists.txt @@ -182,7 +182,7 @@ target_link_libraries(libopenrct2 Threads::Threads) # For some reason, these flags break the check for pthreads. Add them after. 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() if (NOT MINGW AND NOT MSVC AND NOT EMSCRIPTEN) diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index 0fd858f5f4..d9ca7881bd 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -76,6 +76,13 @@ #include #include +#ifdef __EMSCRIPTEN__ +extern "C" { +extern void EmscriptenSaveGame(bool isTrackDesign, bool isAutosave, bool saveAs, LoadSaveType type); +extern void EmscriptenResetAutosave(); +} +#endif + using namespace OpenRCT2; uint16_t gCurrentDeltaTime; @@ -455,8 +462,14 @@ void SaveGame() { if (!gFirstTimeSaving && !gIsAutosaveLoaded) { +#ifndef __EMSCRIPTEN__ const auto savePath = Path::WithExtension(gScenarioSavePath, ".park"); SaveGameWithName(savePath); +#else + const auto savePath = Path::WithExtension("save", ".park"); + SaveGameWithName(savePath); + EmscriptenSaveGame(false, false, false, LoadSaveType::park); +#endif } else { @@ -512,6 +525,7 @@ void SaveGameAs() ContextOpenIntent(intent.get()); } +#ifndef __EMSCRIPTEN__ static void LimitAutosaveCount(const size_t numberOfFilesToKeep, bool processLandscapeFolder) { size_t autosavesCount = 0; @@ -613,6 +627,14 @@ void GameAutosave() if (!ScenarioSave(gameState, path, saveFlags)) 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) { @@ -643,6 +665,9 @@ static void NewGameWindowCallback(const utf8* path) GetContext()->LoadParkFromFile(path, false, true); GameLoadScripts(); GameNotifyMapChanged(); +#ifdef __EMSCRIPTEN__ + EmscriptenResetAutosave(); +#endif } /** @@ -685,6 +710,9 @@ void GameLoadOrQuitNoSavePrompt() gFirstTimeSaving = true; GameNotifyMapChange(); GameUnloadScripts(); +#ifdef __EMSCRIPTEN__ + EmscriptenResetAutosave(); +#endif auto* context = OpenRCT2::GetContext(); context->SetActiveScene(context->GetTitleScene()); diff --git a/src/openrct2/platform/Platform.Emscripten.cpp b/src/openrct2/platform/Platform.Emscripten.cpp new file mode 100644 index 0000000000..5eb5453bdd --- /dev/null +++ b/src/openrct2/platform/Platform.Emscripten.cpp @@ -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 + +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(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 GetSearchablePathsRCT1() + { + return { "/RCT" }; + } + + std::vector GetSearchablePathsRCT2() + { + return { "/RCT" }; + } +} // namespace OpenRCT2::Platform + +#endif diff --git a/src/openrct2/platform/Platform.Linux.cpp b/src/openrct2/platform/Platform.Linux.cpp index f233a3ea75..28ab83344f 100644 --- a/src/openrct2/platform/Platform.Linux.cpp +++ b/src/openrct2/platform/Platform.Linux.cpp @@ -7,7 +7,7 @@ * 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" @@ -201,7 +201,7 @@ namespace OpenRCT2::Platform { 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. // If you are not using the port or package, you may have to change this line! strlcpy(exePath, "/usr/local/bin/", sizeof(exePath));