mirror of
https://github.com/OpenRCT2/OpenRCT2.git
synced 2025-01-22 10:21:57 -05:00
Merge pull request #23515 from ethanaobrien/develop
Fix emscripten (webassembly) support
This commit is contained in:
commit
a002834f39
29 changed files with 684 additions and 31 deletions
23
.github/workflows/ci.yml
vendored
23
.github/workflows/ci.yml
vendored
|
@ -627,6 +627,29 @@ jobs:
|
|||
name: OpenRCT2-${{ needs.build_variables.outputs.name }}-Android
|
||||
path: artifacts
|
||||
if-no-files-found: error
|
||||
emscripten:
|
||||
name: Emscripten
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-code-formatting, build_variables]
|
||||
container: openrct2/openrct2-build:19-emscripten
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.13
|
||||
with:
|
||||
key: emscripten
|
||||
- name: Install GCC problem matcher
|
||||
uses: ammaraskar/gcc-problem-matcher@master
|
||||
- name: Build OpenRCT2
|
||||
run: |
|
||||
. scripts/setenv
|
||||
build-emscripten
|
||||
- name: Upload artifacts (CI)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: build/www
|
||||
name: OpenRCT2-${{ needs.build_variables.outputs.name }}-emscripten
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
|
|
40
cmake/FindSpeexDSP.cmake
Normal file
40
cmake/FindSpeexDSP.cmake
Normal file
|
@ -0,0 +1,40 @@
|
|||
#
|
||||
# - Find speexdsp libraries
|
||||
#
|
||||
# SPEEXDSP_INCLUDE_DIRS - where to find speexdsp headers.
|
||||
# SPEEXDSP_LIBRARIES - List of libraries when using speexdsp.
|
||||
# SPEEXDSP_FOUND - True if speexdsp is found.
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_search_module(PC_SPEEXDSP QUIET speexdsp)
|
||||
|
||||
find_path(SPEEXDSP_INCLUDE_DIR
|
||||
NAMES
|
||||
speex/speex_resampler.h
|
||||
HINTS
|
||||
${PC_SPEEXDSP_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_library(SPEEXDSP_LIBRARY
|
||||
NAMES
|
||||
speexdsp
|
||||
HINTS
|
||||
${PC_SPEEXDSP_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(SpeexDSP
|
||||
REQUIRED_VARS SPEEXDSP_LIBRARY SPEEXDSP_INCLUDE_DIR
|
||||
VERSION_VAR PC_SPEEXDSP_VERSION)
|
||||
|
||||
if(SPEEXDSP_FOUND)
|
||||
set(SPEEXDSP_LIBRARIES ${SPEEXDSP_LIBRARY})
|
||||
set(SPEEXDSP_INCLUDE_DIRS ${SPEEXDSP_INCLUDE_DIR})
|
||||
set(SPEEX_INCLUDE_DIRS ${SPEEXDSP_INCLUDE_DIR})
|
||||
else()
|
||||
set(SPEEXDSP_LIBRARIES)
|
||||
set(SPEEXDSP_INCLUDE_DIRS)
|
||||
set(SPEEX_INCLUDE_DIRS)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(SPEEXDSP_LIBRARIES SPEEXDSP_INCLUDE_DIRS SPEEX_INCLUDE_DIRS)
|
|
@ -3792,3 +3792,5 @@ STR_6726 :Y:
|
|||
STR_6727 :Dive Loop (left)
|
||||
STR_6728 :Dive Loop (right)
|
||||
STR_6729 :Cable lift hill must start immediately after station or block brake
|
||||
STR_6730 :Export emscripten data
|
||||
STR_6731 :Import emscripten data
|
||||
|
|
117
emscripten/deps.js
Normal file
117
emscripten/deps.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
var EmscriptenDeps = {
|
||||
ExportPersistentData: () =>
|
||||
{
|
||||
if (!window.JSZip)
|
||||
{
|
||||
alert("JSZip library not found. Aborting");
|
||||
return;
|
||||
}
|
||||
const zipFolder = (folder) =>
|
||||
{
|
||||
let zip = new JSZip();
|
||||
const processFolder = (name) => {
|
||||
let contents;
|
||||
try {
|
||||
contents = Module.FS.readdir(name);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
contents.forEach((entry) => {
|
||||
if ([".", ".."].includes(entry)) return;
|
||||
try {
|
||||
Module.FS.readFile(name + entry);
|
||||
processFile(name + entry);
|
||||
} catch(e) {
|
||||
processFolder(name + entry + "/");
|
||||
}
|
||||
})
|
||||
}
|
||||
const processFile = (name) => {
|
||||
zip.file(name, Module.FS.readFile(name));
|
||||
}
|
||||
processFolder(folder);
|
||||
return zip;
|
||||
}
|
||||
const zip = zipFolder("/persistent/");
|
||||
zip.generateAsync({type: "blob"}).then(blob => {
|
||||
const a = document.createElement("a");
|
||||
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = "OpenRCT2-emscripten.zip";
|
||||
a.click();
|
||||
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
||||
})
|
||||
},
|
||||
ImportPersistentData: () =>
|
||||
{
|
||||
if (!window.JSZip)
|
||||
{
|
||||
alert("JSZip library not found. Aborting");
|
||||
return;
|
||||
}
|
||||
const clearDatabase = async(dir) => {
|
||||
await new Promise(res => Module.FS.syncfs(false, res));
|
||||
const processFolder = (path) => {
|
||||
let contents;
|
||||
try {
|
||||
contents = Module.FS.readdir(path);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
contents.forEach((entry) => {
|
||||
if ([".", ".."].includes(entry)) return;
|
||||
try {
|
||||
Module.FS.readFile(path + entry);
|
||||
Module.FS.unlink(path + entry);
|
||||
} catch(e) {
|
||||
processFolder(path + entry + "/");
|
||||
}
|
||||
})
|
||||
if (path === dir) return;
|
||||
try {
|
||||
Module.FS.rmdir(path, {recursive: true});
|
||||
} catch(e) {
|
||||
console.log("Could not remove:", path);
|
||||
}
|
||||
}
|
||||
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.addEventListener("change", async (e) => {
|
||||
let zip = new JSZip();
|
||||
try {
|
||||
zip = await zip.loadAsync(e.target.files[0]);
|
||||
} catch(e) {
|
||||
alert("Not a zip file!");
|
||||
return;
|
||||
}
|
||||
await clearDatabase("/persistent/");
|
||||
for (const k in zip.files) {
|
||||
const entry = zip.files[k];
|
||||
if (entry.dir) {
|
||||
try {
|
||||
Module.FS.mkdir("/"+k);
|
||||
} catch(e) {}
|
||||
} else {
|
||||
Module.FS.writeFile("/"+k, await entry.async("uint8array"));
|
||||
}
|
||||
}
|
||||
console.log("Database restored");
|
||||
})
|
||||
input.click();
|
||||
}
|
||||
};
|
||||
|
||||
mergeInto(LibraryManager.library, EmscriptenDeps);
|
30
emscripten/static/index.html
Normal file
30
emscripten/static/index.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>OpenRCT2</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js" crossorigin=anonymous></script>
|
||||
<script src="openrct2.js"></script>
|
||||
<style>
|
||||
* { padding: 0; margin: 0; }
|
||||
canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<script src="index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="loadingWebassembly">Please wait... Loading webassembly</p>
|
||||
<div id="beforeLoad" style="display:none;">
|
||||
<p id="statusMsg">Please select your RCT2 assets (zip file):</p>
|
||||
<input type="file" id="selectFile"></input>
|
||||
</div>
|
||||
<canvas id="canvas" style="display:none;"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
|
231
emscripten/static/index.js
Normal file
231
emscripten/static/index.js
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
(async () =>
|
||||
{
|
||||
await new Promise(res => window.addEventListener("DOMContentLoaded", res));
|
||||
if (!window.SharedArrayBuffer)
|
||||
{
|
||||
document.getElementById("loadingWebassembly").innerText = "Error! SharedArrayBuffer is not defined. This page required the CORP and COEP response headers.";
|
||||
}
|
||||
if (!window.WebAssembly)
|
||||
{
|
||||
document.getElementById("loadingWebassembly").innerText = "Error! This page requires WebAssembly. Please upgrade your browser or enable WebAssembly support.";
|
||||
}
|
||||
|
||||
window.Module = await window.OPENRCT2_WEB(
|
||||
{
|
||||
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)
|
||||
{
|
||||
console.log("loading", fileName);
|
||||
return fileName;
|
||||
}
|
||||
});
|
||||
|
||||
Module.FS.mkdir("/persistent");
|
||||
Module.FS.mount(Module.FS.filesystems.IDBFS, {autoPersist: true}, '/persistent');
|
||||
|
||||
Module.FS.mkdir("/RCT");
|
||||
Module.FS.mount(Module.FS.filesystems.IDBFS, {autoPersist: true}, '/RCT');
|
||||
|
||||
Module.FS.mkdir("/OpenRCT2");
|
||||
Module.FS.mount(Module.FS.filesystems.IDBFS, {autoPersist: true}, '/OpenRCT2');
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
ALWAYS be sure to save to /persistent/saves when saving a game! Otherwise it will be wiped!
|
||||
|
||||
You can import/export the /persistent folder in the options menu.`);
|
||||
document.getElementById("loadingWebassembly").remove();
|
||||
|
||||
let filesFound = fileExists("/RCT/Data/ch.dat");
|
||||
|
||||
if (!filesFound)
|
||||
{
|
||||
document.getElementById("beforeLoad").style.display = "";
|
||||
await new Promise(res =>
|
||||
{
|
||||
document.getElementById("selectFile").addEventListener("change", async (e) =>
|
||||
{
|
||||
if (await extractZip(e.target.files[0], (zip) =>
|
||||
{
|
||||
if (zip !== null)
|
||||
{
|
||||
if (zip.file("Data/ch.dat"))
|
||||
{
|
||||
document.getElementById("beforeLoad").remove();
|
||||
return "/RCT/";
|
||||
}
|
||||
else if (zip.file("RCT/Data/ch.dat"))
|
||||
{
|
||||
document.getElementById("beforeLoad").remove();
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
document.getElementById("statusMsg").innerText = "That doesn't look right. Your file should be a zip file containing Data/ch.dat. Please select your OpenRCT2 contents (zip file):";
|
||||
return false;
|
||||
}))
|
||||
{
|
||||
res();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Module.canvas.style.display = "";
|
||||
Module.callMain(["--user-data-path=/persistent/", "--openrct2-data-path=/OpenRCT2/"]);
|
||||
})();
|
||||
|
||||
async function updateAssets() {
|
||||
let currentVersion = "";
|
||||
try {
|
||||
currentVersion = Module.FS.readFile("/OpenRCT2/version", {encoding: "utf8"});
|
||||
console.log("Found asset version", currentVersion);
|
||||
} catch(e) {
|
||||
console.log("No asset version found");
|
||||
};
|
||||
let assetsVersion = "DEBUG";
|
||||
try {
|
||||
assetsVersion = Module.ccall("GetVersion", "string");
|
||||
} catch(e) {
|
||||
console.warn("Could not call 'GetVersion'! Is it added to EXPORTED_FUNCTIONS? Is ccall added to EXPORTED_RUNTIME_METHODS?");
|
||||
};
|
||||
|
||||
//Always pull assets on a debug build
|
||||
if (currentVersion !== assetsVersion || assetsVersion.includes("DEBUG"))
|
||||
{
|
||||
console.log("Updating assets to", assetsVersion);
|
||||
document.getElementById("loadingWebassembly").innerText = "Asset update found. Downloading...";
|
||||
await clearDatabase("/OpenRCT2/");
|
||||
|
||||
// Fetch the assets.zip file
|
||||
const response = await fetch("assets.zip");
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
document.getElementById("loadingWebassembly").innerText = "Error! Assets file not found (404).";
|
||||
} else {
|
||||
document.getElementById("loadingWebassembly").innerText = `Error! Failed to download assets (status: ${response.status}).`;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
document.getElementById("loadingWebassembly").innerText = "Downloaded assets.zip";
|
||||
}
|
||||
|
||||
await extractZip(await response.blob(), () => {
|
||||
return "/OpenRCT2/";
|
||||
});
|
||||
Module.FS.writeFile("/OpenRCT2/version", assetsVersion.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function extractZip(data, checkZip) {
|
||||
let zip = new JSZip();
|
||||
let contents;
|
||||
try {
|
||||
contents = await zip.loadAsync(data);
|
||||
} catch(e) {
|
||||
if (typeof checkZip === "function")
|
||||
{
|
||||
checkZip(null);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
let base = "/";
|
||||
if (typeof checkZip === "function")
|
||||
{
|
||||
const cont = checkZip(contents);
|
||||
if (cont === false) return false;
|
||||
base = cont;
|
||||
}
|
||||
for (const k in contents.files) {
|
||||
const entry = contents.files[k];
|
||||
if (entry.dir)
|
||||
{
|
||||
try {
|
||||
Module.FS.mkdir(base+k);
|
||||
} catch(e) {}
|
||||
}
|
||||
else
|
||||
{
|
||||
Module.FS.writeFile(base+k, await entry.async("uint8array"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async function clearDatabase(dir) {
|
||||
await new Promise(res => Module.FS.syncfs(false, res));
|
||||
const processFolder = (path) => {
|
||||
let contents;
|
||||
try {
|
||||
contents = Module.FS.readdir(path);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
contents.forEach((entry) => {
|
||||
if ([".", ".."].includes(entry)) return;
|
||||
try {
|
||||
Module.FS.readFile(path + entry);
|
||||
Module.FS.unlink(path + entry);
|
||||
} catch(e) {
|
||||
processFolder(path + entry + "/");
|
||||
}
|
||||
})
|
||||
if (path === dir) return;
|
||||
try {
|
||||
Module.FS.rmdir(path, {recursive: true});
|
||||
} catch(e) {
|
||||
console.log("Could not remove:", path);
|
||||
}
|
||||
}
|
||||
processFolder(dir);
|
||||
await new Promise(res => Module.FS.syncfs(false, res));
|
||||
}
|
||||
function fileExists(path) {
|
||||
try {
|
||||
Module.FS.readFile(path);
|
||||
return true;
|
||||
} catch(e) {};
|
||||
return false;
|
||||
}
|
36
scripts/build-emscripten
Executable file
36
scripts/build-emscripten
Executable file
|
@ -0,0 +1,36 @@
|
|||
#!/bin/bash
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
START_DIR=$(pwd)
|
||||
SPEEXDSP_ROOT=/ext/speexdsp
|
||||
ICU_ROOT=/ext/icu/icu4c/source
|
||||
LIBZIP_ROOT=/ext/libzip
|
||||
JSON_DIR=/usr/include/nlohmann/
|
||||
|
||||
emcmake cmake ../ \
|
||||
-G Ninja \
|
||||
-DDISABLE_NETWORK=ON \
|
||||
-DDISABLE_HTTP=ON \
|
||||
-DDISABLE_TTF=ON \
|
||||
-DDISABLE_FLAC=ON \
|
||||
-DDISABLE_DISCORD_RPC=ON \
|
||||
-DCMAKE_SYSTEM_NAME=Emscripten \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DSPEEXDSP_INCLUDE_DIR="$SPEEXDSP_ROOT/include/" \
|
||||
-DSPEEXDSP_LIBRARY="$SPEEXDSP_ROOT/libspeexdsp/.libs/libspeexdsp.a" \
|
||||
-DICU_INCLUDE_DIR="$ICU_ROOT/common" \
|
||||
-DICU_DATA_LIBRARIES=$ICU_ROOT/lib/libicuuc.so \
|
||||
-DICU_DT_LIBRARY_RELEASE="$ICU_ROOT/stubdata/libicudata.so" \
|
||||
-DLIBZIP_LIBRARIES="$LIBZIP_ROOT/build/lib/libzip.a" \
|
||||
-DEMSCRIPTEN_FLAGS="-s USE_SDL=2 -s USE_BZIP2=1 -s USE_LIBPNG=1 -pthread -O3" \
|
||||
-DEMSCRIPTEN_LDFLAGS="-Wno-pthreads-mem-growth -s ASYNCIFY -s FULL_ES3 -s SAFE_HEAP=0 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4GB -s INITIAL_MEMORY=2GB -s MAX_WEBGL_VERSION=2 -s PTHREAD_POOL_SIZE=120 -pthread -sEXPORTED_RUNTIME_METHODS=ccall,FS,callMain,UTF8ToString,stringToNewUTF8 -lidbfs.js --use-preload-plugins -s MODULARIZE=1 -s 'EXPORT_NAME=\"OPENRCT2_WEB\"'"
|
||||
|
||||
emmake ninja
|
||||
|
||||
rm -rf www/
|
||||
mkdir -p www/
|
||||
cd www/
|
||||
cp -r ../openrct2.* ./
|
||||
cp -r ../../emscripten/static/* ./
|
|
@ -17,5 +17,8 @@ file(GLOB_RECURSE OPENRCT2_CLI_SOURCES
|
|||
add_executable(${PROJECT_NAME} ${OPENRCT2_CLI_SOURCES})
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/..")
|
||||
ipo_set_target_properties(${PROJECT_NAME})
|
||||
if (EMSCRIPTEN)
|
||||
target_link_libraries(${PROJECT_NAME} ${ICU_DT_LIBRARY_RELEASE} ${ICU_DATA_LIBRARIES})
|
||||
endif ()
|
||||
target_link_libraries(${PROJECT_NAME} libopenrct2 Threads::Threads)
|
||||
target_link_platform_libraries(${PROJECT_NAME})
|
||||
|
|
|
@ -11,7 +11,16 @@ option(DISABLE_VORBIS "Disable OGG/VORBIS support.")
|
|||
option(DISABLE_OPENGL "Disable OpenGL support.")
|
||||
|
||||
# Third party libraries
|
||||
if (MSVC)
|
||||
if (EMSCRIPTEN)
|
||||
set(USE_FLAGS "${EMSCRIPTEN_FLAGS}")
|
||||
if (NOT DISABLE_VORBIS)
|
||||
set(USE_FLAGS "${USE_FLAGS} -s USE_VORBIS=1 -s USE_OGG=1")
|
||||
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")
|
||||
find_package(SpeexDSP REQUIRED)
|
||||
elseif (MSVC)
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_library(SPEEX_LDFLAGS libspeexdsp)
|
||||
if (NOT DISABLE_FLAC)
|
||||
|
@ -33,7 +42,7 @@ else ()
|
|||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT DISABLE_OPENGL)
|
||||
if (NOT DISABLE_OPENGL AND NOT EMSCRIPTEN)
|
||||
# GL doesn't work nicely with macOS, while find_package doesn't work with multiarch on Ubuntu.
|
||||
if (APPLE)
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
@ -61,7 +70,12 @@ SET_CHECK_CXX_FLAGS(${PROJECT_NAME})
|
|||
ipo_set_target_properties(${PROJECT_NAME})
|
||||
|
||||
# mingw builds cannot use the PkgConfig imported targets
|
||||
if (NOT MSVC AND NOT WIN32)
|
||||
if (EMSCRIPTEN)
|
||||
target_link_libraries(${PROJECT_NAME} "libopenrct2"
|
||||
${SPEEXDSP_LIBRARIES}
|
||||
${ICU_DATA_LIBRARIES}
|
||||
${ICU_DT_LIBRARY_RELEASE})
|
||||
elseif (NOT MSVC AND NOT WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} "libopenrct2"
|
||||
PkgConfig::SDL2
|
||||
PkgConfig::SPEEX)
|
||||
|
@ -81,7 +95,7 @@ if (NOT DISABLE_FLAC)
|
|||
endif ()
|
||||
|
||||
if (NOT DISABLE_VORBIS)
|
||||
if (NOT MSVC AND NOT WIN32)
|
||||
if (NOT MSVC AND NOT WIN32 AND NOT EMSCRIPTEN)
|
||||
target_link_libraries(${PROJECT_NAME} PkgConfig::OGG PkgConfig::VORBISFILE)
|
||||
else ()
|
||||
target_link_libraries(${PROJECT_NAME} ${OGG_LDFLAGS} ${VORBISFILE_LDFLAGS})
|
||||
|
|
|
@ -171,7 +171,7 @@ void TextComposition::HandleMessage(const SDL_Event* e)
|
|||
case SDLK_c:
|
||||
if ((modifier & KEYBOARD_PRIMARY_MODIFIER) && _session.Length)
|
||||
{
|
||||
SDL_SetClipboardText(_session.Buffer->c_str());
|
||||
OpenRCT2::GetContext()->GetUiContext()->SetClipboardText(_session.Buffer->c_str());
|
||||
ContextShowError(STR_COPY_INPUT_TO_CLIPBOARD, kStringIdNone, {});
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
#include <openrct2/platform/Platform.h>
|
||||
#include <openrct2/ui/UiContext.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Audio;
|
||||
using namespace OpenRCT2::Ui;
|
||||
|
@ -43,6 +47,12 @@ int NormalisedMain(int argc, const char** argv)
|
|||
int main(int argc, const char** argv)
|
||||
#endif
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
MAIN_THREAD_EM_ASM({
|
||||
specialHTMLTargets["!canvas"] = Module.canvas;
|
||||
Module.canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); });
|
||||
});
|
||||
#endif
|
||||
std::unique_ptr<IContext> context;
|
||||
int32_t rc = EXIT_SUCCESS;
|
||||
int runGame = CommandLineRun(argv, argc);
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace OpenRCT2::Ui
|
||||
{
|
||||
enum class DIALOG_TYPE
|
||||
|
@ -129,8 +133,12 @@ namespace OpenRCT2::Ui
|
|||
|
||||
void OpenURL(const std::string& url) override
|
||||
{
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::string cmd = String::stdFormat("xdg-open %s", url.c_str());
|
||||
Platform::Execute(cmd);
|
||||
#else
|
||||
MAIN_THREAD_EM_ASM({ window.open(UTF8ToString($0)); }, url.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string ShowFileDialog(SDL_Window* window, const FileDialogDesc& desc) override
|
||||
|
|
|
@ -48,6 +48,11 @@
|
|||
#include <openrct2/world/Location.hpp>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
#endif
|
||||
|
||||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Drawing;
|
||||
using namespace OpenRCT2::Scripting;
|
||||
|
@ -712,7 +717,25 @@ public:
|
|||
|
||||
bool SetClipboardText(const utf8* target) override
|
||||
{
|
||||
#ifndef __EMSCRIPTEN__
|
||||
return (SDL_SetClipboardText(target) == 0);
|
||||
#else
|
||||
return (
|
||||
MAIN_THREAD_EM_ASM_INT(
|
||||
{
|
||||
try
|
||||
{
|
||||
navigator.clipboard.writeText(UTF8ToString($0));
|
||||
return 0;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return -1;
|
||||
};
|
||||
},
|
||||
target)
|
||||
== 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
ITitleSequencePlayer* GetTitleSequencePlayer() override
|
||||
|
@ -752,9 +775,19 @@ private:
|
|||
|
||||
void CreateWindow(const ScreenCoordsXY& windowPos)
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
MAIN_THREAD_EM_ASM({
|
||||
Module.canvas.width = window.innerWidth;
|
||||
Module.canvas.height = window.innerHeight;
|
||||
});
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
emscripten_get_canvas_element_size("!canvas", &width, &height);
|
||||
#else
|
||||
// Get saved window size
|
||||
int32_t width = Config::Get().general.WindowWidth;
|
||||
int32_t height = Config::Get().general.WindowHeight;
|
||||
#endif
|
||||
if (width <= 0)
|
||||
width = 640;
|
||||
if (height <= 0)
|
||||
|
|
|
@ -1103,6 +1103,8 @@ namespace OpenRCT2
|
|||
STR_DRAWING_ENGINE_TIP = 5876,
|
||||
STR_EARLY_COMPLETION_TIP = 6227,
|
||||
STR_EDIT_ASSET_PACKS_BUTTON = 6640,
|
||||
STR_EXPORT_EMSCRIPTEN = 6730,
|
||||
STR_IMPORT_EMSCRIPTEN = 6731,
|
||||
STR_EDIT_THEMES_BUTTON = 5153,
|
||||
STR_EDIT_THEMES_BUTTON_TIP = 5837,
|
||||
STR_EFFECTS_GROUP = 6256,
|
||||
|
@ -2272,4 +2274,4 @@ namespace OpenRCT2
|
|||
STR_ADJUST_SMALLER_WATER_TIP = 2380,
|
||||
STR_WATER = 2383,
|
||||
};
|
||||
}
|
||||
} // namespace OpenRCT2
|
||||
|
|
|
@ -42,7 +42,11 @@ OPENGL_PROC(PFNGLATTACHSHADERPROC, glAttachShader)
|
|||
OPENGL_PROC(PFNGLBINDBUFFERPROC, glBindBuffer)
|
||||
OPENGL_PROC(PFNGLBINDFRAGDATALOCATIONPROC, glBindFragDataLocation)
|
||||
OPENGL_PROC(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer)
|
||||
#ifndef FAKE__EMSCRIPTEN__
|
||||
OPENGL_PROC(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray)
|
||||
#else
|
||||
extern "C" void glBindVertexArray(GLuint array);
|
||||
#endif
|
||||
OPENGL_PROC(PFNGLBLITFRAMEBUFFERPROC, glBlitFramebuffer)
|
||||
OPENGL_PROC(PFNGLBUFFERDATAPROC, glBufferData)
|
||||
OPENGL_PROC(PFNGLBUFFERSUBDATAPROC, glBufferSubData)
|
||||
|
@ -55,7 +59,11 @@ OPENGL_PROC(PFNGLDELETEBUFFERSPROC, glDeleteBuffers)
|
|||
OPENGL_PROC(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers)
|
||||
OPENGL_PROC(PFNGLDELETEPROGRAMPROC, glDeleteProgram)
|
||||
OPENGL_PROC(PFNGLDELETESHADERPROC, glDeleteShader)
|
||||
#ifndef FAKE__EMSCRIPTEN__
|
||||
OPENGL_PROC(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays)
|
||||
#else
|
||||
extern "C" void glDeleteVertexArrays(GLsizei n, const GLuint* arrays);
|
||||
#endif
|
||||
OPENGL_PROC(PFNGLDETACHSHADERPROC, glDetachShader)
|
||||
OPENGL_PROC(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray)
|
||||
OPENGL_PROC(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D)
|
||||
|
@ -67,7 +75,11 @@ OPENGL_PROC(PFNGLGETPROGRAMIVPROC, glGetProgramiv)
|
|||
OPENGL_PROC(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog)
|
||||
OPENGL_PROC(PFNGLGETSHADERIVPROC, glGetShaderiv)
|
||||
OPENGL_PROC(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation)
|
||||
#ifndef FAKE__EMSCRIPTEN__
|
||||
OPENGL_PROC(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays)
|
||||
#else
|
||||
extern "C" void glGenVertexArrays(GLsizei n, GLuint* arrays);
|
||||
#endif
|
||||
OPENGL_PROC(PFNGLLINKPROGRAMPROC, glLinkProgram)
|
||||
OPENGL_PROC(PFNGLSHADERSOURCEPROC, glShaderSource)
|
||||
OPENGL_PROC(PFNGLUNIFORM1IPROC, glUniform1i)
|
||||
|
@ -82,6 +94,11 @@ OPENGL_PROC(PFNGLUNIFORM4FVPROC, glUniform4fv)
|
|||
OPENGL_PROC(PFNGLUSEPROGRAMPROC, glUseProgram)
|
||||
OPENGL_PROC(PFNGLVERTEXATTRIBIPOINTERPROC, glVertexAttribIPointer)
|
||||
OPENGL_PROC(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer)
|
||||
#ifndef FAKE__EMSCRIPTEN__
|
||||
OPENGL_PROC(PFNGLDRAWARRAYSINSTANCEDPROC, glDrawArraysInstanced)
|
||||
OPENGL_PROC(PFNGLVERTEXATTRIBDIVISORPROC, glVertexAttribDivisor)
|
||||
#else
|
||||
extern "C" void glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||
extern "C" void glVertexAttribDivisor(GLuint index, GLuint divisor);
|
||||
#endif
|
||||
OPENGL_PROC(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate)
|
||||
|
|
|
@ -70,7 +70,9 @@ private:
|
|||
|
||||
int32_t _drawCount = 0;
|
||||
|
||||
#ifndef NO_TTF
|
||||
uint32_t _ttfGlId = 0;
|
||||
#endif
|
||||
|
||||
struct
|
||||
{
|
||||
|
|
|
@ -613,6 +613,7 @@ namespace OpenRCT2
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
const CursorState* cursorState = ContextGetCursorState();
|
||||
if (cursorState->touch || Config::Get().general.InvertViewportDrag)
|
||||
{
|
||||
|
@ -622,6 +623,9 @@ namespace OpenRCT2
|
|||
{
|
||||
ContextSetCursorPosition(gInputDragLast);
|
||||
}
|
||||
#else
|
||||
gInputDragLast = newDragCoords;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void InputViewportDragEnd()
|
||||
|
|
|
@ -114,7 +114,7 @@ namespace OpenRCT2::Ui::Windows
|
|||
ContextOpenWindowView(WV_NEW_VERSION_INFO);
|
||||
break;
|
||||
case WIDX_COPY_BUILD_INFO:
|
||||
SDL_SetClipboardText(gVersionInfoFull);
|
||||
OpenRCT2::GetContext()->GetUiContext()->SetClipboardText(gVersionInfoFull);
|
||||
break;
|
||||
case WIDX_CONTRIBUTORS_BUTTON:
|
||||
ContextOpenWindowView(WV_CONTRIBUTORS);
|
||||
|
|
|
@ -45,6 +45,14 @@
|
|||
#include <openrct2/ui/UiContext.h>
|
||||
#include <openrct2/ui/WindowManager.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
extern "C" {
|
||||
extern void ExportPersistentData();
|
||||
extern void ImportPersistentData();
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Audio;
|
||||
|
||||
|
@ -217,6 +225,10 @@ namespace OpenRCT2::Ui::Windows
|
|||
WIDX_PATH_TO_RCT1_BUTTON,
|
||||
WIDX_PATH_TO_RCT1_CLEAR,
|
||||
WIDX_ASSET_PACKS,
|
||||
#ifdef __EMSCRIPTEN__
|
||||
WIDX_EXPORT_EMSCRIPTEN_DATA,
|
||||
WIDX_IMPORT_EMSCRIPTEN_DATA,
|
||||
#endif
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
@ -392,11 +404,15 @@ namespace OpenRCT2::Ui::Windows
|
|||
MakeWidget ({165, 113}, {135, 13}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ), // Autosave dropdown
|
||||
MakeWidget ({288, 114}, { 11, 11}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_AUTOSAVE_FREQUENCY_TIP ), // Autosave dropdown button
|
||||
MakeWidget ({ 23, 130}, {135, 12}, WindowWidgetType::Label, WindowColour::Secondary, STR_AUTOSAVE_AMOUNT, STR_AUTOSAVE_AMOUNT_TIP ),
|
||||
MakeSpinnerWidgets({165, 130}, {135, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, kStringIdNone, STR_AUTOSAVE_AMOUNT_TIP ), // Autosave amount spinner
|
||||
MakeSpinnerWidgets({165, 130}, {135, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, kStringIdNone, STR_AUTOSAVE_AMOUNT_TIP ), // Autosave amount spinner
|
||||
MakeWidget ({ 23, 145}, {276, 12}, WindowWidgetType::Label, WindowColour::Secondary, STR_PATH_TO_RCT1, STR_PATH_TO_RCT1_TIP ), // RCT 1 path text
|
||||
MakeWidget ({ 24, 160}, {266, 14}, WindowWidgetType::Button, WindowColour::Secondary, kStringIdNone, STR_STRING_TOOLTIP ), // RCT 1 path button
|
||||
MakeWidget ({ 24, 160}, {266, 14}, WindowWidgetType::Button, WindowColour::Secondary, kStringIdNone, STR_STRING_TOOLTIP ), // RCT 1 path button
|
||||
MakeWidget ({289, 160}, { 11, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_CLOSE_X, STR_PATH_TO_RCT1_CLEAR_TIP ), // RCT 1 path clear button
|
||||
MakeWidget ({150, 176}, {150, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_EDIT_ASSET_PACKS_BUTTON, kStringIdNone ), // Asset packs
|
||||
MakeWidget ({150, 176}, {150, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_EDIT_ASSET_PACKS_BUTTON, kStringIdNone ), // Asset packs
|
||||
#ifdef __EMSCRIPTEN__
|
||||
MakeWidget ({150, 192}, {150, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_EXPORT_EMSCRIPTEN, kStringIdNone ), // Emscripten data export
|
||||
MakeWidget ({150, 208}, {150, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_IMPORT_EMSCRIPTEN, kStringIdNone ), // Emscripten data import
|
||||
#endif
|
||||
};
|
||||
|
||||
static constexpr std::span<const Widget> window_options_page_widgets[] = {
|
||||
|
@ -1962,6 +1978,14 @@ namespace OpenRCT2::Ui::Windows
|
|||
case WIDX_ASSET_PACKS:
|
||||
ContextOpenWindow(WindowClass::AssetPacks);
|
||||
break;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
case WIDX_EXPORT_EMSCRIPTEN_DATA:
|
||||
ExportPersistentData();
|
||||
break;
|
||||
case WIDX_IMPORT_EMSCRIPTEN_DATA:
|
||||
ImportPersistentData();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,7 +114,17 @@ if (NOT DISABLE_GOOGLE_BENCHMARK)
|
|||
endif ()
|
||||
|
||||
# Third party libraries
|
||||
if (MSVC)
|
||||
if (EMSCRIPTEN)
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${ICU_INCLUDE_DIR})
|
||||
set(USE_FLAGS "${EMSCRIPTEN_FLAGS}")
|
||||
if (NOT DISABLE_VORBIS)
|
||||
set(USE_FLAGS "${USE_FLAGS} -s USE_VORBIS=1 -s USE_OGG=1")
|
||||
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}")
|
||||
find_package(SpeexDSP REQUIRED)
|
||||
elseif (MSVC)
|
||||
find_package(png 1.6 REQUIRED)
|
||||
find_package(zlib REQUIRED)
|
||||
|
||||
|
@ -142,7 +152,7 @@ if (STATIC)
|
|||
${ZLIB_STATIC_LIBRARIES}
|
||||
${LIBZIP_STATIC_LIBRARIES})
|
||||
else ()
|
||||
if (NOT MSVC)
|
||||
if (NOT MSVC AND NOT EMSCRIPTEN)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PkgConfig::PNG
|
||||
PkgConfig::ZLIB
|
||||
|
@ -171,7 +181,12 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
|
|||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} Threads::Threads)
|
||||
|
||||
if (NOT MINGW AND NOT MSVC)
|
||||
# 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")
|
||||
endif()
|
||||
|
||||
if (NOT MINGW AND NOT MSVC AND NOT EMSCRIPTEN)
|
||||
if (APPLE AND NOT MACOS_USE_DEPENDENCIES)
|
||||
execute_process(COMMAND brew --prefix icu4c OUTPUT_VARIABLE HOMEBREW_PREFIX_ICU OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
# Needed for linking with non-broken icu on Apple platforms
|
||||
|
@ -249,7 +264,7 @@ if (NOT OPENRCT2_COMMIT_SHA1_SHORT STREQUAL "HEAD" AND NOT OPENRCT2_COMMIT_SHA1_
|
|||
OPENRCT2_COMMIT_SHA1_SHORT="${OPENRCT2_COMMIT_SHA1_SHORT}")
|
||||
endif()
|
||||
|
||||
if((X86 OR X86_64) AND NOT MSVC)
|
||||
if((X86 OR X86_64) AND NOT MSVC AND NOT EMSCRIPTEN)
|
||||
set_source_files_properties(${CMAKE_CURRENT_LIST_DIR}/drawing/SSE41Drawing.cpp PROPERTIES COMPILE_FLAGS -msse4.1)
|
||||
set_source_files_properties(${CMAKE_CURRENT_LIST_DIR}/drawing/AVX2Drawing.cpp PROPERTIES COMPILE_FLAGS -mavx2)
|
||||
endif()
|
||||
|
|
|
@ -1200,9 +1200,21 @@ namespace OpenRCT2
|
|||
{
|
||||
SwitchToStartUpScene();
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_set_main_loop_arg(
|
||||
[](void* vctx) {
|
||||
auto ctx = reinterpret_cast<Context*>(vctx);
|
||||
if (ctx->_finished)
|
||||
{
|
||||
emscripten_cancel_main_loop();
|
||||
}
|
||||
ctx->RunFrame();
|
||||
},
|
||||
this, 0, 1);
|
||||
#else
|
||||
_stdInOutConsole.Start();
|
||||
RunGameLoop();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ShouldDraw()
|
||||
|
@ -1228,6 +1240,7 @@ namespace OpenRCT2
|
|||
/**
|
||||
* Run the main game loop until the finished flag is set.
|
||||
*/
|
||||
#ifndef __EMSCRIPTEN__
|
||||
void RunGameLoop()
|
||||
{
|
||||
PROFILED_FUNCTION();
|
||||
|
@ -1235,22 +1248,14 @@ namespace OpenRCT2
|
|||
LOG_VERBOSE("begin openrct2 loop");
|
||||
_finished = false;
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
_variableFrame = ShouldRunVariableFrame();
|
||||
do
|
||||
{
|
||||
RunFrame();
|
||||
} while (!_finished);
|
||||
#else
|
||||
emscripten_set_main_loop_arg(
|
||||
[](void* vctx) -> {
|
||||
auto ctx = reinterpret_cast<Context*>(vctx);
|
||||
ctx->RunFrame();
|
||||
},
|
||||
this, 0, 1);
|
||||
#endif // __EMSCRIPTEN__
|
||||
LOG_VERBOSE("finish openrct2 loop");
|
||||
}
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
void RunFrame()
|
||||
{
|
||||
|
|
|
@ -55,6 +55,16 @@ const char gVersionInfoFull[] = OPENRCT2_NAME ", "
|
|||
#endif
|
||||
;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// This must be wrapped in extern "C", according to the emscripten docs, "to prevent C++ name mangling"
|
||||
extern "C" {
|
||||
const char* GetVersion()
|
||||
{
|
||||
return gVersionInfoFull;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
NewVersionInfo GetLatestVersion()
|
||||
{
|
||||
// If the check doesn't succeed, provide current version so we don't bother user
|
||||
|
|
|
@ -37,8 +37,10 @@
|
|||
#elif defined(__loongarch__)
|
||||
#define OPENRCT2_ARCHITECTURE "LoongArch"
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define OPENRCT2_ARCHITECTURE "Emscripten"
|
||||
#ifdef __wasm64__
|
||||
#define OPENRCT2_ARCHITECTURE "wasm64"
|
||||
#elif defined(__wasm32__)
|
||||
#define OPENRCT2_ARCHITECTURE "wasm32"
|
||||
#endif
|
||||
|
||||
#ifndef OPENRCT2_ARCHITECTURE
|
||||
|
|
|
@ -26,6 +26,12 @@
|
|||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
namespace OpenRCT2::Imaging
|
||||
{
|
||||
static constexpr auto kExceptionImageFormatUnknown = "Unknown image format.";
|
||||
|
@ -334,8 +340,27 @@ namespace OpenRCT2::Imaging
|
|||
break;
|
||||
case IMAGE_FORMAT::PNG:
|
||||
{
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::ofstream fs(fs::u8path(path), std::ios::binary);
|
||||
WritePng(fs, image);
|
||||
#else
|
||||
std::ostringstream stream(std::ios::binary);
|
||||
WritePng(stream, image);
|
||||
std::string dataStr = stream.str();
|
||||
void* data = reinterpret_cast<void*>(dataStr.data());
|
||||
MAIN_THREAD_EM_ASM(
|
||||
{
|
||||
const a = document.createElement("a");
|
||||
// Blob requires the data must not be shared
|
||||
const data = new Uint8Array(HEAPU8.subarray($0, $0 + $1));
|
||||
a.href = URL.createObjectURL(new Blob([data]));
|
||||
a.download = UTF8ToString($2).split("/").pop();
|
||||
a.click();
|
||||
setTimeout(function(){ URL.revokeObjectURL(a.href) }, 1000);
|
||||
},
|
||||
data, dataStr.size(), std::string(path).c_str());
|
||||
free(data);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -24,7 +24,7 @@ using money64 = fixed64_1dp;
|
|||
// really tries to use a gigantic constant that can't fit in a double, they are
|
||||
// probably going to be breaking other things anyways.
|
||||
// For more details, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=26374
|
||||
constexpr money64 operator"" _GBP(long double money) noexcept
|
||||
constexpr money64 operator""_GBP(long double money) noexcept
|
||||
{
|
||||
return static_cast<double>(money) * 10;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <cstdint>
|
||||
|
||||
// Note: Only valid for 5 decimal places.
|
||||
constexpr int32_t operator"" _mph(long double speedMph)
|
||||
constexpr int32_t operator""_mph(long double speedMph)
|
||||
{
|
||||
uint32_t wholeNumber = speedMph;
|
||||
uint64_t fraction = (speedMph - wholeNumber) * 100000;
|
||||
|
|
|
@ -158,7 +158,7 @@ namespace OpenRCT2::Platform
|
|||
{
|
||||
LOG_FATAL("failed to get process path");
|
||||
}
|
||||
#elif defined(__OpenBSD__)
|
||||
#elif defined(__OpenBSD__) || defined(__EMSCRIPTEN__)
|
||||
// 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));
|
||||
|
|
|
@ -161,7 +161,7 @@ namespace OpenRCT2::Platform
|
|||
// Return exit code
|
||||
return pclose(fpipe);
|
||||
#else
|
||||
LOG_WARNING("Emscripten cannot execute processes. The commandline was '%s'.", command.c_str());
|
||||
LOG_WARNING("Emscripten cannot execute processes. The commandline was '%s'.", std::string(command).c_str());
|
||||
return -1;
|
||||
#endif // __EMSCRIPTEN__
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
constexpr int operator"" _MPH(unsigned long long x) noexcept
|
||||
constexpr int operator""_MPH(unsigned long long x) noexcept
|
||||
{
|
||||
return x * 29127;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue