Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# Building ScummVM for Webassembly
The [Emscripten](https://emscripten.org/) target provides a script to build ScummVM as a single page browser app.
## Goals
This port of ScummVM has two primary use cases as its goals:
- **Demo App**: The goal of this use case is to provide an easy way for people to discover ScummVM and old adventure games. Game preservation is not just about archival but also accessibility. The primary goal is to make it as easy as possible to play any game which can legally be made available, and there's probably nothing easier than opening a webpage to do so.
- **ScummVM as a PWA** (progressive web app): There are platforms where native ScummVM is not readily available (primarily iOS/iPadOS). A PWA can work around these limitations. To really make this work, a few more features beyond what's in a Demo App would be required:
* Offline Support: PWAs can run offline. This means we have to find a way to cache some data which is downloaded on demand (engine plugins, game data etc.)
* Cloud Storage Integration: Users will have to have a way to bring their own games and export savegame data. This is best possible through cloud storage integration. This already exists in ScummVM, but a few adjustments will be necessary to make this work in a PWA.
See [chkuendig/scummvm-demo](http://github.com/chkuendig/scummvm-demo/) on how a ScummVM demo app can be built (incl. playable demo).
## About Webassembly and Emscripten
Emscripten is an LLVM/Clang-based compiler that compiles C and C++ source code to WebAssembly for execution in web browsers.
**Note:** In general most code can be crosscompiled to webassembly just fine. There's a few minor things which are different, but the mayor difference comnes down to how instructions are processed: Javascript and webassembly do support asynchronous/non-blocking code, but in general everything is running in the same [event loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop). This means also that webassembly code has to pause for the browser to do it's operations - render the page, process inputs, run I/O and so on. One consequence of this is that the page is not re-drawn until the webassembly code "yields" to the browser. Emscripten provides as much tooling as possible for this, but there's sometimes still a need to manually add a call to sleep into some engines.
## How to build for Webassembly
This folder contains a script to help build scummvm with Emscripten, it automatically downloads the correct emsdk version and also takes care of bundling the data and setting up a few demo games.
### Running build.sh
`build.sh` needs to be run from the root of the project.
```Shell
./dists/emscripten/build.sh [Tasks] [Options]
```
**Tasks:** space separated list of tasks to run. These can be:
* `build`: Run all tasks to build the complete app. These tasks are:
* `setup`: Download + install EMSDK and emscripten
* `libs`: Download and compile the required 3rd-party libraries required to build certain engines (libmad, a52dec etc)
* `configure`: Run the configure script with emconfigure with the recommended settings for a simple demo page
* `make`: Run the make scripts with emmake
* `games`: Download some demos and set up all data require for the demo page. See `--bundle-games=` below.
* `dist`: Copy all files into a single build-emscripten folder to bring it all together
* `add-games`: Runs ScummVM once to add all bundled games to the default `scummvm.ini`
* `icons`: Adds additional icons to the `gui-icons.dat` file. Please note that the `scummvm-icons` repository needs to be located in the parent folder of the project.
* `clean`: Cleanup build artifacts (keeps libs + emsdk in place)
* `run`: Start webserver and launch ScummVM in Chrome
**Options:**
* `-h`, `--help`: print a short help text
* `--bundle-games=<games>`: comma-separated list of demos and freeware games to bundle. Either specify a target (e.g. `comi` or a target and a specific file after a `/` , e.g. `comi/comi-win-large-demo-en.zip`)
* `-v`, `--verbose`: print all commands run by the script
* `--*`: all other options are passed on to the scummvm configure script
Independent of the command executed, the script sets up a pre-defined emsdk environment in the subfolder `./dists/emscripten/build.sh`
**Example:**
See e.g. [chkuendig/scummvm-demo/.github/workflows/main.yml](https://github.com/chkuendig/scummvm-demo/blob/main/.github/workflows/main.yml) for an example:
```
./dists/emscripten/build.sh build --verbose --disable-all-engines --enable-plugins --default-dynamic --enable-engine=adl,testbed,scumm,scumm_7_8,grim,monkey4,mohawk,myst,riven,sci32,agos2,sword2,drascula,sky,lure,queen,testbed,director,stark --bundle-games=testbed,comi/comi-win-large-demo-en.zip,warlock,sky/BASS-Floppy-1.3.zip,drascula/drascula-audio-mp3-2.0.zip,monkey4,feeble,queen/FOTAQ_Floppy.zip,ft,grim/grim-win-demo2-en.zip,lsl7,lure,myst,phantasmagoria,riven,hires1,tlj,sword2
```
## Current Status of Port
In general, ScummVM runs in the browser sufficiently to run all demos and freeware games.
* All engines compile (though I didn't test all of them), including ResidualVM with WebGL acceleration and shaders and run as plugins (which means the initial page load is somewhat limited)
* Audio works and 3rd-party libraries for sound and video decoding are integrated.
* All data can be downloaded on demand (or in the case of the testbed generated as part of the build script)
## Known Issues + Possible Improvements
### Emscripten Asyncify Optimizations
ScummVM relies heavily on Asyncify (see note above), and this comes with a quite heavy performance penalty. Possible optimizations in this regard could be:
* Specify a `ASYNCIFY_ONLY` list in `configure` to make asyncify only instrument functions in the call path as described in [emscripten.org: Asyncify](https://emscripten.org/docs/porting/asyncify.html)
* Limit asyncify overhead by having a more specific setting for `ASYNCIFY_IMPORTS` in `configure`. This is especailly critical for plugins as when plugins are enabled, we currently add all functions as imports.
* Don't use asyncify but rewrite main loop to improve performance.
* Look into Stack Switching (emscripten-core/emscripten#16779) or multithreading as an alternative to Asyncify.
### Storage Integration
* Settings can be persisted locally and assets can be loaded over HTTP, but more improvements could be possible:
* Use Range-Requests to download only parts of a file when not the whole file is not needed
* Download all game assets in background once the game has started
* Persist last game and last plugin for offline use
* Pre-load assets asynchronously (not blocking) - i.e. rest of the data of a game which has been launched
* Loading indicators (doesn't work with the current synchronous/blocking filesystem)
Emscripten is currently re-doing their filesystem code, which could help address some of the above issues ( emscripten-core/emscripten#15041 ).
* Locally persisted file system for saved games and settings using the Browser IndexedDB. (using [Emscripten IDBFS](https://emscripten.org/docs/api_reference/Filesystem-API.html#filesystem-api-idbfs))
* Cloud storage (Dropbox, Google Drive etc.) is exposed as a special folder on the file system. Only read access is implemented, but saved games can be synchronized via the regular cloud sync feature
* Screenshots and Logfiles are automatically downloaded after creation
* All other data is stored in memory and removed on reload (incl. temporarily stored logfiles and screenshots)
### UI Integration
* Build a nice webpage around the canvas.
* Allow showing/hiding of console (at the moment there's only the browser console)
* Bonus: Adapt page padding/background color to theme (black when in game)
* Automatically show console in case of exceptions
* 🐞 Aspect Ratio is broken when starting a game until the window is resized once. Good starting points might be https://github.com/emscripten-ports/SDL2/issues/47 or https://github.com/emscripten-core/emscripten/issues/10285
* doesn't seem to affect 3D engines in opengl mode
* definitely affects testbed in OpenGL or other modes

View File

@@ -0,0 +1,27 @@
{
"short_name": "ScummVM",
"name": "ScummVM: Script Creation Utility for Maniac Mansion Virtual Machine",
"icons": [
{
"src": "scummvm-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "scummvm-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "scummvm.html",
"background_color": "#3367D6",
"display": "fullscreen",
"theme_color": "#c60",
"shortcuts": [
],
"description": "ScummVM provides a way to play many classic graphical point-and-click adventure games and RPGs - such as SCUMM games (like Monkey Island and Day of the Tentacle), Revolution's Beneath A Steel Sky, and many more.",
"screenshots": [
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
# ScummVM - Graphic Adventure Engine
#
# ScummVM is the legal property of its developers, whose names
# are too numerous to list here. Please refer to the COPYRIGHT
# file distributed with this source distribution.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os
import json
import sys
from pathlib import Path
sym_links = {}
ignore_files = ['.git', 'index.json']
def rd_sync(dpath, tree, name):
"""Recursively scan directory and build file tree structure."""
try:
files = os.listdir(dpath)
except (OSError, PermissionError):
return tree
for file in files:
# ignore non-essential directories / files
if file in ignore_files or file.startswith('.'):
continue
fpath = os.path.join(dpath, file)
try:
# Avoid infinite loops with symbolic links
lstat = os.lstat(fpath)
if os.path.islink(fpath):
dev = lstat.st_dev
ino = lstat.st_ino
if dev not in sym_links:
sym_links[dev] = {}
# Ignore if we've seen it before
if ino in sym_links[dev]:
continue
sym_links[dev][ino] = True
if os.path.isdir(fpath):
child = {}
tree[file] = child
rd_sync(fpath, child, file)
# Write index.json for this directory
fs_listing = json.dumps(child)
fname = os.path.join(fpath, "index.json")
with open(fname, 'w', encoding='utf-8') as f:
f.write(fs_listing)
# Reset tree entry to empty dict after writing index
tree[file] = {}
else:
# Store file size
stat = os.stat(fpath)
tree[file] = stat.st_size
except (OSError, PermissionError):
# Ignore and move on
continue
return tree
def main():
if len(sys.argv) == 2:
root_folder = sys.argv[1]
fs_listing = json.dumps(rd_sync(root_folder, {}, '/'))
fname = os.path.join(root_folder, "index.json")
with open(fname, 'w', encoding='utf-8') as f:
f.write(fs_listing)
else:
root_folder = os.getcwd()
fs_listing = json.dumps(rd_sync(root_folder, {}, '/'))
print(fs_listing)
if __name__ == "__main__":
main()

403
dists/emscripten/build.sh Normal file
View File

@@ -0,0 +1,403 @@
#!/bin/bash
#
# .dists/emscripten/build.sh -- Sets up an emscripten build environment and builds ScummVM for webassembly
#
# ScummVM is the legal property of its developers, whose names
# are too numerous to list here. Please refer to the COPYRIGHT
# file distributed with this source distribution.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# exit when any command fails
set -e
ROOT_FOLDER=$(pwd)
DIST_FOLDER="$ROOT_FOLDER/dists/emscripten"
LIBS_FOLDER="$DIST_FOLDER/libs"
TASKS=()
CONFIGURE_ARGS=()
_bundle_games=()
_verbose=false
EMSDK_VERSION="${EMSDK_VERSION:-4.0.10}"
EMSCRIPTEN_VERSION="$EMSDK_VERSION"
usage="\
Usage: ./dists/emscripten/build.sh [TASKS] [OPTIONS]
Output the configuration name of the system \`$me' is run on.
Tasks:
(space separated) List of tasks to run. See ./dists/emscripten/README.md for details.
Options:
-h, --help print this help, then exit
-v, --verbose print all commands run by the script
--* all other options are passed on to the configure script
Note: --enable-a52, --enable-faad, --enable-fluidlite, --enable-fribidi,
--enable-mad, --enable-mpcdec, --enable-mpeg2, --enable-mikmod,
--enable-retrowave, --enable-theoradec and --enable-vpx
also download and build the required library
"
_fluidlite=false
_fribidi=false
_liba52=false
_libfaad=false
_libmad=false
_libmpcdec=false
_libmpeg2=false
_libmikmod=false
_libtheoradec=false
_libvpx=false
_retrowave=false
# parse inputs
for i in "$@"; do
case $i in
--enable-a52)
_liba52=true
CONFIGURE_ARGS+=" $i"
;;
--enable-faad)
_libfaad=true
CONFIGURE_ARGS+=" $i"
;;
--enable-fluidlite)
_fluidlite=true
CONFIGURE_ARGS+=" $i"
;;
--enable-fribidi)
_fribidi=true
CONFIGURE_ARGS+=" $i"
;;
--enable-mad)
_libmad=true
CONFIGURE_ARGS+=" $i"
;;
--enable-mpeg2)
_libmpeg2=true
CONFIGURE_ARGS+=" $i"
;;
--enable-mpcdec)
_libmpcdec=true
# We don't pass --enable-mpcdec as configure
# has to establish which API to use (old or new)
;;
--enable-openmpt)
_libopenmpt=true
CONFIGURE_ARGS+=" $i"
;;
--enable-retrowave)
_retrowave=true
CONFIGURE_ARGS+=" $i"
;;
--enable-theoradec)
_libtheoradec=true
CONFIGURE_ARGS+=" $i"
;;
--enable-vpx)
_libvpx=true
CONFIGURE_ARGS+=" $i"
;;
--bundle-games=*)
str="${i#*=}"
_bundle_games="${str//,/ }"
shift # past argument=value
;;
-h | --help)
echo "$usage"
exit
;;
-v | --verbose)
_verbose=true
;;
-* | --*)
CONFIGURE_ARGS+=" $i"
;;
*)
TASKS+="|$i" # save positional arg
shift # past argument
;;
esac
done
TASKS="${TASKS:1}"
if [[ -z "$TASKS" ]]; then
echo "$usage"
exit
fi
# print commands
if [[ "$_verbose" = true ]]; then
set -o xtrace
fi
#################################
# Setup Toolchain
#################################
# Download Emscripten
if [[ ! -d "$DIST_FOLDER/emsdk-$EMSDK_VERSION" ]]; then
echo "$DIST_FOLDER/emsdk-$EMSDK_VERSION not found. Installing Emscripten"
cd "$DIST_FOLDER"
if [[ "$EMSDK_VERSION" = "tot" ]]; then
git clone "https://github.com/emscripten-core/emsdk/" emsdk-tot
else
wget -nc --content-disposition --no-check-certificate "https://github.com/emscripten-core/emsdk/archive/refs/tags/${EMSDK_VERSION}.tar.gz"
tar -xf "emsdk-${EMSDK_VERSION}.tar.gz"
fi
fi
cd "$DIST_FOLDER/emsdk-${EMSDK_VERSION}"
ret=0 # https://stackoverflow.com/questions/18621990/bash-get-exit-status-of-command-when-set-e-is-active
./emsdk activate ${EMSCRIPTEN_VERSION} || ret=$?
if [[ $ret != 0 ]]; then
echo "install missing emscripten version"
cd "$DIST_FOLDER/emsdk-${EMSDK_VERSION}"
./emsdk install ${EMSCRIPTEN_VERSION}
cd "$DIST_FOLDER/emsdk-${EMSDK_VERSION}"
./emsdk activate ${EMSCRIPTEN_VERSION}
fi
source "$DIST_FOLDER/emsdk-$EMSDK_VERSION/emsdk_env.sh"
# export node_path - so we can use all node_modules bundled with emscripten (e.g. requests)
EMSDK_NPM=$(dirname $EMSDK_NODE)/npm
EMSDK_PYTHON="${EMSDK_PYTHON:-python3}"
EMSDK_NPX=$(dirname $EMSDK_NODE)/npx
export NODE_PATH="$(dirname $EMSDK_NODE)/../lib/node_modules/"
LIBS_FLAGS=""
cd "$ROOT_FOLDER"
#################################
# Download + Install Libraries (if not provided by Emscripten-Ports, those are handled by configure)
#################################
if [[ ! -d "$LIBS_FOLDER/build" ]]; then
mkdir -p "$LIBS_FOLDER/build"
fi
if [ "$_liba52" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/liba52.a" ]]; then
echo "building a52dec-0.7.4"
cd "$LIBS_FOLDER"
wget -nc "https://code.videolan.org/videolan/liba52/-/archive/0.7.4/liba52-0.7.4.tar.gz"
tar -xf liba52-0.7.4.tar.gz
cd "$LIBS_FOLDER/liba52-0.7.4/"
autoreconf -i
CFLAGS="-fPIC -Oz" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-a52-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libfaad" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libfaad.a" ]]; then
echo "building faad2-2.8.8"
cd "$LIBS_FOLDER"
wget -nc "https://sourceforge.net/projects/faac/files/faad2-src/faad2-2.8.0/faad2-2.8.8.tar.gz"
tar -xf faad2-2.8.8.tar.gz
cd "$LIBS_FOLDER/faad2-2.8.8/"
CFLAGS="-fPIC -Oz" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-faad-prefix=$LIBS_FOLDER/build"
fi
if [ "$_fluidlite" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libfluidlite.a" ]]; then
echo "building fluidlite-b0f187b"
cd "$LIBS_FOLDER"
wget -nc --content-disposition "https://github.com/divideconcept/FluidLite/archive/b0f187b404e393ee0a495b277154d55d7d03cbeb.tar.gz"
tar -xf FluidLite-b0f187b404e393ee0a495b277154d55d7d03cbeb.tar.gz
cd "$LIBS_FOLDER/FluidLite-b0f187b404e393ee0a495b277154d55d7d03cbeb/"
emcmake cmake -B "build/" -DFLUIDLITE_BUILD_STATIC:BOOL="1" -DCMAKE_INSTALL_PREFIX="$LIBS_FOLDER/build/" -DCMAKE_INSTALL_LIBDIR="lib"
cmake --build "build/"
cmake --install "build/"
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-fluidlite-prefix=$LIBS_FOLDER/build"
fi
if [ "$_fribidi" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libfribidi.a" ]]; then
echo "building fribidi-1.0.10"
cd "$LIBS_FOLDER"
wget -nc "https://github.com/fribidi/fribidi/releases/download/v1.0.10/fribidi-1.0.10.tar.xz"
tar -xf fribidi-1.0.10.tar.xz
cd "$LIBS_FOLDER/fribidi-1.0.10/"
CFLAGS="-fPIC -Oz" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-fribidi-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libmad" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libmad.a" ]]; then
echo "building libmad-0.15.1b"
cd "$LIBS_FOLDER"
wget -nc "https://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz"
tar -xf libmad-0.15.1b.tar.gz
cd "$LIBS_FOLDER/libmad-0.15.1b/"
# libmad needs patching as -fforce-mem has been removed in GCC 4.3 and later
sed -i -e 's/-fforce-mem//g' configure
CFLAGS="-Oz" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --with-pic --enable-fpm=no
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-mad-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libmpeg2" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libmpeg2.a" ]]; then
echo "building libmpeg2-946bf4b5"
cd "$LIBS_FOLDER"
wget -nc --content-disposition "https://code.videolan.org/videolan/libmpeg2/-/archive/946bf4b518aacc224f845e73708f99e394744499/libmpeg2-946bf4b518aacc224f845e73708f99e394744499.tar.gz"
tar -xf libmpeg2-946bf4b518aacc224f845e73708f99e394744499.tar.gz
cd "$LIBS_FOLDER/libmpeg2-946bf4b518aacc224f845e73708f99e394744499/"
autoreconf -i
CFLAGS="-fPIC -Oz" emconfigure ./configure --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --disable-sdl
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-mpeg2-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libmpcdec" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libmpcdec.a" ]]; then
echo "building libmpcdec-1.2.6"
cd "$LIBS_FOLDER"
wget -nc "https://files.musepack.net/source/libmpcdec-1.2.6.tar.bz2"
tar -xf libmpcdec-1.2.6.tar.bz2
cd "$LIBS_FOLDER/libmpcdec-1.2.6/"
CFLAGS="-Oz" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --with-pic --enable-fpm=no
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-mpcdec-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libopenmpt" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libopenmpt.a" ]]; then
echo "building libopenmpt-0.7.13"
cd "$LIBS_FOLDER"
wget -nc "https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.22+release.makefile.tar.gz"
tar -xf libopenmpt-0.6.22+release.makefile.tar.gz
cd "$LIBS_FOLDER/libopenmpt-0.6.22+release/"
CFLAGS="-fPIC -Oz" emmake make -j 5 CONFIG=emscripten EMSCRIPTEN_TARGET=wasm
emmake make install CONFIG=emscripten EMSCRIPTEN_TARGET=wasm PREFIX="$LIBS_FOLDER/build/"
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-openmpt-prefix=$LIBS_FOLDER/build"
fi
if [ "$_retrowave" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libRetroWave.a" ]]; then
echo "build libRetroWave-e6bf60e"
cd "$LIBS_FOLDER"
wget -nc --content-disposition "https://github.com/SudoMaker/RetroWave/archive/e6bf60eed2d2bd1deff688d645be71a32bbf05bb.tar.gz"
tar -xf RetroWave-e6bf60eed2d2bd1deff688d645be71a32bbf05bb.tar.gz
cd "$LIBS_FOLDER/RetroWave-e6bf60eed2d2bd1deff688d645be71a32bbf05bb/"
CFLAGS="-fPIC -s USE_ZLIB=1 -Oz" emcmake cmake -B "build/" -DRETROWAVE_BUILD_PLAYER=0 -DCMAKE_INSTALL_PREFIX="$LIBS_FOLDER/build/" -DCMAKE_INSTALL_LIBDIR="lib"
cmake --build "build/"
cmake --install "build/"
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-retrowave-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libtheoradec" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libtheora.a" ]]; then
echo "build libtheora-1.1.1"
cd "$LIBS_FOLDER"
wget -nc "https://downloads.xiph.org/releases/theora/libtheora-1.1.1.tar.xz"
tar -xf libtheora-1.1.1.tar.xz
cd "$LIBS_FOLDER/libtheora-1.1.1/"
CFLAGS="-fPIC -s USE_OGG=1 -Oz" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --disable-asm
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-theoradec-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libvpx" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libvpx.a" ]]; then
echo "build libvpx-1.15.0"
cd "$LIBS_FOLDER"
wget -nc --content-disposition "https://github.com/webmproject/libvpx/archive/refs/tags/v1.15.0.tar.gz"
tar -xf libvpx-1.15.0.tar.gz
cd "$LIBS_FOLDER/libvpx-1.15.0/"
CFLAGS="-fPIC -Oz" emconfigure ./configure --disable-vp8-encoder --target=generic-gnu --disable-vp9-encoder --prefix="$LIBS_FOLDER/build/"
emmake make -j 5
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-vpx-prefix=$LIBS_FOLDER/build"
fi
#################################
# Configure
#################################
if [[ "configure" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
cd "${ROOT_FOLDER}"
echo "Running configure"
# TODO: Figure out how configure could guess the host
emconfigure ./configure --host=wasm32-unknown-emscripten --build=wasm32-unknown-emscripten ${CONFIGURE_ARGS} ${LIBS_FLAGS}
# TODO: configure currently doesn't clean up all files it creates
rm scummvm-conf.*
fi
#################################
# Make / Compile
#################################
if [[ "make" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
cd "${ROOT_FOLDER}"
echo "Running make"
num_cpus=$(nproc || grep -c ^processor /proc/cpuinfo || echo 1)
emmake make -j ${num_cpus}
fi
#################################
# Bundle everything into a neat package
#################################
if [[ "dist" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
echo "Bundle ScummVM for static file hosting"
emmake make dist-emscripten
fi
#################################
# Run Development Server
#################################
if [[ "run" =~ $(echo ^\(${TASKS}\)$) ]]; then
echo "Run ScummVM"
cd "${ROOT_FOLDER}/build-emscripten/"
emrun --browser=chrome scummvm.html
fi
#################################
# Clean
#################################
if [[ "clean" =~ $(echo ^\(${TASKS}\)$) ]]; then
emmake make clean || true
emmake make distclean || true
emcc --clear-ports --clear-cache
rm -rf ./build-emscripten/ || true
rm scummvm.debug.wasm || true
rm scummvm.wasm || true
rm scummvm.js || true
fi

View File

@@ -0,0 +1,10 @@
// Workaround for https://github.com/emscripten-core/emscripten/pull/9803
// which results in mouse events not working anymore after context switches
// (i.e. when launching a game)
/*global JSEvents*/
JSEvents.removeAllHandlersOnTarget = function(){};
// Make sure to release any resources (e.g. RetroWave or Midi Devices) when leaving the page
window.addEventListener("beforeunload", function (e) {
Module["_raise"](2); // SIGINT
});

View File

@@ -0,0 +1,25 @@
/*global Module*/
Module["arguments"] = [];
// Add all parameters passed via the fragment identifier
if (window.location.hash.length > 0) {
params = decodeURI(window.location.hash.substring(1)).split(" ")
params.forEach((param) => {
Module["arguments"].push(param);
})
}
// MIDI support
var midiOutputMap;
if (!("requestMIDIAccess" in navigator)) {
console.error("No MIDI support in your browser.");
} else {
navigator
.requestMIDIAccess({ sysex: true, software: true })
.then((midiAccess) => {
midiOutputMap = midiAccess.outputs;
midiAccess.onstatechange = (e) => {
midiOutputMap = e.target.outputs;
};
});
}

View File

@@ -0,0 +1,223 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta content="width=device-width,initial-scale=1,viewport-fit=cover" name=viewport>
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="scummvm-192.png">
<title>ScummVM</title>
<style>
body {
margin: 0;
padding: none;
background-color: #000;
}
.emscripten {
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
}
textarea.emscripten {
font-family: monospace;
width: 80%;
}
div.emscripten {
text-align: center;
}
div.emscripten_border {
border: 1px solid black;
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten {
border: 0px none;
background: url("logo.svg");
background-position: center;
background-repeat: no-repeat;
background-color: #cc6600;
position: absolute;
top: 0px;
left: 0px;
margin: 0px;
width: 100%;
height: 100%;
overflow: hidden;
display: block;
}
@media (orientation: landscape) {
canvas.emscripten {
background-size: auto 33%;
}
}
@media (orientation: portrait) {
canvas.emscripten {
background-size: 80% auto;
}
}
#progress {
top: 0px;
left: 0px;
height: 10px;
width: 100%;
overflow: hidden;
display: block;
position: absolute;
z-index: 2;
border: 0px;
background: #c60
}
progress::-moz-progress-bar {
background: #f6e08a;
}
progress::-webkit-progress-value {
background: #f6e08a;
}
progress {
color: #f6e08a;
}
#status {
position: absolute;
bottom: 5em;
right: 0px;
padding: 5px;
text-align: right;
border-top-left-radius: 1em;
border-bottom-left-radius: 1em;
padding-left: 1em;
padding-right: 1em;
z-index: 3;
border: 3px solid black;
border-right: none;
background: #f6e08a;
font: bold large/1.4 "Trebuchet MS", Verdana, Tahoma, Sans-Serif;
}
#status.error {
background: red
}
</style>
</head>
<body>
<div class=emscripten>
<progress hidden id=progress max=100 value=0></progress>
</div>
<div class="emscripten" id="status">Downloading ScummVM...</div>
<div class=emscripten_border>
<canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas>
</div>
<hr>
<textarea class="emscripten" id="output" rows="8"></textarea>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
function loadingDoneMessage() {
document.getElementById("progress").style.zIndex = 0;
return "All downloads complete."
}
var Module = {
preRun: [],
postRun: [],
print: (function () {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return function (text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
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) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2]) * 100;
progressElement.max = parseInt(m[4]) * 100;
progressElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
}
if (text && text.length > 0) {
console.log((new Date()).toLocaleTimeString() + " " + text)
text += "⚡️"
statusElement.style.display = "block";
} else {
statusElement.style.display = "none";
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function (left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies - left) + '/' + this.totalDependencies + ')' : loadingDoneMessage());
}
};
Module.setStatus('Downloading ScummVM...');
window.onerror = function () {
statusElement.classList.add("error")
Module.setStatus('Exception thrown, see JavaScript console');
Module.setStatus = function (text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
</body>
</html>