SDL: Restore support for the Nokia N-Gage (#12148)

From 7ae64592c90cd4e0065b9a93aee851fbccd75b2a Mon Sep 17 00:00:00 2001
From: Michael Fitzmayer <[EMAIL REDACTED]>
Date: Thu, 22 May 2025 20:07:22 +0200
Subject: [PATCH] Restore support for the Nokia N-Gage (#12148)

---
 .github/actions/setup-ngage-sdk/action.yml    | 102 +++
 .github/workflows/create-test-plan.py         |  21 +-
 .github/workflows/generic.yml                 |   5 +
 CMakeLists.txt                                | 102 ++-
 cmake/PreseedNokiaNGageCache.cmake            | 189 +++++
 cmake/sdlplatform.cmake                       |   2 +
 cmake/test/CMakeLists.txt                     |  14 +-
 cmake/test/main_gui.c                         |  35 +-
 cmake/test/sdltest.c                          |  19 +-
 docs/README-ngage.md                          |  65 +-
 examples/CMakeLists.txt                       |   4 +
 include/SDL3/SDL_assert.h                     |   2 +-
 include/SDL3/SDL_begin_code.h                 |   2 +-
 include/SDL3/SDL_platform_defines.h           |  12 +-
 include/build_config/SDL_build_config.h.cmake |   4 +
 src/SDL.c                                     |   2 +
 src/SDL_error.c                               |   2 +
 src/SDL_log.c                                 |  24 +
 src/audio/SDL_audio.c                         |   3 +
 src/audio/SDL_sysaudio.h                      |   1 +
 src/audio/ngage/SDL_ngageaudio.c              | 103 +++
 src/audio/ngage/SDL_ngageaudio.cpp            | 368 +++++++++
 src/audio/ngage/SDL_ngageaudio.h              |  44 ++
 src/audio/ngage/SDL_ngageaudio.hpp            |  98 +++
 src/core/ngage/SDL_ngage.cpp                  |  77 ++
 src/core/ngage/SDL_ngage.h                    |  36 +
 src/cpuinfo/SDL_cpuinfo.c                     |   8 +
 src/dynapi/SDL_dynapi.h                       |   2 +
 src/filesystem/ngage/SDL_sysfilesystem.c      |  67 ++
 src/filesystem/ngage/SDL_sysfilesystem.cpp    |  68 ++
 src/locale/ngage/SDL_syslocale.cpp            | 307 ++++++++
 src/main/ngage/SDL_sysmain_callbacks.c        |  31 +
 src/main/ngage/SDL_sysmain_main.cpp           | 199 +++++
 src/main/ngage/SDL_sysmain_main.hpp           |  46 ++
 src/render/SDL_render.c                       |   3 +
 src/render/SDL_sysrender.h                    |   1 +
 src/render/ngage/SDL_render_ngage.c           | 544 +++++++++++++
 src/render/ngage/SDL_render_ngage.cpp         | 744 ++++++++++++++++++
 src/render/ngage/SDL_render_ngage_c.h         | 105 +++
 src/render/ngage/SDL_render_ngage_c.hpp       |  91 +++
 src/render/ngage/SDL_render_ops.cpp           | 152 ++++
 src/render/ngage/SDL_render_ops.hpp           |  32 +
 src/stdlib/SDL_string.c                       |   2 +
 src/stdlib/SDL_vacopy.h                       |   3 +-
 src/time/ngage/SDL_systime.cpp                | 184 +++++
 src/timer/ngage/SDL_systimer.cpp              |  47 ++
 src/video/SDL_sysvideo.h                      |   1 +
 src/video/SDL_video.c                         |   3 +
 src/video/ngage/SDL_ngagevideo.c              | 175 ++++
 src/video/ngage/SDL_ngagevideo.h              |  39 +
 test/CMakeLists.txt                           |  36 +-
 51 files changed, 4183 insertions(+), 43 deletions(-)
 create mode 100644 .github/actions/setup-ngage-sdk/action.yml
 create mode 100644 cmake/PreseedNokiaNGageCache.cmake
 create mode 100644 src/audio/ngage/SDL_ngageaudio.c
 create mode 100644 src/audio/ngage/SDL_ngageaudio.cpp
 create mode 100644 src/audio/ngage/SDL_ngageaudio.h
 create mode 100644 src/audio/ngage/SDL_ngageaudio.hpp
 create mode 100644 src/core/ngage/SDL_ngage.cpp
 create mode 100644 src/core/ngage/SDL_ngage.h
 create mode 100644 src/filesystem/ngage/SDL_sysfilesystem.c
 create mode 100644 src/filesystem/ngage/SDL_sysfilesystem.cpp
 create mode 100644 src/locale/ngage/SDL_syslocale.cpp
 create mode 100644 src/main/ngage/SDL_sysmain_callbacks.c
 create mode 100644 src/main/ngage/SDL_sysmain_main.cpp
 create mode 100644 src/main/ngage/SDL_sysmain_main.hpp
 create mode 100644 src/render/ngage/SDL_render_ngage.c
 create mode 100644 src/render/ngage/SDL_render_ngage.cpp
 create mode 100644 src/render/ngage/SDL_render_ngage_c.h
 create mode 100644 src/render/ngage/SDL_render_ngage_c.hpp
 create mode 100644 src/render/ngage/SDL_render_ops.cpp
 create mode 100644 src/render/ngage/SDL_render_ops.hpp
 create mode 100644 src/time/ngage/SDL_systime.cpp
 create mode 100644 src/timer/ngage/SDL_systimer.cpp
 create mode 100644 src/video/ngage/SDL_ngagevideo.c
 create mode 100644 src/video/ngage/SDL_ngagevideo.h

diff --git a/.github/actions/setup-ngage-sdk/action.yml b/.github/actions/setup-ngage-sdk/action.yml
new file mode 100644
index 0000000000000..fa83418ba21c6
--- /dev/null
+++ b/.github/actions/setup-ngage-sdk/action.yml
@@ -0,0 +1,102 @@
+name: 'Setup Nonka N-Gage SDK'
+description: 'Download and setup Nokia N-Gage SDK'
+inputs:
+  path:
+    description: 'Installation path'
+    default: 'default'
+runs:
+  using: 'composite'
+  steps:
+    - uses: actions/setup-python@v5
+      with:
+        python-version: '3.x'
+    - name: 'Verify platform'
+      id: calc
+      shell: sh
+      run: |
+        case "${{ runner.os }}-${{ runner.arch }}" in
+          "Windows-X86" | "Windows-X64")
+            echo "ok!"
+            echo "cache-key=ngage-sdk-windows" >> ${GITHUB_OUTPUT}
+            default_install_path="C:/ngagesdk"
+            ;;
+          *)
+            echo "Unsupported ${{ runner.os }}-${{ runner.arch }}"
+            exit 1;
+            ;;
+        esac
+        install_path="${{ inputs.path }}"
+        if [ "x$install_path" = "xdefault" ]; then
+          install_path="$default_install_path"
+        fi
+        echo "install-path=$install_path" >> ${GITHUB_OUTPUT}
+
+        toolchain_repo="https://github.com/ngagesdk/ngage-toolchain"
+        toolchain_branch="main"
+        echo "toolchain-repo=${toolchain_repo}"     >> ${GITHUB_OUTPUT}
+        echo "toolchain-branch=${toolchain_branch}" >> ${GITHUB_OUTPUT}
+
+        sdk_repo="https://github.com/ngagesdk/sdk"
+        sdk_branch="main"
+        echo "sdk-repo=${sdk_repo}"       >> ${GITHUB_OUTPUT}
+        echo "sdk-branch=${sdk_branch}"   >> ${GITHUB_OUTPUT}
+
+        tools_repo="https://github.com/ngagesdk/tools"
+        tools_branch="main"
+        echo "tools-repo=${tools_repo}"       >> ${GITHUB_OUTPUT}
+        echo "tools-branch=${tools_branch}"   >> ${GITHUB_OUTPUT}
+
+        extras_repo="https://github.com/ngagesdk/extras"
+        extras_branch="main"
+        echo "extras-repo=${extras_repo}"       >> ${GITHUB_OUTPUT}
+        echo "extras-branch=${extras_branch}"   >> ${GITHUB_OUTPUT}
+#    - name: 'Restore cached ${{ steps.calc.outputs.archive }}'
+#      id: cache-restore
+#      uses: actions/cache/restore@v4
+#      with:
+#        path: '${{ runner.temp }}'
+#        key: ${{ steps.calc.outputs.cache-key }}
+    - name: 'Download N-Gage SDK'
+#      if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }}
+      shell: pwsh
+      run: |
+
+        Invoke-WebRequest "${{ steps.calc.outputs.toolchain-repo }}/archive/refs/heads/${{ steps.calc.outputs.toolchain-branch }}.zip"  -OutFile "${{ runner.temp }}/ngage-toolchain.zip"
+        Invoke-WebRequest "${{ steps.calc.outputs.sdk-repo }}/archive/refs/heads/${{ steps.calc.outputs.sdk-branch }}.zip"              -OutFile "${{ runner.temp }}/sdk.zip"
+        Invoke-WebRequest "${{ steps.calc.outputs.tools-repo }}/archive/refs/heads/${{ steps.calc.outputs.tools-branch }}.zip"          -OutFile "${{ runner.temp }}/tools.zip"
+        Invoke-WebRequest "${{ steps.calc.outputs.extras-repo }}/archive/refs/heads/${{ steps.calc.outputs.extras-branch }}.zip"        -OutFile "${{ runner.temp }}/extras.zip"
+
+#    - name: 'Cache ${{ steps.calc.outputs.archive }}'
+#      if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }}
+#      uses: actions/cache/save@v4
+#      with:
+#        path: |
+#          ${{ runner.temp }}/apps.zip
+#          ${{ runner.temp }}/sdk.zip
+#          ${{ runner.temp }}/tools.zip
+#        key: ${{ steps.calc.outputs.cache-key }}
+    - name: 'Extract N-Gage SDK'
+      shell: pwsh
+      run: |
+        New-Item -ItemType Directory -Path "${{ steps.calc.outputs.install-path }}" -Force
+
+        New-Item -ItemType Directory -Path "${{ runner.temp }}/ngage-toolchain-temp" -Force 
+        7z "-o${{ runner.temp }}/ngage-toolchain-temp"      x "${{ runner.temp }}/ngage-toolchain.zip"
+        Move-Item -Path "${{ runner.temp }}/ngage-toolchain-temp/ngage-toolchain-${{ steps.calc.outputs.toolchain-branch }}/*" -Destination "${{ steps.calc.outputs.install-path }}"
+
+        7z "-o${{ steps.calc.outputs.install-path }}/sdk"   x "${{ runner.temp }}/sdk.zip"
+        Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/sdk-${{ steps.calc.outputs.sdk-branch }}"       -Destination "${{ steps.calc.outputs.install-path }}/sdk/sdk"
+
+        7z "-o${{ steps.calc.outputs.install-path }}/sdk"   x "${{ runner.temp }}/tools.zip"
+        Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/tools-${{ steps.calc.outputs.tools-branch }}"   -Destination "${{ steps.calc.outputs.install-path }}/sdk/tools"
+
+        7z "-o${{ steps.calc.outputs.install-path }}/sdk"   x "${{ runner.temp }}/extras.zip"
+        Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/extras-${{ steps.calc.outputs.extras-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/extras"
+    - name: 'Set output variables'
+      id: final
+      shell: sh
+      run: |
+        echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/gcc/bin" >> $GITHUB_PATH
+        echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/ngagesdk/bin" >> $GITHUB_PATH
+        echo "NGAGESDK=${{ steps.calc.outputs.install-path }}" >> $GITHUB_ENV
+        echo "CMAKE_TOOLCHAIN_FILE=${{ steps.calc.outputs.install-path }}/cmake/ngage-toolchain.cmake" >> $GITHUB_ENV
diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py
index 04f4c10923976..b711c02edefd8 100755
--- a/.github/workflows/create-test-plan.py
+++ b/.github/workflows/create-test-plan.py
@@ -54,6 +54,7 @@ class SdlPlatform(Enum):
     Riscos = "riscos"
     FreeBSD = "freebsd"
     NetBSD = "netbsd"
+    NGage = "ngage"
 
 
 class Msys2Platform(Enum):
@@ -139,11 +140,12 @@ class JobSpec:
     "riscos": JobSpec(name="RISC OS",                                       os=JobOs.UbuntuLatest,      platform=SdlPlatform.Riscos,      artifact="SDL-riscos",             container="riscosdotinfo/riscos-gccsdk-4.7:latest", ),
     "netbsd": JobSpec(name="NetBSD",                                        os=JobOs.UbuntuLatest,      platform=SdlPlatform.NetBSD,      artifact="SDL-netbsd-x64", ),
     "freebsd": JobSpec(name="FreeBSD",                                      os=JobOs.UbuntuLatest,      platform=SdlPlatform.FreeBSD,     artifact="SDL-freebsd-x64", ),
+    "ngage": JobSpec(name="N-Gage",                                         os=JobOs.WindowsLatest,     platform=SdlPlatform.NGage,       artifact="SDL-ngage", ),
 }
 
 
 class StaticLibType(Enum):
-    MSVC = "SDL3-static.lib"
+    STATIC_LIB = "SDL3-static.lib"
     A = "libSDL3.a"
 
 
@@ -223,6 +225,7 @@ class JobDetails:
     check_sources: bool = False
     setup_python: bool = False
     pypi_packages: list[str] = dataclasses.field(default_factory=list)
+    setup_gage_sdk_path: str = ""
 
     def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]:
         data = {
@@ -290,6 +293,7 @@ def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]:
             "check-sources": self.check_sources,
             "setup-python": self.setup_python,
             "pypi-packages": my_shlex_join(self.pypi_packages),
+            "setup-ngage-sdk-path": self.setup_gage_sdk_path,
         }
         return {k: v for k, v in data.items() if v != ""}
 
@@ -365,7 +369,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta
             job.msvc_project_flags.append("-p:TreatWarningsAsError=true")
             job.test_pkg_config = False
             job.shared_lib = SharedLibType.WIN32
-            job.static_lib = StaticLibType.MSVC
+            job.static_lib = StaticLibType.STATIC_LIB
             job.cmake_arguments.extend((
                 "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase",
                 "-DCMAKE_EXE_LINKER_FLAGS=-DEBUG",
@@ -740,6 +744,19 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta
                     job.cpactions_arch = "x86-64"
                     job.cpactions_setup_cmd = "export PATH=\"/usr/pkg/sbin:/usr/pkg/bin:/sbin:$PATH\"; export PKG_CONFIG_PATH=\"/usr/pkg/lib/pkgconfig\";export PKG_PATH=\"https://cdn.netBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f \"1 2\" -d.)/All/\";echo \"PKG_PATH=$PKG_PATH\";echo \"uname -a -> \"$(uname -a)\"\";sudo -E sysctl -w security.pax.aslr.enabled=0;sudo -E sysctl -w security.pax.aslr.global=0;sudo -E pkgin clean;sudo -E pkgin update"
                     job.cpactions_install_cmd = "sudo -E pkgin -y install cmake dbus pkgconf ninja-build pulseaudio libxkbcommon wayland wayland-protocols libinotify libusb1"
+        case SdlPlatform.NGage:
+            build_parallel = False
+            job.cmake_build_type = "Release"
+            job.setup_ninja = True
+            job.static_lib = StaticLibType.STATIC_LIB
+            job.shared_lib = None
+            job.clang_tidy = False
+            job.werror = False  # FIXME: enable SDL_WERROR
+            job.shared = False
+            job.run_tests = False
+            job.setup_gage_sdk_path = "C:/ngagesdk"
+            job.cmake_toolchain_file = "C:/ngagesdk/cmake/ngage-toolchain.cmake"
+            job.test_pkg_config = False
         case _:
             raise ValueError(f"Unsupported platform={spec.platform}")
 
diff --git a/.github/workflows/generic.yml b/.github/workflows/generic.yml
index 9776431e3da83..083859b341625 100644
--- a/.github/workflows/generic.yml
+++ b/.github/workflows/generic.yml
@@ -93,6 +93,11 @@ jobs:
         with:
           arch: ${{ matrix.platform.msvc-vcvars-arch }}
           sdk: ${{ matrix.platform.msvc-vcvars-sdk }}
+      - name: 'Set up Nokia N-Gage SDK'
+        uses: ./.github/actions/setup-ngage-sdk
+        if: ${{ matrix.platform.setup-ngage-sdk-path != '' }}
+        with:
+          path: '${{ matrix.platform.setup-ngage-sdk-path }}'
       - name: 'Set up Windows GDK Desktop'
         uses: ./.github/actions/setup-gdk-desktop
         if: ${{ matrix.platform.setup-gdk-folder != '' }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a36d82acdd50c..fd62e985f01fc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -76,6 +76,7 @@ include("${SDL3_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake")
 include("${SDL3_SOURCE_DIR}/cmake/3rdparty.cmake")
 include("${SDL3_SOURCE_DIR}/cmake/PreseedMSVCCache.cmake")
 include("${SDL3_SOURCE_DIR}/cmake/PreseedEmscriptenCache.cmake")
+include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake")
 
 SDL_DetectCompiler()
 SDL_DetectTargetCPUArchitectures(SDL_CPUS)
@@ -155,7 +156,7 @@ endif()
 # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers,
 #  so we'll just use libusb when it's available. libusb does not support iOS,
 #  so we default to yes on iOS.
-if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID)
+if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID OR NGAGE)
   set(SDL_HIDAPI_LIBUSB_AVAILABLE FALSE)
 else()
   set(SDL_HIDAPI_LIBUSB_AVAILABLE TRUE)
@@ -219,7 +220,7 @@ if(EMSCRIPTEN)
   set(SDL_SHARED_AVAILABLE OFF)
 endif()
 
-if(VITA OR PSP OR PS2 OR N3DS OR RISCOS)
+if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE)
   set(SDL_SHARED_AVAILABLE OFF)
 endif()
 
@@ -414,6 +415,24 @@ if(VITA)
   set_option(VIDEO_VITA_PVR  "Build with PSVita PVR gles/gles2 support" OFF)
 endif()
 
+if (NGAGE)
+  set(SDL_GPU              OFF)
+  set(SDL_CAMERA           OFF)
+  set(SDL_JOYSTICK         OFF)
+  set(SDL_HAPTIC           OFF)
+  set(SDL_HIDAPI           OFF)
+  set(SDL_POWER            OFF)
+  set(SDL_SENSOR           OFF)
+  set(SDL_DIALOG           OFF)
+  set(SDL_DISKAUDIO        OFF)
+  set(SDL_DUMMYAUDIO       OFF)
+  set(SDL_DUMMYCAMERA      OFF)
+  set(SDL_DUMMYVIDEO       OFF)
+  set(SDL_OFFSCREEN        OFF)
+  set(SDL_RENDER_GPU       OFF)
+  set(SDL_VIRTUAL_JOYSTICK OFF)
+endif()
+
 if(NOT (SDL_SHARED OR SDL_STATIC))
   message(FATAL_ERROR "SDL_SHARED and SDL_STATIC cannot both be disabled")
 endif()
@@ -2931,6 +2950,81 @@ elseif(N3DS)
   set(HAVE_SDL_LOCALE TRUE)
 
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/n3ds/*.c")
+
+elseif(NGAGE)
+
+  enable_language(CXX)
+
+  set(SDL_MAIN_USE_CALLBACKS 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.c")
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.cpp")
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp")
+  set(HAVE_SDL_MAIN_CALLBACKS TRUE)
+
+  if(SDL_AUDIO)
+    set(SDL_AUDIO_DRIVER_NGAGE 1)
+    sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.c")
+    sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp")
+    set(HAVE_SDL_AUDIO TRUE)
+  endif()
+
+  set(SDL_FILESYSTEM_NGAGE 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.c")
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.cpp")
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/*.c")
+  set(HAVE_SDL_FILESYSTEM TRUE)
+
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/ngage/*.cpp")
+
+  if(SDL_RENDER)
+    set(SDL_VIDEO_RENDER_NGAGE 1)
+    sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/ngage/*.c")
+  endif()
+
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ngage/*.cpp")
+  set(SDL_TIME_NGAGE 1)
+
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/ngage/*.cpp")
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
+
+  set(SDL_TIMER_NGAGE 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/ngage/*.cpp")
+
+  set(SDL_FSOPS_POSIX        1)
+
+  set(SDL_VIDEO_DRIVER_NGAGE 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/ngage/*.c")
+  set(HAVE_SDL_TIMERS TRUE)
+
+  set_option(SDL_LEAN_AND_MEAN "Enable lean and mean" ON)
+  if(SDL_LEAN_AND_MEAN)
+    sdl_compile_definitions(
+      PRIVATE
+      SDL_LEAN_AND_MEAN
+    )
+  endif()
+
+  sdl_link_dependency(ngage
+    LINK_OPTIONS "SHELL:-s MAIN_COMPAT=0"
+    PKG_CONFIG_LINK_OPTIONS "-s;MAIN_COMPAT=0"
+    LIBS
+      NRenderer
+      3dtypes
+      cone
+      libgcc
+      libgcc_ngage
+      mediaclientaudiostream
+      charconv
+      bitgdi
+      euser
+      estlib
+      ws32
+      hal
+      fbscli
+      efsrv
+      scdv
+      gdi
+  )
 endif()
 
 sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog.c)
@@ -3111,8 +3205,8 @@ endif()
 
 # We always need to have threads and timers around
 if(NOT HAVE_SDL_THREADS)
-  # The emscripten platform has been carefully vetted to work without threads
-  if(EMSCRIPTEN)
+  # The Emscripten and N-Gage platform has been carefully vetted to work without threads
+  if(EMSCRIPTEN OR NGAGE)
     set(SDL_THREADS_DISABLED 1)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/generic/*.c")
   else()
diff --git a/cmake/PreseedNokiaNGageCache.cmake b/cmake/PreseedNokiaNGageCache.cmake
new file mode 100644
index 0000000000000..298177ba15d59
--- /dev/null
+++ b/cmake/PreseedNokiaNGageCache.cmake
@@ -0,0 +1,189 @@
+if(NGAGESDK)
+  function(SDL_Preseed_CMakeCache)
+    set(COMPILER_SUPPORTS_ARMNEON                        ""    CACHE INTERNAL "Test COMPILER_SUPPORTS_ARMNEON")
+    set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS      ""    CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS")
+    set(COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET         ""    CACHE INTERNAL "Test COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET")
+    set(HAVE_CLANG_COMMENT_BLOCK_COMMANDS                ""    CACHE INTERNAL "Test HAVE_CLANG_COMMENT_BLOCK_COMMANDS")
+    set(HAVE_ALLOCA_H                                    ""    CACHE INTERNAL "Have include alloca.h")
+    set(HAVE_LIBM                                        "1"   CACHE INTERNAL "Have library m")
+    set(HAVE_POSIX_SPAWN                                 ""    CACHE INTERNAL "Have symbol posix_spawn")
+    set(HAVE_MALLOC                                      "1"   CACHE INTERNAL "Have include malloc.h")
+    set(LIBC_HAS_ABS                                     "1"   CACHE INTERNAL "Have symbol abs")
+    set(LIBC_HAS_ACOS                                    "1"   CACHE INTERNAL "Have symbol acos")
+    set(LIBC_HAS_ACOSF                                   ""    CACHE INTERNAL "Have symbol acosf")
+    set(LIBC_HAS_ASIN                                    "1"   CACHE INTERNAL "Have symbol asin")
+    set(LIBC_HAS_ASINF                                   ""    CACHE INTERNAL "Have symbol asinf")
+    set(LIBC_HAS_ATAN                                    "1"   CACHE INTERNAL "Have symbol atan")
+    set(LIBC_HAS_ATAN2                                   "1"   CACHE INTERNAL "Have symbol atan2")
+    set(LIBC_HAS_ATAN2F                                  ""    CACHE INTERNAL "Have symbol atan2f")
+    set(LIBC_HAS_ATANF                                   ""    CACHE INTERNAL "Have symbol atanf")
+    set(LIBC_HAS_ATOF                                    ""    CACHE INTERNAL "Have symbol atof")
+    set(LIBC_HAS_ATOI                                    ""    CACHE INTERNAL "Have symbol atoi")
+    set(LIBC_HAS_BCOPY                                   "1"   CACHE INTERNAL "Have symbol bcopy")
+    set(LIBC_HAS_CALLOC                                  ""    CACHE INTERNAL "Have symbol calloc")
+    set(LIBC_HAS_CEIL                                    "1"   CACHE INTERNAL "Have symbol ceil")
+    set(LIBC_HAS_CEILF                                   ""    CACHE INTERNAL "Have symbol ceilf")
+    set(LIBC_HAS_COPYSIGN                                "1"   CACHE INTERNAL "Have symbol copysign")
+    set(LIBC_HAS_COPYSIGNF                               "1"   CACHE INTERNAL "Have symbol copysignf")
+    set(LIBC_HAS_COS                                     "1"   CACHE INTERNAL "Have symbol cos")
+    set(LIBC_HAS_COSF                                    ""    CACHE INTERNAL "Have symbol cosf")
+    set(LIBC_HAS_EXP                                     "1"   CACHE INTERNAL "Have symbol exp")
+    set(LIBC_HAS_EXPF                                    ""    CACHE INTERNAL "Have symbol expf")
+    set(LIBC_HAS_FABS                                    "1"   CACHE INTERNAL "Have symbol fabs")
+    set(LIBC_HAS_FABSF                                   "1"   CACHE INTERNAL "Have symbol fabsf")
+    set(LIBC_HAS_FLOAT_H                                 "1"   CACHE INTERNAL "Have include float.h")
+    set(LIBC_HAS_FLOOR                                   "1"   CACHE INTERNAL "Have symbol floor")
+    set(LIBC_HAS_FLOORF                                  ""    CACHE INTERNAL "Have symbol floorf")
+    set(LIBC_HAS_FMOD                                    ""    CACHE INTERNAL "Have symbol fmod")
+    set(LIBC_HAS_FMODF                                   ""    CACHE INTERNAL "Have symbol fmodf")
+    set(LIBC_HAS_FOPEN64                                 ""    CACHE INTERNAL "Have symbol fopen64")
+    set(LIBC_HAS_FREE                                    "1"   CACHE INTERNAL "Have symbol free")
+    set(LIBC_HAS_FSEEKO                                  ""    CACHE INTERNAL "Have symbol fseeko")
+    set(LIBC_HAS_FSEEKO64                                ""    CACHE INTERNAL "Have symbol fseeko64")
+    set(LIBC_HAS_GETENV                                  ""    CACHE INTERNAL "Have symbol getenv")
+    set(LIBC_HAS_ICONV_H                                 ""    CACHE INTERNAL "Have include iconv.h")
+    set(LIBC_HAS_INDEX                                   "1"   CACHE INTERNAL "Have symbol index")
+    set(LIBC_HAS_INTTYPES_H                              "1"   CACHE INTERNAL "Have include inttypes.h")
+    set(LIBC_HAS_ISINF                                   "1"   CACHE INTERNAL "Have include isinf(double)")
+    set(LIBC_ISINF_HANDLES_FLOAT                         "1"   CACHE INTERNAL "Have include isinf(float)")
+    set(LIBC_HAS_ISINFF                                  "1"   CACHE INTERNAL "Have include isinff(float)")
+    set(LIBC_HAS_ISNAN                                   "1"   CACHE INTERNAL "Have include isnan(double)")
+    set(LIBC_ISNAN_HANDLES_FLOAT                         "1"   CACHE INTERNAL "Have include isnan(float)")
+    set(LIBC_HAS_ISNANF                                  "1"   CACHE INTERNAL "Have include isnanf(float)")
+    set(LIBC_HAS_ITOA                                    ""    CACHE INTERNAL "Have symbol itoa")
+    set(LIBC_HAS_LIMITS_H                                "1"   CACHE INTERNAL "Have include limits.h")
+    set(LIBC_HAS_LOG                                     "1"   CACHE INTERNAL "Have symbol log")
+    set(LIBC_HAS_LOG10                                   ""    CACHE INTERNAL "Have symbol log10")
+    set(LIBC_HAS_LOG10F                                  ""    CACHE INTERNAL "Have symbol log10f")
+    set(LIBC_HAS_LOGF                                    ""    CACHE INTERNAL "Have symbol logf")
+    set(LIBC_HAS_LROUND                                  ""    CACHE INTERNAL "Have symbol lround")
+    set(LIBC_HAS_LROUNDF                                 ""    CACHE INTERNAL "Have symbol lroundf")
+    set(LIBC_HAS_MALLOC                                  "1"   CACHE INTERNAL "Have symbol malloc")
+    set(LIBC_HAS_MALLOC_H                                ""    CACHE INTERNAL "Have include malloc.h")
+    set(LIBC_HAS_MATH_H                                  "1"   CACHE INTERNAL "Have include math.h")
+    set(LIBC_HAS_MEMCMP                                  "1"   CACHE INTERNAL "Have symbol memcmp")
+    set(LIBC_HAS_MEMCPY                                  ""    CACHE INTERNAL "Have symbol memcpy")
+    set(LIBC_HAS_MEMMOVE                                 ""    CACHE INTERNAL "Have symbol memmove")
+    set(LIBC_HAS_MEMORY_H                                ""    CACHE INTERNAL "Have include memory.h")
+    set(LIBC_HAS_MEMSET                                  ""    CACHE INTERNAL "Have symbol memset")
+    set(LIBC_HAS_MODF                                    "1"   CACHE INTERNAL "Have symbol modf")
+    set(LIBC_HAS_MODFF                                   ""    CACHE INTERNAL "Have symbol modff")
+    set(LIBC_HAS_POW                                     "1"   CACHE INTERNAL "Have symbol pow")
+    set(LIBC_HAS_POWF                                    ""    CACHE INTERNAL "Have symbol powf")
+    set(LIBC_HAS_PUTENV                                  ""    CACHE INTERNAL "Have symbol putenv")
+    set(LIBC_HAS_REALLOC                                 ""    CACHE INTERNAL "Have symbol realloc")
+    set(LIBC_HAS_RINDEX                                  "1"   CACHE INTERNAL "Have symbol rindex")
+    set(LIBC_HAS_ROUND                                   ""    CACHE INTERNAL "Have symbol round")
+    set(LIBC_HAS_ROUNDF                                  ""    CACHE INTERNAL "Have symbol roundf")
+    set(LIBC_HAS_SCALBN                                  "1"   CACHE INTERNAL "Have symbol scalbn")
+    set(LIBC_HAS_SCALBNF                                 ""    CACHE INTERNAL "Have symbol scalbnf")
+    set(LIBC_HAS_SETENV                                  ""    CACHE INTERNAL "Have symbol setenv")
+    set(LIBC_HAS_SIGNAL_H                                ""    CACHE INTERNAL "Have include signal.h")
+    set(LIBC_HAS_SIN                                     "1"   CACHE INTERNAL "Have symbol sin")
+    set(LIBC_HAS_SINF                                    ""    CACHE INTERNAL "Have symbol sinf")
+    set(LIBC_HAS_SQR                                     ""    CACHE INTERNAL "Have symbol sqr")
+    set(LIBC_HAS_SQRT                                    "1"   CACHE INTERNAL "Have symbol sqrt")
+    set(LIBC_HAS_SQRTF                                   ""    CACHE INTERNAL "Have symbol sqrtf")
+    set(LIBC_HAS_SSCANF                                  "1"   CACHE INTERNAL "Have symbol sscanf")
+    set(LIBC_HAS_STDARG_H                                "1"   CACHE INTERNAL "Have include stdarg.h")
+    set(LIBC_HAS_STDBOOL_H                               "1"   CACHE INTERNAL "Have include stdbool.h")
+    set(LIBC_HAS_STDDEF_H                                "1"   CACHE INTERNAL "Have include stddef.h")
+    set(LIBC_HAS_STDINT_H                                "1"   CACHE INTERNAL "Have include stdint.h")
+    set(LIBC_HAS_STDIO_H                                 "1"   CACHE INTERNAL "Have include stdio.h")
+    set(LIBC_HAS_STDLIB_H                                "1"   CACHE INTERNAL "Have include stdlib.h")
+    set(LIBC_HAS_STRCASESTR                              ""    CACHE INTERNAL "Have symbol strcasestr")
+    set(LIBC_HAS_STRCHR                                  "1"   CACHE INTERNAL "Have symbol strchr")
+    set(LIBC_HAS_STRCMP                                  "1"   CACHE INTERNAL "Have symbol strcmp")
+    set(LIBC_HAS_STRINGS_H                               ""    CACHE INTERNAL "Have include strings.h")
+    set(LIBC_HAS_STRING_H                                "1"   CACHE INTERNAL "Have include string.h")
+    set(LIBC_HAS_STRLCAT                                 ""    CACHE INTERNAL "Have symbol strlcat")
+    set(LIBC_HAS_STRLCPY                                 ""    CACHE INTERNAL "Have symbol strlcpy")
+    set(LIBC_HAS_STRLEN                                  "1"   CACHE INTERNAL "Have symbol strlen")
+    set(LIBC_HAS_STRNCMP                                 "1"   CACHE INTERNAL "Have symbol strncmp")
+    set(LIBC_HAS_STRNLEN                                 ""    CACHE INTERNAL "Have symbol strnlen")
+    set(LIBC_HAS_STRNSTR                                 ""    CACHE INTERNAL "Have symbol strnstr")
+    set(LIBC_HAS_STRPBRK                                 "1"   CACHE INTERNAL "Have symbol strpbrk")
+    set(LIBC_HAS_STRRCHR                                 "1"   CACHE INTERNAL "Have symbol strrchr")
+    set(LIBC_HAS_STRSTR                                  "1"   CACHE INTERNAL "Have symbol strstr")
+    set(LIBC_HAS_STRTOD                                  ""    CACHE INTERNAL "Have symbol strtod")
+    set(LIBC_HAS_STRTOK_R                                ""    CACHE INTERNAL "Have symbol strtok_r")
+    set(LIBC_HAS_STRTOL                                  ""    CACHE INTERNAL "Have symbol strtol")
+    set(LIBC_HAS_STRTOLL                                 ""    CACHE INTERNAL "Have symbol strtoll")
+    set(LIBC_HAS_STRTOUL                                 ""    CACHE INTERNAL "Have symbol strtoul")
+    set(LIBC_HAS_STRTOULL                                ""    CACHE INTERNAL "Have symbol strtoull")
+    set(LIBC_HAS_SYS_TYPES_H                             "1"   CACHE INTERNAL "Have include sys/types.h")
+    set(LIBC_HAS_TAN                                     "1"   CACHE INTERNAL "Have symbol tan")
+    set(LIBC_HAS_TANF                                    ""    CACHE INTERNAL "Have symbol tanf")
+    set(LIBC_HAS_TIME_H                                  "1"   CACHE INTERNAL "Have include time.h")
+    set(LIBC_HAS_TRUNC                                   ""    CACHE INTERNAL "Have symbol trunc")
+    set(LIBC_HAS_TRUNCF                                  ""    CACHE INTERNAL "Have symbol truncf")
+    set(LIBC_HAS_UNSETENV                                ""    CACHE INTERNAL "Have symbol unsetenv")
+    set(LIBC_HAS_VSNPRINTF                               ""    CACHE INTERNAL "Have symbol vsnprintf")
+    set(LIBC

(Patch may be truncated, please check the link at the top of this post.)