SDL: Add support for the Nokia N-Gage (#5597)

From fbd230bb6c918c6b2e94d09f6a10a32affea08bb Mon Sep 17 00:00:00 2001
From: Michael Fitzmayer <[EMAIL REDACTED]>
Date: Tue, 3 May 2022 17:51:49 +0200
Subject: [PATCH] Add support for the Nokia N-Gage (#5597)

* Add initial support for the Nokia N-Gage

* N-Gage: disable clipping for the time being, issue needs to be resolved later

* Move va_copy definition to SDL_internal.h

* Move stdlib.h include to SDL_config_ngage.h, much cleaner this way

* Remove redundant include, add HAVE_STDLIB_H

* Revert "N-Gage: disable clipping for the time being, issue needs to be resolved later"

This reverts commit 4f5f0fc36cc7f34fad05e45671dfa7b8dc32fd51.

* N-Gage: fix clipping issue by providing proper math functions
---
 docs/README-ngage.md                     |  44 +++
 docs/README.md                           |   1 +
 include/SDL_config.h                     |   2 +
 include/SDL_config_ngage.h               |  89 +++++
 include/SDL_platform.h                   |   4 +
 src/SDL.c                                |   2 +
 src/SDL_internal.h                       |   5 +-
 src/dynapi/SDL_dynapi.h                  |   2 +
 src/main/ngage/SDL_ngage_main.cpp        |  82 +++++
 src/thread/SDL_thread_c.h                |   2 +
 src/thread/ngage/SDL_sysmutex.cpp        | 107 ++++++
 src/thread/ngage/SDL_syssem.cpp          | 195 +++++++++++
 src/thread/ngage/SDL_systhread.cpp       | 147 ++++++++
 src/thread/ngage/SDL_systhread_c.h       |  25 ++
 src/timer/ngage/SDL_systimer.cpp         | 100 ++++++
 src/video/SDL_sysvideo.h                 |   1 +
 src/video/SDL_video.c                    |   3 +
 src/video/ngage/SDL_ngageevents.cpp      | 200 +++++++++++
 src/video/ngage/SDL_ngageevents_c.h      |  28 ++
 src/video/ngage/SDL_ngageframebuffer.cpp | 424 +++++++++++++++++++++++
 src/video/ngage/SDL_ngageframebuffer_c.h |  38 ++
 src/video/ngage/SDL_ngagevideo.cpp       | 192 ++++++++++
 src/video/ngage/SDL_ngagevideo.h         |  75 ++++
 src/video/ngage/SDL_ngagewindow.cpp      | 129 +++++++
 src/video/ngage/SDL_ngagewindow.h        |  45 +++
 25 files changed, 1941 insertions(+), 1 deletion(-)
 create mode 100644 docs/README-ngage.md
 create mode 100644 include/SDL_config_ngage.h
 create mode 100644 src/main/ngage/SDL_ngage_main.cpp
 create mode 100644 src/thread/ngage/SDL_sysmutex.cpp
 create mode 100644 src/thread/ngage/SDL_syssem.cpp
 create mode 100644 src/thread/ngage/SDL_systhread.cpp
 create mode 100644 src/thread/ngage/SDL_systhread_c.h
 create mode 100644 src/timer/ngage/SDL_systimer.cpp
 create mode 100644 src/video/ngage/SDL_ngageevents.cpp
 create mode 100644 src/video/ngage/SDL_ngageevents_c.h
 create mode 100644 src/video/ngage/SDL_ngageframebuffer.cpp
 create mode 100644 src/video/ngage/SDL_ngageframebuffer_c.h
 create mode 100644 src/video/ngage/SDL_ngagevideo.cpp
 create mode 100644 src/video/ngage/SDL_ngagevideo.h
 create mode 100644 src/video/ngage/SDL_ngagewindow.cpp
 create mode 100644 src/video/ngage/SDL_ngagewindow.h

diff --git a/docs/README-ngage.md b/docs/README-ngage.md
new file mode 100644
index 00000000000..73878874a00
--- /dev/null
+++ b/docs/README-ngage.md
@@ -0,0 +1,44 @@
+Nokia N-Gage
+============
+
+SDL2 port for Symbian S60v1/2 with a main focus on the Nokia N-Gage
+(Classic and QD) by [Michael Fitzmayer](https://github.com/mupfdev).
+
+Compiling
+---------
+
+SDL is part of the [N-Gage SDK.](https://github.com/ngagesdk) project.
+The library is included in the
+[toolchain](https://github.com/ngagesdk/ngage-toolchain) as a
+sub-module.
+
+A complete example project based on SDL2 can be found in the GitHub
+account of the SDK: [Example
+project](https://github.com/ngagesdk/wordle).
+
+Current level of implementation
+-------------------------------
+
+The video driver currently provides full screen video support with
+keyboard input.
+
+At the moment only the software renderer works.
+
+Audio is not yet implemented.
+
+Acknowledgements
+----------------
+
+Thanks to Hannu Viitala, Kimmo Kinnunen and Markus Mertama for the
+valuable insight into Symbian programming.  Without the SDL 1.2 port for
+CDoom, this adaptation would not have been possible.
+
+I would like to thank my friends
+[Razvan](https://twitter.com/bewarerazvan) and [Dan
+Whelan](https://danwhelan.ie/), for their continuous support.  Without
+you and the [N-Gage community](https://discord.gg/dbUzqJ26vs), I would
+have lost my patience long ago.
+
+Last but not least, I would like to say a special thank you to the
+[EKA2L1](https://12z1.com/) team.  Thank you for all your patience and
+support in troubleshooting.
diff --git a/docs/README.md b/docs/README.md
index efbe3ad0ed6..fd0078a5cf4 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -51,6 +51,7 @@ More documentation and FAQs are available online at [the wiki](http://wiki.libsd
 - [Windows](README-windows.md)
 - [WinRT](README-winrt.md)
 - [PSVita](README-vita.md)
+- [Nokia N-Gage](README-ngage.md)
 
 If you need help with the library, or just want to discuss SDL related
 issues, you can join the [SDL Discourse](https://discourse.libsdl.org/),
diff --git a/include/SDL_config.h b/include/SDL_config.h
index 7afefabe252..7499885155f 100644
--- a/include/SDL_config.h
+++ b/include/SDL_config.h
@@ -43,6 +43,8 @@
 #include "SDL_config_os2.h"
 #elif defined(__EMSCRIPTEN__)
 #include "SDL_config_emscripten.h"
+#elif defined(__NGAGE__)
+#include "SDL_config_ngage.h"
 #else
 /* This is a minimal configuration just to get SDL running on new platforms. */
 #include "SDL_config_minimal.h"
diff --git a/include/SDL_config_ngage.h b/include/SDL_config_ngage.h
new file mode 100644
index 00000000000..07f92541e2e
--- /dev/null
+++ b/include/SDL_config_ngage.h
@@ -0,0 +1,89 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_config_ngage_h_
+#define SDL_config_ngage_h_
+#define SDL_config_h_
+
+#include "SDL_platform.h"
+
+typedef signed char        int8_t;
+typedef unsigned char      uint8_t;
+typedef signed short       int16_t;
+typedef unsigned short     uint16_t;
+typedef signed int         int32_t;
+typedef unsigned int       uint32_t;
+typedef signed long long   int64_t;
+typedef unsigned long long uint64_t;
+typedef unsigned long      uintptr_t;
+
+#define HAVE_STDARG_H    1
+#define HAVE_STDDEF_H    1
+#define HAVE_STDIO_H     1
+#define HAVE_STDLIB_H    1
+#define HAVE_MATH_H      1
+#define HAVE_CEIL        1
+#define HAVE_COPYSIGN    1
+#define HAVE_COS         1
+#define HAVE_EXP         1
+#define HAVE_FABS        1
+#define HAVE_FLOOR       1
+#define HAVE_LOG         1
+#define HAVE_LOG10       1
+#define HAVE_SCALBN      1
+#define HAVE_SIN         1
+#define HAVE_SQRT        1
+#define HAVE_TAN         1
+#define HAVE_MALLOC      1
+#define SDL_MAIN_NEEDED  1
+#define LACKS_SYS_MMAN_H 1
+
+/* Enable the N-Gage thread support (src/thread/ngage/\*.c) */
+#define SDL_THREAD_NGAGE 1
+
+/* Enable the N-Gage timer support (src/timer/ngage/\*.c) */
+#define SDL_TIMER_NGAGE  1
+
+/* Enable the N=Hahe video driver (src/video/ngage/\*.c) */
+#define SDL_VIDEO_DRIVER_NGAGE 1
+
+/* Enable the dummy audio driver (src/audio/dummy/\*.c) */
+#define SDL_AUDIO_DRIVER_DUMMY  1
+
+/* Enable the stub joystick driver (src/joystick/dummy/\*.c) */
+#define SDL_JOYSTICK_DISABLED   1
+
+/* Enable the stub haptic driver (src/haptic/dummy/\*.c) */
+#define SDL_HAPTIC_DISABLED 1
+
+/* Enable the stub HIDAPI */
+#define SDL_HIDAPI_DISABLED 1
+
+/* Enable the stub sensor driver (src/sensor/dummy/\*.c) */
+#define SDL_SENSOR_DISABLED 1
+
+/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */
+#define SDL_LOADSO_DISABLED 1
+
+/* Enable the dummy filesystem driver (src/filesystem/dummy/\*.c) */
+#define SDL_FILESYSTEM_DUMMY 1
+
+#endif /* SDL_config_ngage_h_ */
diff --git a/include/SDL_platform.h b/include/SDL_platform.h
index efa93e3fc13..2663e143efc 100644
--- a/include/SDL_platform.h
+++ b/include/SDL_platform.h
@@ -65,6 +65,10 @@
 #undef __LINUX__ /* do we need to do this? */
 #define __ANDROID__ 1
 #endif
+#if defined(__NGAGE__)
+#undef __NGAGE__
+#define __NGAGE__ 1
+#endif
 
 #if defined(__APPLE__)
 /* lets us know what version of Mac OS X we're compiling on */
diff --git a/src/SDL.c b/src/SDL.c
index 92300f59a32..82861c76d41 100644
--- a/src/SDL.c
+++ b/src/SDL.c
@@ -567,6 +567,8 @@ SDL_GetPlatform(void)
     return "PlayStation Portable";
 #elif __VITA__
     return "PlayStation Vita";
+#elif __NGAGE__
+    return "Nokia N-Gage";
 #else
     return "Unknown (see SDL_platform.h)";
 #endif
diff --git a/src/SDL_internal.h b/src/SDL_internal.h
index 2ec32f69767..7d3f9cd9e5f 100644
--- a/src/SDL_internal.h
+++ b/src/SDL_internal.h
@@ -27,7 +27,10 @@
 #endif
 
 /* Do our best to make sure va_copy is working */
-#if defined(_MSC_VER) && _MSC_VER <= 1800
+#if defined(__NGAGE__)
+#undef va_copy
+#define va_copy(dst, src)   dst = src
+#elif defined(_MSC_VER) && _MSC_VER <= 1800
 /* Visual Studio 2013 tries to link with _vacopy in the C runtime. Newer versions do an inline assignment */
 #undef va_copy
 #define va_copy(dst, src)   dst = src
diff --git a/src/dynapi/SDL_dynapi.h b/src/dynapi/SDL_dynapi.h
index 452078b1a2a..e37444fd956 100644
--- a/src/dynapi/SDL_dynapi.h
+++ b/src/dynapi/SDL_dynapi.h
@@ -59,6 +59,8 @@
 #define SDL_DYNAMIC_API 0  /* Turn off for static analysis, so reports are more clear. */
 #elif defined(__VITA__)
 #define SDL_DYNAMIC_API 0  /* vitasdk doesn't support dynamic linking */
+#elif defined(__NGAGE__)
+#define SDL_DYNAMIC_API 0  /* The N-Gage doesn't support dynamic linking either */
 #elif defined(DYNAPI_NEEDS_DLOPEN) && !defined(HAVE_DLOPEN)
 #define SDL_DYNAMIC_API 0  /* we need dlopen(), but don't have it.... */
 #endif
diff --git a/src/main/ngage/SDL_ngage_main.cpp b/src/main/ngage/SDL_ngage_main.cpp
new file mode 100644
index 00000000000..bb0f07c26a0
--- /dev/null
+++ b/src/main/ngage/SDL_ngage_main.cpp
@@ -0,0 +1,82 @@
+/*
+    EPOC version (originally for SDL 1.2) by Hannu Viitala
+    (hannu.j.viitala@mbnet.fi).
+*/
+#include "../../SDL_internal.h"
+
+/* Include the SDL main definition header */
+#include "SDL_main.h"
+
+#include <e32std.h>
+#include <e32def.h>
+#include <e32svr.h>
+#include <e32base.h>
+#include <estlib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <w32std.h>
+#include <apgtask.h>
+
+#include "SDL_error.h"
+
+extern "C" int main(int argc, char *argv[]);
+
+TInt E32Main()
+{
+    /*  Get the clean-up stack */
+    CTrapCleanup* cleanup = CTrapCleanup::New();
+
+    /* Arrange for multi-threaded operation */
+    SpawnPosixServerThread();
+
+    /* Get args and environment */
+    int    argc = 0;
+    char** argv = 0;
+    char** envp = 0;
+
+    __crt0(argc,argv,envp);
+
+    /* Start the application! */
+
+    /* Create stdlib */
+    _REENT;
+
+    /* Set process and thread priority and name */
+
+    RThread  currentThread;
+    RProcess thisProcess;
+    TParse   exeName;
+    exeName.Set(thisProcess.FileName(), NULL, NULL);
+    currentThread.Rename(exeName.Name());
+    currentThread.SetProcessPriority(EPriorityLow);
+    currentThread.SetPriority(EPriorityMuchLess);
+
+    /* Increase heap size */
+    RHeap* newHeap  = NULL;
+    RHeap* oldHeap  = NULL;
+    TInt   heapSize = 7500000;
+    int    ret;
+
+    newHeap = User::ChunkHeap(NULL, heapSize, heapSize, KMinHeapGrowBy);
+
+    if (NULL == newHeap)
+    {
+        ret = 3;
+        goto cleanup;
+    }
+    else
+    {
+        oldHeap = User::SwitchHeap(newHeap);
+        /* Call stdlib main */
+        ret = main(argc, argv);
+    }
+
+cleanup:
+    _cleanup();
+
+    CloseSTDLIB();
+    delete cleanup;
+    return ret;
+}
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/thread/SDL_thread_c.h b/src/thread/SDL_thread_c.h
index fb0885a2db3..b4df526d1cc 100644
--- a/src/thread/SDL_thread_c.h
+++ b/src/thread/SDL_thread_c.h
@@ -40,6 +40,8 @@
 #include "stdcpp/SDL_systhread_c.h"
 #elif SDL_THREAD_OS2
 #include "os2/SDL_systhread_c.h"
+#elif SDL_THREAD_NGAGE
+#include "ngage/SDL_systhread_c.h"
 #else
 #error Need thread implementation for this platform
 #include "generic/SDL_systhread_c.h"
diff --git a/src/thread/ngage/SDL_sysmutex.cpp b/src/thread/ngage/SDL_sysmutex.cpp
new file mode 100644
index 00000000000..a79b8ae0e04
--- /dev/null
+++ b/src/thread/ngage/SDL_sysmutex.cpp
@@ -0,0 +1,107 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+/* An implementation of mutexes using semaphores */
+
+#include <e32std.h>
+
+#include "SDL_thread.h"
+#include "SDL_systhread_c.h"
+
+struct SDL_mutex
+{
+    TInt handle;
+};
+
+extern TInt CreateUnique(TInt (*aFunc)(const TDesC& aName, TAny*, TAny*), TAny*, TAny*);
+
+static TInt NewMutex(const TDesC& aName, TAny* aPtr1, TAny*)
+{
+    return ((RMutex*)aPtr1)->CreateGlobal(aName);
+}
+
+/* Create a mutex */
+SDL_mutex *
+SDL_CreateMutex(void)
+{
+    RMutex rmutex;
+
+    TInt status = CreateUnique(NewMutex, &rmutex, NULL);
+    if(status != KErrNone)
+    {
+        SDL_SetError("Couldn't create mutex.");
+    }
+    SDL_mutex* mutex = new /*(ELeave)*/ SDL_mutex;
+    mutex->handle = rmutex.Handle();
+    return(mutex);
+}
+
+/* Free the mutex */
+void
+SDL_DestroyMutex(SDL_mutex * mutex)
+{
+    if (mutex)
+    {
+        RMutex rmutex;
+        rmutex.SetHandle(mutex->handle);
+        rmutex.Signal();
+        rmutex.Close();
+        delete(mutex);
+        mutex = NULL;
+    }
+}
+
+/* Lock the mutex */
+int
+SDL_LockMutex(SDL_mutex * mutex)
+{
+    if (mutex == NULL)
+    {
+        SDL_SetError("Passed a NULL mutex.");
+        return -1;
+    }
+
+    RMutex rmutex;
+    rmutex.SetHandle(mutex->handle);
+    rmutex.Wait();
+
+    return(0);
+}
+
+/* Unlock the mutex */
+int
+SDL_mutexV(SDL_mutex * mutex)
+{
+    if ( mutex == NULL )
+    {
+        SDL_SetError("Passed a NULL mutex.");
+        return -1;
+    }
+
+    RMutex rmutex;
+    rmutex.SetHandle(mutex->handle);
+    rmutex.Signal();
+
+    return(0);
+}
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/thread/ngage/SDL_syssem.cpp b/src/thread/ngage/SDL_syssem.cpp
new file mode 100644
index 00000000000..f7e97ad52e7
--- /dev/null
+++ b/src/thread/ngage/SDL_syssem.cpp
@@ -0,0 +1,195 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+/* An implementation of semaphores using mutexes and condition variables */
+
+#include <e32std.h>
+
+#include "SDL_error.h"
+#include "SDL_thread.h"
+
+#define SDL_MUTEX_TIMEOUT -2
+
+struct SDL_semaphore
+{
+    TInt handle;
+    TInt count;
+};
+
+struct TInfo
+{
+    TInfo(TInt aTime, TInt aHandle) :
+        iTime(aTime), iHandle(aHandle), iVal(0) {}
+    TInt iTime;
+    TInt iHandle;
+    TInt iVal;
+};
+
+extern TInt CreateUnique(TInt (*aFunc)(const TDesC& aName, TAny*, TAny*), TAny*, TAny*);
+
+static TBool RunThread(TAny* aInfo)
+{
+    TInfo* info = STATIC_CAST(TInfo*, aInfo);
+    User::After(info->iTime);
+    RSemaphore sema;
+    sema.SetHandle(info->iHandle);
+    sema.Signal();
+    info->iVal = SDL_MUTEX_TIMEOUT;
+    return 0;
+}
+
+static TInt
+NewThread(const TDesC& aName, TAny* aPtr1, TAny* aPtr2)
+{
+    return ((RThread*)(aPtr1))->Create
+        (aName,
+         RunThread,
+         KDefaultStackSize,
+         NULL,
+         aPtr2);
+}
+
+static TInt NewSema(const TDesC& aName, TAny* aPtr1, TAny* aPtr2)
+{
+    TInt value = *((TInt*) aPtr2);
+    return ((RSemaphore*)aPtr1)->CreateGlobal(aName, value);
+}
+
+static void WaitAll(SDL_sem *sem)
+{
+    RSemaphore sema;
+    sema.SetHandle(sem->handle);
+    sema.Wait();
+    while(sem->count < 0)
+    {
+        sema.Wait();
+    }
+}
+
+SDL_sem *
+SDL_CreateSemaphore(Uint32 initial_value)
+{
+    RSemaphore s;
+    TInt status = CreateUnique(NewSema, &s, &initial_value);
+    if(status != KErrNone)
+    {
+        SDL_SetError("Couldn't create semaphore");
+    }
+    SDL_semaphore* sem = new /*(ELeave)*/ SDL_semaphore;
+    sem->handle = s.Handle();
+    sem->count = initial_value;
+    return(sem);
+}
+
+void
+SDL_DestroySemaphore(SDL_sem * sem)
+{
+    if (sem)
+    {
+        RSemaphore sema;
+        sema.SetHandle(sem->handle);
+        sema.Signal(sema.Count());
+        sema.Close();
+        delete sem;
+        sem = NULL;
+    }
+}
+
+int
+SDL_SemWaitTimeout(SDL_sem * sem, Uint32 timeout)
+{
+    if (! sem)
+    {
+        SDL_SetError("Passed a NULL sem");
+        return -1;
+    }
+
+    if (timeout == SDL_MUTEX_MAXWAIT)
+    {
+        WaitAll(sem);
+        return SDL_MUTEX_MAXWAIT;
+    }
+
+    RThread thread;
+    TInfo*  info   = new (ELeave)TInfo(timeout, sem->handle);
+    TInt    status = CreateUnique(NewThread, &thread, info);
+
+    if(status != KErrNone)
+    {
+        return status;
+    }
+
+    thread.Resume();
+    WaitAll(sem);
+
+    if(thread.ExitType() == EExitPending)
+    {
+        thread.Kill(SDL_MUTEX_TIMEOUT);
+    }
+
+    thread.Close();
+    return info->iVal;
+}
+
+int
+SDL_SemTryWait(SDL_sem *sem)
+{
+    if(sem->count > 0)
+    {
+        sem->count--;
+    }
+    return SDL_MUTEX_TIMEOUT;
+}
+
+int
+SDL_SemWait(SDL_sem * sem)
+{
+    return SDL_SemWaitTimeout(sem, SDL_MUTEX_MAXWAIT);
+}
+
+Uint32
+SDL_SemValue(SDL_sem * sem)
+{
+    if (! sem)
+    {
+        SDL_SetError("Passed a NULL sem.");
+        return 0;
+    }
+    return sem->count;
+}
+
+int
+SDL_SemPost(SDL_sem * sem)
+{
+    if (! sem)
+    {
+        SDL_SetError("Passed a NULL sem.");
+        return -1;
+    }
+    sem->count++;
+    RSemaphore sema;
+    sema.SetHandle(sem->handle);
+    sema.Signal();
+    return 0;
+}
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/thread/ngage/SDL_systhread.cpp b/src/thread/ngage/SDL_systhread.cpp
new file mode 100644
index 00000000000..18596d99469
--- /dev/null
+++ b/src/thread/ngage/SDL_systhread.cpp
@@ -0,0 +1,147 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#if SDL_THREAD_NGAGE
+
+/* N-Gage thread management routines for SDL */
+
+#include <e32std.h>
+
+extern "C" {
+#undef NULL
+#include "SDL_error.h"
+#include "SDL_thread.h"
+#include "../SDL_systhread.h"
+#include "../SDL_thread_c.h"
+};
+
+static int object_count;
+
+static int
+RunThread(TAny* data)
+{
+    SDL_RunThread((SDL_Thread*)data);
+    return(0);
+}
+
+static TInt
+NewThread(const TDesC& aName, TAny* aPtr1, TAny* aPtr2)
+{
+    return ((RThread*)(aPtr1))->Create
+        (aName,
+         RunThread,
+         KDefaultStackSize,
+         NULL,
+         aPtr2);
+}
+
+int
+CreateUnique(TInt (*aFunc)(const TDesC& aName, TAny*, TAny*), TAny* aPtr1, TAny* aPtr2)
+{
+    TBuf<16> name;
+    TInt     status = KErrNone;
+    do
+    {
+        object_count++;
+        name.Format(_L("SDL_%x"), object_count);
+        status = aFunc(name, aPtr1, aPtr2);
+    }
+    while(status == KErrAlreadyExists);
+    return status;
+}
+
+int
+SDL_SYS_CreateThread(SDL_Thread *thread)
+{
+    RThread rthread;
+
+    TInt status = CreateUnique(NewThread, &rthread, thread);
+    if (status != KErrNone)
+    {
+        delete(((RThread*)(thread->handle)));
+        thread->handle = NULL;
+        SDL_SetError("Not enough resources to create thread");
+        return(-1);
+    }
+
+    rthread.Resume();
+    thread->handle = rthread.Handle();
+    return(0);
+}
+
+void
+SDL_SYS_SetupThread(const char *name)
+{
+    return;
+}
+
+SDL_threadID
+SDL_ThreadID(void)
+{
+    RThread   current;
+    TThreadId id = current.Id();
+    return id;
+}
+
+int
+SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
+{
+    return (0);
+}
+
+void
+SDL_SYS_WaitThread(SDL_Thread * thread)
+{
+    RThread t;
+    t.Open(thread->threadid);
+    if(t.ExitReason() == EExitPending)
+    {
+        TRequestStatus status;
+        t.Logon(status);
+        User::WaitForRequest(status);
+    }
+    t.Close();
+}
+
+void
+SDL_SYS_DetachThread(SDL_Thread * thread)
+{
+    return;
+}
+
+/* WARNING: This function is really a last resort.
+ * Threads should be signaled and then exit by themselves.
+ * TerminateThread() doesn't perform stack and DLL cleanup.
+ */
+void
+SDL_SYS_KillThread(SDL_Thread *thread)
+{
+    RThread rthread;
+    rthread.SetHandle(thread->handle);
+    rthread.Kill(0);
+    rthread.Close();
+}
+
+#endif /* SDL_THREAD_NGAGE */
+
+/* vim: ts=4 sw=4
+ */
diff --git a/src/thread/ngage/SDL_systhread_c.h b/src/thread/ngage/SDL_systhread_c.h
new file mode 100644
index 00000000000..dedceb3965a
--- /dev/null
+++ b/src/thread/ngage/SDL_systhread_c.h
@@ -0,0 +1,25 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+typedef int SYS_ThreadHandle;
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/timer/ngage/SDL_systimer.cpp b/src/timer/ngage/SDL_systimer.cpp
new file mode 100644
index 00000000000..543d8494d75
--- /dev/null
+++ b/src/timer/ngage/SDL_systimer.cpp
@@ -0,0 +1,100 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#if defined(SDL_TIMER_NGAGE)
+
+#include <e32std.h>
+#include <e32hal.h>
+
+#include "SDL_timer.h"
+
+static SDL_bool ticks_started = SDL_FALSE;
+static TUint    start         = 0;
+static TInt     tickPeriodMilliSeconds;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void
+SDL_TicksInit(void)
+{
+    if (ticks_started)
+    {
+        return;
+    }
+    ticks_started = SDL_TRUE;
+    start         = User::TickCount();
+
+    TTimeIntervalMicroSeconds32 period;
+    TInt                        tmp = UserHal::TickPeriod(period);
+
+    (void)tmp; /* Suppress redundant warning. */
+
+    tickPeriodMilliSeconds = period.Int() / 1000;
+}
+
+void
+SDL_TicksQuit(void)
+{
+    ticks_started = SDL_FALSE;
+}
+
+Uint64
+SDL_GetTicks64(void)
+{
+    if (! ticks_started)
+    {
+        SDL_TicksInit();
+    }
+
+    TUint deltaTics = User::TickCount() - start;
+
+    // Overlaps early, but should do the trick for now.
+    return (Uint64)(deltaTics * tickPeriodMilliSeconds);
+}
+
+Uint64
+SDL_GetPerformanceCounter(void)
+{
+    return (Uint64)User::TickCount();
+}
+
+Uint64
+SDL_GetPerformanceFrequency(void)
+{
+    return 1000000;
+}
+
+void
+SDL_Delay(Uint32 ms)
+{
+    User::After(TTimeIntervalMicroSeconds32(ms * 1000));
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SDL_TIMER_NGAGE */
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index b68b92c3786..cea3f542c9c 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -457,6 +457,7 @@ extern VideoBootStrap VIVANTE_bootstrap;
 extern VideoBootStrap Emscripten_bootstrap;
 extern VideoBootStrap QNX_bootstrap;
 extern VideoBootStrap OFFSCREEN_bootstrap;
+extern VideoBootStrap NGAGE_bootstrap;
 extern VideoBootStrap OS2DIVE_bootstrap;
 extern VideoBootStrap OS2VMAN_bootstrap;
 
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 2b896c44ba2..059316a5dd5 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -118,6 +118,9 @@ static VideoBootStrap *bootstrap[] = {
 #if SDL_VIDEO_DRIVER_OFFSCREEN
     &OFFSCREEN_bootstrap,
 #endif
+#if SDL_VIDEO_DRIVER_NGAGE
+    &NGAGE_bootstrap,
+#endif
 #if SDL_VIDEO_DRIVER_OS2
     &OS2DIVE_bootstrap,
     &OS2VMAN_bootstrap,
diff --git a/src/video/ngage/SDL_ngageevents.cpp b/src/video/ngage/SDL_ngageevents.cpp
new file mode 100644
index 00000000000..239f6b62c59
--- /dev/null
+++ b/src/video/ngage/SDL_ngageevents.cpp
@@ -0,0 +1,200 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "../../SDL_internal.h"
+
+#if SDL_VIDEO_DRIVER_NGAGE
+
+/* Being a ngage driver, there's no event stream. We just define stubs for
+   most of the API. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../../events/SDL_events_c.h"
+#include "../../events/SDL_keyboard_c.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+#include "SDL_ngagevideo.h"
+#include "SDL_ngageevents_c.h"
+
+int HandleWsEvent(_THIS, const TWsEvent& aWsEvent);
+
+void
+NGAGE_PumpEvents(_THIS)
+{
+    SDL_VideoData *phdata = (SDL_VideoData*)_this->driverdata;
+
+    while (phdata->NGAGE_WsEventStatus != KRequestPending)
+    {
+        phdata->NGAGE_WsSession.GetEvent(phdata->NGAGE_WsEvent);
+
+        HandleWsEvent(_this, phdata->NGAGE_WsEvent);
+
+        phdata->NGAGE_WsEventStatus = KRequestPending;
+        phdata->NGAGE_WsSession.EventReady(&phdata->NGAGE_WsEventStatus);
+    }
+}
+
+/*****************************************************************************/
+/* Internal                                                                  */
+/*****************************************************************************/
+
+#include <bautils.h>
+#include <hal.h>
+
+extern void DisableKeyBlocking(_THIS);
+extern void RedrawWindowL(_THIS);
+
+TBool isCursorVisible = EFalse;
+
+static SDL_Scancode ConvertScancode(_THIS, int key)
+{
+    SDL_Keycode keycode;
+
+    switch(key)
+    {
+        case EStdKeyBackspace:    // Clear key
+            keycode = SDLK_BACKSPACE;
+            break;
+        case 0x31:                // 1
+            keycode = SDLK_1;
+            break;
+        case 0x32:                // 2
+            keycode = SDLK_2;
+            break;
+        case 0x33:                // 3
+            keycode = SDLK_3;
+            break;
+        case 0x34:                // 4
+            keycode = SDLK_4;
+            break;
+        case 0x35:                // 5
+            keycode = SDLK_5;
+            break;
+        case 0x36:                // 6
+  

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