EMSCRIPTEN: Major build improvements

- Updated Emscripten to version 3.1.8 (+ additional patches)
- Support for dynamic plugins
- Adding ScummvmFS with support for HTTP Range Requests for game data
- Automated games/demos bundling and ini config generation during build
- Allow passing CLI arguments via fragment identifier of the website (i.e. scummvm.html#—debuglevel=9 )
- UI improvements with nicer status messages, splash screen + favicon
- Fixed HiDPI handling and responsiveness
- Bugfix: Don't crash if gamepad support isn't available
This commit is contained in:
Christian Kündig 2022-06-08 23:20:39 +02:00 committed by Eugene Sandulenko
parent 8f0174689b
commit bc3679e928
21 changed files with 1948 additions and 287 deletions

88
configure vendored
View File

@ -838,6 +838,7 @@ Special configuration feature:
psp for PlayStation Portable
samsungtv for Samsung TV
switch for Nintendo Switch
wasm32-unknown-emscripten for WebAssembly
wii for Nintendo Wii
Game engines:
@ -1709,7 +1710,7 @@ wasm32-*)
_endian=little # the endian check below fails, but emscripten is always little endian anyway
_host_os=emscripten
_host_cpu=wasm32
datadir='/scummvm'
datadir='/data'
CXX="em++"
;;
wii)
@ -2888,24 +2889,46 @@ EOF
;;
emscripten)
# mandatory emscripten flags
append_var LDFLAGS "-s FULL_ES2=1 -s MAX_WEBGL_VERSION=1 -s ASYNCIFY -s FORCE_FILESYSTEM=1 -s ALLOW_MEMORY_GROWTH=1"
# enable emscripten-ports and set up paths accordingly
append_var LDFLAGS "-s USE_SDL=2 -s USE_SDL_MIXER=2 -s USE_OGG=1 -s USE_VORBIS=1 -s USE_LIBJPEG=1 -s USE_FREETYPE=1 -s USE_ZLIB"
_sdlpath="$EMSDK/upstream/emscripten/system/bin/"
_pngpath="$EMSDK/upstream/emscripten/cache/"
_freetypepath="$EMSDK/upstream/emscripten/cache/ports-builds/freetype/"
VORBIS_CFLAGS="-I$EMSDK/upstream/emscripten/cache//ports-builds/vorbis/include"
VORBIS_LIBS="-L$EMSDK/upstream/emscripten/cache/ports-builds/vorbis/lib"
FREETYPE2_CFLAGS="-I$_freetypepath/include"
_freetype_found="true"
if test "$_debug_build" = no; then
_optimization_level=-O3 -g4
append_var LDFLAGS "-s ALLOW_MEMORY_GROWTH=1 -s ASYNCIFY -s FORCE_FILESYSTEM=1"
append_var DEFINES "-DEMSCRIPTEN"
add_line_to_config_mk 'EMSCRIPTEN = 1'
if test "$_debug_build" = yes; then
_optimization_level=-O2
append_var LDFLAGS "-O2 -g3 -s ASSERTIONS=2"
else
_optimization_level=-O2
_optimization_level=-O3
append_var LDFLAGS "-O3"
fi
# activate emscripten-ports
if test "$_freetype2" != no; then
append_var LDFLAGS "-s USE_FREETYPE=1 -s SUPPORT_LONGJMP=1" # freetype requires setjmp
# neither pkg-config nor freetype-config work, so we setup freetype manually
_freetypepath="$EMSCRIPTEN/cache/ports-builds/freetype/"
FREETYPE2_CFLAGS="-I$_freetypepath/include" # there were link errors / missing symbols without this
_freetype_found="true"
else
#use link time optimization to further reduce exe size (this can't be used with setjmp whcih freetype requires)
# TODO: Figure out why this is a conflict and/or if freetype can be built without setjmp
append_var CXXFLAGS "-flto"
append_var LDFLAGS "-flto"
fi
if test "$_jpeg" != no; then
append_var LDFLAGS "-s USE_LIBJPEG=1"
fi
if test "$_png" != no; then
append_var LDFLAGS "-s USE_LIBPNG=1"
fi
if test "$_sdl" != no; then
append_var LDFLAGS "-s USE_SDL=2 "
fi
if test "$_vorbis" != no; then
append_var LDFLAGS "-s USE_OGG=1" # vorbis needs to be linked against OGG (even if we use an external vorbis lib)
fi
if test "$_zlib" != no; then
append_var LDFLAGS "-s USE_ZLIB=1"
fi
;;
freebsd*)
@ -3247,8 +3270,11 @@ if test -n "$_host"; then
;;
wasm*-emscripten)
_backend="sdl"
HOSTEXEPRE=
HOSTEXEEXT=.html
# Disable cloud and SDL_Net as this is handled in the browser
_cloud=no
_sdlnet=no
_libcurl=no
_curl=no
_ar="emar cr"
_ranlib="emranlib"
;;
@ -4127,12 +4153,13 @@ PLUGIN_LDFLAGS += -Wl,-T$(srcdir)/backends/plugins/ds/plugin.ld -mthumb -mthumb
;;
emscripten)
_plugin_prefix="lib"
_plugin_suffix=".wasm"
_plugin_suffix=".so"
append_var DEFINES "-DUNCACHED_PLUGINS"
append_var CXXFLAGS "-fPIC"
append_var LIBS ""
_mak_plugins='
PLUGIN_EXTRA_DEPS =
PLUGIN_LDFLAGS += -s SIDE_MODULE=1 -s EXPORT_ALL=1
PLUGIN_LDFLAGS += $(LDFLAGS) -s SIDE_MODULE=1 -s ASYNCIFY_IMPORTS=["*"] -s EXPORT_ALL=1
PRE_OBJS_FLAGS := -s MAIN_MODULE=1 -s EXPORT_ALL=1
POST_OBJS_FLAGS :=
'
@ -5389,7 +5416,11 @@ if test "$_opengl_mode" != none ; then
darwin*)
_opengl_mode=gl
;;
emscripten)
_opengl_mode=gles2
_opengl_glad=no
append_var LDFLAGS "-s FULL_ES2=1 -s MAX_WEBGL_VERSION=1"
;;
*)
# As SDL2 supports everything, let the user choose if he wants to
test "$_opengl_mode" = "auto" && _opengl_mode=any
@ -5403,9 +5434,6 @@ if test "$_opengl_mode" != none ; then
_opengl_mode=gles2
_opengl_glad=yes
;;
wasm*-emscripten)
_opengl_mode=gles2
;;
*)
# On all other platforms, by default don't enable OpenGL
test "$_opengl_mode" = "auto" && _opengl_mode=none
@ -6185,6 +6213,22 @@ case $_host_os in
# append_var LDFLAGS "-Wl,--retain-symbols-file,ds.syms"
fi
;;
emscripten)
append_var LDFLAGS "--pre-js ./dists/emscripten/custom_shell-pre.js --post-js ./dists/emscripten/custom_shell-post.js --shell-file ./dists/emscripten/custom_shell.html"
# we remove some linker flags for libs which will be added by emscripten from emscripten-ports to avoid duplicate symbols
if test "${LDFLAGS#*-s USE_LIBJPEG=1}" != "$LDFLAGS"; then
LIBS=`echo ${LIBS} | sed 's/-ljpeg//g'`
fi
if test "${LDFLAGS#*-s USE_LIBPNG=1}" != "$LDFLAGS"; then
LIBS=`echo ${LIBS} | sed 's/-lpng -lz//g'`
fi
if test "${LDFLAGS#*-s USE_OGG=1}" != "$LDFLAGS"; then
LIBS=`echo ${LIBS} | sed 's/-logg//g'`
fi
if test "${LDFLAGS#*-s USE_ZLIB=1}" != "$LDFLAGS"; then
LIBS=`echo ${LIBS} | sed 's/-lz//g'`
fi
;;
mingw*)
if test "$_windows_unicode" = yes; then
append_var DEFINES "-DUNICODE -D_UNICODE"

View File

@ -1,66 +1,93 @@
# Building ScummVM for Webassembly
The [Emscripten](https://emscripten.org/) target provides a script to build ScummVM as a single page browser app.
> Emscripten is an LLVM/Clang-based compiler that compiles C and C++ source code to WebAssembly for execution in web browsers.
## Current State
* All engines compile (though I didn't test all of them), including ResidualVM with WebGL acceleration and shaders.
* Audio works and 3rd-party libraries for sound and video decoding are integrated.
* Proof of concept integration with [BrowserFS](https://github.com/jvilk/browserfs) to download game data lazily when required and to support local savegames.
## 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 `emscripten/build.sh`
### Running build.sh
`emscripten/build.sh` needs to be run from the root of the project.
`build.sh` needs to be run from the root of the project.
```Shell
./dists/emscripten/build.sh libs|configure|make|data|dist|all|clean
./dists/emscripten/build.sh [Tasks] [Options]
```
It accepts a single parameter with 7 valid commands:
* `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
* `data`: Download some demos and set up all data require for the demo page
* `dist`: Copy all files into a single build-emscripten folder to bring it all together
* `all`: Run all of the above commands
* `clean`: Remove all object files, built libs, bundled data etc
**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`
* `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 work 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
Some ideas for possible improvements:
### Emscripten Optimizations
* Optimize asyncify behaviour (we only have SDL functions calling wait currently), e.g with [SDL_HINT_EMSCRIPTEN_ASYNCIFY](https://wiki.libsdl.org/SDL_HINT_EMSCRIPTEN_ASYNCIFY).
* Specify a `ASYNCIFY_ONLY` list to to make asyncify only instrument functions in the call path as described in [emscripten.org: Asyncify](https://emscripten.org/docs/porting/asyncify.html)
* Optimize asyncify behaviour (we only have SDL functions calling wait currently), e.g with [SDL_HINT_EMSCRIPTEN_ASYNCIFY](https://wiki.libsdl.org/SDL_HINT_EMSCRIPTEN_ASYNCIFY)
* 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`.
* Don't use asyncify but rewrite main loop to improve performance
* Shrink code size or execution speed with `-Os` or `-Oz` [emcc arguments](https://emscripten.org/docs/tools_reference/emcc.html#emcc-compiler-optimization-options).
### Storage Integration
* BrowserFS seems abandoned and never did a stable 2.0.0 release. Maybe there's a better way to handle storage?
* File loading improvements:
* Load assets with HTTP Range request headers.
* Load assets asynchronously (not blocking) via a worker.
* BrowserFS seems abandoned and never did a stable 2.0.0 release. It's worth replacing it.
* `scummvm_fs.js` is an early prototype for a custom FS which can be adopted for ScummVM specific needs, i.e.
* Download all game assets in background once the game has started
* Presist last game and last plugin for offline use
* Implement support for range requests (currently not supported with `emrun` so another development server would have to be included as well)
* Pre-load assets asynchronously (not blocking) - i.e. rest of the data of a game which has been launched
* Loading indicators
* Add support for save games (and game data?) on personal cloud storage (Dropbox, Google Drive).
### UI Integration
* Responsiveness: Adjust the canvas size when resizing the browser.
* Bug: Fullscreen mode doesn't work.
* Build a nice webpage around the canvas.
* Allow hiding of console, replace buttons/checkboxes from default emscripten template.
* Allow showing/hiding of console, replace buttons/checkboxes from default emscripten template.
* Bonus: Adapt page padding/background color to theme (black when in game)
* ScummVM shouldn't be able to "close" (there's no concept for that:
* Remove "exit" buttons from all menus.
* Change any programmatic "exits" to cause a restart of Scummvm (or refresh of the page).
* Automatically show console in case of exceptions
* Pass CLI parameters for ScummVM via URL parameters to allow for "deep-linking" to a specific game.
### Other Bugs + Tasks
* Bug: Vorbis support is broken - parts seems to have been patched out so `-lvorbisfile` triggers an error during configure (and [emscripten-core/emscripten#9849](https://github.com/emscripten-core/emscripten/pull/9849) doesn't seem to fix this).
* Bug: Going back to main menu from Grim (and other Residual Games?) messes up the render context and the UI is unusable.
* Check all disabled features (e.g. TiMidity++) and see if they could be enabled (some might never make sense, e.g. anything requiring MIDI Hardware, Update Checking etc).
* 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

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,6 @@
[scummvm]
gfx_mode=opengl
pluginspath=/plugins
grouping=company
renderer=opengl_shaders

View File

@ -0,0 +1,496 @@
/*
* ScummvmFS - A custom Emscripten filesystem for ScummVM
*
* This is the filesystem used to load any read-only files used by ScummVM: data, games and
* plugins. It supports range-requests and caches data in memory to minimize latency when loading
* data from the network.
*
* Adapted from Emscripten's NodeFS and BrowserFS' EmscriptenFS + XHR backend:
* https://github.com/emscripten-core/emscripten/blob/main/src/library_nodefs.js
* https://github.com/jvilk/BrowserFS/blob/master/src/generic/emscripten_fs.ts
* https://github.com/jvilk/BrowserFS/blob/master/src/generic/xhr.ts
*/
const DIR_MODE = 16895; // 040777
const FILE_MODE = 33206; // 100666
const SEEK_SET = 0;
const SEEK_CUR = 1;
const SEEK_END = 2;
const RANGE_REQUEST_BLOCK_SIZE = 1024 * 1024
const ERRNO_CODES = {
// TODO: We should get these from Emscripten - see https://github.com/emscripten-core/emscripten/issues/10061 and https://github.com/emscripten-core/emscripten/issues/14783
EPERM: 1, // Operation not permitted
ENOENT: 2, // No such file or directory
EINVAL: 22 // I©nvalid argument
};
const DEBUG = false
export class ScummvmFS {
url;
fs_index;
stream_ops;
node_ops;
FS;
constructor(_FS, _url) {
this.FS = _FS;
this.url = _url
var req = new XMLHttpRequest(); // a new request
req.open("GET", _url + "/index.json", false);
req.send(null);
var json_index = JSON.parse(req.responseText)
this.fs_index = {}
var walk_index = function (path, dir) {
logger(path, "walk_index")
this.fs_index[path] = null
if (path != "/") {
path = path + "/"
}
for (var key in dir) {
if (typeof dir[key] === 'object') {
walk_index(path + key, dir[key]) // toLowerCase to simulate a case-insensitive filesystem
} else {
if (key !== "index.json") {
this.fs_index[path + key] = dir[key] // toLowerCase to simulate a case-insensitive filesystem
}
}
}
}.bind(this)
walk_index("/", json_index)
}
listDirectory(_path) {
const path = _path.path
var result = []
for (var node in this.fs_index) {
if (node.startsWith(path) && node.lastIndexOf("/") <= path.length && node !== path && node.substr(path.length + 1).length > 0 && node.charAt(path.length) == "/") {
result.push(node.substr(path.length + 1))
}
}
return { ok: true, data: result };
}
// used for open
get(_path) {
const path = _path.path
logger(path, "get")
if (path in this.fs_index) {
// if this.fs_index[path] is still a integer (hence a file), we now initialize the array to store any file data
if (Number.isInteger(this.fs_index[path])) { // if not a number we either already have iniitalized the data or it's a folder
const size = this.fs_index[path];
var data;
data = new Array(Math.ceil(size / RANGE_REQUEST_BLOCK_SIZE)) // data will be an array of blocks
this.fs_index[path] = { size: this.fs_index[path], data: data }
return { ok: true, data: data, size: size };
} else if (typeof this.fs_index[path] == "object" && this.fs_index[path] !== null) {
return { ok: true, data: this.fs_index[path].data, size: this.fs_index[path].size }; // already initialized
} else {
return { ok: true, data: null }; // directory
}
} else {
return { ok: false }
}
}
// used for close, mknod
put(args) {
const path = args.path
logger(path, "put")
if (!this.fs_index[path]) {
throw new FS.ErrnoError(ERRNO_CODES.ENOENT);
}
return { ok: true, data: this.fs_index[path].data };
}
read(args) {
const path = args.path;
logger(path, "read, args:" + JSON.stringify(args))
if (typeof this.fs_index[path] !== "object") {
console.error("File hasn't been opened yet")
throw new FS.ErrnoError(ERRNO_CODES.EPERM);
}
const start = args.start;
const end = (args.end > (this.fs_index[path].size)) ? (this.fs_index[path].size) : args.end // sometimes we get requests beyond the end of the file (????)
var first_block = Math.floor(start / RANGE_REQUEST_BLOCK_SIZE)
var last_block = Math.floor(end / RANGE_REQUEST_BLOCK_SIZE)
if (start > end) {
return { ok: true, data: [] };
}
var alreadyLoaded = false;
if (Array.isArray(this.fs_index[path].data)) {
alreadyLoaded = true
for (var idx = first_block; idx <= last_block; idx++) {
if (this.fs_index[path].data[idx] == undefined) {
logger(path, "block " + idx + " missing")
alreadyLoaded = false
break;
}
}
}
let data = null;
logger(path, "file alreadyLoaded=" + alreadyLoaded)
if (alreadyLoaded) {
data = new Uint8Array(end - start + 1);
for (var block = first_block; block <= last_block; block++) {
// TODO: we should start at start at the request start and not block start (same for end)
for (var idx = Math.max(start, block * RANGE_REQUEST_BLOCK_SIZE); idx <= Math.min(end, block * RANGE_REQUEST_BLOCK_SIZE + this.fs_index[path].data[block].length - 1); idx++) {
if (idx >= start && idx <= end) {
data[idx - start] = this.fs_index[path].data[block][idx - block * RANGE_REQUEST_BLOCK_SIZE]
}
}
}
logger(path, "cache loaded ")
} else {
data = this.download(path, this.url, first_block, last_block, start, end);
}
return { ok: true, data: data };
}
download(path, _url, first_block, last_block, start, end) {
self = this;
let data = null;
const req = new XMLHttpRequest();
const url = _url + path;
req.open('GET', url, false);
let err = null;
// On most platforms, we cannot set the responseType of synchronous downloads.
// Classic hack to download binary data as a string.
req.overrideMimeType('text/plain; charset=x-user-defined');
// Trying to use range requests where possible
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
var range_start = first_block * RANGE_REQUEST_BLOCK_SIZE
var range_end = Math.min((last_block + 1) * RANGE_REQUEST_BLOCK_SIZE, this.fs_index[path].size) - 1
if (this.fs_index[path].size > (range_start - range_end + 1)) {
req.setRequestHeader('Range', 'bytes=' + range_start + '-' + range_end);
}
if (this.fs_index[path].data === null) {
this.fs_index[path].data = new Array(Math.ceil(this.fs_index[path].size / RANGE_REQUEST_BLOCK_SIZE));
}
req.onreadystatechange = function (e) {
if (req.readyState === 4) {
var text = req.responseText;
logger(path, "Downloaded " + text.length + " bytes");
data = new Uint8Array(end - start + 1);
if (req.status === 200) { // range request wasn't respected or requested
for (let i = 0; i < text.length; i++) {
if (i >= start && i <= end) {
// This will automatically throw away the upper bit of each
// character for us.
data[i - start] = text.charCodeAt(i);
}
var block = Math.floor(i / RANGE_REQUEST_BLOCK_SIZE)
if (self.fs_index[path].data[block] === undefined) {
self.fs_index[path].data[block] = new Uint8Array(RANGE_REQUEST_BLOCK_SIZE)
}
self.fs_index[path].data[block][i - block * RANGE_REQUEST_BLOCK_SIZE] = text.charCodeAt(i)
}
logger(path, "Downloaded [full download]");
} else if (req.status === 206) { // partial response to range request
var start_offset = start - range_start
var end_offset = range_end - end
logger(path, "First block: " + first_block + " last block: " + last_block + "Text length: " + text.length)
var char_length = Math.round((range_end - range_start + 1) / text.length);
if (char_length == 2 && text.length == (range_end - range_start + 1) / 2 - 1) {
// The above hack to get binary data as text breaks if the first two bytes of the range are U+FEFF which is a BOM
// for UTF16 and the browsers convert the data into a UTF16 string. I initially tied to fix this by breaking up
// the UTF16 characters into 2 bytes and prepend the stripped BOM again, but it turned out that there were other
// issues how browsers handle UTF16 (e.g. 0xDFC3, 0xDFAD, 0xDFFB, 0xDF5B all somehow getting converted to 0xFFFD
// - i.e. "REPLACEMENT CHARACTER") so this now just reruns shifts the start of the download.
// That's wasting some data, but it's a rare enough occurance
//
// TODO: The only proper fix for this is to implement a asynchronous filesystem for emscripten. Something which currently
// isn't possible
data = self.download(path, _url, first_block - 1, last_block, start, end)
} else if (char_length == 1) {
for (let i = 0; i < (range_end - range_start + 1); i++) {
var block = Math.floor((range_start + i) / RANGE_REQUEST_BLOCK_SIZE)
if (self.fs_index[path].data[block] === undefined) {
self.fs_index[path].data[block] = new Uint8Array(RANGE_REQUEST_BLOCK_SIZE)
}
var block_pos = (range_start + i) - (block * RANGE_REQUEST_BLOCK_SIZE)
// This will automatically throw away the upper bit of each
// character for us.
self.fs_index[path].data[block][block_pos] = text.charCodeAt(i)
}
logger(path, "First block length: " + self.fs_index[path].data[block] + " last block length: " + self.fs_index[path].data[block] + "Text length: " + text.length)
for (var block = first_block; block <= last_block; block++) {
// TODO: we should start at start at the request start and not block start (same for end)
for (var idx = Math.max(start, block * RANGE_REQUEST_BLOCK_SIZE); idx <= Math.min(end, block * RANGE_REQUEST_BLOCK_SIZE + self.fs_index[path].data[block].length - 1); idx++) {
if (idx >= start && idx <= end) {
data[idx - start] = self.fs_index[path].data[block][idx - block * RANGE_REQUEST_BLOCK_SIZE]
}
}
}
logger(path, "Downloaded [range request]");
}
} else {
console.error(req);
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
}
};
req.send();
if (err) {
throw err;
}
return data;
}
mount(mount) {
return this.createNode(null, "/", DIR_MODE, 0);
}
createNode(parent, name, mode, size) {
logger(name, "createNode")
if (!this.FS.isDir(mode) && !this.FS.isFile(mode)) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
let node = this.FS.createNode(parent, name, mode);
node.node_ops = this.node_ops;
node.stream_ops = this.stream_ops;
node.size = size
return node;
}
convertResult(result) {
if (result.ok) {
return result.data;
}
else {
let error;
if (result.status === 404) {
error = new FS.ErrnoError(ERRNO_CODES.ENOENT);
}
else {
error = new FS.ErrnoError(ERRNO_CODES.EPERM);
}
error.cause = result.error;
throw error;
}
}
node_ops = {
getattr: (node) => {
return {
dev: 1,
ino: node.id,
mode: node.mode,
nlink: 1,
uid: 0,
gid: 0,
rdev: undefined,
size: node.size,
atime: new Date(node.timestamp),
mtime: new Date(node.timestamp),
ctime: new Date(node.timestamp),
blksize: 4096,
blocks: 0,
};
},
setattr: (node, attr) => {
// Doesn't really do anything
if (attr.mode !== undefined) {
node.mode = attr.mode;
}
if (attr.timestamp !== undefined) {
node.timestamp = attr.timestamp;
}
},
lookup: (parent, name) => {
logger(name, "lookup ")
if (parent instanceof FS.FSStream) { //sometimes we get a stream instead of a node
parent = parent.node;
}
const path = realPath(parent, name);
const result = this.get({ path });
if (!result.ok) {
// I wish Javascript had inner exceptions
throw new FS.ErrnoError(ERRNO_CODES.ENOENT);
}
return this.createNode(parent, name, result.data === null ? DIR_MODE : FILE_MODE, result.data ? result.size : null);
},
mknod: (parent, name, mode, dev) => {
logger(name, "mknod ")
const node = this.createNode(parent, name, mode, 0);
const path = realPath(node);
if (this.FS.isDir(node.mode)) {
this.convertResult(this.put({ path, value: null }));
}
else {
this.convertResult(this.put({ path, value: "" }));
}
return node;
},
rename: (oldNode, newDir, newName) => {
throw new FS.ErrnoError(ERRNO_CODES.EPERM);
const oldPath = realPath(oldNode);
const newPath = realPath(newDir, newName);
this.convertResult(this.move({ path: oldPath, newPath: newPath }));
oldNode.name = newName;
},
unlink: (parent, name) => {
throw new FS.ErrnoError(ERRNO_CODES.EPERM);
const path = realPath(parent, name);
this.convertResult(this.delete({ path }));
},
rmdir: (parent, name) => {
throw new FS.ErrnoError(ERRNO_CODES.EPERM);
const path = realPath(parent, name);
this.convertResult(this.delete({ path }));
},
readdir: (node) => {
const path = realPath(node);
let result = this.convertResult(this.listDirectory({ path }));
if (!result.includes(".")) {
result.push(".");
}
if (!result.includes("..")) {
result.push("..");
}
return result;
},
symlink: (parent, newName, oldPath) => {
throw new FS.ErrnoError(ERRNO_CODES.EPERM);
},
readlink: (node) => {
throw new FS.ErrnoError(ERRNO_CODES.EPERM);
}
}
stream_ops = {
open: (stream) => {
logger(stream.path, "Open stream ")
const path = realPath(stream.node);
if (FS.isFile(stream.node.mode)) {
const result = this.get({ path });
if (result.data === null || result.data === undefined) {
return;
}
stream.fileData = result.data;
stream.fileSize = result.size;
}
},
close: (stream) => {
logger(stream.path, "close stream ")
const path = realPath(stream.node);
if (FS.isFile(stream.node.mode) && stream.fileData) {
const fileData = stream.fileData
// TODO: Track open/closed files differently so we can warn but don't lose the cached data
//stream.fileData = undefined;
this.convertResult(this.put({ path, value: fileData }));
}
},
read: (stream, buffer, offset, length, position) => {
if (!position) {
position = stream.position
}
// logger(stream.path, "read stream - offset:" + offset + " length:" + length + " position:" + position)
const path = realPath(stream.node);
var _a, _b;
if (length <= 0)
return 0;
var size = length
if (typeof stream.fileData === 'object' && stream.fileSize < position + length) {
size = stream.fileSize - position
}
// logger(stream.path, "Length, Position " + length + "," + position)
// logger(stream.path, "Size " + size)
// logger(stream.path, "stream.fileSize " + stream.fileSize)
if (size < 0) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
if (size > 0) {
var fileData = this.convertResult(this.read({ path: path, start: position, end: position + size - 1 }));
logger(stream.path, "fileData (start: " + (position) + " end: " + (position + size - 1).toString() + " (length: " + fileData.length + ")")
if (DEBUG) {
logger(stream.path, Uint8Array2hex(fileData))
}
buffer.set(fileData, offset);
}
// buffer.set(stream.fileData.subarray(position, position + size), offset);
return size;
},
write: (stream, buffer, offset, length, position) => {
// this FS actually can't write
throw new FS.ErrnoError(ERRNO_CODES.EPERM);
},
llseek: (stream, offset, whence) => {
let position = offset; // SEEK_SET
if (whence === SEEK_CUR) {
position += stream.position;
}
else if (whence === SEEK_END) {
if (this.FS.isFile(stream.node.mode)) {
position += stream.fileSize;
}
} else if (whence !== SEEK_SET) {
console.error("Illegal Whence: " + whence)
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
if (position < 0) {
console.error("CRITICAL: Position < 0")
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
stream.position = position
return position;
}
}
}
function realPath(node, fileName) {
const parts = [];
while (node.parent !== node) {
parts.push(node.name);
node = node.parent;
}
parts.push(node.mount.opts.root);
parts.reverse();
if (fileName !== undefined && fileName !== null) {
parts.push(fileName);
}
return parts.join("/");
}
function logger(path, message) {
if (DEBUG) {
console.log(path + ": " + message)
}
}
function Uint8Array2hex(byteArray) {
return Array.prototype.map.call(byteArray, function (byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2).toUpperCase();
}).join(' ');
}

View File

@ -0,0 +1,45 @@
const http = require('http');
const fs = require('fs');
const puppeteer = require('puppeteer');
const static = require('node-static');
var file = new (static.Server)("./");
const server = http.createServer(function (req, res) {
file.serve(req, res);
}).listen(8080, async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:8080/scummvm.html#--add --path=/games --recursive');
await page.screenshot({ path: 'example.png' });
const regex = /Added ([0-9]+) games/;
page.on('console', async msg => {
const text = msg.text()
console.log(text)
const match = text.match(regex);
if (match != null && match.length > 0) {
console.log("Detection finished, exporting ini file for " + match[1] + " detected games.")
const localStorage = await page.evaluate(() => Object.assign({}, window.localStorage));
const ini_inode_id = "1b4a97d1-4ce0-417f-985c-e0f22ca21aef" // defined in custom_shell.html
const ini_lines = Buffer.from(localStorage[ini_inode_id], 'base64').toString().split('\n');
// GRIM games check data consistency by reading all files. That's an expensive operation over
// the network. Since we anyway should have known good data at build time, this script disables
// that check.
for (var i = 0; i < ini_lines.length; i++) {
if (ini_lines[i] == "engineid=grim") {
ini_lines[i] = "check_gamedata=false\n" + ini_lines[i]
}
}
fs.writeFileSync("scummvm.ini", ini_lines.join('\n'));
browser.close();
server.close();
console.log('Done');
}
});
});

View File

@ -0,0 +1,177 @@
const request = require('request');
const fs = require('fs');
const fsprocess = require('process');
const { url } = require('inspector');
process.on('uncaughtException', (err, origin) => {
console.error(err)
console.error(origin)
process.exitCode = 2
})
const args_games = process.argv.slice(2);
/*
Copied from https://github.com/scummvm/scummvm-web/blob/master/include/DataUtils.php
*/
const SHEET_URL = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQamumX0p-DYQa5Umi3RxX-pHM6RZhAj1qvUP0jTmaqutN9FwzyriRSXlO9rq6kR60pGIuPvCDzZL3s/pub?output=tsv';
const SHEET_IDS = {
'platforms': '1061029686',
'compatibility': '854570757',
'games': '1946612063',
'engines': '0',
'companies': '226191984',
'versions': '1225902887',
'game_demos': '713475305',
'series': '1095671818',
'screenshots': '1985243204',
'scummvm_downloads': '1057392663',
'game_downloads': '1287892109',
'director_demos': '1256563740',
}
// Small Helper function as having followRedirect:true sometimes lead to ECONNRESET errors
function getGoogleSheet(url, callback) {
request({
url: url,
followRedirect: function (response) {
return false
}
}, function (error, response, body) {
if (response.headers && response.headers.location) {
request({
url: response.headers.location,
followRedirect: false
}, callback);
} else {
callback(error, response, body)
}
});
}
function parseTSV(text) {
const lines = text.split("\r\n")
const headers = lines[0].split("\t")
var ret = []
for (var i = 1; i < lines.length; i++) {
ret[i - 1] = {}
lines[i].split("\t").forEach((value, col) => ret[i - 1][headers[col]] = value)
}
return ret
}
var games = {}
// Get Freeware Games
function get_freeware_games() {
console.error("download_games.js: Fetching list of freeware games")
return new Promise((resolve, reject) => {
var url = SHEET_URL + "&gid=" + SHEET_IDS['game_downloads'];
getGoogleSheet(url, (error, response, body) => {
if (error) {
reject(error)
return
}
parseTSV(body).forEach((downloads) => {
if (downloads['category'] == "games" && !(downloads['game_id'] in games)) {
games[downloads['game_id']] = "/frs/extras/" + downloads['url']
}
filename = downloads['url'].substring(downloads['url'].lastIndexOf("/"))
games[downloads['game_id'] + filename] = "/frs/extras/" + downloads['url']
})
resolve()
})
});
}
// Get Demos Games
function get_demos() {
console.error("download_games.js: Fetching list of game demos")
return new Promise((resolve, reject) => {
var url = SHEET_URL + "&gid=" + SHEET_IDS['game_demos'];
getGoogleSheet(url, (error, response, body) => {
if (error) {
reject(error)
return
}
parseTSV(body).forEach((downloads) => {
if (!(downloads['id'] in games)) {
games[downloads['id']] = downloads['url']
}
filename = downloads['url'].substring(downloads['url'].lastIndexOf("/"))
games[downloads['id'] + filename] = downloads['url']
})
resolve()
})
});
}
// Get Director Demos
function get_director_demos() {
console.error("download_games.js: Fetching list of director demos")
return new Promise((resolve, reject) => {
var url = SHEET_URL + "&gid=" + SHEET_IDS['director_demos'];
getGoogleSheet(url, (error, response, body) => {
if (error || body == undefined) {
reject(error)
return
}
parseTSV(body).forEach((downloads) => {
if (!(downloads['id'] in games)) {
games[downloads['id']] = downloads['url']
}
filename = downloads['url'].substring(downloads['url'].lastIndexOf("/"))
games[downloads['id'] + filename] = downloads['url']
});
resolve()
});
});
}
// Download a file
var download_file = function (uri, filename) {
// TODO: Rewrite as promise to serialize this (easier to return status updates)
return request.get(uri).on('error', function (err) {
throw err
}).on('response', function (response) {
if (response.statusCode == 200) {
console.error("download_games.js: Downloading " + uri)
} else {
console.error(response)
throw new Error(response.statusCode)
}
}).pipe(fs.createWriteStream(filename))
}
const download_all_games = async (gameIds) => {
for (var gameId of gameIds) {
if (gameId.startsWith("http")) {
var url = gameId
var filename = url.substring(url.lastIndexOf("/") + 1)
console.log(filename)
if (!fs.existsSync(filename)) {
await download_file(url, filename)
}
} else if (!(gameId in games)) {
console.error("download_games.js: GameID " + gameId + " not known")
process.exit(1)
} else {
var url = "https://downloads.scummvm.org" + games[gameId]
if (gameId.includes("/")) {
gameId = gameId.substring(0, gameId.lastIndexOf("/"))
}
var filename = url.substring(url.lastIndexOf("/") + 1)
if (!filename.startsWith(gameId)) { filename = gameId + "-" + filename }
console.log(filename)
if (!fs.existsSync(filename)) {
await download_file(url, filename)
}
}
}
}
// start everything
get_freeware_games()
.then(get_demos)
.then(get_director_demos)
.then(() => {
download_all_games(args_games)
});

View File

@ -0,0 +1,59 @@
/*
* Based on https://github.com/jvilk/BrowserFS/blob/master/scripts/make_http_index.ts
* Copyright (c) 2013, 2014, 2015, 2016, 2017 John Vilk and other BrowserFS contributors.
* MIT License https://github.com/jvilk/BrowserFS/blob/master/LICENSE
*/
"use strict";
const fs = require("fs");
const path = require("path");
const symLinks = {};
const ignoreFiles = ['.git', 'node_modules', 'bower_components', 'build', 'index.json'];
function rdSync(dpath, tree, name) {
const files = fs.readdirSync(dpath);
files.forEach((file) => {
// ignore non-essential directories / files
if (ignoreFiles.indexOf(file) !== -1 || file[0] === '.') {
return;
}
const fpath = `${dpath}/${file}`;
try {
// Avoid infinite loops.
const lstat = fs.lstatSync(fpath);
if (lstat.isSymbolicLink()) {
if (!symLinks[lstat.dev]) {
symLinks[lstat.dev] = {};
}
// Ignore if we've seen it before
if (symLinks[lstat.dev][lstat.ino]) {
return;
}
symLinks[lstat.dev][lstat.ino] = true;
}
const fstat = fs.statSync(fpath);
if (fstat.isDirectory()) {
const child = tree[file] = {};
rdSync(fpath, child, file);
}
else {
tree[file] = fstat.size;
}
}
catch (e) {
// Ignore and move on.
}
});
return tree;
}
const fsListing = JSON.stringify(rdSync(process.cwd(), {}, '/'));
if (process.argv.length === 3) {
const fname = process.argv[2];
let parent = path.dirname(fname);
while (!fs.existsSync(parent)) {
fs.mkdirSync(parent);
parent = path.dirname(parent);
}
fs.writeFileSync(fname, fsListing, { encoding: 'utf8' });
}
else {
console.log(fsListing);
}

View File

@ -1,206 +1,357 @@
#!/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/>.
#
# print commands
set -o xtrace
# exit when any command fails
set -e
if [ "$#" -ne 1 ]; then
echo "$0: exactly 1 arguments expected: configure, make, data, all"
exit 3
if [[ "$_verbose" = true ]]; then
set -o xtrace
fi
EMSDK_VERSION="2.0.26"
# exit when any command fails
set -e
EMSDK_VERSION="3.1.8"
ROOT_FOLDER=$(pwd)
DIST_FOLDER="$ROOT_FOLDER/dists/emscripten"
LIBS_FOLDER="$DIST_FOLDER/libs"
if [[ ! -d "$DIST_FOLDER" ]]; then
echo "/dists/emscripten/ not found. Please make sure to run this script from the root of the project - ./dists/emscripten/build.sh "
exit 1
fi
if [[ "$1" =~ ^(clean)$ ]]; then
make clean
make distclean
rm -rf ./dists/emscripten/libs/build
rm -rf ./dists/emscripten/libs/*/
rm -rf ./dists/emscripten/emsdk*/
rm scummvm.debug.wasm
find . -name "*.o"
find . -name "*.a"
find . -name "*.wasm"
exit 0
TASKS=()
CONFIGURE_ARGS=()
_bundle_games=()
_verbose=false
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
--bundle-games= comma-separated list of demos and freeware games to bundle.
-v, --verbose print all commands run by the script
--* all other options are passed on to the configure script
"
# parse inputs
for i in "$@"; do
case $i in
--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
# Activate Emscripten
if [[ ! -d "$DIST_FOLDER/emsdk-$EMSDK_VERSION" ]]; then
echo "$DIST_FOLDER/emsdk-$EMSDK_VERSION not found. Installing Emscripten"
cd "$DIST_FOLDER"
wget -nc --content-disposition "https://github.com/emscripten-core/emsdk/archive/refs/tags/${EMSDK_VERSION}.tar.gz"
tar xzvf "emsdk-${EMSDK_VERSION}.tar.gz"
cd "emsdk-${EMSDK_VERSION}"
./emsdk install ${EMSDK_VERSION}
./emsdk activate ${EMSDK_VERSION}
#################################
# Setup Toolchain
#################################
if [[ "setup" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
# Activate 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 "https://github.com/emscripten-core/emsdk/archive/refs/tags/${EMSDK_VERSION}.tar.gz"
tar -xf "emsdk-${EMSDK_VERSION}.tar.gz"
fi
cd "$DIST_FOLDER/emsdk-${EMSDK_VERSION}"
./emsdk install ${EMSCRIPTEN_VERSION}
# We currently require a few patches for unreleased changes in SDL2 and Emscripten
if [[ "$EMSCRIPTEN_VERSION" == "3.1.8" ]]; then
cd upstream/emscripten
# until https://github.com/emscripten-core/emscripten/pull/15893 gets merged and released, we need to manually patch it
patch -p1 --verbose <"$DIST_FOLDER/emscripten-15893.patch"
# some additional fixes on top of 15893:
patch -p1 --verbose <"$DIST_FOLDER/emscripten-15893-fix.patch"
# until https://github.com/emscripten-core/emscripten/pull/16559 gets merged and released, we need to manually patch it
patch -p1 --verbose <"$DIST_FOLDER/emscripten-16559.patch"
# until https://github.com/emscripten-core/emscripten/pull/16687 gets merged and released, we need to manually patch it
patch -p1 --verbose <"$DIST_FOLDER/emscripten-16687.patch"
fi
cd "$DIST_FOLDER/emsdk-${EMSDK_VERSION}"
./emsdk activate ${EMSCRIPTEN_VERSION}
# install some required npm packages
source "$DIST_FOLDER/emsdk-$EMSDK_VERSION/emsdk_env.sh"
EMSDK_NPM=$(dirname $EMSDK_NODE)/npm
export NODE_PATH=$(dirname $EMSDK_NODE)/../lib/node_modules/
"$EMSDK_NPM" -g install "puppeteer@13.5.1"
"$EMSDK_NPM" -g install "request@2.88.2"
"$EMSDK_NPM" -g install "node-static@0.7.11"
fi
fi
source "$DIST_FOLDER/emsdk-$EMSDK_VERSION/emsdk_env.sh"
# Download + Install Libraries
mkdir -p "$LIBS_FOLDER"
if [[ ! -d "$LIBS_FOLDER/build" ]]; then
echo "$LIBS_FOLDER/build/ not found. Building plugins..."
echo "build libtheora-1.1.1"
cd "$LIBS_FOLDER"
pwd
wget -nc "https://downloads.xiph.org/releases/theora/libtheora-1.1.1.tar.xz"
tar -xf libtheora-1.1.1.tar.xz
cd "./libtheora-1.1.1/"
CFLAGS="-fPIC -s USE_OGG=1 -s USE_VORBIS=1 " emconfigure ./configure --host=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --disable-asm
emmake make -j 3
emmake make install
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 "./faad2-2.8.8/"
CFLAGS="-fPIC" emconfigure ./configure --host=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make
emmake make install
echo "building libmad-0.15.1b"
cd "$LIBS_FOLDER"
# libmad needs patching: https://stackoverflow.com/questions/14015747/gccs-fforce-mem-option
wget -nc "http://www.linuxfromscratch.org/patches/blfs/svn/libmad-0.15.1b-fixes-1.patch"
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/"
patch -Np1 -i ../libmad-0.15.1b-fixes-1.patch
emconfigure ./configure --host=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --with-pic --enable-fpm=no
emmake make
emmake make install
echo "building libmpeg2-0.5.1"
cd "$LIBS_FOLDER"
wget -nc "http://libmpeg2.sourceforge.net/files/libmpeg2-0.5.1.tar.gz"
tar -xf libmpeg2-0.5.1.tar.gz
cd "$LIBS_FOLDER/libmpeg2-0.5.1/"
CFLAGS="-fPIC" emconfigure ./configure --host=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --disable-sdl
emmake make
emmake make install
echo "building a52dec-0.7.4"
cd "$LIBS_FOLDER"
wget -nc "https://liba52.sourceforge.io/files/a52dec-0.7.4.tar.gz"
tar -xf a52dec-0.7.4.tar.gz
cd "$LIBS_FOLDER/a52dec-0.7.4/"
CFLAGS="-fPIC" emconfigure ./configure --host=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make -j 3
emmake make install
fi
# export node_path - so we can use all node_modules bundled with emscripten (e.g. requests)
EMSDK_NPM=$(dirname $EMSDK_NODE)/npm
export NODE_PATH=$(dirname $EMSDK_NODE)/../lib/node_modules/
LIBS_FLAGS=""
cd "$ROOT_FOLDER"
## Emscripten configuration (should probably go into the configure file)
## IMPORTANT: ASYNCIFY WITH -O0 doesnt work (presumably because the stack gets too big)
export LDFLAGS="-O2 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -s LLD_REPORT_UNDEFINED -s INITIAL_MEMORY=33554432"
#debugging
export LDFLAGS="${LDFLAGS} -g -gseparate-dwarf=scummvm.debug.wasm -s SEPARATE_DWARF_URL=\"http://localhost:8080/scummvm.debug.wasm\""
# linker flags (bundle JS and default assets)
export LDFLAGS_LINKER=" --pre-js ./dists/emscripten/pre.js --post-js ./dists/emscripten/post.js --shell-file ./dists/emscripten/custom_shell.html "
if [[ "$1" =~ ^(configure|all)$ ]]; then
echo "clean, & configure"
make clean || true
emconfigure ./configure --enable-debug --enable-verbose-build --host=wasm32-unknown-emscripten \
--disable-all-engines \
--enable-engine=testbed,scumm,scumm_7_8,grim,monkey4,mohawk,myst,riven,sci32,agos2,sword2,drascula,sky,lure,queen,testbed \
--with-theoradec-prefix="$LIBS_FOLDER/build/" \
--with-faad-prefix="$LIBS_FOLDER/build/" \
--with-mad-prefix="$LIBS_FOLDER/build/" \
--with-mpeg2-prefix="$LIBS_FOLDER/build/" \
--with-a52-prefix="$LIBS_FOLDER/build/"
# TODO: enable dynamic linking so we can enable more plugins
# https://forums.scummvm.org/viewtopic.php?t=14918
# https://github.com/emscripten-core/emscripten/wiki/Linking
# https://freecontent.manning.com/dynamic-linking-a-crash-course/
# https://iandouglasscott.com/2019/07/18/experimenting-with-webassembly-dynamic-linking-with-clang/
# HACK: the preload flags break emcc during configure as emcc enables NODERAWFS when run as part of configure
# which doesn't support preloading assets, so we have to manually add those after configure to the config.mk file
echo "LDFLAGS += ${LDFLAGS_LINKER}" >>config.mk
# configure currently doesn't clean up all files it created
rm scummvm-conf.*
#################################
# Clean
#################################
if [[ "clean" =~ $(echo ^\(${TASKS}\)$) ]]; then
emmake make clean || true
emmake make distclean || true
rm -rf ./dists/emscripten/libs/build || true
rm -rf ./dists/emscripten/libs/*/ || true
rm -rf ./build-emscripten/ || true
rm scummvm.debug.wasm || true
find . -name "*.o" || true
find . -name "*.a" || true
find . -name "*.wasm" || true
exit 0
fi
if [[ "$1" =~ ^(data|all)$ ]]; then
cd "${ROOT_FOLDER}"
rm -rf ./build-emscripten/games/
mkdir -p ./build-emscripten/games/
cd dists/engine-data
./create-testbed-data.sh
mv testbed "${ROOT_FOLDER}/build-emscripten/games/testbed"
#################################
# Download + Install Libraries
#################################
if [[ "libs" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
games=true
if [ "$games" = true ]; then
mkdir -p ./dists/emscripten/games/
cd "${ROOT_FOLDER}/dists/emscripten/games/"
wget -nc https://downloads.scummvm.org/frs/demos/scumm/ft-dos-demo-en.zip
unzip -n ft-dos-demo-en -d "${ROOT_FOLDER}/build-emscripten/games/ft-dos-demo-en/"
wget -nc https://downloads.scummvm.org/frs/demos/mohawk/myst-win-demo-en.zip
unzip -n myst-win-demo-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/myst-win-demo-en/"
wget -nc https://downloads.scummvm.org/frs/demos/mohawk/riven-win-demo-en.zip
unzip -n riven-win-demo-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/riven-win-demo-en/" -x DXSETUP/* -x QTWSETUP/*
wget -nc https://downloads.scummvm.org/frs/demos/sword2/sword2-win-demo-en.zip
unzip -n sword2-win-demo-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/sword2-win-demo-en/"
wget -nc https://downloads.scummvm.org/frs/demos/scumm/comi-win-large-demo-en.zip
unzip -n comi-win-large-demo-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/comi-win-large-demo-en/"
wget -nc https://downloads.scummvm.org/frs/demos/grim/emi-win-demo-en.zip
unzip -n emi-win-demo-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/emi-win-demo-en/"
wget -nc https://downloads.scummvm.org/frs/demos/grim/grim-win-demo2-en.zip
unzip -n grim-win-demo2-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/grim-win-demo2-en/"
wget -nc https://downloads.scummvm.org/frs/demos/agos/feeble-dos-ni-demo-en.zip
unzip -n feeble-dos-ni-demo-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/feeble-dos-ni-demo-en/"
wget -nc https://downloads.scummvm.org/frs/demos/sci/lsl7-dos-demo-en.zip
unzip -n lsl7-dos-demo-en.zip -d "${ROOT_FOLDER}/build-emscripten/games/lsl7-dos-demo-en/"
wget -nc https://downloads.scummvm.org/frs/demos/sci/phantasmagoria-dos-win-demo-en.zip
unzip -n phantasmagoria-dos-win-demo-en -d "${ROOT_FOLDER}/build-emscripten/games/phantasmagoria-dos-win-demo-en/"
wget -nc https://downloads.scummvm.org/frs/extras/Beneath%20a%20Steel%20Sky/BASS-Floppy-1.3.zip
unzip -n BASS-Floppy-1.3.zip -d "${ROOT_FOLDER}/build-emscripten/games/bass-floppy/"
wget -nc https://downloads.scummvm.org/frs/extras/Drascula_%20The%20Vampire%20Strikes%20Back/drascula-1.0.zip
unzip -n drascula-1.0.zip -d "${ROOT_FOLDER}/build-emscripten/games/drascula/"
wget -nc https://downloads.scummvm.org/frs/extras/Drascula_%20The%20Vampire%20Strikes%20Back/drascula-audio-mp3-2.0.zip
unzip -n drascula-audio-mp3-2.0.zip -d "${ROOT_FOLDER}/build-emscripten/games/drascula/"
wget -nc https://downloads.scummvm.org/frs/extras/Flight%20of%20the%20Amazon%20Queen/FOTAQ_Floppy.zip
unzip -n FOTAQ_Floppy.zip -d "${ROOT_FOLDER}/build-emscripten/games/fotaq-floppy/"
wget -nc https://downloads.scummvm.org/frs/extras/Lure%20of%20the%20Temptress/lure-1.1.zip
unzip -n lure-1.1.zip -d "${ROOT_FOLDER}/build-emscripten/games/lure/"
if [[ ! -d "$LIBS_FOLDER/build" ]]; then
mkdir -p "$LIBS_FOLDER/build"
fi
cd "${ROOT_FOLDER}/build-emscripten/games/"
NODE_DIR=$(dirname "$EMSDK_NODE")
"$NODE_DIR/npx" -p browserfs make_xhrfs_index >index.json
# Emscripten has an official port for vorbis, but it doesn't properly link vorbisfile https://github.com/emscripten-core/emscripten/pull/14005
if [[ ! -f "$LIBS_FOLDER/build/lib/libvorbis.a" ]]; then
echo "building libvorbis-1.3.7"
cd "$LIBS_FOLDER"
wget -nc "https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.gz"
tar -xf libvorbis-1.3.7.tar.gz
cd "$LIBS_FOLDER/libvorbis-1.3.7"
CFLAGS="-fPIC -s USE_OGG=1" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make -j 3
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-vorbis-prefix=$LIBS_FOLDER/build/"
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 "./libtheora-1.1.1/"
CFLAGS="-fPIC -s USE_OGG=1" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --disable-asm
emmake make -j 3
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-theoradec-prefix=$LIBS_FOLDER/build/"
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 "./faad2-2.8.8/"
CFLAGS="-fPIC" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-faad-prefix=$LIBS_FOLDER/build/"
if [[ ! -f "$LIBS_FOLDER/build/lib/libmad.a" ]]; then
echo "building libmad-0.15.1b"
cd "$LIBS_FOLDER"
# libmad needs patching: https://stackoverflow.com/questions/14015747/gccs-fforce-mem-option
wget -nc "http://www.linuxfromscratch.org/patches/blfs/svn/libmad-0.15.1b-fixes-1.patch"
wget -nc "https://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz"
rm -rf "$LIBS_FOLDER/libmad-0.15.1b/"
tar -xf libmad-0.15.1b.tar.gz
cd "$LIBS_FOLDER/libmad-0.15.1b/"
patch -Np1 -i ../libmad-0.15.1b-fixes-1.patch &&
emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --with-pic --enable-fpm=no
emmake make
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-mad-prefix=$LIBS_FOLDER/build/"
if [[ ! -f "$LIBS_FOLDER/build/lib/libmpeg2.a" ]]; then
echo "building libmpeg2-0.5.1"
cd "$LIBS_FOLDER"
wget -nc "http://libmpeg2.sourceforge.net/files/libmpeg2-0.5.1.tar.gz"
tar -xf libmpeg2-0.5.1.tar.gz
cd "$LIBS_FOLDER/libmpeg2-0.5.1/"
CFLAGS="-fPIC" emconfigure ./configure --host=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/" --disable-sdl
emmake make
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-mpeg2-prefix=$LIBS_FOLDER/build/"
if [[ ! -f "$LIBS_FOLDER/build/lib/liba52.a" ]]; then
echo "building a52dec-0.7.4"
cd "$LIBS_FOLDER"
wget -nc "https://liba52.sourceforge.io/files/a52dec-0.7.4.tar.gz"
tar -xf a52dec-0.7.4.tar.gz
cd "$LIBS_FOLDER/a52dec-0.7.4/"
CFLAGS="-fPIC" emconfigure ./configure --host=wasm32-unknown-none --build=wasm32-unknown-none --prefix="$LIBS_FOLDER/build/"
emmake make -j 3
emmake make install
fi
LIBS_FLAGS="${LIBS_FLAGS} --with-a52-prefix=$LIBS_FOLDER/build/"
fi
if [[ "$1" =~ ^(make|all)$ ]]; then
#################################
# 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"
emmake make
emmake make dist-generic
# preload data
"$EMSDK_PYTHON" "$EMSDK/upstream/emscripten/tools/file_packager.py" files.data --preload ./dist-generic/scummvm/data@/scummvm --use-preload-cache --js-output=files.js
rm -rf dist-generic/
fi
if [[ "$1" =~ ^(dist|all)$ ]]; then
# The following steps copy stuff to build-emscripten:
mkdir -p "${ROOT_FOLDER}/build-emscripten/"
#################################
# Create Games & Testbed Data
#################################
if [[ "games" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
cd "${ROOT_FOLDER}"
mkdir -p build-emscripten
mv scummvm.* build-emscripten/
mv files.* build-emscripten/
cp dists/emscripten/scummvm-512.png build-emscripten/
cp dists/emscripten/scummvm-192.png build-emscripten/
cp dists/emscripten/manifest.json build-emscripten/
echo "Creating Games + Testbed Data"
mkdir -p "${ROOT_FOLDER}/build-emscripten/games/"
if [[ "testbed" =~ $(echo ^\(${_bundle_games// /|}\)$) ]]; then
_bundle_games="${_bundle_games//testbed/}"
rm -rf "${ROOT_FOLDER}/build-emscripten/games/testbed"
cd "${ROOT_FOLDER}/dists/engine-data"
./create-testbed-data.sh
mv testbed "${ROOT_FOLDER}/build-emscripten/games/testbed"
fi
if [ -n "$_bundle_games" ]; then
mkdir -p "${DIST_FOLDER}/games/"
cd "${DIST_FOLDER}/games/"
files=$("$EMSDK_NODE" --unhandled-rejections=strict --trace-warnings "$DIST_FOLDER/build-download_games.js" ${_bundle_games})
for dir in "${ROOT_FOLDER}/build-emscripten/games/"*; do # cleanup games folder
if [ $(basename $dir) != "testbed" ]; then
rm -rf "$dir"
fi
done
for f in $files; do # unpack into games folder
echo "Unzipping $f ..."
unzip -q -n "$f" -d "${ROOT_FOLDER}/build-emscripten/games/${f%.zip}"
# some zip files have weird permissions, this fixes that:
find "${ROOT_FOLDER}/build-emscripten/games/${f%.zip}" -type d -exec chmod 0755 {} \;
find "${ROOT_FOLDER}/build-emscripten/games/${f%.zip}" -type f -exec chmod 0644 {} \;
done
fi
cd "${ROOT_FOLDER}/build-emscripten/games/"
"$EMSDK_NODE" "$DIST_FOLDER/build-make_http_index.js" >index.json
fi
#################################
# Bundle everything into a neat package
#################################
if [[ "dist" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
cd "${ROOT_FOLDER}"
mv "${ROOT_FOLDER}"/scummvm.* "${ROOT_FOLDER}"/build-emscripten/ || true
# prepare data
if [[ -d "${ROOT_FOLDER}/dist-generic/scummvm/data" ]]; then
echo "Bundle ScummVM + Data"
rm -rf "${ROOT_FOLDER}/build-emscripten/data"
mv "${ROOT_FOLDER}/dist-generic/scummvm/data" "${ROOT_FOLDER}/build-emscripten/"
cd "${ROOT_FOLDER}/build-emscripten/data"
"$EMSDK_NODE" "$DIST_FOLDER/build-make_http_index.js" >index.json
rm -rf "${ROOT_FOLDER}/dist-generic/"
fi
# bundle plugins
echo "Bundle Plugins"
mkdir -p "${ROOT_FOLDER}/build-emscripten/plugins"
mv "${ROOT_FOLDER}/plugins/"* "${ROOT_FOLDER}/build-emscripten/plugins/" || true
cd "${ROOT_FOLDER}/build-emscripten/plugins"
"$EMSDK_NODE" "$DIST_FOLDER/build-make_http_index.js" >index.json
# add logos and other assets
cd "${ROOT_FOLDER}"
cp "$DIST_FOLDER/assets/"* "${ROOT_FOLDER}/build-emscripten/"
cp "$ROOT_FOLDER/gui/themes/common-svg/logo.svg" "${ROOT_FOLDER}/build-emscripten/"
cp "$ROOT_FOLDER/icons/scummvm.ico" "${ROOT_FOLDER}/build-emscripten/favicon.ico"
fi
#################################
# Automatically detect games and create scummvm.ini file
#################################
if [[ "add-games" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
cd "${ROOT_FOLDER}"
cp "$DIST_FOLDER/assets/scummvm.ini" "${ROOT_FOLDER}/build-emscripten/"
cd "${ROOT_FOLDER}/build-emscripten/"
"$EMSDK_NODE" "$DIST_FOLDER/build-add_games.js"
fi
#################################
# Run Development Server
#################################
if [[ "run" =~ $(echo ^\(${TASKS}\)$) ]]; then
echo "Run ScummVM"
cd "${ROOT_FOLDER}/build-emscripten/"
# emrun doesn't support range requests. Once it will, we don't need node-static anymore
# emrun --browser=chrome scummvm.html
EMSDK_NPX=$(dirname $EMSDK_NODE)/npx
$EMSDK_NPX -p node-static static .
fi

View File

@ -0,0 +1,17 @@
/*global Module*/
Module["arguments"] = [];
Module["arguments"].push("--config=/local/scummvm.ini");
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API only works in secure contexts and supported browsers.
// This disables joystick support to avoid a crash when initializing the sdl subsystem without the gamepad API being available.
if (!navigator.getGamepads && !navigator.webkitGetGamepads) {
Module["arguments"].push("--joystick=-1")
}
// 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);
})
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
From 2ea11b4734de5656aaf3285145831e2bb9ad53ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christian=20Ku=CC=88ndig?= <kuendig@scandit.com>
Date: Sun, 24 Apr 2022 18:55:51 +0200
Subject: [PATCH] Keeping a reference to the original function in
instrumentWasmExports and using that in _dlsym_js to pass the right method to
addFunction.
---
src/library_async.js | 3 +++
src/library_dylink.js | 4 ++--
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/library_async.js b/src/library_async.js
index 14511ba05..32ce7abd3 100644
--- a/src/library_async.js
+++ b/src/library_async.js
@@ -130,6 +130,9 @@ mergeInto(LibraryManager.library, {
}
}
};
+#if MAIN_MODULE
+ ret[x].orig = original;
+#endif
} else {
ret[x] = original;
}
diff --git a/src/library_dylink.js b/src/library_dylink.js
index 291c50ef2..3c70cfca7 100644
--- a/src/library_dylink.js
+++ b/src/library_dylink.js
@@ -964,8 +967,8 @@ var LibraryDylink = {
#endif
#if ASYNCIFY
- if(symbol in GOT && GOT[symbol].value != 0) {
- return GOT[symbol].value
+ if ('orig' in result) {
+ result = result.orig;
}
#endif
// Insert the function into the wasm table. If its a direct wasm function
--
2.31.0

View File

@ -0,0 +1,386 @@
From 33d2935283bdf734dff5f5a2560571480f47b6b7 Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Thu, 6 Jan 2022 14:55:48 +0900
Subject: [PATCH 01/10] import mutable globals used in Asyncify pass
---
emcc.py | 9 +++++++++
emscripten.py | 1 +
src/library.js | 6 ++++++
3 files changed, 16 insertions(+)
diff --git a/emcc.py b/emcc.py
index 34df446434c..e5a425d1f31 100755
--- a/emcc.py
+++ b/emcc.py
@@ -554,6 +554,8 @@ def get_binaryen_passes():
passes += ['--fpcast-emu']
if settings.ASYNCIFY:
passes += ['--asyncify']
+ if settings.MAIN_MODULE or settings.SIDE_MODULE:
+ passes += ['--pass-arg=asyncify-side-module']
if settings.ASSERTIONS:
passes += ['--pass-arg=asyncify-asserts']
if settings.ASYNCIFY_ADVISE:
@@ -1854,6 +1856,13 @@ def phase_linker_setup(options, state, newargs, user_settings):
'__heap_base',
'__stack_pointer',
]
+
+ if settings.ASYNCIFY:
+ settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
+ '__asyncify_state',
+ '__asyncify_data'
+ ]
+
# Unconditional dependency in library_dylink.js
settings.REQUIRED_EXPORTS += ['setThrew']
diff --git a/emscripten.py b/emscripten.py
index cd0c27dc2f3..b7d1bff3994 100644
--- a/emscripten.py
+++ b/emscripten.py
@@ -344,6 +344,7 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile):
if settings.ASYNCIFY:
exports += ['asyncify_start_unwind', 'asyncify_stop_unwind', 'asyncify_start_rewind', 'asyncify_stop_rewind']
+ metadata['globalImports'] += ['__asyncify_state', '__asyncify_data']
report_missing_symbols(forwarded_json['libraryFunctions'])
diff --git a/src/library.js b/src/library.js
index 401e17d086a..2c4d375d52e 100644
--- a/src/library.js
+++ b/src/library.js
@@ -3521,6 +3521,12 @@ LibraryManager.library = {
__c_longjmp: "new WebAssembly.Tag({'parameters': ['{{{ POINTER_TYPE }}}']})",
__c_longjmp_import: true,
#endif
+#if ASYNCIFY
+ __asyncify_state: "new WebAssembly.Global({'value': 'i32', 'mutable': true}, 0)",
+ __asyncify_state__import: true,
+ __asyncify_data: "new WebAssembly.Global({'value': 'i32', 'mutable': true}, 0)",
+ __asyncify_data__import: true,
+#endif
#endif
};
From 065cb3c3aef101b8b8e249e32a44843830e5fa73 Mon Sep 17 00:00:00 2001
From: nokotan <kamenokonokotan@gmail.com>
Date: Sun, 23 Jan 2022 21:54:03 +0900
Subject: [PATCH 02/10] move globals metadata modification
---
emscripten.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/emscripten.py b/emscripten.py
index b7d1bff3994..810eeb93dda 100644
--- a/emscripten.py
+++ b/emscripten.py
@@ -324,6 +324,9 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile):
if settings.INITIAL_TABLE == -1:
settings.INITIAL_TABLE = dylink_sec.table_size + 1
+ if settings.ASYNCIFY:
+ metadata['globalImports'] += ['__asyncify_state', '__asyncify_data']
+
glue, forwarded_data = compile_settings()
if DEBUG:
logger.debug(' emscript: glue took %s seconds' % (time.time() - t))
@@ -344,7 +347,6 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile):
if settings.ASYNCIFY:
exports += ['asyncify_start_unwind', 'asyncify_stop_unwind', 'asyncify_start_rewind', 'asyncify_stop_rewind']
- metadata['globalImports'] += ['__asyncify_state', '__asyncify_data']
report_missing_symbols(forwarded_json['libraryFunctions'])
From fc1edebd9df7e1c84e0fd7d700dd80dbf5a6e83b Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Tue, 25 Jan 2022 22:00:21 +0900
Subject: [PATCH 03/10] Remove redundant spaces
---
emcc.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/emcc.py b/emcc.py
index e5a425d1f31..2c51a6d4264 100755
--- a/emcc.py
+++ b/emcc.py
@@ -1862,7 +1862,7 @@ def phase_linker_setup(options, state, newargs, user_settings):
'__asyncify_state',
'__asyncify_data'
]
-
+
# Unconditional dependency in library_dylink.js
settings.REQUIRED_EXPORTS += ['setThrew']
From 163609c529d31d9c6a07866ffb6f0d6a2901d8c9 Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Tue, 25 Jan 2022 22:10:36 +0900
Subject: [PATCH 04/10] Add test_asyncify_side_module
---
tests/test_core.py | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/tests/test_core.py b/tests/test_core.py
index 6eeee235e5b..e004def18e6 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -7903,6 +7903,34 @@ def test_asyncify_indirect_lists(self, args, should_pass):
if should_pass:
raise
+ @needs_dylink
+ @no_memory64('TODO: asyncify for wasm64')
+ def test_asyncify_side_module(self):
+ self.set_setting('ASYNCIFY')
+ self.emcc_args += ['-sASYNCIFY_IMPORTS=["_Z8my_sleepi"]']
+ self.dylink_test(r'''
+ #include <stdio.h>
+ #include "header.h"
+
+ int main() {
+ my_sleep(1);
+ return 0;
+ }
+ ''', r'''
+ #include <emscripten.h>
+ #include <stdio.h>
+ #include "header.h"
+
+ void my_sleep(int milli_seconds) {
+ // put variable onto stack
+ volatile int value = 42;
+ printf("%d ", value);
+ emscripten_sleep(milli_seconds);
+ // variable on stack in side module function should be restored.
+ printf("%d\n", value);
+ }
+ ''', '42 42', header='void my_sleep(int);')
+
@no_asan('asyncify stack operations confuse asan')
@no_memory64('TODO: asyncify for wasm64')
def test_emscripten_scan_registers(self):
From 8066f18bf46c2d694bba624e00cb6f38c11499a5 Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Wed, 26 Jan 2022 01:29:14 +0900
Subject: [PATCH 05/10] flake8, add EXIT_RUNTIME
---
tests/test_core.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tests/test_core.py b/tests/test_core.py
index e004def18e6..b30806192ab 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -7907,13 +7907,14 @@ def test_asyncify_indirect_lists(self, args, should_pass):
@no_memory64('TODO: asyncify for wasm64')
def test_asyncify_side_module(self):
self.set_setting('ASYNCIFY')
+ self.set_setting('EXIT_RUNTIME', 1)
self.emcc_args += ['-sASYNCIFY_IMPORTS=["_Z8my_sleepi"]']
self.dylink_test(r'''
#include <stdio.h>
#include "header.h"
int main() {
- my_sleep(1);
+ my_sleep(1);
return 0;
}
''', r'''
From 2f451abf5fa81f6bc08a9a671d7251f630c5ea67 Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Sun, 30 Jan 2022 02:39:01 +0900
Subject: [PATCH 06/10] add instrumentWasmExports
---
src/library_dylink.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/library_dylink.js b/src/library_dylink.js
index e71ceb1b65b..8a0a3df9469 100644
--- a/src/library_dylink.js
+++ b/src/library_dylink.js
@@ -583,6 +583,9 @@ var LibraryDylink = {
// add new entries to functionsInTableMap
updateTableMap(tableBase, metadata.tableSize);
moduleExports = relocateExports(instance.exports, memoryBase);
+#if ASYNCIFY
+ moduleExports = Asyncify.instrumentWasmExports(moduleExports);
+#endif
if (!flags.allowUndefined) {
reportUndefinedSymbols();
}
From d09570de2e65ead70ab31f4a843ecde29512436c Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Sun, 30 Jan 2022 02:39:56 +0900
Subject: [PATCH 07/10] add searched symbols in getDataRewindFunc
---
src/library_async.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/library_async.js b/src/library_async.js
index da09a1ae2d8..0ceb072dd37 100644
--- a/src/library_async.js
+++ b/src/library_async.js
@@ -205,6 +205,11 @@ mergeInto(LibraryManager.library, {
var id = {{{ makeGetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'i32') }}};
var name = Asyncify.callStackIdToName[id];
var func = Module['asm'][name];
+#if RELOCATABLE
+ if (!func) {
+ func = Module[asmjsMangle(name)];
+ }
+#endif
return func;
},
From 3a71ca6d2d93f1d933dc43b9aa34e56086a30534 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christian=20K=C3=BCndig?= <christian@kuendig.info>
Date: Fri, 18 Feb 2022 18:15:53 +0100
Subject: [PATCH 08/10] Fixing dlsym for emscripten-core/emscripten#15893
---
src/library_dylink.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/library_dylink.js b/src/library_dylink.js
index 8a0a3df9469..9fca8adf488 100644
--- a/src/library_dylink.js
+++ b/src/library_dylink.js
@@ -965,6 +965,12 @@ var LibraryDylink = {
#if DYLINK_DEBUG
err('dlsym: ' + symbol + ' getting table slot for: ' + result);
#endif
+
+#if ASYNCIFY
+ if(symbol in GOT && GOT[symbol].value != 0) {
+ return GOT[symbol].value
+ }
+#endif
// Insert the function into the wasm table. If its a direct wasm function
// the second argument will not be needed. If its a JS function we rely
// on the `sig` attribute being set based on the `<func>__sig` specified
From 7e451a8df7144c6293a57c8563538d409b904fe0 Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Sun, 6 Mar 2022 00:05:00 +0900
Subject: [PATCH 09/10] Add test case test_asyncify_dlfcn
---
tests/test_core.py | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/tests/test_core.py b/tests/test_core.py
index b30806192ab..0c978f39343 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -7932,6 +7932,39 @@ def test_asyncify_side_module(self):
}
''', '42 42', header='void my_sleep(int);')
+ @needs_dylink
+ @no_memory64('TODO: asyncify for wasm64')
+ def test_asyncify_dlfcn(self):
+ self.set_setting('ASYNCIFY')
+ self.set_setting('EXIT_RUNTIME', 1)
+ self.emcc_args += ['-sASYNCIFY_IGNORE_INDIRECT=0']
+ self.dylink_test(r'''
+ #include <iostream>
+ #include <dlfcn.h>
+
+ typedef int (*func_t)();
+
+ int main(int argc, char **argv)
+ {
+ void *_dlHandle = dlopen("liblib.so", RTLD_NOW | RTLD_LOCAL);
+ func_t my_func = (func_t)dlsym(_dlHandle, "side_module_run");
+ printf("%d\n", my_func());
+ return 0;
+ }
+ ''', r'''
+ #include <iostream>
+ #include <emscripten/emscripten.h>
+
+ extern "C"
+ {
+ int side_module_run()
+ {
+ emscripten_sleep(1000);
+ return 42;
+ }
+ }
+ ''', '42', need_reverse=False)
+
@no_asan('asyncify stack operations confuse asan')
@no_memory64('TODO: asyncify for wasm64')
def test_emscripten_scan_registers(self):
From 1b85abaab0186939a36207f2480cf836b096840f Mon Sep 17 00:00:00 2001
From: kamenokonokotan <kamenokonokotan@gmail.com>
Date: Wed, 6 Apr 2022 01:40:08 +0900
Subject: [PATCH 10/10] Update test case
---
tests/test_core.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/tests/test_core.py b/tests/test_core.py
index 0c978f39343..7009c539f12 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -7914,7 +7914,9 @@ def test_asyncify_side_module(self):
#include "header.h"
int main() {
+ printf("before sleep\n");
my_sleep(1);
+ printf("after sleep\n");
return 0;
}
''', r'''
@@ -7925,12 +7927,12 @@ def test_asyncify_side_module(self):
void my_sleep(int milli_seconds) {
// put variable onto stack
volatile int value = 42;
- printf("%d ", value);
+ printf("%d\n", value);
emscripten_sleep(milli_seconds);
// variable on stack in side module function should be restored.
printf("%d\n", value);
}
- ''', '42 42', header='void my_sleep(int);')
+ ''', 'before sleep\n42\n42\nafter sleep\n', header='void my_sleep(int);')
@needs_dylink
@no_memory64('TODO: asyncify for wasm64')
@@ -7959,11 +7961,13 @@ def test_asyncify_dlfcn(self):
{
int side_module_run()
{
+ printf("before sleep\n");
emscripten_sleep(1000);
+ printf("after sleep\n");
return 42;
}
}
- ''', '42', need_reverse=False)
+ ''', 'before sleep\nafter sleep\n42', need_reverse=False)
@no_asan('asyncify stack operations confuse asan')
@no_memory64('TODO: asyncify for wasm64')

View File

@ -0,0 +1,61 @@
From 229c16de0b827321a0c3e55975e980017d234d43 Mon Sep 17 00:00:00 2001
From: Charlie Birks <charlie@daft.games>
Date: Tue, 22 Mar 2022 13:13:34 +0000
Subject: [PATCH 1/2] Update SDL2 for #16462
---
tools/ports/sdl2.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/ports/sdl2.py b/tools/ports/sdl2.py
index f499ca8fa15..7c0a30cbd71 100644
--- a/tools/ports/sdl2.py
+++ b/tools/ports/sdl2.py
@@ -5,8 +5,8 @@
import os
-TAG = 'release-2.0.20'
-HASH = '67e1abe1183b04836b35d724fd495c83c9559b4530d4a5c9bcc89648af0ac7cc51c02f7055a1664fe5f5f90953d22a6c431fa8bc5cdd77c94a97f107c47e2d62'
+TAG = '4b8d69a41687e5f6f4b05f7fd9804dd9fcac0347'
+HASH = '2d4d577c7584da22306b05a44bc08200460a33cd414fed2dc948e2a86e7b2d1a5cbc13bacadb63618823ba63c210f21c570adbab39f7645bf902196fa91c6b4e'
SUBDIR = 'SDL-' + TAG
From 1eb16caf951bf0a38dda07d3335b4fdeb397ebc7 Mon Sep 17 00:00:00 2001
From: Charlie Birks <charlie@daft.games>
Date: Fri, 25 Mar 2022 11:24:00 +0000
Subject: [PATCH 2/2] Add an extra move to the SDL2 mouse test
The "first" event now has valid relative motion, so don't need that workaround either.
---
tests/sdl2_mouse.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/sdl2_mouse.c b/tests/sdl2_mouse.c
index f7786cdb02d..6ec8871d427 100644
--- a/tests/sdl2_mouse.c
+++ b/tests/sdl2_mouse.c
@@ -26,11 +26,10 @@ void one() {
printf("motion : %d,%d %d,%d\n", m->x, m->y, m->xrel, m->yrel);
if (mouse_motions == 0) {
- // xrel/yrel will be zero for the first motion
#ifdef TEST_SDL_MOUSE_OFFSETS
- assert(eq(m->x, 5) && eq(m->y, 15) && eq(m->xrel, 0) && eq(m->yrel, 0));
+ assert(eq(m->x, 5) && eq(m->y, 15) && eq(m->xrel, 5) && eq(m->yrel, 15));
#else
- assert(eq(m->x, 10) && eq(m->y, 20) && eq(m->xrel, 0) && eq(m->yrel, 0));
+ assert(eq(m->x, 10) && eq(m->y, 20) && eq(m->xrel, 10) && eq(m->yrel, 20));
#endif
} else if (mouse_motions == 1) {
#ifdef TEST_SDL_MOUSE_OFFSETS
@@ -93,6 +92,7 @@ int main() {
}
void main_2(void* arg) {
+ emscripten_run_script("window.simulateMouseEvent(0, 0, -1)");
emscripten_run_script("window.simulateMouseEvent(10, 20, -1)"); // move from 0,0 to 10,20
emscripten_run_script("window.simulateMouseEvent(10, 20, 0)"); // click
emscripten_run_script("window.simulateMouseEvent(10, 20, 0)"); // click some more, but this one should be ignored through PeepEvent

View File

@ -0,0 +1,22 @@
From 5ed10829c6d806d630d98943432c222cf8f02017 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christian=20K=C3=BCndig?= <christian@kuendig.info>
Date: Sat, 9 Apr 2022 16:12:40 +0200
Subject: [PATCH] SDL2: Fix SDL_OpenURL
---
tools/ports/sdl2.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/ports/sdl2.py b/tools/ports/sdl2.py
index 7c0a30cbd71..fb6ea398bfd 100644
--- a/tools/ports/sdl2.py
+++ b/tools/ports/sdl2.py
@@ -58,7 +58,7 @@ def create(final):
power/emscripten/SDL_syspower.c joystick/emscripten/SDL_sysjoystick.c
filesystem/emscripten/SDL_sysfilesystem.c timer/unix/SDL_systimer.c haptic/dummy/SDL_syshaptic.c
main/dummy/SDL_dummy_main.c locale/SDL_locale.c locale/emscripten/SDL_syslocale.c misc/SDL_url.c
- misc/dummy/SDL_sysurl.c'''.split()
+ misc/emscripten/SDL_sysurl.c'''.split()
thread_srcs = ['SDL_syscond.c', 'SDL_sysmutex.c', 'SDL_syssem.c', 'SDL_systhread.c', 'SDL_systls.c']
thread_backend = 'generic' if not settings.USE_PTHREADS else 'pthread'
srcs += ['thread/%s/%s' % (thread_backend, s) for s in thread_srcs]

View File

@ -1,4 +0,0 @@
/*global Module*/
Module["arguments"] = [];
Module["arguments"].push("--config=/data/local/scummvm.ini");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB