It’s been a while since my last post about porting ScummVM. I have kept working on it the past 18 months since my last post. Life was busy, and many of the changes needed more time to get into a mergeable state, which led me to focus on new things instead of wrapping up what I was working on. Cleaning up is less fun than starting new.
The good news though is that in the past few months, I started pushing more upstream. So much in fact, that I have decided to break up part 4 into 3 sub-chapters (if The Avengers and Mission Impossible can split movies into to two parts, I can do three for my posts!)
This first part will be focus on maintenance. Part b (now online) and c (see here) will focus on new features (Audio/Video and file access).
Testbed
ScummVM has a special game engine, called ’testbed’. Its only purpose is to perform series of tests which help backend authors to see correctness of their implementation. Basically a “fake game” using all features. I have known about this, but haven’t really spent time with it much in the past. Looking into it closer, I found it to be super useful - although not very actively maintained. So I dug in and made sure all tests passed on the web:
- SCUMM: Add small helpers for shorter/safer/clearer enhancement checks (May 25) - TESTBED: Fix broken tests and non-interactive mode (July 2)
Key improvements:
- Make Interactive Mode configurable in GUI: I wanted to be able to run testbed in automated mode without adjusting the scummvm.ini - this is now possible. I managed to mess this one up during rebasing, so I had to push a fix for it in the second PR. 🫠
- Fix file writing if game data directory is read-only: This is a bug we discussed a while ago on discord. If the game directory is read-only the write tests and the filelog are now stored in the savegame location.
- Support opening logfile in Emscripten: This is similar to the screenshot export from (Emscripten: Screenshot and Logfile support and minor bugfixes & improvements). I added a button to Open the log file. This led to quite some discussion, so I ended up implementing it in a cross-platform way. File will open in the ScummVM-UI via the textviewer widget. I still plan on eventually make these files downloadable, but for now this is good enough.
- Fix freeze when playing MIDI in Emscripten: “This type of loop needs to yield to the browser regularly in single-threaded Emscripten” - what it actually needs to do is sleep for a while to not hammer the CPU to 100%. This one was funny as I added it to work around a bug in Emscripten (everything freezes if Game Engines don’t yield once in a while), but the fix was needed anyway (so we didn’t end up gating it with an
#ifdefand didn’t remove it after I fixed the underlying Emscripten issue). - Fixing bugs.scummvm.org#15754 - tests failing for files with trailing dots. I noticed this failing test a while ago, and opened a bug hoping someone more knowledgeable would fix it. But since this didn’t happen, I fixed it myself. Turned out to be an issue with the test. It’s not clear if this ever worked as the handling of trailing dots is in SearchMan, not FSDirectory. I checked the AGOS engine and it also uses SearchMan, so this should now be correct. Maybe this test pre-dates SearchMan?
- The ImageAlbum test also didn’t work. Also a bug in the test itself which should now be fixed (the images are in a subfolder that wasn’t added to SearchMan).
- There was a correctly failing test in the save game tests. I fixed the issue by properly setting the correct error when a save game can’t be opened.
SDL Timers
This was one of the things that I knew was broken, but not sure why. Since the first port, some engines never worked and just crashed when launching a game with a somewhat odd unreachable code error. I noticed the same thing happened in the Threading/Mutex locking tests in Testbed above. Which meant I finally had a somewhat restricted test environment to reproduce it.
Fix timers and event polling. We used to rely on SDL timers as all other SDL backends. Unfortunately this never worked as these timers (when not using threads and webworkers) are implemented as javascript timeouts, which combined with Asyncify means we can have weird re-entry issues (timer completes when the wasm runtime is paused for another async operation).
I have re-read the Emscripten page warning about this re-entry issue many many times, but it took me to see it in action to understand what was happening. Fixing it was somewhat easy, there’s other single-threaded backends in ScummVM having to figure out timers (Nintendo 64, Nintendo DS, Atari), and I just copied the solution from them: Check if a timer has to be executed whenever the main loop is polling for events.
In practice all engines that used timers crashed. This is now fixed by moving to a model where we use the event loop for timers. Using threads unfortunately currently doesn’t work with dynamic loading which we need to keep initial binary size in check.
Fixed in (BACKENDS: EMSCRIPTEN: Fix timers and event polling), merged in (EMSCRIPTEN: Adding audio libraries, implementing FS in C++ and smaller fixes - opened August 2)
GLSL Shaders
While I have worked in gaming before and studied computer science, I have basically no experience working on 3D rendering. So noticing that some games crashed at launch with a Shader issue sure sounded like a daunting task. In the end, it was not that difficult:
OpenGLES 2.0 GLSL requires:
- Control flow is limited to forward branching and to loops where the maximum number of iterations can easily be determined at compile time. (Appendix A, 4 Control Flow)
- In the fragment shader, support for indexing is only mandated for constant-index-expressions. (Appendix A, 5 Indexing of Arrays, Vectors and Matrices)
Generally this doesn’t seem to be a limit enforced on most hardware, but for WebGL, this conformity seems to be enforced by the ANGLE Compiler used in all major browsers.
This PR fixes the following two errors when launching TWP or Freescape games:
- ’[]’ : Index expression must be constant
- ‘i’ : Loop index cannot be compared with non-constant expression
So I had to figure out correct upper boundary for the new iteration limits. This was a fun task - you want to make sure we don’t loop too many times to help the compiler optimize, on the other hand, if we don’t loop enough - everything will break. See the relevant commits:
- (TWP: GLSL loop index cannot be compared with non-constant expression)
- (FREESCAPE: GLSL index expression must be constant and loop index cannot be compared with non-constant expression)
It was eventually merged in (TWP: FREESCAPE: Fix non-compliant GLSL - opened May 9)
SDL 3
Past updates had me sending patches upstream to SDL2, but this time it was the other way around: SDL3 was officially released on January 21st, and an SDL3 backend was added to ScummVM on Feb 18 ( - BACKENDS: Add SDL3 backend + update imgui, actually opened already in December)
Emscripten being a pretty new platform, I hoped that SDL3 had a few good quality of life improvements and started work on using SDL3 as the default:
- Fix SDLPlugin: Fix by using the correct types (doesn’t compile with SDL3) - (BACKENDS: SDL: Fix SDLPlugin for SDL3)
- Fix Touch Events: Touch events were completely broken. I knew other backends use this (Switch most importantly), so fixing this was quite motivating - (BACKENDS: SDL: Fix touch events for SDL3)
- Converting
SDL_FingerIDto proper 64-bit type (in SDL2 it was signed, in SDL3 it’s unsigned) to avoid an issue where on iOS devices theidturned negative (which we use to indicate that a finger isn’t down anymore) - Converting SDL3 nanosecond timestamps to milliseconds as that’s what ScummVM uses everywhere. This resolves an issue where simulated keypresses weren’t triggered as a difference between a (truncated from 64bit) uint32 and uint64 timestamp was calculated to determine the duration of a tap, as well as double clicks not being detected correctly.
- Converting
- Add Emscripten backend to configure: Setup SDL3 manually and pick SDL3 always - (EMSCRIPTEN: Add Emscripten backend to configure and setup SDL3 manually)
I got this nicely packed up and merged in a single PR: (SDL: EMSCRIPTEN: SDL3 bug fixes and use SDL3 for emscripten backend - July 31st)
CI, Warnings and smaller things
Build Improvements, CI and Buildbot
- - CI: Add Emscripten builds (July 14)
- - 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) (August 1)
- - BACKENDS: OPENGL3D: Fix builds with USE_FORCED_GLES and without USE_GLAD (June 30)
- - DISTS: EMSCRIPTEN: Update Emscripten SDK and cleanup build.sh (June 30)
Support for Bidirectional (BiDi) text
A bidirectional text contains two text directionalities, right-to-left (RTL) and left-to-right (LTR). Some so-called right-to-left scripts such as the Persian script and Arabic are mostly, but not exclusively, right-to-left - mathematical expressions, numeric dates and numbers bearing units are embedded from left to right.
RTL text can also become bi-direction when text from a left-to-right language such as English is embedded in them; or vice versa, if Arabic is embedded in a left-to-right script such as English.
In order to have proper Arabic and Hebrew support, the bidi algorithm needs to be implemented. GNU FriBidi is an implementation of that, which I’ve now integrated into the Emscripten build.

Comparison:
| With Bidirectional text support | Without Bidirectional text support |
|---|---|
![]() |
Screenshot |
Adding BiDi support enables proper text display for right-to-left languages like Arabic and Hebrew, making ScummVM more accessible to players worldwide. This is particularly important for games that have been localized to these languages.
- EMSCRIPTEN: Add FriBidi library support
Warnings
I also started to work a bit on some janitorial tasks:
- - LASTEXPRESS: Rename PAGE_SIZE macro (July 31)
- - COMMON: Silence warning “unused variable” (February 13)
- - SDL: Silence warning “unused function ‘sdlGetAttribute’” (May 9)
- - IMAGE: Silence warning “‘Image::JPEGDecoder::getPalette’ hides overloaded virtual function” (May 9)
Optimizations
I spent some time optimizing the build output to reduce file sizes and improve loading times:
-
Limit plugin exports: By providing the plugin names as inputs to the build process, we can limit what symbols are exported, making our libraries smaller. This required building the plugins first, necessitating a change to the main Makefile. The benefit is significantly smaller WASM files that download and parse faster.
-
Limit asyncify exports: In a similar way, we can tell the asyncify instrumentation to only consider specific imports in plugins that need to be asyncify-instrumented. Now the dynamic engines only export the basic interface defined in
./backends/plugins/elf/plugin.syms. This allows Emscripten to perform much more efficient dead code elimination on the side modules, resulting in smaller individual engine files. -
Main module optimization: I experimented with similar options for the main module but haven’t found a working solution yet. This remains an area for future improvement.


