Merge branch 'master' into jgrpp

# Conflicts:
#	.github/workflows/ci-build.yml
#	.github/workflows/release-source.yml
#	CMakeLists.txt
#	COMPILING.md
#	src/network/network_survey.cpp
#	src/network/network_survey.h
#	src/openttd.cpp
#	src/tests/CMakeLists.txt
pull/611/head
Jonathan G Rennison 6 months ago
commit af1150182a

@ -90,20 +90,20 @@ jobs:
- name: Clang
compiler: clang
cxxcompiler: clang++
libraries: libsdl2-dev nlohmann-json3-dev
libraries: libsdl2-dev
- name: GCC - SDL2
compiler: gcc
cxxcompiler: g++
libraries: libsdl2-dev nlohmann-json3-dev
libraries: libsdl2-dev
- name: GCC - SDL1.2
compiler: gcc
cxxcompiler: g++
libraries: libsdl1.2-dev nlohmann-json3-dev
libraries: libsdl1.2-dev
- name: GCC - Dedicated
compiler: gcc
cxxcompiler: g++
extra-cmake-parameters: -DOPTION_DEDICATED=ON -DCMAKE_CXX_FLAGS_INIT="-DRANDOM_DEBUG"
# Compile without SDL / SDL2 / nlohmann-json, as that should compile fine too.
# Compile without SDL / SDL2, as that should compile fine too.
name: Linux (${{ matrix.name }})
@ -139,6 +139,7 @@ jobs:
liblzma-dev \
libzstd-dev \
liblzo2-dev \
nlohmann-json3-dev \
${{ matrix.libraries }} \
zlib1g-dev \
# EOF

@ -152,14 +152,18 @@ jobs:
- name: Generate survey key
id: survey_key
run: |
#PAYLOAD='{"version":"${{ steps.metadata.outputs.version }}","type":"${{ vars.SURVEY_TYPE }}"}'
#
#echo "${{ secrets.SURVEY_SIGNING_KEY }}" > survey_signing_key.pem
#SIGNATURE=$(echo -n "${PAYLOAD}" | openssl dgst -sha256 -sign survey_signing_key.pem | base64 -w0)
#rm -f survey_signing_key.pem
#
#SURVEY_KEY=$(curl -f -s -X POST -d "${PAYLOAD}" -H "Content-Type: application/json" -H "X-Signature: ${SIGNATURE}" https://survey-participate.openttd.org/create-survey-key/${{ vars.SURVEY_TYPE }})
SURVEY_KEY=""
if [ -z "${{ vars.SURVEY_TYPE }}" ]; then
echo "SURVEY_TYPE variable not found; most likely running in a fork. Skipping step."
SURVEY_KEY=""
else
PAYLOAD='{"version":"${{ steps.metadata.outputs.version }}","type":"${{ vars.SURVEY_TYPE }}"}'
echo "${{ secrets.SURVEY_SIGNING_KEY }}" > survey_signing_key.pem
SIGNATURE=$(echo -n "${PAYLOAD}" | openssl dgst -sha256 -sign survey_signing_key.pem | base64 -w0)
rm -f survey_signing_key.pem
SURVEY_KEY=$(curl -f -s -X POST -d "${PAYLOAD}" -H "Content-Type: application/json" -H "X-Signature: ${SIGNATURE}" https://survey-participate.openttd.org/create-survey-key/${{ vars.SURVEY_TYPE }})
fi
echo "survey_key=${SURVEY_KEY}" >> $GITHUB_OUTPUT

@ -73,6 +73,13 @@ jobs:
zstd \
# EOF
# arm64-windows-static is not (yet) supported for breakpad.
if [ "${{ matrix.arch }}" != "arm64" ]; then
vcpkg install --triplet=${{ matrix.arch }}-windows-static \
breakpad \
# EOF
fi
- name: Install MSVC problem matcher
uses: ammaraskar/msvc-problem-matcher@master

@ -114,13 +114,14 @@ endif()
set(CMAKE_THREAD_PREFER_PTHREAD YES)
# Make sure we have Threads available.
find_package(Threads REQUIRED)
# nlohmann is used for all our JSON needs.
find_package(nlohmann_json REQUIRED)
find_package(ZLIB)
find_package(LibLZMA)
find_package(LZO)
find_package(ZSTD 1.4)
find_package(PNG)
find_package(nlohmann_json)
if(WIN32 OR EMSCRIPTEN)
# Windows uses WinHttp for HTTP requests.
@ -362,7 +363,7 @@ link_package(ZLIB TARGET ZLIB::ZLIB ENCOURAGED)
link_package(LIBLZMA TARGET LibLZMA::LibLZMA ENCOURAGED)
link_package(LZO)
link_package(ZSTD TARGET ZSTD::ZSTD RECOMMENDED)
link_package(nlohmann_json ENCOURAGED)
link_package(nlohmann_json)
if(NOT WIN32 AND NOT EMSCRIPTEN)
link_package(CURL ENCOURAGED)

@ -4,7 +4,7 @@
OpenTTD makes use of the following external libraries:
- (encouraged) nlohmann-json: JSON handling
- (required) nlohmann-json: JSON handling
- (encouraged) zlib: (de)compressing of old (0.3.0-1.0.5) savegames, content downloads,
heightmaps
- (encouraged) liblzma: (de)compressing of savegames (1.1.0 and later)

@ -1,4 +1,4 @@
Module.arguments.push('-mnull', '-snull', '-vsdl:relative_mode');
Module.arguments.push('-mnull', '-snull', '-vsdl');
Module['websocket'] = { url: function(host, port, proto) {
/* openttd.org hosts a WebSocket proxy for the content service. */
if (host == "content.openttd.org" && port == 3978 && proto == "tcp") {
@ -30,46 +30,47 @@ Module.preRun.push(function() {
Module.addRunDependency('syncfs');
FS.syncfs(true, function (err) {
/* FS.mkdir() tends to fail if parent folders do not exist. */
if (!FS.analyzePath(content_download_dir).exists) {
FS.mkdir(content_download_dir);
}
if (!FS.analyzePath(content_download_dir + '/baseset').exists) {
FS.mkdir(content_download_dir + '/baseset');
}
/* Check if the OpenGFX baseset is already downloaded. */
if (!FS.analyzePath(content_download_dir + '/baseset/opengfx-0.6.0.tar').exists) {
window.openttd_downloaded_opengfx = true;
FS.createPreloadedFile(content_download_dir + '/baseset', 'opengfx-0.6.0.tar', 'https://binaries.openttd.org/installer/emscripten/opengfx-0.6.0.tar', true, true);
} else {
/* Fake dependency increase, so the counter is stable. */
Module.addRunDependency('opengfx');
Module.removeRunDependency('opengfx');
}
Module.removeRunDependency('syncfs');
});
window.openttd_syncfs_shown_warning = false;
window.openttd_syncfs = function() {
window.openttd_syncfs = function(callback) {
/* Copy the virtual FS to the persistent storage. */
FS.syncfs(false, function (err) { });
/* On first time, warn the user about the volatile behaviour of
* persistent storage. */
if (!window.openttd_syncfs_shown_warning) {
window.openttd_syncfs_shown_warning = true;
Module.onWarningFs();
}
FS.syncfs(false, function (err) {
/* On first time, warn the user about the volatile behaviour of
* persistent storage. */
if (!window.openttd_syncfs_shown_warning) {
window.openttd_syncfs_shown_warning = true;
Module.onWarningFs();
}
if (callback) callback();
});
}
window.openttd_exit = function() {
Module.onExit();
window.openttd_syncfs(Module.onExit);
}
window.openttd_abort = function() {
Module.onAbort();
window.openttd_syncfs(Module.onAbort);
}
window.openttd_bootstrap = function(current, total) {
Module.onBootstrap(current, total);
}
window.openttd_bootstrap_failed = function() {
Module.onBootstrapFailed();
}
window.openttd_bootstrap_reload = function() {
window.openttd_syncfs(function() {
Module.onBootstrapReload();
setTimeout(function() {
location.reload();
}, 1000);
});
}
window.openttd_server_list = function() {
@ -123,11 +124,3 @@ Module.preRun.push(function() {
return ret;
}
});
Module.postRun.push(function() {
/* Check if we downloaded OpenGFX; if so, sync the virtual FS back to the
* IDBFS so OpenGFX is stored persistent. */
if (window['openttd_downloaded_opengfx']) {
FS.syncfs(false, function (err) { });
}
});

@ -75,7 +75,6 @@
}
#message {
color: #101010;
height: 54px;
padding: 4px 4px;
}
@ -144,6 +143,8 @@
})(),
setStatus: function(text) {
if (document.getElementById("canvas").style.display == "none") return;
var m = text.match(/^([^(]+)\((\d+(\.\d+)?)\/(\d+)\)$/);
if (m) {
@ -171,6 +172,27 @@
document.getElementById("message").innerHTML = "Preparing game ...";
},
onBootstrap: function(current, total) {
document.getElementById("canvas").style.display = "none";
document.getElementById("title").innerHTML = "Missing base graphics";
document.getElementById("message").innerHTML = "OpenTTD is downloading base graphics.<br/><br/>" + current + " / " + total + " bytes downloaded.";
},
onBootstrapFailed: function(current, total) {
document.getElementById("canvas").style.display = "none";
document.getElementById("title").innerHTML = "Missing base graphics";
document.getElementById("message").innerHTML = "Failed to download base graphics.<br/>The game cannot start without base graphics.<br/><br/>Please check your Internet connection and/or the console log.<br/>Reload your browser to try again.";
},
onBootstrapReload: function() {
document.getElementById("canvas").style.display = "none";
document.getElementById("title").innerHTML = "Missing base graphics";
document.getElementById("message").innerHTML = "Downloading base graphics done.<br/><br/>Your browser will reload to start the game.";
},
onExit: function() {
document.getElementById("canvas").style.display = "none";

@ -285,6 +285,76 @@ public:
#endif /* defined(WITH_FREETYPE) */
#if defined(__EMSCRIPTEN__)
# include <emscripten.h>
# include "network/network.h"
# include "network/network_content.h"
# include "openttd.h"
# include "video/video_driver.hpp"
class BootstrapEmscripten : public ContentCallback {
bool downloading{false};
uint total_files{0};
uint total_bytes{0};
uint downloaded_bytes{0};
public:
BootstrapEmscripten()
{
_network_content_client.AddCallback(this);
_network_content_client.Connect();
}
~BootstrapEmscripten()
{
_network_content_client.RemoveCallback(this);
}
void OnConnect(bool success) override
{
if (!success) {
EM_ASM({ if (window["openttd_bootstrap_failed"]) openttd_bootstrap_failed(); });
return;
}
/* Once connected, request the metadata. */
_network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
}
void OnReceiveContentInfo(const ContentInfo *ci) override
{
if (this->downloading) return;
/* And once the metadata is received, start downloading it. */
_network_content_client.Select(ci->id);
_network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes);
this->downloading = true;
EM_ASM({ if (window["openttd_bootstrap"]) openttd_bootstrap($0, $1); }, this->downloaded_bytes, this->total_bytes);
}
void OnDownloadProgress(const ContentInfo *ci, int bytes) override
{
/* A negative value means we are resetting; for example, when retrying or using a fallback. */
if (bytes < 0) {
this->downloaded_bytes = 0;
} else {
this->downloaded_bytes += bytes;
}
EM_ASM({ if (window["openttd_bootstrap"]) openttd_bootstrap($0, $1); }, this->downloaded_bytes, this->total_bytes);
}
void OnDownloadComplete(ContentID cid) override
{
/* _exit_game is used to break out of the outer video driver's MainLoop. */
_exit_game = true;
delete this;
}
};
#endif /* __EMSCRIPTEN__ */
/**
* Handle all procedures for bootstrapping OpenTTD without a base graphics set.
* This requires all kinds of trickery that is needed to avoid the use of
@ -299,12 +369,15 @@ bool HandleBootstrap()
if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure;
/* If there is no network or no non-sprite font, then there is nothing we can do. Go straight to failure. */
#if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA)
#if defined(__EMSCRIPTEN__) || (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA)
if (!_network_available) goto failure;
/* First tell the game we're bootstrapping. */
_game_mode = GM_BOOTSTRAP;
#if defined(__EMSCRIPTEN__)
new BootstrapEmscripten();
#else
/* Initialise the font cache. */
InitializeUnicodeGlyphMap();
/* Next "force" finding a suitable non-sprite font as the local font is missing. */
@ -323,6 +396,7 @@ bool HandleBootstrap()
/* Finally ask the question. */
new BootstrapBackground();
new BootstrapAskForDownloadWindow();
#endif /* __EMSCRIPTEN__ */
/* Process the user events. */
VideoDriver::GetInstance()->MainLoop();

@ -1187,7 +1187,7 @@ STR_TERRAIN_TYPE_CUSTOM :Alçada persona
STR_TERRAIN_TYPE_CUSTOM_VALUE :Alçada personalitzada ({NUM})
###length 4
STR_CITY_APPROVAL_LENIENT :Tolerant
STR_CITY_APPROVAL_LENIENT :Indulgent
STR_CITY_APPROVAL_TOLERANT :Tolerant
STR_CITY_APPROVAL_HOSTILE :Hostil
STR_CITY_APPROVAL_PERMISSIVE :Permissiva (les accions de les companyies no l'afecten)
@ -2651,6 +2651,7 @@ STR_TRANSPARENT_BUILDINGS_TOOLTIP :{BLACK}Commuta
STR_TRANSPARENT_BRIDGES_TOOLTIP :{BLACK}Commuta la transparència dels ponts. Ctrl+Clic per bloquejar
STR_TRANSPARENT_STRUCTURES_TOOLTIP :{BLACK}Commuta la transparència de les estructures com ara fars i antenes. Ctrl+Clic per bloquejar
STR_TRANSPARENT_CATENARY_TOOLTIP :{BLACK}Commuta la transparència de la catenària. CTRL+clic per bloquejar
STR_TRANSPARENT_TEXT_TOOLTIP :{BLACK}Commuta la transparència dels indicadors de càrrega i el text de despesa/ingrés. Ctrl+Clic per a blocar.
STR_TRANSPARENT_INVISIBLE_TOOLTIP :{BLACK}Alternar entre transparència i invisibilitat dels objectes
# Linkgraph legend window

@ -2650,6 +2650,7 @@ STR_TRANSPARENT_BUILDINGS_TOOLTIP :{BLACK}Transpar
STR_TRANSPARENT_BRIDGES_TOOLTIP :{BLACK}Transparantie voor bruggen aan-uit. Ctrl+klik om vast te zetten.
STR_TRANSPARENT_STRUCTURES_TOOLTIP :{BLACK}Transparantie voor gebouwen zoals vuurtorens en zendmasten aan-uit. Ctrl+klik om vast te zetten.
STR_TRANSPARENT_CATENARY_TOOLTIP :{BLACK}Transparantie voor bovenleiding aan-uit. Ctrl+klik om vast te zetten.
STR_TRANSPARENT_TEXT_TOOLTIP :{BLACK}Transparantie omschakelen voor teksten bij laden en kosten/inkomsten. Ctrl+klik om vast te zetten
STR_TRANSPARENT_INVISIBLE_TOOLTIP :{BLACK}Maak objecten onzichtbaar in plaats van transparant
# Linkgraph legend window

@ -37,9 +37,7 @@
#include "../base_media_base.h"
#include "../blitter/factory.hpp"
#ifdef WITH_NLOHMANN_JSON
#include <nlohmann/json.hpp>
#endif /* WITH_NLOHMANN_JSON */
#include "../safeguards.h"
@ -47,8 +45,6 @@ extern std::string _savegame_id;
NetworkSurveyHandler _survey = {};
#ifdef WITH_NLOHMANN_JSON
NLOHMANN_JSON_SERIALIZE_ENUM(NetworkSurveyHandler::Reason, {
{NetworkSurveyHandler::Reason::PREVIEW, "preview"},
{NetworkSurveyHandler::Reason::LEAVE, "leave"},
@ -114,8 +110,12 @@ static void SurveySettings(nlohmann::json &survey)
*/
static void SurveyOpenTTD(nlohmann::json &survey)
{
survey["version"] = std::string(_openttd_revision);
survey["newgrf_version"] = _openttd_newgrf_version;
survey["version"]["revision"] = std::string(_openttd_revision);
survey["version"]["modified"] = _openttd_revision_modified;
survey["version"]["tagged"] = _openttd_revision_tagged;
survey["version"]["hash"] = std::string(_openttd_revision_hash);
survey["version"]["newgrf"] = fmt::format("{:X}", _openttd_newgrf_version);
survey["version"]["content"] = std::string(_openttd_content_version);
survey["build_date"] = std::string(_openttd_build_date);
survey["bits"] =
#ifdef POINTER_IS_64BIT
@ -227,6 +227,21 @@ static void SurveyCompanies(nlohmann::json &survey)
}
}
/**
* Convert timer information to JSON.
*
* @param survey The JSON object.
*/
static void SurveyTimers(nlohmann::json &survey)
{
survey["ticks"] = _scaled_tick_counter;
survey["seconds"] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - _switch_mode_time).count();
YearMonthDay ymd;
ConvertDateToYMD(_date, &ymd);
survey["calendar"] = fmt::format("{:04}-{:02}-{:02} ({})", ymd.year, ymd.month + 1, ymd.day, _date_fract);
}
/**
* Convert GRF information to JSON.
*
@ -298,8 +313,6 @@ std::string SurveyMemoryToText(uint64_t memory)
return fmt::format("{} MiB", Ceil(memory, 4));
}
#endif /* WITH_NLOHMANN_JSON */
/**
* Create the payload for the survey.
*
@ -309,9 +322,6 @@ std::string SurveyMemoryToText(uint64_t memory)
*/
std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview)
{
#ifndef WITH_NLOHMANN_JSON
return "";
#else
nlohmann::json survey;
survey["schema"] = NETWORK_SURVEY_VERSION;
@ -335,8 +345,7 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview)
{
auto &game = survey["game"];
game["ticks"] = _scaled_tick_counter;
game["time"] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - _switch_mode_time).count();
SurveyTimers(game["timers"]);
SurveyCompanies(game["companies"]);
SurveySettings(game["settings"]);
SurveyGrfs(game["grfs"]);
@ -346,7 +355,6 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview)
/* For preview, we indent with 4 whitespaces to make things more readable. */
int indent = for_preview ? 4 : -1;
return survey.dump(indent);
#endif /* WITH_NLOHMANN_JSON */
}
/**

@ -40,12 +40,12 @@ public:
constexpr static bool IsSurveyPossible()
{
#if !(defined(WITH_NLOHMANN_JSON) && defined(SURVEY_KEY))
/* Without JSON library, we cannot send a payload; so we disable the survey. */
#if !defined(SURVEY_KEY)
/* Without a survey key, we cannot send a payload; so we disable the survey. */
return false;
#else
return true;
#endif /* WITH_NLOHMANN_JSON */
#endif /* SURVEY_KEY */
}
private:

@ -164,7 +164,6 @@ void CDECL usererror(const char *s, ...)
/* In effect, the game ends here. As emscripten_set_main_loop() caused
* the stack to be unwound, the code after MainLoop() in
* openttd_main() is never executed. */
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
EM_ASM(if (window["openttd_abort"]) openttd_abort());
#endif
@ -682,6 +681,22 @@ struct AfterNewGRFScan : NewGRFScanCallback {
}
};
void PostMainLoop()
{
WaitTillSaved();
/* only save config if we have to */
if (_save_config) {
SaveToConfig(STCF_ALL);
SaveHotkeysToConfig();
WindowDesc::SaveToConfig();
SaveToHighScore();
}
/* Reset windowing system, stop drivers, free used memory, ... */
ShutdownGame();
}
#if defined(UNIX)
extern void DedicatedFork();
#endif
@ -1040,18 +1055,7 @@ int openttd_main(int argc, char *argv[])
_general_worker_pool.Stop();
WaitTillSaved();
/* only save config if we have to */
if (_save_config) {
SaveToConfig(STCF_ALL);
SaveHotkeysToConfig();
WindowDesc::SaveToConfig();
SaveToHighScore();
}
/* Reset windowing system, stop drivers, free used memory, ... */
ShutdownGame();
PostMainLoop();
return ret;
}

@ -7,8 +7,6 @@
/** @file survey_osx.cpp OSX implementation of OS-specific survey information. */
#ifdef WITH_NLOHMANN_JSON
#include "../../stdafx.h"
#include "../../core/format.hpp"
@ -38,5 +36,3 @@ void SurveyOS(nlohmann::json &json)
json["memory"] = SurveyMemoryToText(MacOSGetPhysicalMemory());
json["hardware_concurrency"] = std::thread::hardware_concurrency();
}
#endif /* WITH_NLOHMANN_JSON */

@ -7,8 +7,6 @@
/** @file survey_unix.cpp Unix implementation of OS-specific survey information. */
#ifdef WITH_NLOHMANN_JSON
#include "../../stdafx.h"
#include <nlohmann/json.hpp>
@ -38,5 +36,3 @@ void SurveyOS(nlohmann::json &json)
json["memory"] = SurveyMemoryToText(pages * page_size);
json["hardware_concurrency"] = std::thread::hardware_concurrency();
}
#endif /* WITH_NLOHMANN_JSON */

@ -7,8 +7,6 @@
/** @file survey_win.cpp Windows implementation of OS-specific survey information. */
#ifdef WITH_NLOHMANN_JSON
#include "../../stdafx.h"
#include "../../core/format.hpp"
@ -41,5 +39,3 @@ void SurveyOS(nlohmann::json &json)
json["memory"] = SurveyMemoryToText(status.ullTotalPhys);
json["hardware_concurrency"] = std::thread::hardware_concurrency();
}
#endif /* WITH_NLOHMANN_JSON */

@ -130,7 +130,10 @@
}
std::string json;
ScriptAdmin::MakeJSON(vm, -1, SQUIRREL_MAX_DEPTH, json);
if (!ScriptAdmin::MakeJSON(vm, -1, SQUIRREL_MAX_DEPTH, json)) {
sq_pushinteger(vm, 0);
return 1;
}
if (json.length() > NETWORK_GAMESCRIPT_JSON_LENGTH) {
ScriptLog::Error("You are trying to send a table that is too large to the AdminPort. No data sent.");

@ -37,7 +37,7 @@ public:
static bool Send(void *table);
#endif /* DOXYGEN_API */
private:
protected:
/**
* Convert a Squirrel structure into a JSON string.
* @param vm The VM to operate on.

@ -220,11 +220,11 @@ const char *ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, const char *p)
SKIP_EMPTY(p);
if (strncmp(p, "false", 5) == 0) {
sq_pushinteger(vm, 0);
sq_pushbool(vm, 0);
return p + 5;
}
if (strncmp(p, "true", 4) == 0) {
sq_pushinteger(vm, 1);
sq_pushbool(vm, 1);
return p + 4;
}
if (strncmp(p, "null", 4) == 0) {

@ -44,6 +44,7 @@ typedef bool (ScriptAsyncModeProc)();
class ScriptObject : public SimpleCountedObject {
friend class ScriptInstance;
friend class ScriptController;
friend class TestScriptController;
protected:
/**
* A class that handles the current active instance. By instantiating it at

@ -947,13 +947,19 @@ void VideoDriver_SDL_Base::LoopOnce()
* normally done at the end of the main loop for non-Emscripten.
* After that, Emscripten just halts, and the HTML shows a nice
* "bye, see you next time" message. */
extern void PostMainLoop();
PostMainLoop();
emscripten_cancel_main_loop();
emscripten_exit_pointerlock();
/* In effect, the game ends here. As emscripten_set_main_loop() caused
* the stack to be unwound, the code after MainLoop() in
* openttd_main() is never executed. */
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
EM_ASM(if (window["openttd_exit"]) openttd_exit());
if (_game_mode == GM_BOOTSTRAP) {
EM_ASM(if (window["openttd_bootstrap_reload"]) openttd_bootstrap_reload());
} else {
EM_ASM(if (window["openttd_exit"]) openttd_exit());
}
#endif
return;
}

Loading…
Cancel
Save