[Scummvm-git-logs] scummvm master -> 61ad913de6b6b23acaece5b9c70cae7245ceaf47
chkuendig
noreply at scummvm.org
Sun Aug 3 23:02:56 UTC 2025
This automated email contains information about 12 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
e4da102863 DISTS: EMSCRIPTEN: Add libvpx to build
fa1d101027 AUDIO: Add a basic soundfont for fluidlite
85cc85814c AUDIO: EMSCRIPTEN: Add fluidlite support
a270658d0d DISTS: EMSCRIPTEN: Add libmikmod to build
b1ffb2c438 DISTS: EMSCRIPTEN: Add libmpcdec to build
412c8f248e MIDI: EMSCRIPTEN: Add Web MIDI Driver
7da7cc9f9e AUDIO: EMSCRIPTEN: Add RetroWave with Web Serial API
2dca73e8a3 DISTS: EMSCRIPTEN: Add Fribidi to build
d79e58b7d3 BACKENDS: EMSCRIPTEN: Fix timers and event polling
13978af6f2 EMSCRIPTEN: CI: Build with more libraries
c4fa025aae EMSCRIPTEN: FS: Implement filesystems in C++ and remove BrowserFS dependency
61ad913de6 DISTS: EMSCRIPTEN: Simplify build by removing extra scripts for demo download and scummvm.ini creation and don't depend
Commit: e4da1028635a859c6dd011c91587390e3c2a5439
https://github.com/scummvm/scummvm/commit/e4da1028635a859c6dd011c91587390e3c2a5439
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
DISTS: EMSCRIPTEN: Add libvpx to build
Changed paths:
dists/emscripten/build.sh
diff --git a/dists/emscripten/build.sh b/dists/emscripten/build.sh
index ece82fe5bd2..468c5471c2a 100755
--- a/dists/emscripten/build.sh
+++ b/dists/emscripten/build.sh
@@ -48,8 +48,8 @@ Options:
--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
- Note: --enable-a52, --enable-faad, --enable-mad, --enable-mpeg2
- and --enable-theoradec also fetches and builds these dependencies
+ Note: --enable-a52, --enable-faad, --enable-mad, --enable-mpeg2,
+ --enable-theoradec and --enable-vpx also download and build the dependency
"
_liba52=false
@@ -57,6 +57,7 @@ _libfaad=false
_libmad=false
_libmpeg2=false
_libtheoradec=false
+_libvpx=false
# parse inputs
for i in "$@"; do
case $i in
@@ -80,6 +81,10 @@ for i in "$@"; do
_libtheoradec=true
CONFIGURE_ARGS+=" $i"
;;
+ --enable-vpx)
+ _libvpx=true
+ CONFIGURE_ARGS+=" $i"
+ ;;
--bundle-games=*)
str="${i#*=}"
_bundle_games="${str//,/ }"
@@ -255,6 +260,20 @@ if [ "$_libtheoradec" = true ]; then
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
#################################
Commit: fa1d1010274ab1fc704cd175d8f06b8f08e2caab
https://github.com/scummvm/scummvm/commit/fa1d1010274ab1fc704cd175d8f06b8f08e2caab
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
AUDIO: Add a basic soundfont for fluidlite
Changed paths:
Makefile.common
base/commandLine.cpp
dists/scummvm.rc
diff --git a/Makefile.common b/Makefile.common
index 44dc7725fa3..6ea286a11e4 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -463,7 +463,7 @@ endif
# Soundfonts
DIST_FILES_SOUNDFONTS=
-ifdef USE_FLUIDSYNTH
+ifneq "$(or $(USE_FLUIDSYNTH),$(USE_FLUIDLITE))" ""
DIST_FILES_SOUNDFONTS:=$(addprefix $(srcdir)/dists/soundfonts/,Roland_SC-55.sf2)
DIST_FILES_DOCS+=$(addprefix $(srcdir)/dists/soundfonts/,COPYRIGHT.Roland_SC-55)
endif
diff --git a/base/commandLine.cpp b/base/commandLine.cpp
index a078cf7daca..3eb74c1dd15 100644
--- a/base/commandLine.cpp
+++ b/base/commandLine.cpp
@@ -334,7 +334,7 @@ void registerDefaults() {
ConfMan.registerDefault("enable_unsupported_game_warning", true);
ConfMan.registerDefault("enable_unsupported_addon_warning", true);
-#ifdef USE_FLUIDSYNTH
+#if defined(USE_FLUIDSYNTH) || defined(USE_FLUIDLITE)
ConfMan.registerDefault("soundfont", "Roland_SC-55.sf2");
#endif
diff --git a/dists/scummvm.rc b/dists/scummvm.rc
index 465f8550a4c..b4c283b5864 100644
--- a/dists/scummvm.rc
+++ b/dists/scummvm.rc
@@ -29,7 +29,7 @@ shaders.dat FILE "gui/themes/shaders.dat"
#include "dists/scummvm_rc_engine_data_core.rh"
-#ifdef USE_FLUIDSYNTH
+#if defined(USE_FLUIDSYNTH) || defined(USE_FLUIDLITE)
Roland_SC-55.sf2 FILE "dists/soundfonts/Roland_SC-55.sf2"
#endif
Commit: 85cc85814ca7472afe77baeab03fa62efaad3c36
https://github.com/scummvm/scummvm/commit/85cc85814ca7472afe77baeab03fa62efaad3c36
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
AUDIO: EMSCRIPTEN: Add fluidlite support
Changed paths:
audio/softsynth/fluidsynth.cpp
backends/platform/sdl/emscripten/emscripten.mk
dists/emscripten/build.sh
diff --git a/audio/softsynth/fluidsynth.cpp b/audio/softsynth/fluidsynth.cpp
index e64d2fc8500..72994bab6e0 100644
--- a/audio/softsynth/fluidsynth.cpp
+++ b/audio/softsynth/fluidsynth.cpp
@@ -353,11 +353,14 @@ int MidiDriver_FluidSynth::open() {
return MERR_DEVICE_NOT_AVAILABLE;
}
-#if defined(ANDROID_BACKEND) && defined(FS_HAS_STREAM_SUPPORT)
+#if (defined(EMSCRIPTEN) || defined(ANDROID_BACKEND)) && defined(FS_HAS_STREAM_SUPPORT)
// In Android, when using SAF we need to wrap IO to make it work
// We can only do this with FluidSynth 2.0
- if (!isUsingInMemorySoundFontData &&
- AndroidFilesystemFactory::instance().hasSAF()) {
+ if (!isUsingInMemorySoundFontData
+#if defined(ANDROID_BACKEND)
+ && AndroidFilesystemFactory::instance().hasSAF()
+#endif
+ ) {
Common::FSNode fsnode(getSoundFontPath());
_engineSoundFontData = fsnode.createReadStream();
isUsingInMemorySoundFontData = _engineSoundFontData != nullptr;
diff --git a/backends/platform/sdl/emscripten/emscripten.mk b/backends/platform/sdl/emscripten/emscripten.mk
index 8ae26cbd0d9..be46fdce5b0 100644
--- a/backends/platform/sdl/emscripten/emscripten.mk
+++ b/backends/platform/sdl/emscripten/emscripten.mk
@@ -18,6 +18,9 @@ endif
ifdef DIST_FILES_VKEYBD
cp $(DIST_FILES_VKEYBD) ./build-emscripten/data
endif
+ifdef DIST_FILES_SOUNDFONTS
+ cp $(DIST_FILES_SOUNDFONTS) ./build-emscripten/data
+endif
ifdef DIST_FILES_SHADERS
mkdir -p ./build-emscripten/data/shaders
cp $(DIST_FILES_SHADERS) ./build-emscripten/data/shaders
diff --git a/dists/emscripten/build.sh b/dists/emscripten/build.sh
index 468c5471c2a..3010021f795 100755
--- a/dists/emscripten/build.sh
+++ b/dists/emscripten/build.sh
@@ -52,12 +52,14 @@ Options:
--enable-theoradec and --enable-vpx also download and build the dependency
"
+_fluidlite=false
_liba52=false
_libfaad=false
_libmad=false
_libmpeg2=false
_libtheoradec=false
_libvpx=false
+
# parse inputs
for i in "$@"; do
case $i in
@@ -69,6 +71,10 @@ for i in "$@"; do
_libfaad=true
CONFIGURE_ARGS+=" $i"
;;
+ --enable-fluidlite)
+ _fluidlite=true
+ CONFIGURE_ARGS+=" $i"
+ ;;
--enable-mad)
_libmad=true
CONFIGURE_ARGS+=" $i"
@@ -215,6 +221,21 @@ if [ "$_libfaad" = true ]; then
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 [ "$_libmad" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libmad.a" ]]; then
echo "building libmad-0.15.1b"
Commit: a270658d0d2c4ea473be807375d2e697231dc16a
https://github.com/scummvm/scummvm/commit/a270658d0d2c4ea473be807375d2e697231dc16a
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
DISTS: EMSCRIPTEN: Add libmikmod to build
Changed paths:
dists/emscripten/build.sh
diff --git a/dists/emscripten/build.sh b/dists/emscripten/build.sh
index 3010021f795..97318243e4d 100755
--- a/dists/emscripten/build.sh
+++ b/dists/emscripten/build.sh
@@ -48,8 +48,10 @@ Options:
--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
- Note: --enable-a52, --enable-faad, --enable-mad, --enable-mpeg2,
- --enable-theoradec and --enable-vpx also download and build the dependency
+ Note: --enable-a52, --enable-faad,
+ --enable-mad, --enable-mpeg2, --enable-mikmod,
+ --enable-theoradec and --enable-vpx
+ also download and build the required library before running configure or make.
"
_fluidlite=false
@@ -57,6 +59,7 @@ _liba52=false
_libfaad=false
_libmad=false
_libmpeg2=false
+_libmikmod=false
_libtheoradec=false
_libvpx=false
@@ -83,6 +86,15 @@ for i in "$@"; do
_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-mikmod)
+ _libmikmod=true
+ CONFIGURE_ARGS+=" $i"
+ ;;
--enable-theoradec)
_libtheoradec=true
CONFIGURE_ARGS+=" $i"
@@ -267,6 +279,34 @@ if [ "$_libmpeg2" = true ]; then
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 [ "$_libmikmod" = true ]; then
+ if [[ ! -f "$LIBS_FOLDER/build/lib/libmikmod.a" ]]; then
+ echo "building libmikmod-3.3.13"
+ cd "$LIBS_FOLDER"
+ wget -nc "https://sourceforge.net/projects/mikmod/files/libmikmod/3.3.13/libmikmod-3.3.13.tar.gz"
+ tar -xf libmikmod-3.3.13.tar.gz
+ cd "$LIBS_FOLDER/libmikmod-3.3.13/"
+ 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-mikmod-prefix=$LIBS_FOLDER/build"
+fi
+
if [ "$_libtheoradec" = true ]; then
if [[ ! -f "$LIBS_FOLDER/build/lib/libtheora.a" ]]; then
echo "build libtheora-1.1.1"
Commit: b1ffb2c4381dc488d494fca4cac0366a0d0c37f3
https://github.com/scummvm/scummvm/commit/b1ffb2c4381dc488d494fca4cac0366a0d0c37f3
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
DISTS: EMSCRIPTEN: Add libmpcdec to build
Changed paths:
dists/emscripten/build.sh
diff --git a/dists/emscripten/build.sh b/dists/emscripten/build.sh
index 97318243e4d..04151445164 100755
--- a/dists/emscripten/build.sh
+++ b/dists/emscripten/build.sh
@@ -48,8 +48,8 @@ Options:
--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
- Note: --enable-a52, --enable-faad,
- --enable-mad, --enable-mpeg2, --enable-mikmod,
+ Note: --enable-a52, --enable-faad, --enable-fluidlite,
+ --enable-mad, --enable-mpcdec, --enable-mpeg2, --enable-mikmod,
--enable-theoradec and --enable-vpx
also download and build the required library before running configure or make.
"
@@ -58,6 +58,7 @@ _fluidlite=false
_liba52=false
_libfaad=false
_libmad=false
+_libmpcdec=false
_libmpeg2=false
_libmikmod=false
_libtheoradec=false
@@ -91,8 +92,8 @@ for i in "$@"; do
# We don't pass --enable-mpcdec as configure
# has to establish which API to use (old or new)
;;
- --enable-mikmod)
- _libmikmod=true
+ --enable-openmpt)
+ _libopenmpt=true
CONFIGURE_ARGS+=" $i"
;;
--enable-theoradec)
@@ -293,18 +294,17 @@ if [ "$_libmpcdec" = true ]; then
LIBS_FLAGS="${LIBS_FLAGS} --with-mpcdec-prefix=$LIBS_FOLDER/build"
fi
-if [ "$_libmikmod" = true ]; then
- if [[ ! -f "$LIBS_FOLDER/build/lib/libmikmod.a" ]]; then
- echo "building libmikmod-3.3.13"
+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://sourceforge.net/projects/mikmod/files/libmikmod/3.3.13/libmikmod-3.3.13.tar.gz"
- tar -xf libmikmod-3.3.13.tar.gz
- cd "$LIBS_FOLDER/libmikmod-3.3.13/"
- 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
+ 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-mikmod-prefix=$LIBS_FOLDER/build"
+ LIBS_FLAGS="${LIBS_FLAGS} --with-openmpt-prefix=$LIBS_FOLDER/build"
fi
if [ "$_libtheoradec" = true ]; then
Commit: 412c8f248edd3f42225ae70b9731e89832a42953
https://github.com/scummvm/scummvm/commit/412c8f248edd3f42225ae70b9731e89832a42953
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
MIDI: EMSCRIPTEN: Add Web MIDI Driver
Changed paths:
A backends/midi/webmidi.cpp
backends/module.mk
base/plugins.cpp
dists/emscripten/custom_shell-pre.js
diff --git a/backends/midi/webmidi.cpp b/backends/midi/webmidi.cpp
new file mode 100644
index 00000000000..aca32b68cc6
--- /dev/null
+++ b/backends/midi/webmidi.cpp
@@ -0,0 +1,210 @@
+/* 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/>.
+ *
+ */
+
+// Disable symbol overrides so that we can use system headers.
+#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
+#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
+
+#ifdef EMSCRIPTEN
+#include <emscripten.h>
+
+#include "audio/mpu401.h"
+#include "audio/musicplugin.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/error.h"
+#include "common/scummsys.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+/* WebMidi MIDI driver
+ */
+class MidiDriver_WebMIDI : public MidiDriver_MPU401 {
+public:
+ MidiDriver_WebMIDI(Common::String name, const char *id);
+ ~MidiDriver_WebMIDI();
+ int open() override;
+ bool isOpen() const override;
+ void close() override;
+ void send(uint32 b) override;
+ void sysEx(const byte *msg, uint16 length) override;
+
+private:
+ Common::String _name;
+ const char *_id;
+};
+
+MidiDriver_WebMIDI::MidiDriver_WebMIDI(Common::String name, const char *id)
+ : _name(name), _id(id), MidiDriver_MPU401() {
+ debug(5, "WebMIDI: Creating device %s %s", _name.c_str(), _id);
+}
+
+MidiDriver_WebMIDI::~MidiDriver_WebMIDI() {
+}
+
+EM_ASYNC_JS(int, _midiOpen, (const char *id_c), {
+ var id = UTF8ToString(id_c);
+ result = await midiOutputMap.get(id).open().catch((error) => {
+ console.error(error);
+ return 1;
+ });
+ return 0;
+});
+
+int MidiDriver_WebMIDI::open() {
+ debug(5, "WebMIDI: Opening device %s", _name.c_str());
+ if (isOpen())
+ return MERR_ALREADY_OPEN;
+ return _midiOpen(_id);
+}
+
+EM_JS(bool, _midiIsOpen, (const char *id_c), {
+ var id = UTF8ToString(id_c);
+ return midiOutputMap.get(id) && midiOutputMap.get(id).connection == "open";
+});
+
+bool MidiDriver_WebMIDI::isOpen() const {
+ return _midiIsOpen(_id);
+}
+
+EM_ASYNC_JS(void, _midiClose, (const char *id_c), {
+ var id = UTF8ToString(id_c);
+ await midiOutputMap.get(id).close(); // TODO: Wait for promise to resolve (should be possible with Asyncify)
+});
+
+void MidiDriver_WebMIDI::close() {
+ debug(5, "WebMIDI: Closing device %s", _name.c_str());
+ MidiDriver_MPU401::close();
+ if(_midiIsOpen(_id))
+ _midiClose(_id);
+ else
+ warning("WebMIDI: Device %s is not open", _name.c_str());
+}
+
+EM_JS(void, _midiSendMessage, (const char *id_c, int status_byte, int first_byte, int second_byte), {
+ var id = UTF8ToString(id_c);
+ if (status_byte < 0xc0 || status_byte > 0xdf) {
+ midiOutputMap.get(id).send([status_byte, first_byte, second_byte]);
+ } else {
+ midiOutputMap.get(id).send([status_byte, first_byte]);
+ }
+});
+
+void MidiDriver_WebMIDI::send(uint32 b) {
+ if(!isOpen()) {
+ warning("WebMIDI: Send called on closed device %s", _name.c_str());
+ return;
+ }
+ debug(5, "WebMIDI: Sending message for device %s %x", _name.c_str(), b);
+ midiDriverCommonSend(b);
+
+ const byte status_byte = b & 0xff;
+ const byte first_byte = (b >> 8) & 0xff;
+ const byte second_byte = (b >> 16) & 0xff;
+ _midiSendMessage(_id, status_byte, first_byte, second_byte);
+}
+
+void MidiDriver_WebMIDI::sysEx(const byte *msg, uint16 length) {
+ if(!isOpen()) {
+ warning("WebMIDI: SysEx called on closed device %s", _name.c_str());
+ return;
+ }
+ midiDriverCommonSysEx(msg, length);
+ warning("WebMIDI: SysEx not implemented yet, skipping %d bytes", length);
+}
+
+// Plugin interface
+
+class WebMIDIMusicPlugin : public MusicPluginObject {
+public:
+ const char *getName() const {
+ return "Web Midi";
+ }
+
+ const char *getId() const {
+ return "WebMidi";
+ }
+
+ MusicDevices getDevices() const;
+ Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
+};
+
+EM_JS(char **, _midiGetOutputNames, (), {
+ deviceNames = Array.from(midiOutputMap || []).map((elem) => elem[1].name);
+ deviceNames.push(""); // we need this to find the end of the array on the native side.
+
+ // convert the strings to C strings
+ var c_strings = deviceNames.map((s) => {
+ var size = lengthBytesUTF8(s) + 1;
+ var ret = Module._malloc(size);
+ stringToUTF8Array(s, HEAP8, ret, size);
+ return ret;
+ });
+
+ // populate return array
+ var ret_arr = Module._malloc(c_strings.length * 4); // 4-bytes per pointer
+ c_strings.forEach((ptr, i) => { Module.setValue(ret_arr + i * 4, ptr, "i32"); });
+
+ return ret_arr;
+});
+
+MusicDevices WebMIDIMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ char **outputNames = _midiGetOutputNames();
+ char **iter = outputNames;
+ while (strcmp(*iter, "") != 0) {
+ char *c_name = *iter++;
+ Common::String name = Common::String(c_name);
+ devices.push_back(MusicDevice(this, name, MT_GM));
+ free(c_name);
+ }
+ free(outputNames);
+ return devices;
+}
+
+EM_JS(const char *, _midiGetOutputId, (const char *name_c), {
+ var name = UTF8ToString(name_c);
+ for (const [key, midiOutput] of midiOutputMap.entries()) {
+ if (midiOutput.name == name) {
+ return stringToNewUTF8(key)
+ }
+ }
+ return;
+});
+
+Common::Error WebMIDIMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle device) const {
+ MusicDevices deviceList = getDevices();
+ for (MusicDevices::iterator j = deviceList.begin(), jend = deviceList.end(); j != jend; ++j) {
+ if (j->getHandle() == device) {
+ *mididriver = new MidiDriver_WebMIDI(j->getName(), _midiGetOutputId(j->getName().c_str()));
+ return Common::kNoError;
+ }
+ }
+ return Common::kUnknownError;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(WebMidi)
+ //REGISTER_PLUGIN_DYNAMIC(WebMidi, PLUGIN_TYPE_MUSIC, WebMIDIMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(WEBMIDI, PLUGIN_TYPE_MUSIC, WebMIDIMusicPlugin);
+//#endif
+
+#endif // EMSCRIPTEN
diff --git a/backends/module.mk b/backends/module.mk
index 66c7ecc37d1..ffe140e8c57 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -94,6 +94,8 @@ MODULE_OBJS += \
endif
ifdef EMSCRIPTEN
+MODULE_OBJS += \
+ midi/webmidi.o
ifdef USE_TTS
MODULE_OBJS += \
text-to-speech/emscripten/emscripten-text-to-speech.o
diff --git a/base/plugins.cpp b/base/plugins.cpp
index ef537100062..0f134b614db 100644
--- a/base/plugins.cpp
+++ b/base/plugins.cpp
@@ -132,6 +132,10 @@ public:
#ifdef USE_FLUIDSYNTH
LINK_PLUGIN(FLUIDSYNTH)
#endif
+
+ #ifdef EMSCRIPTEN
+ LINK_PLUGIN(WEBMIDI)
+ #endif
#ifdef USE_MT32EMU
LINK_PLUGIN(MT32)
#endif
diff --git a/dists/emscripten/custom_shell-pre.js b/dists/emscripten/custom_shell-pre.js
index 4423f0289a8..c567e001ff5 100644
--- a/dists/emscripten/custom_shell-pre.js
+++ b/dists/emscripten/custom_shell-pre.js
@@ -8,3 +8,18 @@ if (window.location.hash.length > 0) {
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;
+ };
+ });
+}
Commit: 7da7cc9f9e205f8455482faba6943c97133049b0
https://github.com/scummvm/scummvm/commit/7da7cc9f9e205f8455482faba6943c97133049b0
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
AUDIO: EMSCRIPTEN: Add RetroWave with Web Serial API
Changed paths:
audio/rwopl3.cpp
audio/rwopl3.h
configure
dists/emscripten/build.sh
dists/emscripten/custom_shell-post.js
diff --git a/audio/rwopl3.cpp b/audio/rwopl3.cpp
index ea726340297..398149c8e11 100644
--- a/audio/rwopl3.cpp
+++ b/audio/rwopl3.cpp
@@ -32,6 +32,9 @@
#include <RetroWaveLib/Platform/Linux_SPI.h>
#include <RetroWaveLib/Platform/POSIX_SerialPort.h>
#include <RetroWaveLib/Platform/Win32_SerialPort.h>
+#ifdef EMSCRIPTEN
+#include <RetroWaveLib/Platform/Web_SerialPort.h>
+#endif
#include "common/config-manager.h"
#include "common/debug.h"
@@ -54,7 +57,7 @@ OPL::~OPL() {
switch (_connType) {
case RWCONNTYPE_POSIX_SERIAL:
-#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+#if (defined(__unix__) && !defined(EMSCRIPTEN)) || (defined(__APPLE__) && defined(__MACH__))
retrowave_deinit_posix_serialport(&_retrowaveGlobalContext);
#endif
break;
@@ -66,6 +69,11 @@ OPL::~OPL() {
case RWCONNTYPE_LINUX_SPI:
#if defined(__linux__)
retrowave_deinit_linux_spi(&_retrowaveGlobalContext);
+#endif
+ break;
+ case RWCONNTYPE_WEB_SERIAL:
+#ifdef EMSCRIPTEN
+ retrowave_deinit_web_serialport(&_retrowaveGlobalContext);
#endif
break;
}
@@ -86,11 +94,16 @@ bool OPL::init() {
_rwMutex->lock();
+#ifdef EMSCRIPTEN
+ // Emscripten only (and always) works over the Web Serial API
+ rc = retrowave_init_web_serialport(&_retrowaveGlobalContext);
+ _connType = RWCONNTYPE_WEB_SERIAL;
+#else
if (bus == "serial") {
if (port.empty()) {
warning("RWOPL3: Missing port specification.");
} else {
-#if defined(__linux__) || defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+#if defined(__linux__) || (defined(__unix__) && !defined(EMSCRIPTEN)) || (defined(__APPLE__) && defined(__MACH__))
char buf[128];
snprintf(buf, sizeof(buf) - 1, "/dev/%s", port.c_str());
@@ -125,8 +138,9 @@ bool OPL::init() {
warning("RWOPL3: SPI is not supported on your platform.");
#endif
} else {
- warning("RWOPL3: Bad bus specification. Valid values are \"serial\" and \"spi\".");
+ warning("RWOPL3: Bad bus specification: %s. Valid values are \"serial\" and \"spi\".", bus.c_str());
}
+#endif
if (rc < 0) {
warning("RWOPL3: Failed to init board - init returned %d.", rc);
diff --git a/audio/rwopl3.h b/audio/rwopl3.h
index 1d154a3db3d..47ffbdb4632 100644
--- a/audio/rwopl3.h
+++ b/audio/rwopl3.h
@@ -39,7 +39,8 @@ private:
enum RWConnType {
RWCONNTYPE_POSIX_SERIAL,
RWCONNTYPE_WIN32_SERIAL,
- RWCONNTYPE_LINUX_SPI
+ RWCONNTYPE_LINUX_SPI,
+ RWCONNTYPE_WEB_SERIAL
};
Config::OplType _type;
diff --git a/configure b/configure
index 84f558be37a..e27efd4f3b2 100755
--- a/configure
+++ b/configure
@@ -3267,6 +3267,8 @@ EOF
append_var LDFLAGS "-s INITIAL_MEMORY=128MB -s STACK_SIZE=32MB -s ALLOW_MEMORY_GROWTH=1"
append_var LDFLAGS "-s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=1048576"
+ append_var LDFLAGS "-s EXPORTED_FUNCTIONS=[_main,_malloc,_free,_raise]"
+ append_var LDFLAGS "-s EXPORTED_RUNTIME_METHODS=[ccall,lengthBytesUTF8,setValue,writeArrayToMemory]"
if test "$_debug_build" = yes; then
_optimization_level=-O2
append_var LDFLAGS "-O2 -g3 -s ASSERTIONS=2"
diff --git a/dists/emscripten/build.sh b/dists/emscripten/build.sh
index 04151445164..236c8cd38bb 100755
--- a/dists/emscripten/build.sh
+++ b/dists/emscripten/build.sh
@@ -63,6 +63,7 @@ _libmpeg2=false
_libmikmod=false
_libtheoradec=false
_libvpx=false
+_retrowave=false
# parse inputs
for i in "$@"; do
@@ -96,6 +97,10 @@ for i in "$@"; do
_libopenmpt=true
CONFIGURE_ARGS+=" $i"
;;
+ --enable-retrowave)
+ _retrowave=true
+ CONFIGURE_ARGS+=" $i"
+ ;;
--enable-theoradec)
_libtheoradec=true
CONFIGURE_ARGS+=" $i"
@@ -307,6 +312,20 @@ if [ "$_libopenmpt" = true ]; then
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"
diff --git a/dists/emscripten/custom_shell-post.js b/dists/emscripten/custom_shell-post.js
index c0b76b1a6c3..8ca267595e9 100644
--- a/dists/emscripten/custom_shell-post.js
+++ b/dists/emscripten/custom_shell-post.js
@@ -2,4 +2,9 @@
// which results in mouse events not working anymore after context switches
// (i.e. when launching a game)
/*global JSEvents*/
-JSEvents.removeAllHandlersOnTarget = function(){};
\ No newline at end of file
+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
+});
Commit: 2dca73e8a3669e02b994b92922fa2b0b4a67f507
https://github.com/scummvm/scummvm/commit/2dca73e8a3669e02b994b92922fa2b0b4a67f507
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
DISTS: EMSCRIPTEN: Add Fribidi to build
Changed paths:
dists/emscripten/build.sh
diff --git a/dists/emscripten/build.sh b/dists/emscripten/build.sh
index 236c8cd38bb..b2122711503 100755
--- a/dists/emscripten/build.sh
+++ b/dists/emscripten/build.sh
@@ -48,13 +48,14 @@ Options:
--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
- Note: --enable-a52, --enable-faad, --enable-fluidlite,
- --enable-mad, --enable-mpcdec, --enable-mpeg2, --enable-mikmod,
- --enable-theoradec and --enable-vpx
+ 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 before running configure or make.
"
_fluidlite=false
+_fribidi=false
_liba52=false
_libfaad=false
_libmad=false
@@ -80,6 +81,10 @@ for i in "$@"; do
_fluidlite=true
CONFIGURE_ARGS+=" $i"
;;
+ --enable-fribidi)
+ _fribidi=true
+ CONFIGURE_ARGS+=" $i"
+ ;;
--enable-mad)
_libmad=true
CONFIGURE_ARGS+=" $i"
@@ -253,6 +258,19 @@ if [ "$_fluidlite" = true ]; then
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
Commit: d79e58b7d38ffd92dacddba379a3a11e8979732d
https://github.com/scummvm/scummvm/commit/d79e58b7d38ffd92dacddba379a3a11e8979732d
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
BACKENDS: EMSCRIPTEN: Fix timers and event polling
Changed paths:
A backends/events/emscriptensdl/emscriptensdl-events.h
backends/platform/sdl/emscripten/emscripten.cpp
backends/platform/sdl/emscripten/emscripten.h
engines/grim/grim.cpp
diff --git a/backends/events/emscriptensdl/emscriptensdl-events.h b/backends/events/emscriptensdl/emscriptensdl-events.h
new file mode 100644
index 00000000000..8bfbe1dea13
--- /dev/null
+++ b/backends/events/emscriptensdl/emscriptensdl-events.h
@@ -0,0 +1,49 @@
+/* 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/>.
+ *
+ */
+
+#if !defined(BACKEND_EVENTS_EMSCRIPTEN_H) && !defined(DISABLE_DEFAULT_EVENTMANAGER)
+#define BACKEND_EVENTS_EMSCRIPTEN_H
+
+#include "backends/events/sdl/sdl-events.h"
+#include "backends/platform/sdl/emscripten/emscripten.h"
+#include "common/events.h"
+
+/**
+ * SDL Events manager for Emscripten
+ */
+class EmscriptenSdlEventSource : public SdlEventSource {
+public:
+ /**
+ * Gets and processes SDL events.
+ */
+ bool pollEvent(Common::Event &event) override {
+
+ bool ret_value = SdlEventSource::pollEvent(event);
+ if (event.type != Common::EVENT_QUIT && event.type != Common::EVENT_RETURN_TO_LAUNCHER) {
+ // yield to the browser and process timers
+ // (after polling the events to ensure synchronous event processing)
+ g_system->delayMillis(0);
+ }
+ return ret_value;
+ };
+};
+
+#endif /* BACKEND_EVENTS_EMSCRIPTEN_H */
diff --git a/backends/platform/sdl/emscripten/emscripten.cpp b/backends/platform/sdl/emscripten/emscripten.cpp
index 94ca5535970..67bd132791c 100644
--- a/backends/platform/sdl/emscripten/emscripten.cpp
+++ b/backends/platform/sdl/emscripten/emscripten.cpp
@@ -21,12 +21,15 @@
#ifdef __EMSCRIPTEN__
-
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
#include <emscripten.h>
+#include "backends/events/emscriptensdl/emscriptensdl-events.h"
+#include "backends/fs/emscripten/emscripten-fs-factory.h"
+#include "backends/mutex/null/null-mutex.h"
#include "backends/platform/sdl/emscripten/emscripten.h"
+#include "backends/timer/default/default-timer.h"
#include "common/file.h"
#ifdef USE_TTS
#include "backends/text-to-speech/emscripten/emscripten-text-to-speech.h"
@@ -74,6 +77,14 @@ void OSystem_Emscripten::initBackend() {
// Initialize Text to Speech manager
_textToSpeechManager = new EmscriptenTextToSpeechManager();
#endif
+
+ // SDL Timers don't work in Emscripten unless threads are enabled or Asyncify is disabled.
+ // We can do neither, so we use the DefaultTimerManager instead.
+ _timerManager = new DefaultTimerManager();
+
+ // Event source
+ _eventSource = new EmscriptenSdlEventSource();
+
// Invoke parent implementation of this method
OSystem_POSIX::initBackend();
}
@@ -148,4 +159,45 @@ void OSystem_Emscripten::exportFile(const Common::Path &filename) {
downloadFile(exportName.c_str(), bytes, size);
delete[] bytes;
}
+
+void OSystem_Emscripten::updateTimers() {
+ // avoid a recursion loop if a timer callback decides to call OSystem::delayMillis()
+ static bool inTimer = false;
+
+ if (!inTimer) {
+ inTimer = true;
+ ((DefaultTimerManager *)_timerManager)->checkTimers();
+ inTimer = false;
+ } else {
+ const Common::ConfigManager::Domain *activeDomain = ConfMan.getActiveDomain();
+ assert(activeDomain);
+
+ warning("%s/%s calls update() from timer",
+ activeDomain->getValOrDefault("engineid").c_str(),
+ activeDomain->getValOrDefault("gameid").c_str());
+ }
+}
+
+Common::MutexInternal *OSystem_Emscripten::createMutex() {
+ return new NullMutexInternal();
+}
+
+void OSystem_Emscripten::delayMillis(uint msecs) {
+ static uint32 lastThreshold = 0;
+ const uint32 threshold = getMillis() + msecs;
+ if (msecs == 0 && threshold - lastThreshold < 10) {
+ return;
+ }
+ uint32 pause = 0;
+ do {
+#ifdef ENABLE_EVENTRECORDER
+ if (!g_eventRec.processDelayMillis())
+#endif
+ SDL_Delay(pause);
+ updateTimers();
+ pause = getMillis() > threshold ? 0 : threshold - getMillis(); // Avoid negative values
+ pause = pause > 10 ? 10 : pause; // ensure we don't pause for too long
+ } while (pause > 0);
+ lastThreshold = threshold;
+}
#endif
diff --git a/backends/platform/sdl/emscripten/emscripten.h b/backends/platform/sdl/emscripten/emscripten.h
index 9964ea2d010..2e66932da4f 100644
--- a/backends/platform/sdl/emscripten/emscripten.h
+++ b/backends/platform/sdl/emscripten/emscripten.h
@@ -36,11 +36,16 @@ public:
#ifdef USE_OPENGL
GraphicsManagerType getDefaultGraphicsManager() const override;
#endif
+ Common::MutexInternal *createMutex() override;
void exportFile(const Common::Path &filename);
+ void delayMillis(uint msecs) override;
protected:
Common::Path getDefaultConfigFileName() override;
Common::Path getDefaultLogFileName() override;
+
+private:
+ void updateTimers();
};
#endif
diff --git a/engines/grim/grim.cpp b/engines/grim/grim.cpp
index 9bab6c23953..76e7835a71c 100644
--- a/engines/grim/grim.cpp
+++ b/engines/grim/grim.cpp
@@ -1166,14 +1166,6 @@ void GrimEngine::mainLoop() {
uint32 delayTime = _speedLimitMs - diffTime;
g_system->delayMillis(delayTime);
}
-#if defined(__EMSCRIPTEN__)
- else {
- // If SDL_HINT_EMSCRIPTEN_ASYNCIFY is enabled, SDL pauses the application and gives
- // back control to the browser automatically by calling emscripten_sleep via SDL_Delay.
- // Without this the page would completely lock up.
- g_system->delayMillis(0);
- }
-#endif
}
}
Commit: 13978af6f2c183f5438328375846003faed6f71e
https://github.com/scummvm/scummvm/commit/13978af6f2c183f5438328375846003faed6f71e
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
EMSCRIPTEN: CI: Build with more libraries
Changed paths:
.github/workflows/ci.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ec379fbf3d8..8c739749ef7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,21 +12,33 @@ jobs:
fail-fast: false
matrix:
include:
- - platform: emscripten
+ - name: emscripten
configFlags: --enable-gif --enable-jpeg --enable-ogg --enable-png --enable-vorbis --enable-zlib --enable-freetype2
- - platform: emscripten
- configFlags: --enable-gif --enable-jpeg --enable-ogg --enable-png --enable-vorbis --enable-zlib --enable-freetype2 --enable-a52 --enable-faad --enable-mad --enable-mpeg2 --enable-mpeg2 --enable-theoradec
+ - name: emscripten (extra libs)
+ configFlags: --enable-gif --enable-jpeg --enable-ogg --enable-png --enable-vorbis --enable-zlib --enable-freetype2 --enable-a52 --enable-faad --enable-fluidlite --enable-fribidi --enable-mad --enable-mpcdec --enable-mpeg2 --enable-mpeg2 --enable-mikmod --enable-retrowave --enable-theoradec --enable-vpx
steps:
- uses: actions/checkout at v4
- - name: Call configure
- run: |
- dists/emscripten/build.sh configure --enable-all-engines ${{ matrix.configFlags }}
- name: Build cache
uses: hendrikmuhs/ccache-action at v1.2
with:
key: ${{ matrix.platform }}
max-size: 1G
create-symlink: true
+ - name: Restore libs cache
+ uses: actions/cache/restore at v4
+ with:
+ key: ${{ matrix.configFlags }}
+ path: |
+ dists/emscripten/libs/
+ - name: Call configure
+ run: |
+ CXX='ccache emcc' dists/emscripten/build.sh configure --enable-all-engines ${{ matrix.configFlags }}
+ - name: Save libs cache
+ uses: actions/cache/save at v4
+ with:
+ key: ${{ matrix.configFlags }}
+ path: |
+ dists/emscripten/libs/
- name: Build scummvm
run: |
dists/emscripten/build.sh make
Commit: c4fa025aae98746420a7703796f31df6626c71a2
https://github.com/scummvm/scummvm/commit/c4fa025aae98746420a7703796f31df6626c71a2
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
EMSCRIPTEN: FS: Implement filesystems in C++ and remove BrowserFS dependency
Changed paths:
A backends/fs/emscripten/emscripten-fs-factory.cpp
A backends/fs/emscripten/emscripten-fs-factory.h
A backends/fs/emscripten/emscripten-posix-fs.cpp
A backends/fs/emscripten/emscripten-posix-fs.h
A backends/fs/emscripten/http-fs.cpp
A backends/fs/emscripten/http-fs.h
R dists/emscripten/assets/scummvm_fs.js
backends/module.mk
backends/platform/sdl/emscripten/emscripten.cpp
backends/platform/sdl/emscripten/emscripten.h
backends/platform/sdl/emscripten/emscripten.mk
backends/saves/posix/posix-saves.cpp
configure
dists/emscripten/README.md
dists/emscripten/custom_shell.html
diff --git a/backends/fs/emscripten/emscripten-fs-factory.cpp b/backends/fs/emscripten/emscripten-fs-factory.cpp
new file mode 100644
index 00000000000..e97bc3c7ffc
--- /dev/null
+++ b/backends/fs/emscripten/emscripten-fs-factory.cpp
@@ -0,0 +1,87 @@
+/* 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/>.
+ *
+ */
+
+#ifdef EMSCRIPTEN
+
+#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
+#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
+#include "backends/fs/emscripten/emscripten-fs-factory.h"
+#include "backends/fs/emscripten/emscripten-posix-fs.h"
+#include "backends/fs/emscripten/http-fs.h"
+#include "common/debug.h"
+#include <emscripten.h>
+
+EM_ASYNC_JS(void, _initSettings, (const char *pathPtr), {
+ try {
+ const path = UTF8ToString(pathPtr);
+ const settingsPath = path + "/scummvm.ini";
+
+ // Mount the filesystem
+ FS.mount(IDBFS, { autoPersist: true }, path);
+
+ // Sync the filesystem
+ await new Promise((resolve, reject) => {
+ FS.syncfs(true, (err) => err ? reject(err) : resolve());
+ });
+
+ // Check if settings file exists and download if needed
+ if (!FS.analyzePath(settingsPath).exists) {
+ const response = await fetch("scummvm.ini");
+ if (response.ok) {
+ const text = await response.text();
+ FS.writeFile(settingsPath, text);
+ }
+ }
+ } catch (err) {
+ console.error("Error initializing files:", err);
+ alert("Error initializing files: " + err);
+ throw err;
+ }
+});
+
+EmscriptenFilesystemFactory::EmscriptenFilesystemFactory() {
+ _initSettings(getenv("HOME"));
+ _httpNodes = new Common::HashMap<Common::String, HTTPFilesystemNode *>();
+}
+
+AbstractFSNode *EmscriptenFilesystemFactory::makeCurrentDirectoryFileNode() const {
+ // getcwd() defaults to root on emscripten and ScummVM doesn't use setcwd()
+ return makeRootFileNode();
+}
+
+AbstractFSNode *EmscriptenFilesystemFactory::makeRootFileNode() const {
+ return new EmscriptenPOSIXFilesystemNode("/");
+}
+
+AbstractFSNode *EmscriptenFilesystemFactory::makeFileNodePath(const Common::String &path) const {
+ debug(5, "EmscriptenFilesystemFactory::makeFileNodePath(%s)", path.c_str());
+ assert(!path.empty());
+ if (path.hasPrefix(DATA_PATH)) {
+ if (!_httpNodes->contains(path)) {
+ // finding a node by path requires a http request to the server, so we cache the nodes
+ _httpNodes->setVal(path, new HTTPFilesystemNode(path));
+ }
+ return new HTTPFilesystemNode(*(_httpNodes->getVal(path)));
+ } else {
+ return new EmscriptenPOSIXFilesystemNode(path);
+ }
+}
+#endif
diff --git a/backends/fs/emscripten/emscripten-fs-factory.h b/backends/fs/emscripten/emscripten-fs-factory.h
new file mode 100644
index 00000000000..e16d2dcc63a
--- /dev/null
+++ b/backends/fs/emscripten/emscripten-fs-factory.h
@@ -0,0 +1,45 @@
+/* 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/>.
+ *
+ */
+
+#ifndef EMSCRIPTEN_FILESYSTEM_FACTORY_H
+#define EMSCRIPTEN_FILESYSTEM_FACTORY_H
+
+#include "backends/fs/emscripten/http-fs.h"
+#include "backends/fs/fs-factory.h"
+#include "common/singleton.h"
+
+/**
+ * Creates POSIXFilesystemNode objects.
+ *
+ * Parts of this class are documented in the base interface class, FilesystemFactory.
+ */
+class EmscriptenFilesystemFactory : public FilesystemFactory {
+public:
+ EmscriptenFilesystemFactory();
+ AbstractFSNode *makeRootFileNode() const override;
+ AbstractFSNode *makeCurrentDirectoryFileNode() const override;
+ AbstractFSNode *makeFileNodePath(const Common::String &path) const override;
+
+private:
+ Common::HashMap<Common::String, HTTPFilesystemNode *> *_httpNodes;
+};
+
+#endif /*EMSCRIPTEN_FILESYSTEM_FACTORY_H*/
diff --git a/backends/fs/emscripten/emscripten-posix-fs.cpp b/backends/fs/emscripten/emscripten-posix-fs.cpp
new file mode 100644
index 00000000000..93127977c6e
--- /dev/null
+++ b/backends/fs/emscripten/emscripten-posix-fs.cpp
@@ -0,0 +1,47 @@
+/* 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/>.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
+#include <stdio.h>
+
+#include "backends/fs/emscripten/emscripten-fs-factory.h"
+#include "backends/fs/emscripten/emscripten-posix-fs.h"
+#include "backends/fs/emscripten/http-fs.h"
+#include "backends/fs/posix/posix-fs.h"
+#include "backends/fs/posix/posix-iostream.h"
+#include "common/system.h"
+
+AbstractFSNode *EmscriptenPOSIXFilesystemNode::makeNode(const Common::String &path) const {
+ return g_system->getFilesystemFactory()->makeFileNodePath(path);
+}
+
+EmscriptenPOSIXFilesystemNode::EmscriptenPOSIXFilesystemNode(const Common::String &path) : POSIXFilesystemNode(path) {}
+EmscriptenPOSIXFilesystemNode::EmscriptenPOSIXFilesystemNode() : POSIXFilesystemNode() {}
+
+bool EmscriptenPOSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const {
+
+ if (_path == "/") {
+ HTTPFilesystemNode *data_entry = new HTTPFilesystemNode(DATA_PATH);
+ myList.push_back(data_entry);
+
+ }
+ return POSIXFilesystemNode::getChildren(myList, mode, hidden);
+}
diff --git a/backends/fs/emscripten/emscripten-posix-fs.h b/backends/fs/emscripten/emscripten-posix-fs.h
new file mode 100644
index 00000000000..bda03804bf8
--- /dev/null
+++ b/backends/fs/emscripten/emscripten-posix-fs.h
@@ -0,0 +1,36 @@
+/* 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/>.
+ *
+ */
+
+#ifndef EMSCRIPTEN_FILESYSTEM_H
+#define EMSCRIPTEN_FILESYSTEM_H
+
+#include "backends/fs/posix/posix-fs.h"
+
+class EmscriptenPOSIXFilesystemNode : public POSIXFilesystemNode {
+
+public:
+ EmscriptenPOSIXFilesystemNode(const Common::String &path);
+ EmscriptenPOSIXFilesystemNode();
+ bool getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const override;
+ AbstractFSNode *makeNode(const Common::String &path) const override;
+};
+
+#endif
diff --git a/backends/fs/emscripten/http-fs.cpp b/backends/fs/emscripten/http-fs.cpp
new file mode 100644
index 00000000000..7eecaf228ab
--- /dev/null
+++ b/backends/fs/emscripten/http-fs.cpp
@@ -0,0 +1,272 @@
+/* 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/>.
+ *
+ */
+// Disable symbol overrides so that we can use system headers.
+#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
+#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
+
+#ifdef EMSCRIPTEN
+
+#include "backends/fs/emscripten/http-fs.h"
+#include "backends/cloud/downloadrequest.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/fs/posix/posix-fs.h"
+#include "backends/fs/posix/posix-iostream.h"
+#include "common/debug.h"
+#include "common/formats/json.h"
+#include <emscripten.h>
+
+HTTPFilesystemNode::HTTPFilesystemNode(const Common::String &path, const Common::String &displayName, const Common::String &baseUrl, bool isValid, bool isDirectory, int size) : _path(path), _displayName(displayName), _url(baseUrl), _isValid(isValid), _isDirectory(isDirectory), _size(size) {
+ debug(5, "HTTPFilesystemNode::HTTPFilesystemNode(%s, %s)", path.c_str(), baseUrl.c_str());
+ assert(path.size() > 0);
+ assert(isDirectory || size >= 0 || !isValid);
+ _children = new AbstractFSList();
+}
+
+HTTPFilesystemNode::HTTPFilesystemNode(const Common::String &p) : _path(p), _isValid(false), _isDirectory(false), _size(-1) {
+ debug(5, "HTTPFilesystemNode::HTTPFilesystemNode(%s)", p.c_str());
+ assert(p.size() > 0);
+ _children = new AbstractFSList();
+
+ // Normalize the path (that is, remove unneeded slashes etc.)
+ _path = Common::normalizePath(_path, '/');
+ _displayName = Common::lastPathComponent(_path, '/');
+ if (_path == DATA_PATH) { // need special case for handling the root of the http-filesystem
+ _isDirectory = true;
+ _isValid = true;
+ _url = _path;
+ } else { // we need to peek in the parent folder to see if the node exists and if it's a directory
+ AbstractFSNode *parent = getParent();
+ AbstractFSList tmp = AbstractFSList();
+ parent->getChildren(tmp, Common::FSNode::kListAll, true);
+ for (AbstractFSList::iterator i = tmp.begin(); i != tmp.end(); ++i) {
+ AbstractFSNode *child = *i;
+ if (child->getPath() == _path) {
+ _isDirectory = child->isDirectory();
+ _isValid = true;
+ _url = ((HTTPFilesystemNode *)child)->_url;
+ _size = ((HTTPFilesystemNode *)child)->_size;
+ break;
+ }
+ }
+ }
+ assert(_isDirectory || _size >= 0 || !_isValid);
+ debug(5, "HTTPFilesystemNode::HTTPFilesystemNode(%s) - %s isValid %s isDirectory %s", p.c_str(), _url.c_str(), _isValid ? "True" : "false", _isDirectory ? "True" : "false");
+}
+
+AbstractFSNode *HTTPFilesystemNode::getChild(const Common::String &n) const {
+ assert(!_path.empty());
+ assert(_isDirectory);
+
+ // Make sure the string contains no slashes
+ assert(!n.contains('/'));
+
+ // We assume here that _path is already normalized (hence don't bother to call
+ // Common::normalizePath on the final path).
+ Common::String newPath(_path);
+ if (_path.lastChar() != '/')
+ newPath += '/';
+ newPath += n;
+
+ return makeNode(newPath);
+}
+
+EM_ASYNC_JS(char *, _httpFsFetchIndex, (const char *url), {
+ globalThis['httpFsIndexCache'] = globalThis['httpFsIndexCache'] || {};
+ returnString = "";
+ url = UTF8ToString(url);
+ console.debug("Downloading %s", url);
+ if (globalThis['httpFsIndexCache'][url]) {
+ console.debug("Cache hit for %s", url);
+ returnString = globalThis['httpFsIndexCache'][url];
+ } else {
+ try {
+ const response = await fetch(url);
+ if (response.ok) {
+ returnString = await response.text();
+ globalThis['httpFsIndexCache'][url] = returnString;
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ var size = lengthBytesUTF8(returnString) + 1;
+ var ret = Module._malloc(size);
+ stringToUTF8Array(returnString, HEAP8, ret, size);
+ return ret;
+});
+
+EM_ASYNC_JS(bool, _httpFsFetchFile, (const char *url, byte *dataPtr, int dataSize), {
+ returnBytes = new Uint8Array();
+ url = UTF8ToString(url);
+ console.debug("Downloading %s", url);
+ try {
+ const response = await fetch(url);
+ if (response.ok) {
+ returnBytes = await response.bytes();
+ if (returnBytes.length == dataSize) {
+ Module.writeArrayToMemory(returnBytes, dataPtr);
+ return true;
+ }
+ } else {
+ console.error("HTTPFilesystemNode::_httpFsFetchFile: %s", response.statusText);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+
+ return false;
+});
+
+bool HTTPFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const {
+ if (!_isValid) {
+ return false;
+ }
+ assert(_isDirectory);
+ if (_children->size() == 0) {
+ // if we don't have a children list yet, we need to fetch it from the server
+ debug(5, "HTTPFilesystemNode::getChildren Fetching Children: %s at %s", _path.c_str(), _url.c_str());
+ Common::String url = _url + "/index.json";
+ char *response = _httpFsFetchIndex(url.c_str());
+ if (strcmp(response, "") == 0) {
+ return false;
+ }
+
+ Common::JSONObject jsonObj = Common::JSON::parse(response)->asObject();
+ // add dummy element so we know that we fetched the list
+ _children->push_back(new HTTPFilesystemNode(_path, ".", _url, false, false, 0));
+ for (typename Common::HashMap<Common::String, Common::JSONValue *>::iterator i = jsonObj.begin(); i != jsonObj.end(); ++i) {
+ Common::String name = i->_key;
+ bool isDir = false;
+ int size = -1;
+ Common::String baseUrl = _url + "/" + name;
+
+ if (i->_value->isObject()) {
+ isDir = true;
+ if (i->_value->asObject().contains("baseUrl")) {
+ debug(5, "HTTPFilesystemNode::directoryListedCallback - Directory with baseUrl %s", name.c_str());
+ baseUrl = i->_value->asObject()["baseUrl"]->asString();
+ }
+ } else if (i->_value->isIntegerNumber()) {
+ size = i->_value->asIntegerNumber();
+ }
+ HTTPFilesystemNode *file_node = new HTTPFilesystemNode(_path + "/" + name, name, baseUrl, true, isDir, size);
+ _children->push_back(file_node);
+ }
+ }
+ for (AbstractFSList::iterator i = (*_children).begin(); i != (*_children).end(); ++i) {
+ HTTPFilesystemNode *node = (HTTPFilesystemNode *)*i;
+
+ if (node->_isValid && (mode == Common::FSNode::kListAll ||
+ (mode == Common::FSNode::kListFilesOnly && !node->_isDirectory) ||
+ (mode == Common::FSNode::kListDirectoriesOnly && node->_isDirectory))) {
+ // we need to copy node here as FSNode will take ownership of the pointer and destroy it after use
+ HTTPFilesystemNode *file_node = new HTTPFilesystemNode(*node);
+ myList.push_back(file_node);
+ } else {
+ debug(5, "HTTPFilesystemNode::getChildren - skipping %s", node->_path.c_str());
+ }
+ }
+ return true;
+}
+
+AbstractFSNode *HTTPFilesystemNode::getParent() const {
+ if (_path == "/")
+ return 0; // The filesystem root has no parent
+
+ const char *start = _path.c_str();
+ const char *end = start + _path.size();
+
+ // Strip of the last component. We make use of the fact that at this
+ // point, _path is guaranteed to be normalized
+ while (end > start && *(end - 1) != '/')
+ end--;
+
+ if (end == start) {
+ // This only happens if we were called with a relative path, for which
+ // there simply is no parent.
+ // TODO: We could also resolve this by assuming that the parent is the
+ // current working directory, and returning a node referring to that.
+ return 0;
+ }
+
+ Common::String _parent_path = Common::normalizePath(Common::String(start, end), '/');
+ FilesystemFactory *factory = g_system->getFilesystemFactory();
+ return factory->makeFileNodePath(_parent_path);
+}
+
+Common::SeekableReadStream *HTTPFilesystemNode::createReadStream() {
+ debug(5, "*HTTPFilesystemNode::createReadStream() %s (size %d) ", _path.c_str(), _size);
+ Common::String fsCachePath = Common::normalizePath("/.cache/" + _path, '/');
+ POSIXFilesystemNode *cacheFile = new POSIXFilesystemNode(fsCachePath);
+ // todo: this should not be cached on the filesystem, but in memory
+ // and support range requests
+ // port https://github.com/emscripten-core/emscripten/blob/main/src/lib/libwasmfs_fetch.js over
+ if (!cacheFile->exists() && _size > 0) {
+ byte *buffer = new byte[_size];
+ bool success = _httpFsFetchFile(_url.c_str(), buffer, _size);
+ if (success) {
+ Common::DumpFile *_localFile = new Common::DumpFile();
+ if (!_localFile->open(Common::Path(fsCachePath), true)) {
+ error("Storage: unable to open file to download into");
+ return 0;
+ }
+ debug(5, "HTTPFilesystemNode::createReadStream() file downloaded %s", _path.c_str());
+ _localFile->write(buffer, _size);
+ _localFile->close();
+ free(buffer);
+ } else {
+ warning("Storage: unable to download file %s", _url.c_str());
+ free(buffer);
+ return 0;
+ }
+ } else if (_size == 0) {
+ debug(5, "HTTPFilesystemNode::createReadStream() file empty %s", _path.c_str());
+ Common::DumpFile *_localFile = new Common::DumpFile();
+ if (!_localFile->open(Common::Path(fsCachePath), true)) {
+ warning("Storage: unable to open file to download into");
+ return 0;
+ }
+ _localFile->close();
+ }
+
+ return PosixIoStream::makeFromPath(fsCachePath, StdioStream::WriteMode_Read);
+}
+
+Common::SeekableWriteStream *HTTPFilesystemNode::createWriteStream(bool atomic) {
+ return 0;
+}
+
+bool HTTPFilesystemNode::createDirectory() {
+ return false;
+}
+bool HTTPFilesystemNode::exists() const {
+ return _isValid;
+}
+
+bool HTTPFilesystemNode::isReadable() const {
+ return exists();
+}
+
+bool HTTPFilesystemNode::isWritable() const {
+ return false;
+}
+
+#endif // #if defined(EMSCRIPTEN)
diff --git a/backends/fs/emscripten/http-fs.h b/backends/fs/emscripten/http-fs.h
new file mode 100644
index 00000000000..75bf873892c
--- /dev/null
+++ b/backends/fs/emscripten/http-fs.h
@@ -0,0 +1,78 @@
+/* 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/>.
+ *
+ */
+
+#ifndef HTTP_FILESYSTEM_H
+#define HTTP_FILESYSTEM_H
+
+#include "backends/fs/abstract-fs.h"
+#include "backends/fs/posix/posix-fs.h"
+
+/**
+ * Implementation of the ScummVM file system API based on POSIX.
+ *
+ * Parts of this class are documented in the base interface class, AbstractFSNode.
+ */
+class HTTPFilesystemNode : public AbstractFSNode {
+protected:
+ static Common::HashMap<Common::String, AbstractFSList> _httpFolders;
+ Common::String _displayName;
+ Common::String _path;
+ Common::String _url;
+ AbstractFSList *_children;
+ bool _isDirectory;
+ bool _isValid;
+ int _size;
+
+ /**
+ * Full constructor, for internal use only (hence protected).
+ */
+ HTTPFilesystemNode(const Common::String &path, const Common::String &displayName, const Common::String &baseUrl, bool isValid, bool isDirectory, int size);
+
+ virtual AbstractFSNode *makeNode(const Common::String &path) const {
+ return new HTTPFilesystemNode(path);
+ }
+
+public:
+ /**
+ * Creates a HTTPFilesystemNode for a given path.
+ *
+ * @param path the path the new node should point to.
+ */
+ HTTPFilesystemNode(const Common::String &path);
+ ~HTTPFilesystemNode() {}
+ bool exists() const override;
+ Common::U32String getDisplayName() const override { return _displayName; }
+ Common::String getName() const override { return _displayName; }
+ Common::String getPath() const override { return _path; }
+ bool isDirectory() const override { return _isDirectory; }
+ bool isReadable() const override;
+ bool isWritable() const override;
+
+ AbstractFSNode *getChild(const Common::String &n) const override;
+ bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const override;
+ AbstractFSNode *getParent() const override;
+
+ Common::SeekableReadStream *createReadStream() override;
+ Common::SeekableWriteStream *createWriteStream(bool atomic) override;
+ bool createDirectory() override;
+};
+
+#endif
diff --git a/backends/module.mk b/backends/module.mk
index ffe140e8c57..c3585d624d4 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -95,6 +95,9 @@ endif
ifdef EMSCRIPTEN
MODULE_OBJS += \
+ fs/emscripten/emscripten-fs-factory.o \
+ fs/emscripten/emscripten-posix-fs.o \
+ fs/emscripten/http-fs.o \
midi/webmidi.o
ifdef USE_TTS
MODULE_OBJS += \
diff --git a/backends/platform/sdl/emscripten/emscripten.cpp b/backends/platform/sdl/emscripten/emscripten.cpp
index 67bd132791c..7e590e9df39 100644
--- a/backends/platform/sdl/emscripten/emscripten.cpp
+++ b/backends/platform/sdl/emscripten/emscripten.cpp
@@ -28,6 +28,7 @@
#include "backends/events/emscriptensdl/emscriptensdl-events.h"
#include "backends/fs/emscripten/emscripten-fs-factory.h"
#include "backends/mutex/null/null-mutex.h"
+#include "backends/fs/emscripten/emscripten-fs-factory.h"
#include "backends/platform/sdl/emscripten/emscripten.h"
#include "backends/timer/default/default-timer.h"
#include "common/file.h"
@@ -89,6 +90,15 @@ void OSystem_Emscripten::initBackend() {
OSystem_POSIX::initBackend();
}
+void OSystem_Emscripten::init() {
+ // Initialze File System Factory
+ EmscriptenFilesystemFactory *fsFactory = new EmscriptenFilesystemFactory();
+ _fsFactory = fsFactory;
+
+ // Invoke parent implementation of this method
+ OSystem_SDL::init();
+}
+
bool OSystem_Emscripten::hasFeature(Feature f) {
if (f == kFeatureFullscreenMode)
return true;
@@ -182,6 +192,15 @@ Common::MutexInternal *OSystem_Emscripten::createMutex() {
return new NullMutexInternal();
}
+void OSystem_Emscripten::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
+ // Add the global DATA_PATH (and some sub-folders) to the directory search list
+ // Note: gui-icons folder is added in GuiManager::initIconsSet
+ Common::FSNode dataNode(DATA_PATH);
+ if (dataNode.exists() && dataNode.isDirectory()) {
+ s.addDirectory(dataNode, priority, 2, false);
+ }
+}
+
void OSystem_Emscripten::delayMillis(uint msecs) {
static uint32 lastThreshold = 0;
const uint32 threshold = getMillis() + msecs;
diff --git a/backends/platform/sdl/emscripten/emscripten.h b/backends/platform/sdl/emscripten/emscripten.h
index 2e66932da4f..fd44427148e 100644
--- a/backends/platform/sdl/emscripten/emscripten.h
+++ b/backends/platform/sdl/emscripten/emscripten.h
@@ -39,6 +39,8 @@ public:
Common::MutexInternal *createMutex() override;
void exportFile(const Common::Path &filename);
void delayMillis(uint msecs) override;
+ void init() override;
+ void addSysArchivesToSearchSet(Common::SearchSet &s, int priority) override;
protected:
Common::Path getDefaultConfigFileName() override;
diff --git a/backends/platform/sdl/emscripten/emscripten.mk b/backends/platform/sdl/emscripten/emscripten.mk
index be46fdce5b0..778c182c1f2 100644
--- a/backends/platform/sdl/emscripten/emscripten.mk
+++ b/backends/platform/sdl/emscripten/emscripten.mk
@@ -3,6 +3,7 @@
dist-emscripten: $(EXECUTABLE) $(PLUGINS)
mkdir -p ./build-emscripten/data
mkdir -p ./build-emscripten/data/games
+ mkdir -p ./build-emscripten/data/gui-icons
mkdir -p ./build-emscripten/doc
cp $(EXECUTABLE) ./build-emscripten/
cp $(EXECUTABLE:html=wasm) ./build-emscripten/
diff --git a/backends/saves/posix/posix-saves.cpp b/backends/saves/posix/posix-saves.cpp
index b8b3932d1ab..2469849943a 100644
--- a/backends/saves/posix/posix-saves.cpp
+++ b/backends/saves/posix/posix-saves.cpp
@@ -52,6 +52,11 @@ POSIXSaveFileManager::POSIXSaveFileManager() {
savePath.joinInPlace("Savegames");
ConfMan.registerDefault("savepath", savePath);
}
+
+#elif defined(EMSCRIPTEN)
+ savePath = getenv("HOME");
+ savePath.joinInPlace("saves");
+ ConfMan.registerDefault("savepath", savePath);
#else
const char *envVar;
diff --git a/configure b/configure
index e27efd4f3b2..68bbcbbe951 100755
--- a/configure
+++ b/configure
@@ -3263,8 +3263,8 @@ EOF
append_var DEFINES "-DEMSCRIPTEN"
add_line_to_config_mk 'EMSCRIPTEN = 1'
_port_mk="backends/platform/sdl/emscripten/emscripten.mk"
- append_var LDFLAGS "-s FORCE_FILESYSTEM"
append_var LDFLAGS "-s INITIAL_MEMORY=128MB -s STACK_SIZE=32MB -s ALLOW_MEMORY_GROWTH=1"
+ append_var LDFLAGS "-s FORCE_FILESYSTEM -lidbfs.js"
append_var LDFLAGS "-s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=1048576"
append_var LDFLAGS "-s EXPORTED_FUNCTIONS=[_main,_malloc,_free,_raise]"
diff --git a/dists/emscripten/README.md b/dists/emscripten/README.md
index 94e5363605c..4256e856382 100644
--- a/dists/emscripten/README.md
+++ b/dists/emscripten/README.md
@@ -73,8 +73,8 @@ ScummVM relies heavily on Asyncify (see note above), and this comes with a quite
* Look into Stack Switching (emscripten-core/emscripten#16779) or multithreading as an alternative to Asyncify.
### Storage Integration
-* 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.
+* 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
diff --git a/dists/emscripten/assets/scummvm_fs.js b/dists/emscripten/assets/scummvm_fs.js
deleted file mode 100644
index 7539cb2f447..00000000000
--- a/dists/emscripten/assets/scummvm_fs.js
+++ /dev/null
@@ -1,496 +0,0 @@
-
-/*
- * 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 occurrence
- //
- // 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: 0,
- size: node.size,
- atime: new Date(),
- mtime: new Date(),
- ctime: new Date(),
- 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(' ');
-}
diff --git a/dists/emscripten/custom_shell.html b/dists/emscripten/custom_shell.html
index c26e907fcb7..1d655a2af3c 100644
--- a/dists/emscripten/custom_shell.html
+++ b/dists/emscripten/custom_shell.html
@@ -127,13 +127,6 @@
<hr>
<textarea class="emscripten" id="output" rows="8"></textarea>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.min.js"
- integrity="sha512-mz0EI+Ay1uIJP7rZEX8C/JlTAcHRIQ8Sny4vxmmj8MSzDJgG9NxxY2pUmOGv1lO7imFIFMyjjCzEXEywNgaUdQ=="
- crossorigin="anonymous"></script>
- <script type="module">
- import { ScummvmFS } from './scummvm_fs.js'
- window.ScummvmFS = ScummvmFS
- </script>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
@@ -144,141 +137,9 @@
return "All downloads complete."
}
- function decodeInode(base64str) {
- _readUInt16LE = function readUInt16LE(byteArray, offset) {
- offset = offset >>> 0
- return byteArray[offset] | (byteArray[offset + 1] << 8)
- }
- _readUInt32LE = function (byteArray, offset) {
- return ((byteArray[offset]) |
- (byteArray[offset + 1] << 8) |
- (byteArray[offset + 2] << 16)) +
- (byteArray[offset + 3] * 0x1000000)
- }
- var binary_string = window.atob(base64str);
- var bytes = new Uint8Array(binary_string.length);
- for (var i = 0; i < binary_string.length; i++) {
- bytes[i] = binary_string.charCodeAt(i);
- }
- return {
- 'id': binary_string.substr(30),
- 'size': _readUInt32LE(bytes, 0),
- 'mode': _readUInt16LE(bytes, 4),
- 'atime': 0, // don't feel like implementing ieee754 (maybe we could piggyback on BrowserFS here?)
- 'mtime': 0, // don't feel like implementing ieee754 (maybe we could piggyback on BrowserFS here?)
- 'ctime': 0 // don't feel like implementing ieee754 (maybe we could piggyback on BrowserFS here?)
- }
- }
-
- function encodeInode(id, size, mode, atime, mtime, ctime) {
- _writeUInt16LE = function (byteArray, value, offset) {
- value = +value
- offset = offset >>> 0
- byteArray[offset] = (value & 0xff)
- byteArray[offset + 1] = (value >>> 8)
- return offset + 2
- }
- _writeUInt32LE = function (byteArray, value, offset) {
- value = +value
- offset = offset >>> 0
- byteArray[offset + 3] = (value >>> 24)
- byteArray[offset + 2] = (value >>> 16)
- byteArray[offset + 1] = (value >>> 8)
- byteArray[offset] = (value & 0xff)
- return offset + 4
- }
- var bytes = new Uint8Array(30 + id.length);
- _writeUInt32LE(bytes, size, 0);
- _writeUInt16LE(bytes, mode, 4);
- //buff.writeDoubleLE(this.atime, 6); don't feel like implementing ieee754 (maybe we could piggyback on BrowserFS here?)
- //buff.writeDoubleLE(this.mtime, 14); don't feel like implementing ieee754 (maybe we could piggyback on BrowserFS here?)
- //.writeDoubleLE(this.ctime, 22); don't feel like implementing ieee754 (maybe we could piggyback on BrowserFS here?)
- for (var i = 0; i < id.length; i++) {
- bytes[30 + i] = id.charCodeAt(i);
- }
- var binary_string = ""
- for (var i = 0; i < bytes.length; i++) {
- binary_string += String.fromCharCode(bytes[i]);
- }
- return btoa(binary_string)
- }
- function setupDefaultLocalData() {
- if (localStorage.getItem("/") === null) {
- return fetch("scummvm.ini").then((response) => {
- return response.text().then(function (text) {
- // default values, created by running scummvm with --add --recursive --path=/data/games
- //var req = new XMLHttpRequest(); // a new request
- //req.open("GET", , false);
- //req.send(null);
- ini_data = btoa(text)
-
- folder_inode_id = "b3da6754-64c0-40f0-92ad-83b6ca6ffec9"
- folder_inode = {
- "id": folder_inode_id,
- "size": 4096,
- "mode": 16895
- }
- ini_inode_id = "1b4a97d1-4ce0-417f-985c-e0f22ca21aef"
- ini_inode = {
- "id": ini_inode_id,
- "size": atob(ini_data).length,
- "mode": 33206
- }
- folder_entry_id = "70879b79-8d58-400c-8143-332242320b34"
- folder_listing = { "scummvm.ini": folder_entry_id }
- defaultLocalStorage = {}
- defaultLocalStorage["/"] = encodeInode(folder_inode['id'], folder_inode['size'], folder_inode['mode'], 0, 0, 0)
- defaultLocalStorage[folder_inode_id] = btoa(JSON.stringify(folder_listing))
- defaultLocalStorage[folder_entry_id] = encodeInode(ini_inode['id'], ini_inode['size'], ini_inode['mode'], 0, 0, 0)
- defaultLocalStorage[ini_inode_id] = ini_data
- for (key in defaultLocalStorage) {
- localStorage.setItem(key, defaultLocalStorage[key]);
- }
- })
- })
- }
- return Promise.resolve()
- }
-
- function setupLocalFilesystem() {
- return setupDefaultLocalData().then(() => {
- return new Promise((resolve, reject) => {
- BrowserFS.FileSystem.LocalStorage.Create(function (err, lsfs) {
- if (err) return reject(err)
- BrowserFS.FileSystem.MountableFileSystem.Create({
- '/': lsfs
- }, function (err, mfs) {
- if (err) return reject(err)
- BrowserFS.initialize(mfs);
- // BrowserFS is now ready to use!
- var BFS = new BrowserFS.EmscriptenFS(void 0,void 0,{"EPERM": 1,"ENOENT": 2,"EINVAL": 22},void 0); // working around Emscripten stripping ERRNO_CODES in optimized builds
- // Mount the file systems into Emscripten.
- FS.mount(BFS, { root: '/' }, '/home/web_user');
- return resolve()
- })
- })
- });
- });
- }
- function setupHTTPFilesystem(folder_name) {
-
- FS.mkdir("/" + folder_name)
- FS.mount(new ScummvmFS(FS, folder_name), {}, "/" + folder_name + "/");
- }
- function setupFilesystem() {
- addRunDependency('scummvm-fs-setup');
- setupLocalFilesystem().then(() => {
-
- setupHTTPFilesystem("games")
- setupHTTPFilesystem("data")
-
- removeRunDependency('scummvm-fs-setup');
-
- });
- }
var Module = {
- preRun: [setupFilesystem],
+ preRun: [],
postRun: [],
print: (function () {
var element = document.getElementById('output');
@@ -312,7 +173,6 @@
return canvas;
})(),
setStatus: function (text) {
- console.log((new Date()).toLocaleTimeString() + " " + 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+)\)/);
@@ -332,6 +192,7 @@
progressElement.hidden = true;
}
if (text && text.length > 0) {
+ console.log((new Date()).toLocaleTimeString() + " " + text)
text += "â¡ï¸"
statusElement.style.display = "block";
} else {
Commit: 61ad913de6b6b23acaece5b9c70cae7245ceaf47
https://github.com/scummvm/scummvm/commit/61ad913de6b6b23acaece5b9c70cae7245ceaf47
Author: Christian Kündig (christian at kuendig.info)
Date: 2025-08-04T01:02:47+02:00
Commit Message:
DISTS: EMSCRIPTEN: Simplify build by removing extra scripts for demo download and scummvm.ini creation and don't depend on nodejs at all (+ switch to O2 by default)
Changed paths:
A dists/emscripten/build-make_http_index.py
R dists/emscripten/assets/scummvm.ini
R dists/emscripten/build-add_games.js
R dists/emscripten/build-download_games.js
R dists/emscripten/build-make_http_index.js
backends/platform/sdl/emscripten/emscripten.mk
configure
dists/emscripten/build.sh
diff --git a/backends/platform/sdl/emscripten/emscripten.mk b/backends/platform/sdl/emscripten/emscripten.mk
index 778c182c1f2..bfad4e5f75b 100644
--- a/backends/platform/sdl/emscripten/emscripten.mk
+++ b/backends/platform/sdl/emscripten/emscripten.mk
@@ -1,6 +1,11 @@
+# use system python as fallback
+# if EMSDK_PYTHON is not set
+EMSDK_PYTHON ?= python3
+
# Special generic target for emscripten static file hosting bundle
dist-emscripten: $(EXECUTABLE) $(PLUGINS)
+ mkdir -p ./build-emscripten/
mkdir -p ./build-emscripten/data
mkdir -p ./build-emscripten/data/games
mkdir -p ./build-emscripten/data/gui-icons
@@ -30,7 +35,7 @@ ifeq ($(DYNAMIC_MODULES),1)
mkdir -p ./build-emscripten/data/plugins
@for i in $(PLUGINS); do cp $$i ./build-emscripten/data/plugins; done
endif
- $(EMSDK_NODE) "$(srcdir)/dists/emscripten/build-make_http_index.js" ./build-emscripten/data
+ $(EMSDK_PYTHON) "$(srcdir)/dists/emscripten/build-make_http_index.py" ./build-emscripten/data
cp "$(srcdir)/dists/emscripten/assets/"* ./build-emscripten/
cp "$(srcdir)/gui/themes/common-svg/logo.svg" ./build-emscripten/
cp "$(srcdir)/icons/scummvm.ico" ./build-emscripten/favicon.ico
diff --git a/configure b/configure
index 68bbcbbe951..ce581e09fb3 100755
--- a/configure
+++ b/configure
@@ -3269,12 +3269,11 @@ EOF
append_var LDFLAGS "-s EXPORTED_FUNCTIONS=[_main,_malloc,_free,_raise]"
append_var LDFLAGS "-s EXPORTED_RUNTIME_METHODS=[ccall,lengthBytesUTF8,setValue,writeArrayToMemory]"
+
+ append_var LDFLAGS "-O2"
+ _optimization_level=-O2
if test "$_debug_build" = yes; then
- _optimization_level=-O2
- append_var LDFLAGS "-O2 -g3 -s ASSERTIONS=2"
- else
- _optimization_level=-O3
- append_var LDFLAGS "-O3"
+ append_var LDFLAGS "-g3 -s ASSERTIONS=2"
fi
# enable SDL3 and set it up correctly
diff --git a/dists/emscripten/assets/scummvm.ini b/dists/emscripten/assets/scummvm.ini
deleted file mode 100644
index 5338ebb7306..00000000000
--- a/dists/emscripten/assets/scummvm.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[scummvm]
-grouping=company
-gui_launcher_chooser=grid
-
diff --git a/dists/emscripten/build-add_games.js b/dists/emscripten/build-add_games.js
deleted file mode 100644
index dc110fc7410..00000000000
--- a/dists/emscripten/build-add_games.js
+++ /dev/null
@@ -1,45 +0,0 @@
-
-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');
- }
- });
-
-
-});
\ No newline at end of file
diff --git a/dists/emscripten/build-download_games.js b/dists/emscripten/build-download_games.js
deleted file mode 100644
index 56de2e69737..00000000000
--- a/dists/emscripten/build-download_games.js
+++ /dev/null
@@ -1,186 +0,0 @@
-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': '1989596967',
- 'games': '1775285192',
- 'engines': '0',
- 'companies': '226191984',
- 'versions': '1225902887',
- 'game_demos': '1303420306',
- 'series': '1095671818',
- 'screenshots': '168506355',
- 'scummvm_downloads': '1057392663',
- 'game_downloads': '810295288',
- '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) => {
- var gameId = downloads['game_id'];
- if (downloads['category'] == "games" && !(gameId in games)) {
- games[gameId] = "/frs/extras/" + downloads['url']
- games[gameId.substring(gameId.lastIndexOf(":") + 1)] = "/frs/extras/" + downloads['url'] // allow specifying game names without target/engine name
- }
- filename = downloads['url'].substring(downloads['url'].lastIndexOf("/"))
- games[gameId + filename] = "/frs/extras/" + downloads['url']
- games[gameId.substring(gameId.lastIndexOf(":") + 1) + filename] = "/frs/extras/" + downloads['url'] // allow specifying game names without target/engine name
- })
- 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) => {
- var gameId = downloads['id']
- if (!(gameId in games)) {
- games[gameId] = downloads['url']
- games[gameId.substring(gameId.lastIndexOf(":") + 1)] = downloads['url'] // allow specifying game names without target/engine name
- }
- filename = downloads['url'].substring(downloads['url'].lastIndexOf("/"))
- games[gameId + filename] = downloads['url']
- games[gameId.substring(gameId.lastIndexOf(":") + 1) + filename] = downloads['url'] // allow specifying game names without target/engine name
- })
- 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) => {
- var gameId = downloads['id']
- if (!(gameId in games)) {
- games[gameId] = downloads['url']
- games[gameId.substring(gameId.lastIndexOf(":") + 1)] = downloads['url'] // allow specifying game names without target/engine name
- }
- filename = downloads['url'].substring(downloads['url'].lastIndexOf("/"))
- games[gameId + filename] = downloads['url']
- games[gameId.substring(gameId.lastIndexOf(":") + 1) + filename] = downloads['url'] // allow specifying game names without target/engine name
- });
- 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("/"))
- }
- gameId = gameId.substring(gameId.lastIndexOf(":") + 1)// remove target from target:gameId
- 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)
-
- });
diff --git a/dists/emscripten/build-make_http_index.js b/dists/emscripten/build-make_http_index.js
deleted file mode 100644
index ea012c57d07..00000000000
--- a/dists/emscripten/build-make_http_index.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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;
-}
-if (process.argv.length === 3) {
- const rootFolder = process.argv[2];
- const fsListing = JSON.stringify(rdSync(rootFolder, {}, '/'));
- const fname = rootFolder + "/index.json"
- fs.writeFileSync(fname, fsListing, { encoding: 'utf8' });
-}
-else {
- let rootFolder = process.cwd()
- const fsListing = JSON.stringify(rdSync(rootFolder, {}, '/'));
- console.log(fsListing);
-}
\ No newline at end of file
diff --git a/dists/emscripten/build-make_http_index.py b/dists/emscripten/build-make_http_index.py
new file mode 100644
index 00000000000..0eac8567dfe
--- /dev/null
+++ b/dists/emscripten/build-make_http_index.py
@@ -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()
diff --git a/dists/emscripten/build.sh b/dists/emscripten/build.sh
index b2122711503..4fa5419b583 100755
--- a/dists/emscripten/build.sh
+++ b/dists/emscripten/build.sh
@@ -45,13 +45,12 @@ Tasks:
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
- 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 before running configure or make.
+ 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
@@ -173,16 +172,6 @@ if [[ $ret != 0 ]]; then
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
- EMSDK_PYTHON="${EMSDK_PYTHON:-python3}"
- export NODE_PATH=$(dirname $EMSDK_NODE)/../lib/node_modules/
- "$EMSDK_NODE" "$EMSDK_NPM" -g install "puppeteer at 13.5.1"
- "$EMSDK_NODE" "$EMSDK_NPM" -g install "request at 2.88.2"
- "$EMSDK_NODE" "$EMSDK_NPM" -g install "node-static at 0.7.11"
-
fi
source "$DIST_FOLDER/emsdk-$EMSDK_VERSION/emsdk_env.sh"
@@ -195,21 +184,9 @@ export NODE_PATH="$(dirname $EMSDK_NODE)/../lib/node_modules/"
LIBS_FLAGS=""
cd "$ROOT_FOLDER"
-#################################
-# 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
#################################
-# Download + Install Libraries (if not part of Emscripten-Ports, these are handled by configure)
+# 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"
@@ -391,12 +368,10 @@ fi
if [[ "make" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
cd "${ROOT_FOLDER}"
echo "Running make"
- emmake make
+ num_cpus=$(nproc || grep -c ^processor /proc/cpuinfo || echo 1)
+ emmake make -j ${num_cpus}
fi
-# The following steps copy stuff to build-emscripten:
-mkdir -p "${ROOT_FOLDER}/build-emscripten/"
-
#################################
# Bundle everything into a neat package
#################################
@@ -405,78 +380,6 @@ if [[ "dist" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]];
emmake make dist-emscripten
fi
-#################################
-# Create Games & Testbed Data
-#################################
-if [[ "games" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
- cd "${ROOT_FOLDER}"
- 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
- echo "Fetching gmaes: $_bundle_games"
- 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
-#################################
-# Add icons
-#################################
-if [[ "icons" =~ $(echo ^\(${TASKS}\)$) || "build" =~ $(echo ^\(${TASKS}\)$) ]]; then
- _icons_dir="${ROOT_FOLDER}/../scummvm-icons/"
- if [[ -d "$_icons_dir" ]]; then
- echo "Adding files from icons repository "
- cd "${ROOT_FOLDER}/../scummvm-icons/"
- cd "$_icons_dir"
- "$EMSDK_PYTHON" gen-set.py
- echo "add icons"
- mkdir -p "${ROOT_FOLDER}/build-emscripten/data/gui-icons"
- cp -r "$_icons_dir/icons" "${ROOT_FOLDER}/build-emscripten/data/gui-icons/"
- echo "add xml"
- cp -r "$_icons_dir/"*.xml "${ROOT_FOLDER}/build-emscripten/data/gui-icons/"
- echo "update index"
- cd "${ROOT_FOLDER}/build-emscripten/data/gui-icons"
- "$EMSDK_NODE" "$DIST_FOLDER/build-make_http_index.js" >index.json
- cd "${ROOT_FOLDER}/build-emscripten/data"
- "$EMSDK_NODE" "$DIST_FOLDER/build-make_http_index.js" >index.json
- else
- echo "Icons repository not found"
- fi
-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
#################################
@@ -485,3 +388,16 @@ if [[ "run" =~ $(echo ^\(${TASKS}\)$) ]]; then
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
More information about the Scummvm-git-logs
mailing list