[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