Changeset - r24439:3207de2680bf
[Not reviewed]
master
0 15 10
Patric Stout - 4 years ago 2020-12-05 20:57:47
truebrain@openttd.org
Add: support for emscripten (play-OpenTTD-in-the-browser)

Emscripten compiles to WASM, which can be loaded via
HTML / JavaScript. This allows you to play OpenTTD inside a
browser.

Co-authored-by: milek7 <me@milek7.pl>
25 files changed with 844 insertions and 12 deletions:
0 comments (0 inline, 0 general)
.github/workflows/ci-build.yml
Show inline comments
 
@@ -10,6 +10,55 @@ env:
 
  CTEST_OUTPUT_ON_FAILURE: 1
 

	
 
jobs:
 
  emscripten:
 
    name: Emscripten
 

	
 
    runs-on: ubuntu-20.04
 
    container:
 
      # If you change this version, change the number in the cache step too.
 
      image: emscripten/emsdk:2.0.10
 

	
 
    steps:
 
    - name: Checkout
 
      uses: actions/checkout@v2
 

	
 
    - name: Setup cache
 
      uses: actions/cache@v2
 
      with:
 
        path: /emsdk/upstream/emscripten/cache
 
        key: 2.0.10-${{ runner.os }}
 

	
 
    - name: Build (host tools)
 
      run: |
 
        mkdir build-host
 
        cd build-host
 

	
 
        echo "::group::CMake"
 
        cmake .. -DOPTION_TOOLS_ONLY=ON
 
        echo "::endgroup::"
 

	
 
        echo "::group::Build"
 
        echo "Running on $(nproc) cores"
 
        make -j$(nproc) tools
 
        echo "::endgroup::"
 

	
 
    - name: Install GCC problem matcher
 
      uses: ammaraskar/gcc-problem-matcher@master
 

	
 
    - name: Build
 
      run: |
 
        mkdir build
 
        cd build
 

	
 
        echo "::group::CMake"
 
        emcmake cmake .. -DHOST_BINARY_DIR=../build-host
 
        echo "::endgroup::"
 

	
 
        echo "::group::Build"
 
        echo "Running on $(nproc) cores"
 
        emmake make -j$(nproc)
 
        echo "::endgroup::"
 

	
 
  linux:
 
    name: Linux
 

	
CMakeLists.txt
Show inline comments
 
@@ -15,6 +15,10 @@ if(NOT CMAKE_BUILD_TYPE)
 
    set(CMAKE_BUILD_TYPE Debug)
 
endif()
 

	
 
if (EMSCRIPTEN)
 
    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/os/emscripten/cmake")
 
endif()
 

	
 
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
 
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9)
 

	
 
@@ -232,6 +236,39 @@ if(APPLE)
 
    )
 
endif()
 

	
 
if(EMSCRIPTEN)
 
    add_library(WASM::WASM INTERFACE IMPORTED)
 

	
 
    # Allow heap-growth, and start with a bigger memory size.
 
    target_link_libraries(WASM::WASM INTERFACE "-s ALLOW_MEMORY_GROWTH=1")
 
    target_link_libraries(WASM::WASM INTERFACE "-s INITIAL_MEMORY=33554432")
 

	
 
    # Export functions to Javascript.
 
    target_link_libraries(WASM::WASM INTERFACE "-s EXPORTED_FUNCTIONS='[\"_main\", \"_em_openttd_add_server\"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"cwrap\"]'")
 

	
 
    # Preload all the files we generate during build.
 
    # As we do not compile with FreeType / FontConfig, we also have no way to
 
    # render several languages (like Chinese, ..), so where do you draw the
 
    # line what languages to include and which not? In the end, especially as
 
    # the more languages you add the slower downloading becomes, we decided to
 
    # only ship the English language.
 
    target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_BINARY_DIR}/baseset@/baseset")
 
    target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_BINARY_DIR}/lang/english.lng@/lang/english.lng")
 
    target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_SOURCE_DIR}/bin/ai@/ai")
 
    target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_SOURCE_DIR}/bin/game@/game")
 

	
 
    # We use IDBFS for persistent storage.
 
    target_link_libraries(WASM::WASM INTERFACE "-lidbfs.js")
 

	
 
    # Use custom pre-js and shell.html.
 
    target_link_libraries(WASM::WASM INTERFACE "--pre-js ${CMAKE_SOURCE_DIR}/os/emscripten/pre.js")
 
    target_link_libraries(WASM::WASM INTERFACE "--shell-file ${CMAKE_SOURCE_DIR}/os/emscripten/shell.html")
 

	
 
    # Build the .html (which builds the .js, .wasm, and .data too).
 
    set_target_properties(openttd PROPERTIES SUFFIX ".html")
 
    target_link_libraries(openttd WASM::WASM)
 
endif()
 

	
 
if(NOT PERSONAL_DIR STREQUAL "(not set)")
 
    add_definitions(
 
        -DWITH_PERSONAL_DIR
cmake/Options.cmake
Show inline comments
 
@@ -55,7 +55,13 @@ function(set_options)
 
    option(OPTION_DEDICATED "Build dedicated server only (no GUI)" OFF)
 
    option(OPTION_INSTALL_FHS "Install with Filesystem Hierarchy Standard folders" ${DEFAULT_OPTION_INSTALL_FHS})
 
    option(OPTION_USE_ASSERTS "Use assertions; leave enabled for nightlies, betas, and RCs" ON)
 
    option(OPTION_USE_THREADS "Use threads" ON)
 
    if(EMSCRIPTEN)
 
        # Although pthreads is supported, it is not in a way yet that is
 
        # useful for us.
 
        option(OPTION_USE_THREADS "Use threads" OFF)
 
    else()
 
        option(OPTION_USE_THREADS "Use threads" ON)
 
    endif()
 
    option(OPTION_USE_NSIS "Use NSIS to create windows installer; enable only for stable releases" OFF)
 
    option(OPTION_TOOLS_ONLY "Build only tools target" OFF)
 
    option(OPTION_DOCS_ONLY "Build only docs target" OFF)
os/emscripten/Dockerfile
Show inline comments
 
new file 100644
 
FROM emscripten/emsdk
 

	
 
COPY emsdk-liblzma.patch /
 
RUN cd /emsdk/upstream/emscripten && patch -p1 < /emsdk-liblzma.patch
os/emscripten/README.md
Show inline comments
 
new file 100644
 
## How to build with Emscripten
 

	
 
Building with Emscripten works with emsdk 2.0.10 and above.
 

	
 
Currently there is no LibLZMA support upstream; for this we suggest to apply
 
the provided patch in this folder to your emsdk installation.
 

	
 
For convenience, a Dockerfile is supplied that does this patches for you
 
against upstream emsdk docker. Best way to use it:
 

	
 
Build the docker image:
 
```
 
  docker build -t emsdk-lzma .
 
```
 

	
 
Build the host tools first:
 
```
 
  mkdir build-host
 
  docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-lzma cmake .. -DOPTION_TOOLS_ONLY=ON
 
  docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-lzma make -j5 tools
 
```
 

	
 
Next, build the game with emscripten:
 

	
 
```
 
  mkdir build
 
  docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-lzma emcmake cmake .. -DHOST_BINARY_DIR=$(pwd)/build-host -DCMAKE_BUILD_TYPE=RelWithDebInfo -DOPTION_USE_ASSERTS=OFF
 
  docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-lzma emmake make -j5
 
```
 

	
 
And now you have in your build folder files like "openttd.html".
 

	
 
To run it locally, you would have to start a local webserver, like:
 

	
 
```
 
  cd build
 
  python3 -m http.server
 
````
 

	
 
Now you can play the game via http://127.0.0.1:8000/openttd.html .
os/emscripten/cmake/FindLibLZMA.cmake
Show inline comments
 
new file 100644
 
# LibLZMA is a recent addition to the emscripten SDK, so it is possible
 
# someone hasn't updated his SDK yet. Test out if the SDK supports LibLZMA.
 
include(CheckCXXSourceCompiles)
 
set(CMAKE_REQUIRED_FLAGS "-sUSE_LIBLZMA=1")
 

	
 
check_cxx_source_compiles("
 
    #include <lzma.h>
 
    int main() { return 0; }"
 
    LIBLZMA_FOUND
 
)
 

	
 
if (LIBLZMA_FOUND)
 
        add_library(LibLZMA::LibLZMA INTERFACE IMPORTED)
 
        set_target_properties(LibLZMA::LibLZMA PROPERTIES
 
                INTERFACE_COMPILE_OPTIONS "-sUSE_LIBLZMA=1"
 
                INTERFACE_LINK_LIBRARIES "-sUSE_LIBLZMA=1"
 
        )
 
else()
 
        message(WARNING "You are using an emscripten SDK without LibLZMA support. Many savegames won't be able to load in OpenTTD. Please apply 'emsdk-liblzma.patch' to your local emsdk installation.")
 
endif()
os/emscripten/cmake/FindPNG.cmake
Show inline comments
 
new file 100644
 
add_library(PNG::PNG INTERFACE IMPORTED)
 
set_target_properties(PNG::PNG PROPERTIES
 
        INTERFACE_COMPILE_OPTIONS "-sUSE_LIBPNG=1"
 
        INTERFACE_LINK_LIBRARIES "-sUSE_LIBPNG=1"
 
)
 

	
 
set(PNG_FOUND on)
os/emscripten/cmake/FindSDL2.cmake
Show inline comments
 
new file 100644
 
add_library(SDL2::SDL2 INTERFACE IMPORTED)
 
set_target_properties(SDL2::SDL2 PROPERTIES
 
        INTERFACE_COMPILE_OPTIONS "-sUSE_SDL=2"
 
        INTERFACE_LINK_LIBRARIES "-sUSE_SDL=2"
 
)
 

	
 
set(SDL2_FOUND on)
os/emscripten/cmake/FindZLIB.cmake
Show inline comments
 
new file 100644
 
add_library(ZLIB::ZLIB INTERFACE IMPORTED)
 
set_target_properties(ZLIB::ZLIB PROPERTIES
 
        INTERFACE_COMPILE_OPTIONS "-sUSE_ZLIB=1"
 
        INTERFACE_LINK_LIBRARIES "-sUSE_ZLIB=1"
 
)
 

	
 
set(ZLIB_FOUND on)
os/emscripten/emsdk-liblzma.patch
Show inline comments
 
new file 100644
 
From 90dd4d4c6b1cedec338ff5b375fffca93700f7bc Mon Sep 17 00:00:00 2001
 
From: milek7 <me@milek7.pl>
 
Date: Tue, 8 Dec 2020 01:03:31 +0100
 
Subject: [PATCH] Add liblzma port
 

	
 
---
 
Source: https://github.com/emscripten-core/emscripten/pull/12990
 

	
 
Modifed by OpenTTD to have the bare minimum needed to work. Otherwise there
 
are constantly conflicts when trying to apply this patch to different versions
 
of emsdk.
 

	
 
diff --git a/embuilder.py b/embuilder.py
 
index 818262190ed..ab7d5adb7b2 100755
 
--- a/embuilder.py
 
+++ b/embuilder.py
 
@@ -60,6 +60,7 @@
 
     'harfbuzz',
 
     'icu',
 
     'libjpeg',
 
+    'liblzma',
 
     'libpng',
 
     'ogg',
 
     'regal',
 
@@ -197,6 +198,8 @@ def main():
 
       build_port('ogg', libname('libogg'))
 
     elif what == 'libjpeg':
 
       build_port('libjpeg', libname('libjpeg'))
 
+    elif what == 'liblzma':
 
+      build_port('liblzma', libname('liblzma'))
 
     elif what == 'libpng':
 
       build_port('libpng', libname('libpng'))
 
     elif what == 'sdl2':
 
diff --git a/src/settings.js b/src/settings.js
 
index 61cd98939ba..be6fcb678c6 100644
 
--- a/src/settings.js
 
+++ b/src/settings.js
 
@@ -1197,6 +1197,9 @@ var USE_BZIP2 = 0;
 
 // 1 = use libjpeg from emscripten-ports
 
 var USE_LIBJPEG = 0;
 

	
 
+// 1 = use liblzma from emscripten-ports
 
+var USE_LIBLZMA = 0;
 
+
 
 // 1 = use libpng from emscripten-ports
 
 var USE_LIBPNG = 0;
 

	
 
diff --git a/tools/ports/liblzma.py b/tools/ports/liblzma.py
 
new file mode 100644
 
index 00000000000..e9567ef36ff
 
--- /dev/null
 
+++ b/tools/ports/liblzma.py
 
@@ -0,0 +1,160 @@
 
+# Copyright 2020 The Emscripten Authors.  All rights reserved.
 
+# Emscripten is available under two separate licenses, the MIT license and the
 
+# University of Illinois/NCSA Open Source License.  Both these licenses can be
 
+# found in the LICENSE file.
 
+
 
+import os
 
+import shutil
 
+
 
+VERSION = '5.2.5'
 
+HASH = '7443674247deda2935220fbc4dfc7665e5bb5a260be8ad858c8bd7d7b9f0f868f04ea45e62eb17c0a5e6a2de7c7500ad2d201e2d668c48ca29bd9eea5a73a3ce'
 
+
 
+
 
+def needed(settings):
 
+  return settings.USE_LIBLZMA
 
+
 
+
 
+def get(ports, settings, shared):
 
+  libname = ports.get_lib_name('liblzma')
 
+  ports.fetch_project('liblzma', 'https://tukaani.org/xz/xz-' + VERSION + '.tar.gz', 'xz-' + VERSION, sha512hash=HASH)
 
+
 
+  def create():
 
+    ports.clear_project_build('liblzma')
 
+
 
+    source_path = os.path.join(ports.get_dir(), 'liblzma', 'xz-' + VERSION)
 
+    dest_path = os.path.join(ports.get_build_dir(), 'liblzma')
 
+
 
+    shared.try_delete(dest_path)
 
+    os.makedirs(dest_path)
 
+    shutil.rmtree(dest_path, ignore_errors=True)
 
+    shutil.copytree(source_path, dest_path)
 
+
 
+    build_flags = ['-DHAVE_CONFIG_H', '-DTUKLIB_SYMBOL_PREFIX=lzma_', '-fvisibility=hidden']
 
+    exclude_dirs = ['xzdec', 'xz', 'lzmainfo']
 
+    exclude_files = ['crc32_small.c', 'crc64_small.c', 'crc32_tablegen.c', 'crc64_tablegen.c', 'price_tablegen.c', 'fastpos_tablegen.c'
 
+                     'tuklib_exit.c', 'tuklib_mbstr_fw.c', 'tuklib_mbstr_width.c', 'tuklib_open_stdxxx.c', 'tuklib_progname.c']
 
+    include_dirs_rel = ['../common', 'api', 'common', 'check', 'lz', 'rangecoder', 'lzma', 'delta', 'simple']
 
+
 
+    open(os.path.join(dest_path, 'src', 'config.h'), 'w').write(config_h)
 
+
 
+    final = os.path.join(dest_path, libname)
 
+    include_dirs = [os.path.join(dest_path, 'src', 'liblzma', p) for p in include_dirs_rel]
 
+    ports.build_port(os.path.join(dest_path, 'src'), final, flags=build_flags, exclude_dirs=exclude_dirs, exclude_files=exclude_files, includes=include_dirs)
 
+
 
+    ports.install_headers(os.path.join(dest_path, 'src', 'liblzma', 'api'), 'lzma.h')
 
+    ports.install_headers(os.path.join(dest_path, 'src', 'liblzma', 'api', 'lzma'), '*.h', 'lzma')
 
+
 
+    return final
 
+
 
+  return [shared.Cache.get(libname, create, what='port')]
 
+
 
+
 
+def clear(ports, settings, shared):
 
+  shared.Cache.erase_file(ports.get_lib_name('liblzma'))
 
+
 
+
 
+def process_args(ports):
 
+  return []
 
+
 
+
 
+def show():
 
+  return 'liblzma (USE_LIBLZMA=1; public domain)'
 
+
 
+
 
+config_h = r'''
 
+#define ASSUME_RAM 128
 
+#define ENABLE_NLS 1
 
+#define HAVE_CHECK_CRC32 1
 
+#define HAVE_CHECK_CRC64 1
 
+#define HAVE_CHECK_SHA256 1
 
+#define HAVE_CLOCK_GETTIME 1
 
+#define HAVE_DCGETTEXT 1
 
+#define HAVE_DECL_CLOCK_MONOTONIC 1
 
+#define HAVE_DECL_PROGRAM_INVOCATION_NAME 1
 
+#define HAVE_DECODERS 1
 
+#define HAVE_DECODER_ARM 1
 
+#define HAVE_DECODER_ARMTHUMB 1
 
+#define HAVE_DECODER_DELTA 1
 
+#define HAVE_DECODER_IA64 1
 
+#define HAVE_DECODER_LZMA1 1
 
+#define HAVE_DECODER_LZMA2 1
 
+#define HAVE_DECODER_POWERPC 1
 
+#define HAVE_DECODER_SPARC 1
 
+#define HAVE_DECODER_X86 1
 
+#define HAVE_DLFCN_H 1
 
+#define HAVE_ENCODERS 1
 
+#define HAVE_ENCODER_ARM 1
 
+#define HAVE_ENCODER_ARMTHUMB 1
 
+#define HAVE_ENCODER_DELTA 1
 
+#define HAVE_ENCODER_IA64 1
 
+#define HAVE_ENCODER_LZMA1 1
 
+#define HAVE_ENCODER_LZMA2 1
 
+#define HAVE_ENCODER_POWERPC 1
 
+#define HAVE_ENCODER_SPARC 1
 
+#define HAVE_ENCODER_X86 1
 
+#define HAVE_FCNTL_H 1
 
+#define HAVE_FUTIMENS 1
 
+#define HAVE_GETOPT_H 1
 
+#define HAVE_GETOPT_LONG 1
 
+#define HAVE_GETTEXT 1
 
+#define HAVE_IMMINTRIN_H 1
 
+#define HAVE_INTTYPES_H 1
 
+#define HAVE_LIMITS_H 1
 
+#define HAVE_MBRTOWC 1
 
+#define HAVE_MEMORY_H 1
 
+#define HAVE_MF_BT2 1
 
+#define HAVE_MF_BT3 1
 
+#define HAVE_MF_BT4 1
 
+#define HAVE_MF_HC3 1
 
+#define HAVE_MF_HC4 1
 
+#define HAVE_OPTRESET 1
 
+#define HAVE_POSIX_FADVISE 1
 
+#define HAVE_PTHREAD_CONDATTR_SETCLOCK 1
 
+#define HAVE_PTHREAD_PRIO_INHERIT 1
 
+#define HAVE_STDBOOL_H 1
 
+#define HAVE_STDINT_H 1
 
+#define HAVE_STDLIB_H 1
 
+#define HAVE_STRINGS_H 1
 
+#define HAVE_STRING_H 1
 
+#define HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC 1
 
+#define HAVE_SYS_PARAM_H 1
 
+#define HAVE_SYS_STAT_H 1
 
+#define HAVE_SYS_TIME_H 1
 
+#define HAVE_SYS_TYPES_H 1
 
+#define HAVE_UINTPTR_T 1
 
+#define HAVE_UNISTD_H 1
 
+#define HAVE_VISIBILITY 1
 
+#define HAVE_WCWIDTH 1
 
+#define HAVE__BOOL 1
 
+#define HAVE___BUILTIN_ASSUME_ALIGNED 1
 
+#define HAVE___BUILTIN_BSWAPXX 1
 
+#define MYTHREAD_POSIX 1
 
+#define NDEBUG 1
 
+#define PACKAGE "xz"
 
+#define PACKAGE_BUGREPORT "lasse.collin@tukaani.org"
 
+#define PACKAGE_NAME "XZ Utils"
 
+#define PACKAGE_STRING "XZ Utils 5.2.5"
 
+#define PACKAGE_TARNAME "xz"
 
+#define PACKAGE_VERSION "5.2.5"
 
+#define SIZEOF_SIZE_T 4
 
+#define STDC_HEADERS 1
 
+#define TUKLIB_CPUCORES_SYSCONF 1
 
+#define TUKLIB_FAST_UNALIGNED_ACCESS 1
 
+#define TUKLIB_PHYSMEM_SYSCONF 1
 
+#ifndef _ALL_SOURCE
 
+# define _ALL_SOURCE 1
 
+#endif
 
+#ifndef _GNU_SOURCE
 
+# define _GNU_SOURCE 1
 
+#endif
 
+#ifndef _POSIX_PTHREAD_SEMANTICS
 
+# define _POSIX_PTHREAD_SEMANTICS 1
 
+#endif
 
+#ifndef _TANDEM_SOURCE
 
+# define _TANDEM_SOURCE 1
 
+#endif
 
+#ifndef __EXTENSIONS__
 
+# define __EXTENSIONS__ 1
 
+#endif
 
+#define VERSION "5.2.5"
 
+'''
os/emscripten/loading.png
Show inline comments
 
new file 100755
 
binary diff not shown
Show images
os/emscripten/pre.js
Show inline comments
 
new file 100644
 
Module.arguments.push('-mnull', '-snull', '-vsdl:relative_mode');
 
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") {
 
        return "wss://content.openttd.org/";
 
    }
 

	
 
    /* Everything else just tries to make a default WebSocket connection.
 
     * If you run your own server you can setup your own WebSocket proxy in
 
     * front of it and let people connect to your server via the proxy. You
 
     * are best to add another "if" statement as above for this. */
 
    return null;
 
} };
 

	
 
Module.preRun.push(function() {
 
    personal_dir = '/home/web_user/.openttd';
 
    content_download_dir = personal_dir + '/content_download'
 

	
 
    /* Because of the "-c" above, all user-data is stored in /user_data. */
 
    FS.mkdir(personal_dir);
 
    FS.mount(IDBFS, {}, personal_dir);
 

	
 
    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://installer.cdn.openttd.org/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() {
 
        /* 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();
 
        }
 
    }
 

	
 
    window.openttd_exit = function() {
 
        Module.onExit();
 
    }
 

	
 
    window.openttd_abort = function() {
 
        Module.onAbort();
 
    }
 

	
 
    window.openttd_server_list = function() {
 
        add_server = Module.cwrap("em_openttd_add_server", null, ["string", "number"]);
 

	
 
        /* Add servers that support WebSocket here. Example:
 
         *  add_server("localhost", 3979); */
 
    }
 

	
 
    /* https://github.com/emscripten-core/emscripten/pull/12995 implements this
 
    * properly. Till that time, we use a polyfill. */
 
   SOCKFS.websocket_sock_ops.createPeer_ = SOCKFS.websocket_sock_ops.createPeer;
 
   SOCKFS.websocket_sock_ops.createPeer = function(sock, addr, port)
 
   {
 
       let func = Module['websocket']['url'];
 
       Module['websocket']['url'] = func(addr, port, (sock.type == 2) ? 'udp' : 'tcp');
 
       let ret = SOCKFS.websocket_sock_ops.createPeer_(sock, addr, port);
 
       Module['websocket']['url'] = func;
 
       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) { });
 
    }
 
});
os/emscripten/shell.html
Show inline comments
 
new file 100644
 
<!doctype html>
 
<html lang="en-us">
 
  <head>
 
    <meta charset="utf-8">
 
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 
    <title>OpenTTD</title>
 
    <style>
 
      body {
 
        font-family: Tahoma, Arial, Helvetica, sans-serif;
 
        font-size: 14px;
 
        margin: 0;
 
        padding: 0;
 
      }
 

	
 
      div.background {
 
        background-image: url("data:image/png;base64,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");
 
        border: 0px none;
 
        display: flex;
 
        height: 100%;
 
        position: absolute;
 
        width: 100%;
 
        z-index: 1;
 
      }
 

	
 
      div.overlay {
 
        height: 40px;
 
        margin: 0 auto;
 
        opacity: 0;
 
        pointer-events: none;
 
        position: absolute;
 
        text-align: center;
 
        transition: opacity 0.3s;
 
        width: 100%;
 
        z-index: 3;
 
      }
 
      div.overlay > div {
 
        pointer-events: none;
 
      }
 

	
 
      div.background > div, div.overlay > div {
 
        margin: auto;
 
      }
 

	
 
      #filesystem {
 
        background-color: #e00000;
 
        border: 1px solid #fc6458;
 
        color: #fcf880;
 
        display: none;
 
        outline: #a00000;
 
        padding: 0 4px;
 
        width: 600px;
 
      }
 

	
 
      #title, #message {
 
        background-color: #838383;
 
        border: 1px solid #a8a8a8;
 
        outline: 1px solid #626262;
 
        padding: 0 4px;
 
        min-width: 260px;
 
      }
 

	
 
      #box.error #title, #box.error #message {
 
        background-color: #e00000;
 
        border: 1px solid #fc6458;
 
        outline: #a00000;
 
      }
 
      #box.error #message {
 
        color: #fcf880;
 
      }
 

	
 
      #title {
 
        color: #fcfcfc;
 
        height: 20px;
 
        text-shadow: 1px 1px #101010;
 
      }
 
      #message {
 
        color: #101010;
 
        height: 54px;
 
        padding: 4px 4px;
 
      }
 

	
 
      canvas.emscripten {
 
        border: 0px none;
 
        height: 100%;
 
        position: absolute;
 
        width: 100%;
 
        z-index: 2;
 
      }
 
    </style>
 
  </head>
 
  <body>
 
    <div class="background">
 
      <div id="box">
 
        <div id="title">
 
          Loading ...
 
        </div>
 
        <div id="message">
 
        </div>
 
      </div>
 
    </div>
 
    <div class="overlay" id="overlay">
 
      <div id="filesystem">
 
        Warning: savegames are stored in the Indexed DB of your browser.<br/>Your browser can delete savegames without notice!
 
      </div>
 
    </div>
 
    <div>
 
      <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
 
    </div>
 

	
 
    <script type='text/javascript'>
 
      var statusElement = document.getElementById('status');
 
      var progressElement = document.getElementById('progress');
 
      var spinnerElement = document.getElementById('spinner');
 

	
 
      var Module = {
 
        preRun: [],
 
        postRun: [],
 
        arguments: [],
 
        totalDependencies: 42,
 
        doneDependencies: 0,
 
        lastDependencies: 1,
 

	
 
        print: function(text) {
 
            if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
 
            console.log(text);
 
        },
 

	
 
        printErr: function(text) {
 
            if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
 
            console.error(text);
 
        },
 

	
 
        canvas: (function() {
 
          var canvas = document.getElementById('canvas');
 

	
 
          // As a default initial behavior, pop up an alert when webgl context is lost. To make your
 
          // application robust, you may want to override this behavior before shipping!
 
          // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
 
          canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
 

	
 
          return canvas;
 
        })(),
 

	
 
        setStatus: function(text) {
 
          var m = text.match(/^([^(]+)\((\d+(\.\d+)?)\/(\d+)\)$/);
 

	
 
          if (m) {
 
            text = "(" + m[2] + " / " + m[4] + ") " + m[1];
 
          }
 

	
 
          document.getElementById("message").innerHTML = text;
 
        },
 

	
 
        monitorRunDependencies: function(left) {
 
          /* If it goes up, a new dependency was added; down means one is
 
           * removed. We only track the latter. */
 
          if (left < Module.lastDependencies) {
 
            Module.doneDependencies += 1;
 
          }
 
          Module.lastDependencies = left;
 

	
 
          total = Module.totalDependencies;
 
          doing = Module.doneDependencies + 1;
 
          if (doing > total) {
 
            doing = total;
 
          }
 

	
 
          document.getElementById("title").innerHTML = "(" + doing + " / " + total + ") Loading ...";
 
          document.getElementById("message").innerHTML = "Preparing game ...";
 
        },
 

	
 
        onExit: function() {
 
          document.getElementById("canvas").style.display = "none";
 

	
 
          document.getElementById("title").innerHTML = "Thank you for playing!";
 
          document.getElementById("message").innerHTML = "We hope you enjoyed OpenTTD!<br/><br/>Reload your browser to restart the game.";
 
        },
 

	
 
        onAbort: function() {
 
          document.getElementById("canvas").style.display = "none";
 

	
 
          document.getElementById("box").className = "error";
 
          document.getElementById("title").innerHTML = "Crash :(";
 
          document.getElementById("message").innerHTML = "The game crashed!<br/><br/>Please reload your browser to restart the game.";
 
        },
 

	
 
        onWarningFs: function() {
 
          document.getElementById("filesystem").style.display = "inline-block";
 
          document.getElementById("overlay").style.opacity = 1;
 
          setTimeout(function() {
 
            document.getElementById("overlay").style.opacity = 0;
 
            setTimeout(function() {
 
              document.getElementById("filesystem").style.display = "none";
 
            }, 300);
 
          }, 10000);
 
        }
 
      };
 

	
 
      window.onerror = function() {
 
        Module.onAbort();
 
      };
 
    </script>
 
    {{{ SCRIPT }}}
 
  </body>
 
</html>
src/fontcache.cpp
Show inline comments
 
@@ -26,7 +26,6 @@
 
#include "safeguards.h"
 

	
 
static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter.
 
static const int MAX_FONT_SIZE     = 72; ///< Maximum font size.
 

	
 
/** Default heights for the different sizes of fonts. */
 
static const int _default_font_height[FS_END]   = {10, 6, 18, 10};
 
@@ -202,6 +201,8 @@ bool SpriteFontCache::GetDrawGlyphShadow
 

	
 
FreeTypeSettings _freetype;
 

	
 
static const int MAX_FONT_SIZE = 72; ///< Maximum font size.
 

	
 
static const byte FACE_COLOUR = 1;
 
static const byte SHADOW_COLOUR = 2;
 

	
src/gfx_type.h
Show inline comments
 
@@ -162,7 +162,9 @@ struct DrawPixelInfo {
 
union Colour {
 
	uint32 data; ///< Conversion of the channel information to a 32 bit number.
 
	struct {
 
#if TTD_ENDIAN == TTD_BIG_ENDIAN
 
#if defined(__EMSCRIPTEN__)
 
		uint8 r, g, b, a;  ///< colour channels as used in browsers
 
#elif TTD_ENDIAN == TTD_BIG_ENDIAN
 
		uint8 a, r, g, b; ///< colour channels in BE order
 
#else
 
		uint8 b, g, r, a; ///< colour channels in LE order
 
@@ -177,7 +179,9 @@ union Colour {
 
	 * @param a The channel for the alpha/transparency.
 
	 */
 
	Colour(uint8 r, uint8 g, uint8 b, uint8 a = 0xFF) :
 
#if TTD_ENDIAN == TTD_BIG_ENDIAN
 
#if defined(__EMSCRIPTEN__)
 
		r(r), g(g), b(b), a(a)
 
#elif TTD_ENDIAN == TTD_BIG_ENDIAN
 
		a(a), r(r), g(g), b(b)
 
#else
 
		b(b), g(g), r(r), a(a)
src/ini.cpp
Show inline comments
 
@@ -13,6 +13,9 @@
 
#include "string_func.h"
 
#include "fileio_func.h"
 
#include <fstream>
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#endif
 

	
 
#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500)
 
# include <unistd.h>
 
@@ -115,6 +118,10 @@ bool IniFile::SaveToDisk(const char *fil
 
	}
 
#endif
 

	
 
#ifdef __EMSCRIPTEN__
 
	EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
 
#endif
 

	
 
	return true;
 
}
 

	
src/network/core/address.cpp
Show inline comments
 
@@ -299,7 +299,15 @@ static SOCKET ConnectLoopProc(addrinfo *
 

	
 
	if (!SetNoDelay(sock)) DEBUG(net, 1, "[%s] setting TCP_NODELAY failed", type);
 

	
 
	if (connect(sock, runp->ai_addr, (int)runp->ai_addrlen) != 0) {
 
	int err = connect(sock, runp->ai_addr, (int)runp->ai_addrlen);
 
#ifdef __EMSCRIPTEN__
 
	/* Emscripten is asynchronous, and as such a connect() is still in
 
	 * progress by the time the call returns. */
 
	if (err != 0 && errno != EINPROGRESS)
 
#else
 
	if (err != 0)
 
#endif
 
	{
 
		DEBUG(net, 1, "[%s] could not connect %s socket: %s", type, family, strerror(errno));
 
		closesocket(sock);
 
		return INVALID_SOCKET;
src/network/core/os_abstraction.h
Show inline comments
 
@@ -83,6 +83,16 @@ typedef unsigned long in_addr_t;
 
#	include <errno.h>
 
#	include <sys/time.h>
 
#	include <netdb.h>
 

	
 
#   if defined(__EMSCRIPTEN__)
 
/* Emscripten doesn't support AI_ADDRCONFIG and errors out on it. */
 
#		undef AI_ADDRCONFIG
 
#		define AI_ADDRCONFIG 0
 
/* Emscripten says it supports FD_SETSIZE fds, but it really only supports 64.
 
 * https://github.com/emscripten-core/emscripten/issues/1711 */
 
#		undef FD_SETSIZE
 
#		define FD_SETSIZE 64
 
#   endif
 
#endif /* UNIX */
 

	
 
/* OS/2 stuff */
 
@@ -148,12 +158,16 @@ typedef unsigned long in_addr_t;
 
 */
 
static inline bool SetNonBlocking(SOCKET d)
 
{
 
#ifdef _WIN32
 
	u_long nonblocking = 1;
 
#ifdef __EMSCRIPTEN__
 
	return true;
 
#else
 
#	ifdef _WIN32
 
	u_long nonblocking = 1;
 
#	else
 
	int nonblocking = 1;
 
#	endif
 
	return ioctlsocket(d, FIONBIO, &nonblocking) == 0;
 
#endif
 
	return ioctlsocket(d, FIONBIO, &nonblocking) == 0;
 
}
 

	
 
/**
 
@@ -163,10 +177,14 @@ static inline bool SetNonBlocking(SOCKET
 
 */
 
static inline bool SetNoDelay(SOCKET d)
 
{
 
#ifdef __EMSCRIPTEN__
 
	return true;
 
#else
 
	/* XXX should this be done at all? */
 
	int b = 1;
 
	/* The (const char*) cast is needed for windows */
 
	return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)) == 0;
 
#endif
 
}
 

	
 
/* Make sure these structures have the size we expect them to be */
src/network/network.cpp
Show inline comments
 
@@ -1154,3 +1154,14 @@ bool IsNetworkCompatibleVersion(const ch
 
	const char *hash2 = ExtractNetworkRevisionHash(other);
 
	return hash1 && hash2 && (strncmp(hash1, hash2, GITHASH_SUFFIX_LEN) == 0);
 
}
 

	
 
#ifdef __EMSCRIPTEN__
 
extern "C" {
 

	
 
void CDECL em_openttd_add_server(const char *host, int port)
 
{
 
	NetworkUDPQueryServer(NetworkAddress(host, port), true);
 
}
 

	
 
}
 
#endif
src/network/network_content.cpp
Show inline comments
 
@@ -23,6 +23,10 @@
 
#include <zlib.h>
 
#endif
 

	
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#endif
 

	
 
#include "../safeguards.h"
 

	
 
extern bool HasScenario(const ContentInfo *ci, bool md5sum);
 
@@ -289,6 +293,13 @@ void ClientNetworkContentSocketHandler::
 
{
 
	bytes = 0;
 

	
 
#ifdef __EMSCRIPTEN__
 
	/* Emscripten is loaded via an HTTPS connection. As such, it is very
 
	 * difficult to make HTTP connections. So always use the TCP method of
 
	 * downloading content. */
 
	fallback = true;
 
#endif
 

	
 
	ContentIDList content;
 
	for (const ContentInfo *ci : this->infos) {
 
		if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue;
 
@@ -535,6 +546,10 @@ void ClientNetworkContentSocketHandler::
 
			unlink(GetFullFilename(this->curInfo, false));
 
		}
 

	
 
#ifdef __EMSCRIPTEN__
 
		EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
 
#endif
 

	
 
		this->OnDownloadComplete(this->curInfo->id);
 
	} else {
 
		ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_EXTRACT, INVALID_STRING_ID, WL_ERROR);
src/network/network_gui.cpp
Show inline comments
 
@@ -39,6 +39,9 @@
 

	
 
#include "../safeguards.h"
 

	
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#endif
 

	
 
static void ShowNetworkStartServerWindow();
 
static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
 
@@ -475,6 +478,14 @@ public:
 
		this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
 
		this->SetFocusedWidget(WID_NG_FILTER);
 

	
 
		/* As the master-server doesn't support "websocket" servers yet, we
 
		 * let "os/emscripten/pre.js" hardcode a list of servers people can
 
		 * join. This means the serverlist is curated for now, but it is the
 
		 * best we can offer. */
 
#ifdef __EMSCRIPTEN__
 
		EM_ASM(if (window["openttd_server_list"]) openttd_server_list());
 
#endif
 

	
 
		this->last_joined = NetworkGameListAddItem(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port));
 
		this->server = this->last_joined;
 
		if (this->last_joined != nullptr) NetworkUDPQueryServer(this->last_joined->address);
 
@@ -615,6 +626,12 @@ public:
 
		this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_SEL)->SetDisplayedPlane(sel == nullptr || !sel->online || sel->info.grfconfig == nullptr);
 
		this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_MISSING_SEL)->SetDisplayedPlane(sel == nullptr || !sel->online || sel->info.grfconfig == nullptr || !sel->info.version_compatible || sel->info.compatible);
 

	
 
#ifdef __EMSCRIPTEN__
 
		this->SetWidgetDisabledState(WID_NG_FIND, true);
 
		this->SetWidgetDisabledState(WID_NG_ADD, true);
 
		this->SetWidgetDisabledState(WID_NG_START, true);
 
#endif
 

	
 
		this->DrawWidgets();
 
	}
 

	
src/openttd.cpp
Show inline comments
 
@@ -73,6 +73,11 @@
 

	
 
#include "safeguards.h"
 

	
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#	include <emscripten/html5.h>
 
#endif
 

	
 
void CallLandscapeTick();
 
void IncreaseDate();
 
void DoPaletteAnimations();
 
@@ -104,6 +109,15 @@ void CDECL usererror(const char *s, ...)
 
	ShowOSErrorBox(buf, false);
 
	if (VideoDriver::GetInstance() != nullptr) VideoDriver::GetInstance()->Stop();
 

	
 
#ifdef __EMSCRIPTEN__
 
	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_abort"]) openttd_abort());
 
#endif
 

	
 
	exit(1);
 
}
 

	
src/saveload/saveload.cpp
Show inline comments
 
@@ -45,6 +45,9 @@
 
#include "../error.h"
 
#include <atomic>
 
#include <string>
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#endif
 

	
 
#include "table/strings.h"
 

	
 
@@ -2495,6 +2498,10 @@ static void SaveFileDone()
 

	
 
	InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SAVELOAD_FINISH);
 
	_sl.saveinprogress = false;
 

	
 
#ifdef __EMSCRIPTEN__
 
	EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
 
#endif
 
}
 

	
 
/** Set the error message from outside of the actual loading/saving of the game (AfterLoadGame and friends) */
src/video/sdl2_v.cpp
Show inline comments
 
@@ -27,6 +27,10 @@
 
#include <mutex>
 
#include <condition_variable>
 
#include <algorithm>
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#	include <emscripten/html5.h>
 
#endif
 

	
 
#include "../safeguards.h"
 

	
 
@@ -673,7 +677,19 @@ void VideoDriver_SDL::LoopOnce()
 
	InteractiveRandom(); // randomness
 

	
 
	while (PollEvent() == -1) {}
 
	if (_exit_game) return;
 
	if (_exit_game) {
 
#ifdef __EMSCRIPTEN__
 
		/* Emscripten is event-driven, and as such the main loop is inside
 
		 * the browser. So if _exit_game goes true, the main loop ends (the
 
		 * cancel call), but we still have to call the cleanup that is
 
		 * 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. */
 
		emscripten_cancel_main_loop();
 
		MainLoopCleanup();
 
#endif
 
		return;
 
	}
 

	
 
	mod = SDL_GetModState();
 
	keys = SDL_GetKeyboardState(&numkeys);
 
@@ -722,9 +738,17 @@ void VideoDriver_SDL::LoopOnce()
 
		_local_palette = _cur_palette;
 
	} else {
 
		/* Release the thread while sleeping */
 
		if (_draw_mutex != nullptr) draw_lock.unlock();
 
		CSleep(1);
 
		if (_draw_mutex != nullptr) draw_lock.lock();
 
		if (_draw_mutex != nullptr) {
 
			draw_lock.unlock();
 
			CSleep(1);
 
			draw_lock.lock();
 
		} else {
 
/* Emscripten is running an event-based mainloop; there is already some
 
 * downtime between each iteration, so no need to sleep. */
 
#ifndef __EMSCRIPTEN__
 
			CSleep(1);
 
#endif
 
		}
 

	
 
		NetworkDrawChatMessage();
 
		DrawMouseCursor();
 
@@ -778,6 +802,10 @@ void VideoDriver_SDL::MainLoop()
 

	
 
	DEBUG(driver, 1, "SDL2: using %sthreads", _draw_threaded ? "" : "no ");
 

	
 
#ifdef __EMSCRIPTEN__
 
	/* Run the main loop event-driven, based on RequestAnimationFrame. */
 
	emscripten_set_main_loop_arg(&this->EmscriptenLoop, this, 0, 1);
 
#else
 
	while (!_exit_game) {
 
		LoopOnce();
 
	}
 
@@ -803,6 +831,15 @@ void VideoDriver_SDL::MainLoopCleanup()
 
		_draw_mutex = nullptr;
 
		_draw_signal = nullptr;
 
	}
 

	
 
#ifdef __EMSCRIPTEN__
 
	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());
 
#endif
 
}
 

	
 
bool VideoDriver_SDL::ChangeResolution(int w, int h)
src/video/sdl2_v.h
Show inline comments
 
@@ -46,6 +46,11 @@ private:
 
	void MainLoopCleanup();
 
	bool CreateMainSurface(uint w, uint h, bool resize);
 

	
 
#ifdef __EMSCRIPTEN__
 
	/* Convert a constant pointer back to a non-constant pointer to a member function. */
 
	static void EmscriptenLoop(void *self) { ((VideoDriver_SDL *)self)->LoopOnce(); }
 
#endif
 

	
 
	/**
 
	 * This is true to indicate that keyboard input is in text input mode, and SDL_TEXTINPUT events are enabled.
 
	 */
0 comments (0 inline, 0 general)