summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsafarp <safar.pavel@gmail.com>2022-03-21 21:51:29 +0100
committerLinus Groh <mail@linusgroh.de>2022-03-30 14:11:53 +0100
commit278241859bf586dd77b6cdb48d7edce6270803c2 (patch)
tree2c2893168e74e0197abaceb53b4993b0cd8bfa0c
parent502445ad16b5f628f18e3eddc96a8f88e6602c8a (diff)
downloadserenity-278241859bf586dd77b6cdb48d7edce6270803c2.zip
Ports: Add Jagged Alliance 2 Stracciatella port
-rw-r--r--Ports/AvailablePorts.md1
-rwxr-xr-xPorts/ja2/package.sh20
-rw-r--r--Ports/ja2/patches/ReadMe.md5
-rw-r--r--Ports/ja2/patches/fix_SDL2_compilation.patch2452
4 files changed, 2478 insertions, 0 deletions
diff --git a/Ports/AvailablePorts.md b/Ports/AvailablePorts.md
index 263a783349..feb6619d1c 100644
--- a/Ports/AvailablePorts.md
+++ b/Ports/AvailablePorts.md
@@ -78,6 +78,7 @@ Please make sure to keep this list up to date when adding and updating ports. :^
| [`imagemagick`](imagemagick/) | ImageMagick | 7.1.0-23 | https://imagemagick.org |
| [`imgcat`](imgcat/) | imgcat | 2.5.0 | https://github.com/eddieantonio/imgcat |
| [`indent`](indent/) | GNU indent | 2.2.11 | https://www.gnu.org/software/indent/ |
+| [`ja2`](ja2/) | Jagged Alliance 2 Stracciatella | 0.15.x | https://github.com/safarp/ja2-stracciatella/tree/0.15.x |
| [`jot`](jot/) | jot (OpenBSD) | 6.6 | https://github.com/ibara/libpuffy |
| [`jq`](jq/) | jq | 1.6 | https://stedolan.github.io/jq/ |
| [`klong`](klong/) | Klong | 20190926 | https://t3x.org/klong/ |
diff --git a/Ports/ja2/package.sh b/Ports/ja2/package.sh
new file mode 100755
index 0000000000..e546e823b8
--- /dev/null
+++ b/Ports/ja2/package.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env -S bash ../.port_include.sh
+port=ja2
+version=0.15.x
+depends=("SDL2")
+workdir="ja2-stracciatella-${version}"
+files="https://github.com/ja2-stracciatella/ja2-stracciatella/archive/refs/heads/${version}.zip ja2-stracciatella-${version}.zip 178375de4859d16a76276c781455bf48d3fa862841387c8aa6cfa4162f4f0ca4"
+auth_type=sha256
+makeopts="SERENITY=1"
+launcher_name="Jagged Alliance 2"
+launcher_category=Games
+launcher_command="/opt/ja2/ja2"
+
+install() {
+ installdir="${SERENITY_INSTALL_ROOT}/opt/ja2"
+ run mkdir -p "${installdir}"
+ run cp -r ja2 mods externalized "${installdir}"
+ echo "INFO: Jagged Alliance 2 data have to be provided! Copy DATA directory located in the original Jagged Alliance 2 installation into the '${installdir}'."
+ echo "INFO: Boot up SerenityOS and run '/opt/ja2/ja2' executable in order to produce ja2.ini configuration file in the '/home/anon/.ja2'."
+ echo "INFO: Edit the configuration file and set 'data_dir' value to '/opt/ja2'."
+}
diff --git a/Ports/ja2/patches/ReadMe.md b/Ports/ja2/patches/ReadMe.md
new file mode 100644
index 0000000000..16b537b1fd
--- /dev/null
+++ b/Ports/ja2/patches/ReadMe.md
@@ -0,0 +1,5 @@
+# Patches for ja2
+
+## `fix_SDL2_compilation.patch`
+
+Migrates the source code and Makefile to use SDL 2.0 instead of version 1.2. \ No newline at end of file
diff --git a/Ports/ja2/patches/fix_SDL2_compilation.patch b/Ports/ja2/patches/fix_SDL2_compilation.patch
new file mode 100644
index 0000000000..242a396364
--- /dev/null
+++ b/Ports/ja2/patches/fix_SDL2_compilation.patch
@@ -0,0 +1,2452 @@
+diff --git a/Build/Laptop/IMP_Begin_Screen.cc b/Build/Laptop/IMP_Begin_Screen.cc
+index 81db08629..63c6babdb 100644
+--- a/Build/Laptop/IMP_Begin_Screen.cc
++++ b/Build/Laptop/IMP_Begin_Screen.cc
+@@ -398,7 +398,7 @@ static void GetPlayerKeyBoardInputForIMPBeginScreen(void)
+ // handle input events
+ while( DequeueEvent(&InputEvent) )
+ {
+- if( !HandleTextInput( &InputEvent ) && (InputEvent.usEvent == KEY_DOWN || InputEvent.usEvent == KEY_REPEAT) )
++ if( !HandleTextInput( &InputEvent ) && (InputEvent.usEvent == TEXT_INPUT || InputEvent.usEvent == KEY_DOWN || InputEvent.usEvent == KEY_REPEAT) )
+ {
+ switch( InputEvent.usParam )
+ {
+diff --git a/Build/Laptop/IMP_HomePage.cc b/Build/Laptop/IMP_HomePage.cc
+index 1512a045a..bc0157a99 100644
+--- a/Build/Laptop/IMP_HomePage.cc
++++ b/Build/Laptop/IMP_HomePage.cc
+@@ -230,7 +230,7 @@ static void GetPlayerKeyBoardInputForIMPHomePage(void)
+ InputAtom InputEvent;
+ while (DequeueEvent(&InputEvent))
+ {
+- if( !HandleTextInput( &InputEvent ) && (InputEvent.usEvent == KEY_DOWN || InputEvent.usEvent == KEY_REPEAT || InputEvent.usEvent == KEY_UP ) )
++ if( !HandleTextInput( &InputEvent ) && (InputEvent.usEvent == TEXT_INPUT || InputEvent.usEvent == KEY_DOWN || InputEvent.usEvent == KEY_REPEAT || InputEvent.usEvent == KEY_UP ) )
+ {
+ switch( InputEvent.usParam )
+ {
+@@ -247,7 +247,7 @@ static void GetPlayerKeyBoardInputForIMPHomePage(void)
+ case SDLK_ESCAPE: HandleLapTopESCKey(); break;
+
+ default:
+- if(InputEvent.usEvent == KEY_DOWN || InputEvent.usEvent == KEY_REPEAT )
++ if(InputEvent.usEvent == TEXT_INPUT)
+ {
+ HandleTextEvent(&InputEvent);
+ }
+diff --git a/Build/SaveLoadGame.cc b/Build/SaveLoadGame.cc
+index dbf14b827..9f485037b 100644
+--- a/Build/SaveLoadGame.cc
++++ b/Build/SaveLoadGame.cc
+@@ -2545,8 +2545,8 @@ static void UpdateMercMercContractInfo(void)
+ INT8 GetNumberForAutoSave( BOOLEAN fLatestAutoSave )
+ {
+ BOOLEAN fFile1Exist, fFile2Exist;
+- SGP_FILETIME CreationTime1, LastAccessedTime1, LastWriteTime1;
+- SGP_FILETIME CreationTime2, LastAccessedTime2, LastWriteTime2;
++ time_t LastWriteTime1;
++ time_t LastWriteTime2;
+
+ fFile1Exist = FALSE;
+ fFile2Exist = FALSE;
+@@ -2559,15 +2559,13 @@ INT8 GetNumberForAutoSave( BOOLEAN fLatestAutoSave )
+
+ if( GCM->doesGameResExists( zFileName1 ) )
+ {
+- AutoSGPFile hFile(GCM->openUserPrivateFileForReading(std::string(zFileName1)));
+- GetFileManFileTime( hFile, &CreationTime1, &LastAccessedTime1, &LastWriteTime1 );
++ GetFileManFileTime( zFileName1, &LastWriteTime1 );
+ fFile1Exist = TRUE;
+ }
+
+ if( GCM->doesGameResExists( zFileName2 ) )
+ {
+- AutoSGPFile hFile(GCM->openUserPrivateFileForReading(std::string(zFileName2)));
+- GetFileManFileTime( hFile, &CreationTime2, &LastAccessedTime2, &LastWriteTime2 );
++ GetFileManFileTime( zFileName2, &LastWriteTime2 );
+ fFile2Exist = TRUE;
+ }
+
+diff --git a/Build/Utils/Quantize.cc b/Build/Utils/Quantize.cc
+index 998179af2..c036a7833 100644
+--- a/Build/Utils/Quantize.cc
++++ b/Build/Utils/Quantize.cc
+@@ -127,7 +127,7 @@ static size_t GetPaletteColors(const NODE* const pTree, SGPPaletteEntry* const p
+ dst->r = pTree->nRedSum / pTree->nPixelCount;
+ dst->g = pTree->nGreenSum / pTree->nPixelCount;
+ dst->b = pTree->nBlueSum / pTree->nPixelCount;
+- dst->unused = 0;
++ dst->a = 0;
+ }
+ else
+ {
+diff --git a/Build/Utils/Text_Input.cc b/Build/Utils/Text_Input.cc
+index fc64d5813..4869ce29a 100644
+--- a/Build/Utils/Text_Input.cc
++++ b/Build/Utils/Text_Input.cc
+@@ -562,10 +562,19 @@ BOOLEAN HandleTextInput(InputAtom const* const a)
+ // Not in text input mode
+ if (!gfTextInputMode) return FALSE;
+ // Unless we are psycho typers, we only want to process these key events.
+- if (a->usEvent != KEY_DOWN && a->usEvent != KEY_REPEAT) return FALSE;
++ if (a->usEvent != TEXT_INPUT && a->usEvent != KEY_DOWN && a->usEvent != KEY_REPEAT) return FALSE;
+ // Currently in a user field, so return unless TAB is pressed.
+ if (!gfEditingText && a->usParam != SDLK_TAB) return FALSE;
+
++ if (a->usEvent == TEXT_INPUT) {
++ wchar_t const c = a->Char;
++ /* If the key has no character associated, bail out */
++ AssertMsg(c != L'\0', "TEXT_INPUT event sent null character");
++ DeleteHilitedText();
++ HandleRegularInput(c);
++ return TRUE;
++ }
++
+ switch (a->usKeyState)
+ {
+ case 0:
+@@ -642,7 +651,8 @@ BOOLEAN HandleTextInput(InputAtom const* const a)
+ }
+ break;
+
+- default: goto enter_character;
++ default:
++ return TRUE;
+ }
+ break;
+
+@@ -670,14 +680,9 @@ BOOLEAN HandleTextInput(InputAtom const* const a)
+ gubCursorPos = 0;
+ return TRUE;
+
+- default: // Check for typing keys
+-enter_character:
+- wchar_t const c = a->Char;
+- /* If the key has no character associated, bail out */
+- if (c == L'\0') return FALSE;
+- DeleteHilitedText();
+- HandleRegularInput(c);
++ default:
+ return TRUE;
++
+ }
+
+ case CTRL_DOWN:
+diff --git a/Makefile b/Makefile
+index 6db811f3f..53103d351 100644
+--- a/Makefile
++++ b/Makefile
+@@ -38,12 +38,18 @@ CFLAGS += -DGAME_VERSION=\"$(GAME_VERSION)\"
+
+
+ ifdef LOCAL_SDL_LIB
+-CFLAGS_SDL= -I./$(LOCAL_SDL_LIB)/include/SDL -D_GNU_SOURCE=1 -Dmain=SDL_main
+-LDFLAGS_SDL=-L./$(LOCAL_SDL_LIB)/lib -lmingw32 -lSDLmain -lSDL -mwindows
++CFLAGS_SDL= -I./$(LOCAL_SDL_LIB)/include/SDL2 -D_GNU_SOURCE=1 -Dmain=SDL_main
++LDFLAGS_SDL=-L./$(LOCAL_SDL_LIB)/lib -lmingw32 -lSDL2main -lSDL2 -mwindows
+ endif
+
+ ifndef LOCAL_SDL_LIB
+
++# using Serenity SDL2 library
++ifdef SERENITY
++CFLAGS_SDL= -I${SERENITY_BUILD_DIR}/Root/usr/local/include/SDL2 -D_REENTRANT
++LDFLAGS_SDL= -L${SERENITY_BUILD_DIR}/Root/usr/local/lib -lSDL2
++endif
++
+ # using system SDL library
+
+ SDL_CONFIG ?= sdl-config
+@@ -156,6 +162,7 @@ CCFLAGS += -Wimplicit-int
+ CCFLAGS += -Wmissing-prototypes
+
+ CXXFLAGS += $(CFLAGS)
++CXXFLAGS += -std=c++11
+
+ LDFLAGS += -lm
+
+@@ -723,10 +730,10 @@ build-on-mac:
+ make "CFLAGS_SDL=$(MACOS_STATIC_CFLAGS_SDL)" "LDFLAGS_SDL=$(MACOS_STATIC_LDFLAGS_SDL)"
+
+ build-on-win:
+- PATH=/cygdrive/c/MinGW/bin:$$PATH make all USE_MINGW=1 MINGW_PREFIX=/cygdrive/c/MinGW/bin/mingw32 LOCAL_SDL_LIB=_build/lib-SDL-devel-1.2.15-mingw32
++ PATH=/cygdrive/c/MinGW/bin:$$PATH make all USE_MINGW=1 MINGW_PREFIX=/cygdrive/c/MinGW/bin/mingw32 LOCAL_SDL_LIB=_build/lib-SDL2-2.0.4-mingw/i686-w64-mingw32 WITH_LPTHREAD=0
+ cp /cygdrive/c/MinGW/bin/libstdc++-6.dll .
+ cp /cygdrive/c/MinGW/bin/libgcc_s_dw2-1.dll .
+- cp _build/lib-SDL-devel-1.2.15-mingw32/bin/SDL.dll .
++ cp _build/lib-SDL2-2.0.4-mingw/i686-w64-mingw32/bin/SDL2.dll .
+
+ SOURCE_DIR_NAME := ja2-stracciatella_$(VERSION)
+ build-source-archive:
+diff --git a/sgp/FileMan.cc b/sgp/FileMan.cc
+index 0b607c213..cdfca08a2 100644
+--- a/sgp/FileMan.cc
++++ b/sgp/FileMan.cc
+@@ -9,12 +9,10 @@
+ #include "LibraryDataBase.h"
+ #include "MemMan.h"
+ #include "PODObj.h"
+-#include "Logger.h"
+
+ #include "boost/filesystem.hpp"
+
+ #include "slog/slog.h"
+-#define TAG "FileMan"
+
+ #if _WIN32
+ #include <shlobj.h>
+@@ -23,6 +21,7 @@
+ #endif
+
+ #include "PlatformIO.h"
++#include "Debug.h"
+
+ #if MACOS_USE_RESOURCES_FROM_BUNDLE && defined __APPLE__ && defined __MACH__
+ #include <CoreFoundation/CFBundle.h>
+@@ -30,11 +29,15 @@
+
+ #if CASE_SENSITIVE_FS
+ #include <dirent.h>
++#include <SDL_rwops.h>
+ #endif
+
+ // XXX: remove FileMan class and make it into a namespace
+
+ #define LOCAL_CURRENT_DIR "tmp"
++#define SDL_RWOPS_SGP 222
++
++#define DEBUG_TAG_FILEMAN "Fileman"
+
+ enum FileOpenFlags
+ {
+@@ -117,18 +120,23 @@ std::string FileMan::findConfigFolderAndSwitchIntoIt()
+
+ if (mkdir(configFolderPath.c_str(), 0700) != 0 && errno != EEXIST)
+ {
+- LOG_ERROR("Unable to create directory '%s'\n", configFolderPath.c_str());
++ SLOGE(DEBUG_TAG_FILEMAN, "Unable to create tmp directory '%s'", configFolderPath.c_str());
+ throw std::runtime_error("Unable to local directory");
+ }
++ return switchTmpFolder(configFolderPath);
++}
+
++/** Switch config folder. */
++std::string FileMan::switchTmpFolder(std::string home)
++{
+ // Create another directory and set is as the current directory for the process
+ // Temporary files will be created in this directory.
+ // ----------------------------------------------------------------------------
+
+- std::string tmpPath = FileMan::joinPaths(configFolderPath, LOCAL_CURRENT_DIR);
++ std::string tmpPath = FileMan::joinPaths(home, LOCAL_CURRENT_DIR);
+ if (mkdir(tmpPath.c_str(), 0700) != 0 && errno != EEXIST)
+ {
+- LOG_ERROR("Unable to create tmp directory '%s'\n", tmpPath.c_str());
++ SLOGE(DEBUG_TAG_FILEMAN, "Unable to create tmp directory '%s'", tmpPath.c_str());
+ throw std::runtime_error("Unable to create tmp directory");
+ }
+ else
+@@ -136,7 +144,7 @@ std::string FileMan::findConfigFolderAndSwitchIntoIt()
+ SetFileManCurrentDirectory(tmpPath.c_str());
+ }
+
+- return configFolderPath;
++ return home;
+ }
+
+
+@@ -237,19 +245,8 @@ void FileClose(SGPFile* f)
+ MemFree(f);
+ }
+
+-
+-#ifdef JA2TESTVERSION
+-# include "Timer_Control.h"
+-extern UINT32 uiTotalFileReadTime;
+-extern UINT32 uiTotalFileReadCalls;
+-#endif
+-
+ void FileRead(SGPFile* const f, void* const pDest, size_t const uiBytesToRead)
+ {
+-#ifdef JA2TESTVERSION
+- const UINT32 uiStartTime = GetJA2Clock();
+-#endif
+-
+ BOOLEAN ret;
+ if (f->flags & SGPFILE_REAL)
+ {
+@@ -260,12 +257,6 @@ void FileRead(SGPFile* const f, void* const pDest, size_t const uiBytesToRead)
+ ret = LoadDataFromLibrary(&f->u.lib, pDest, (UINT32)uiBytesToRead);
+ }
+
+-#ifdef JA2TESTVERSION
+- //Add the time that we spent in this function to the total.
+- uiTotalFileReadTime += GetJA2Clock() - uiStartTime;
+- uiTotalFileReadCalls++;
+-#endif
+-
+ if (!ret) throw std::runtime_error("Reading from file failed");
+ }
+
+@@ -276,6 +267,80 @@ void FileWrite(SGPFile* const f, void const* const pDest, size_t const uiBytesTo
+ if (fwrite(pDest, uiBytesToWrite, 1, f->u.file) != 1) throw std::runtime_error("Writing to file failed");
+ }
+
++static int64_t SGPSeekRW(SDL_RWops *context, int64_t offset, int whence)
++{
++ SGPFile* sgpFile = (SGPFile*)(context->hidden.unknown.data1);
++ FileSeekMode mode = FILE_SEEK_FROM_CURRENT;
++ switch (whence) {
++ case RW_SEEK_SET:
++ mode = FILE_SEEK_FROM_START;
++ break;
++ case RW_SEEK_END:
++ mode = FILE_SEEK_FROM_END;
++ break;
++ default:
++ break;
++ }
++
++ FileSeek(sgpFile, offset, mode);
++
++ return int64_t(FileGetPos(sgpFile));
++}
++
++static int64_t SGPSizeRW(SDL_RWops *context)
++{
++ SGPFile* sgpFile = (SGPFile*)(context->hidden.unknown.data1);
++
++ return FileGetSize(sgpFile);
++}
++
++static size_t SGPReadRW(SDL_RWops *context, void *ptr, size_t size, size_t maxnum)
++{
++ SGPFile* sgpFile = (SGPFile*)(context->hidden.unknown.data1);
++ UINT32 posBefore = UINT32(FileGetPos(sgpFile));
++
++ FileRead(sgpFile, ptr, size * maxnum);
++
++ UINT32 posAfter = UINT32(FileGetPos(sgpFile));
++
++ return (posAfter - posBefore) / size;
++}
++
++static size_t SGPWriteRW(SDL_RWops *context, const void *ptr, size_t size, size_t num)
++{
++ AssertMsg(false, "SGPWriteRW not supported");
++ return 0;
++}
++
++static int SGPCloseRW(SDL_RWops *context)
++{
++ if(context->type != SDL_RWOPS_SGP)
++ {
++ return SDL_SetError("Wrong kind of SDL_RWops for SGPCloseRW()");
++ }
++ SGPFile* sgpFile = (SGPFile*)(context->hidden.unknown.data1);
++
++ FileClose(sgpFile);
++ SDL_FreeRW(context);
++
++ return 0;
++}
++
++SDL_RWops* FileGetRWOps(SGPFile* const f) {
++ SDL_RWops* rwOps = SDL_AllocRW();
++ if(rwOps == NULL) {
++ return NULL;
++ }
++ rwOps->type = SDL_RWOPS_SGP;
++ rwOps->size = SGPSizeRW;
++ rwOps->seek = SGPSeekRW;
++ rwOps->read = SGPReadRW;
++ rwOps->write= SGPWriteRW;
++ rwOps->close= SGPCloseRW;
++ rwOps->hidden.unknown.data1 = f;
++
++ return rwOps;
++}
+
+ void FileSeek(SGPFile* const f, INT32 distance, FileSeekMode const how)
+ {
+@@ -397,63 +462,36 @@ BOOLEAN FileClearAttributes(const std::string &filename)
+
+ BOOLEAN FileClearAttributes(const char* const filename)
+ {
+-#if 1 // XXX TODO
+- SLOGW(TAG, "ignoring %s(\"%s\")", __func__, filename);
+- return FALSE;
+- // UNIMPLEMENTED
+-#else
+- return SetFileAttributes(filename, FILE_ATTRIBUTE_NORMAL);
+-#endif
++ using namespace boost::filesystem;
++
++ permissions(filename, ( add_perms | owner_read | owner_write | group_read | group_write ));
++ return true;
+ }
+
+
+-BOOLEAN GetFileManFileTime(const SGPFile* f, SGP_FILETIME* const pCreationTime, SGP_FILETIME* const pLastAccessedTime, SGP_FILETIME* const pLastWriteTime)
++BOOLEAN GetFileManFileTime(const char* fileName, time_t* const pLastWriteTime)
+ {
+-#if 1 // XXX TODO
+- UNIMPLEMENTED;
+- return FALSE;
+-#else
+- //Initialize the passed in variables
+- memset(pCreationTime, 0, sizeof(*pCreationTime));
+- memset(pLastAccessedTime, 0, sizeof(*pLastAccessedTime));
+- memset(pLastWriteTime, 0, sizeof(*pLastWriteTime));
+-
+- if (f->flags & SGPFILE_REAL)
++ using namespace boost::filesystem;
++ *pLastWriteTime = last_write_time(fileName);
++ if(*pLastWriteTime == -1)
+ {
+- const HANDLE hRealFile = f->u.file;
+-
+- //Gets the UTC file time for the 'real' file
+- SGP_FILETIME sCreationUtcFileTime;
+- SGP_FILETIME sLastAccessedUtcFileTime;
+- SGP_FILETIME sLastWriteUtcFileTime;
+- GetFileTime(hRealFile, &sCreationUtcFileTime, &sLastAccessedUtcFileTime, &sLastWriteUtcFileTime);
+-
+- //converts the creation UTC file time to the current time used for the file
+- FileTimeToLocalFileTime(&sCreationUtcFileTime, pCreationTime);
+-
+- //converts the accessed UTC file time to the current time used for the file
+- FileTimeToLocalFileTime(&sLastAccessedUtcFileTime, pLastAccessedTime);
+-
+- //converts the write UTC file time to the current time used for the file
+- FileTimeToLocalFileTime(&sLastWriteUtcFileTime, pLastWriteTime);
+- return TRUE;
++ return FALSE;
+ }
+- else
+- {
+- return GetLibraryFileTime(&f->u.lib, pLastWriteTime);
+- }
+-#endif
++ return TRUE;
+ }
+
+
+-INT32 CompareSGPFileTimes(const SGP_FILETIME* const pFirstFileTime, const SGP_FILETIME* const pSecondFileTime)
++INT32 CompareSGPFileTimes(const time_t* const pFirstFileTime, const time_t* const pSecondFileTime)
+ {
+-#if 1 // XXX TODO
+- UNIMPLEMENTED;
+- return 0;
+-#else
+- return CompareFileTime(pFirstFileTime, pSecondFileTime);
+-#endif
++ if ( *pFirstFileTime < *pSecondFileTime )
++ {
++ return -1;
++ }
++ if ( *pFirstFileTime > *pSecondFileTime )
++ {
++ return 1;
++ }
++ return 0;
+ }
+
+
+@@ -462,19 +500,19 @@ FILE* GetRealFileHandleFromFileManFileHandle(const SGPFile* f)
+ return f->flags & SGPFILE_REAL ? f->u.file : f->u.lib.lib->hLibraryHandle;
+ }
+
+-UINT32 GetFreeSpaceOnHardDriveWhereGameIsRunningFrom(void)
++uintmax_t GetFreeSpaceOnHardDriveWhereGameIsRunningFrom(void)
+ {
+-#if 1 // XXX TODO
+- FIXME
+- return 1024 * 1024 * 1024; // XXX TODO return an arbitrary number for now
+-#else
+- //get the drive letter from the exec dir
+- STRING512 zDrive;
+- _splitpath(GetExecutableDirectory(), zDrive, NULL, NULL, NULL);
+-
+- sprintf(zDrive, "%s\\", zDrive);
+- return GetFreeSpaceOnHardDrive(zDrive);
+-#endif
++ using namespace boost::filesystem;
++ space_info si = space(current_path());
++ if (si.available == -1)
++ {
++ /* something is wrong, tell everyone no space available */
++ return 0;
++ }
++ else
++ {
++ return si.available;
++ }
+ }
+
+ /** Join two path components. */
+@@ -562,7 +600,7 @@ bool FileMan::findObjectCaseInsensitive(const char *directory, const char *name,
+ }
+ }
+
+- // LOG_INFO("XXXXX Looking for %s/[ %s ] : %s\n", directory, name, result ? "success" : "failure");
++ // SLOGI(DEBUG_TAG_FILEMAN,"Looking for %s/[ %s ] : %s", directory, name, result ? "success" : "failure");
+ return result;
+ }
+ #endif
+@@ -820,3 +858,10 @@ bool FileMan::checkFileExistance(const char *folder, const char *fileName)
+ path /= fileName;
+ return boost::filesystem::exists(path);
+ }
++
++void FileMan::moveFile(const char *from, const char *to)
++{
++ boost::filesystem::path fromPath(from);
++ boost::filesystem::path toPath(to);
++ boost::filesystem::rename(fromPath, toPath);
++}
+diff --git a/sgp/FileMan.h b/sgp/FileMan.h
+index a7f96cbf3..d4fdf3c2a 100644
+--- a/sgp/FileMan.h
++++ b/sgp/FileMan.h
+@@ -10,8 +10,6 @@
+ #ifdef _WIN32
+ # define WIN32_LEAN_AND_MEAN
+ # include <windows.h>
+-#else
+-# include <glob.h>
+ #endif
+
+ /* Delete the file at path. Returns true iff deleting the file succeeded or
+@@ -21,6 +19,7 @@ void FileDelete(const std::string &path);
+
+ void FileRead( SGPFile*, void* pDest, size_t uiBytesToRead);
+ void FileWrite(SGPFile*, void const* pDest, size_t uiBytesToWrite);
++SDL_RWops* FileGetRWOps(SGPFile* const f);
+
+ template<typename T, typename U> static inline void FileWriteArray(SGPFile* const f, T const& n, U const* const data)
+ {
+@@ -51,13 +50,13 @@ BOOLEAN FileClearAttributes(const char* filename);
+ BOOLEAN FileClearAttributes(const std::string &filename);
+
+
+-BOOLEAN GetFileManFileTime(const SGPFile* hFile, SGP_FILETIME* pCreationTime, SGP_FILETIME* pLastAccessedTime, SGP_FILETIME* pLastWriteTime);
++BOOLEAN GetFileManFileTime(const char* fileName, time_t* pLastWriteTime);
+
+ /* returns
+ * - -1 if the First file time is less than second file time. (first file is older)
+ * - 0 First file time is equal to second file time.
+ * - +1 First file time is greater than second file time (first file is newer). */
+-INT32 CompareSGPFileTimes(const SGP_FILETIME* const pFirstFileTime, const SGP_FILETIME* const pSecondFileTime);
++INT32 CompareSGPFileTimes(const time_t* const pFirstFileTime, const time_t* const pSecondFileTime);
+
+ /* Pass in the Fileman file handle of an OPEN file and it will return..
+ * - if its a Real File, the return will be the handle of the REAL file
+@@ -65,7 +64,7 @@ INT32 CompareSGPFileTimes(const SGP_FILETIME* const pFirstFileTime, const SGP_FI
+ FILE* GetRealFileHandleFromFileManFileHandle(const SGPFile* hFile);
+
+ //Gets the amount of free space on the hard drive that the main executeablt is runnning from
+-UINT32 GetFreeSpaceOnHardDriveWhereGameIsRunningFrom(void);
++uintmax_t GetFreeSpaceOnHardDriveWhereGameIsRunningFrom(void);
+
+ /***
+ * New file manager.
+@@ -78,6 +77,9 @@ public:
+ /** Find config folder and switch into it. */
+ static std::string findConfigFolderAndSwitchIntoIt();
+
++ /** Switch config folder into it. */
++ static std::string switchTmpFolder(std::string homeDir);
++
+ /** Open file for writing.
+ * If file is missing it will be created.
+ * If file exists, it's content will be removed. */
+@@ -156,6 +158,9 @@ public:
+ /** Check file existance. */
+ static bool checkFileExistance(const char *folder, const char *fileName);
+
++ /** Move a file */
++ static void moveFile(const char *from, const char *to);
++
+ private:
+ /** Private constructor to avoid instantiation. */
+ FileMan() {};
+diff --git a/sgp/Input.cc b/sgp/Input.cc
+index 3b64602b5..6d82a147a 100644
+--- a/sgp/Input.cc
++++ b/sgp/Input.cc
+@@ -1,3 +1,5 @@
++#include <bitset>
++#include "UTF8String.h"
+ #include "Types.h"
+ #include "Input.h"
+ #include "MemMan.h"
+@@ -7,11 +9,12 @@
+ #include "Local.h"
+ #include "UILayout.h"
+
++#include "slog/slog.h"
+
+-// The gfKeyState table is used to track which of the keys is up or down at any one time. This is used while polling
+-// the interface.
+
+-BOOLEAN gfKeyState[SDLK_LAST]; // TRUE = Pressed, FALSE = Not Pressed
++// The gfKeyState table is used to track which of the keys is up or down at any one time. This is used while polling
++// the interface. true = pressed, false = not pressed.
++static std::bitset<2 * SDL_NUM_SCANCODES> gfKeyState;
+ static BOOLEAN fCursorWasClipped = FALSE;
+ static SGPRect gCursorClipRect;
+
+@@ -22,9 +25,11 @@ static UINT32 guiSingleClickTimer;
+
+ static UINT32 guiLeftButtonRepeatTimer;
+ static UINT32 guiRightButtonRepeatTimer;
++static UINT32 guiMiddleButtonRepeatTimer;
+
+ BOOLEAN gfLeftButtonState; // TRUE = Pressed, FALSE = Not Pressed
+ BOOLEAN gfRightButtonState; // TRUE = Pressed, FALSE = Not Pressed
++BOOLEAN gfMiddleButtonState;// TRUE = Pressed, FALSE = Not Pressed
+ UINT16 gusMouseXPos; // X position of the mouse on screen
+ UINT16 gusMouseYPos; // y position of the mouse on screen
+
+@@ -49,7 +54,7 @@ static void QueueMouseEvent(UINT16 ubInputEvent)
+ }
+
+
+-static void QueueKeyEvent(UINT16 ubInputEvent, SDLKey Key, SDLMod Mod, wchar_t Char)
++static void QueueKeyEvent(UINT16 ubInputEvent, SDL_Keycode Key, SDL_Keymod Mod, wchar_t Char)
+ {
+ // Can we queue up one more event, if not, the event is lost forever
+ if (gusQueueCount == lengthof(gEventQueue)) return;
+@@ -68,6 +73,16 @@ static void QueueKeyEvent(UINT16 ubInputEvent, SDLKey Key, SDLMod Mod, wchar_t C
+ gusTailIndex = (gusTailIndex + 1) % lengthof(gEventQueue);
+ }
+
++void SetSafeMousePosition(int x, int y) {
++ if (x < 0) x = 0;
++ if (y < 0) y = 0;
++ if (x > SCREEN_WIDTH) x = SCREEN_WIDTH;
++ if (y > SCREEN_HEIGHT) y = SCREEN_HEIGHT;
++
++ gusMouseXPos = x;
++ gusMouseYPos = y;
++}
++
+
+ BOOLEAN DequeueSpecificEvent(InputAtom* Event, UINT32 uiMaskFlags)
+ {
+@@ -103,12 +118,11 @@ BOOLEAN DequeueEvent(InputAtom* Event)
+
+ static void UpdateMousePos(const SDL_MouseButtonEvent* BtnEv)
+ {
+- gusMouseXPos = BtnEv->x;
+- gusMouseYPos = BtnEv->y;
++ SetSafeMousePosition(BtnEv->x, BtnEv->y);
+ }
+
+
+-#if defined WITH_MAEMO
++#if defined(WITH_MAEMO) || defined __APPLE__
+ static BOOLEAN g_down_right;
+ #endif
+
+@@ -123,8 +137,13 @@ void MouseButtonDown(const SDL_MouseButtonEvent* BtnEv)
+ #if defined WITH_MAEMO
+ /* If the menu button (mapped to F4) is pressed, then treat the event as
+ * right click */
+- const Uint8* const key_state = SDL_GetKeyState(NULL);
+- g_down_right = key_state[SDLK_F4];
++ const Uint8* const key_state = SDL_GetKeyboardState(NULL);
++ g_down_right = key_state[SDL_SCANCODE_F4];
++ if (g_down_right) goto right_button;
++#endif
++#if defined(__APPLE__)
++ const Uint8* const key_state = SDL_GetKeyboardState(NULL);
++ g_down_right = key_state[SDL_SCANCODE_LGUI] || key_state[SDL_SCANCODE_RGUI];
+ if (g_down_right) goto right_button;
+ #endif
+ guiLeftButtonRepeatTimer = GetClock() + BUTTON_REPEAT_TIMEOUT;
+@@ -134,7 +153,7 @@ void MouseButtonDown(const SDL_MouseButtonEvent* BtnEv)
+ }
+
+ case SDL_BUTTON_RIGHT:
+-#if defined WITH_MAEMO
++#if defined(WITH_MAEMO) || defined(__APPLE__)
+ right_button:
+ #endif
+ guiRightButtonRepeatTimer = GetClock() + BUTTON_REPEAT_TIMEOUT;
+@@ -142,8 +161,11 @@ right_button:
+ QueueMouseEvent(RIGHT_BUTTON_DOWN);
+ break;
+
+- case SDL_BUTTON_WHEELUP: QueueMouseEvent(MOUSE_WHEEL_UP); break;
+- case SDL_BUTTON_WHEELDOWN: QueueMouseEvent(MOUSE_WHEEL_DOWN); break;
++ case SDL_BUTTON_MIDDLE:
++ guiMiddleButtonRepeatTimer = GetClock() + BUTTON_REPEAT_TIMEOUT;
++ gfMiddleButtonState = TRUE;
++ QueueMouseEvent(MIDDLE_BUTTON_DOWN);
++ break;
+ }
+ }
+
+@@ -155,7 +177,7 @@ void MouseButtonUp(const SDL_MouseButtonEvent* BtnEv)
+ {
+ case SDL_BUTTON_LEFT:
+ {
+-#if defined WITH_MAEMO
++#if defined(WITH_MAEMO) || defined(__APPLE__)
+ if (g_down_right) goto right_button;
+ #endif
+ guiLeftButtonRepeatTimer = 0;
+@@ -174,21 +196,55 @@ void MouseButtonUp(const SDL_MouseButtonEvent* BtnEv)
+ }
+
+ case SDL_BUTTON_RIGHT:
+-#if defined WITH_MAEMO
++#if defined WITH_MAEMO || defined(__APPLE__)
+ right_button:
+ #endif
+ guiRightButtonRepeatTimer = 0;
+ gfRightButtonState = FALSE;
+ QueueMouseEvent(RIGHT_BUTTON_UP);
+ break;
++
++ case SDL_BUTTON_MIDDLE:
++ guiMiddleButtonRepeatTimer = 0;
++ gfMiddleButtonState = FALSE;
++ QueueMouseEvent(MIDDLE_BUTTON_UP);
++ break;
++ }
++}
++
++void MouseWheelScroll(const SDL_MouseWheelEvent* WheelEv)
++{
++ if (WheelEv->y > 0)
++ {
++ QueueMouseEvent(MOUSE_WHEEL_UP);
++ }
++ else
++ {
++ QueueMouseEvent(MOUSE_WHEEL_DOWN);
+ }
+ }
+
+
+-static void KeyChange(SDL_keysym const* const key_sym, bool const pressed)
++// Remap SDL keycodes with bit 30 set to the range 512..1023
++// Necessary to be able to use the keycode as an index for the gfKeyState bitset.
++static SDL_Keycode RemapKeycode(SDL_Keycode const key)
++{
++ return (key & SDLK_SCANCODE_MASK)
++ ? (key & ~SDLK_SCANCODE_MASK) + SDL_NUM_SCANCODES
++ : key;
++}
++
++
++bool _KeyDown(SDL_Keycode const keycode)
++{
++ return gfKeyState[RemapKeycode(keycode)];
++}
++
++
++static void KeyChange(SDL_Keysym const* const key_sym, bool const pressed)
+ {
+- SDLKey key = key_sym->sym;
+- SDLMod const mod = key_sym->mod;
++ SDL_Keycode key = key_sym->sym;
++ SDL_Keymod const mod = (SDL_Keymod) key_sym->mod;
+ bool const num = mod & KMOD_NUM;
+ switch (key)
+ {
+@@ -197,19 +253,19 @@ static void KeyChange(SDL_keysym const* const key_sym, bool const pressed)
+ case SDLK_F4: return;
+ #endif
+
+- case SDLK_KP0: key = num ? SDLK_0 : SDLK_INSERT; break;
+- case SDLK_KP1: key = num ? SDLK_1 : SDLK_END; break;
+- case SDLK_KP2: key = num ? SDLK_2 : SDLK_DOWN; break;
+- case SDLK_KP3: key = num ? SDLK_3 : SDLK_PAGEDOWN; break;
+- case SDLK_KP4: key = num ? SDLK_4 : SDLK_LEFT; break;
+- case SDLK_KP5:
++ case SDLK_KP_0: key = num ? SDLK_0 : SDLK_INSERT; break;
++ case SDLK_KP_1: key = num ? SDLK_1 : SDLK_END; break;
++ case SDLK_KP_2: key = num ? SDLK_2 : SDLK_DOWN; break;
++ case SDLK_KP_3: key = num ? SDLK_3 : SDLK_PAGEDOWN; break;
++ case SDLK_KP_4: key = num ? SDLK_4 : SDLK_LEFT; break;
++ case SDLK_KP_5:
+ if (!num) return;
+ key = SDLK_5;
+ break;
+- case SDLK_KP6: key = num ? SDLK_6 : SDLK_RIGHT; break;
+- case SDLK_KP7: key = num ? SDLK_7 : SDLK_HOME; break;
+- case SDLK_KP8: key = num ? SDLK_8 : SDLK_UP; break;
+- case SDLK_KP9: key = num ? SDLK_9 : SDLK_PAGEUP; break;
++ case SDLK_KP_6: key = num ? SDLK_6 : SDLK_RIGHT; break;
++ case SDLK_KP_7: key = num ? SDLK_7 : SDLK_HOME; break;
++ case SDLK_KP_8: key = num ? SDLK_8 : SDLK_UP; break;
++ case SDLK_KP_9: key = num ? SDLK_9 : SDLK_PAGEUP; break;
+ case SDLK_KP_PERIOD: key = num ? SDLK_PERIOD : SDLK_DELETE; break;
+ case SDLK_KP_DIVIDE: key = SDLK_SLASH; break;
+ case SDLK_KP_MULTIPLY: key = SDLK_ASTERISK; break;
+@@ -218,12 +274,12 @@ static void KeyChange(SDL_keysym const* const key_sym, bool const pressed)
+ case SDLK_KP_ENTER: key = SDLK_RETURN; break;
+
+ default:
+- if (key >= lengthof(gfKeyState)) return;
++ if ((key & ~SDLK_SCANCODE_MASK) >= SDL_NUM_SCANCODES) return;
+ break;
+ }
+
+ UINT event_type;
+- BOOLEAN& key_state = gfKeyState[key];
++ bool key_state = _KeyDown(key);
+ if (pressed)
+ {
+ event_type = key_state ? KEY_REPEAT : KEY_DOWN;
+@@ -233,32 +289,33 @@ static void KeyChange(SDL_keysym const* const key_sym, bool const pressed)
+ if (!key_state) return;
+ event_type = KEY_UP;
+ }
+- key_state = pressed;
+- QueueKeyEvent(event_type, key, mod, key_sym->unicode);
++ gfKeyState[RemapKeycode(key)] = pressed;
++
++ QueueKeyEvent(event_type, key, mod, '\0');
+ }
+
+
+-void KeyDown(const SDL_keysym* KeySym)
++void KeyDown(const SDL_Keysym* KeySym)
+ {
+ switch (KeySym->sym)
+ {
+ case SDLK_LSHIFT:
+ case SDLK_RSHIFT:
+- _KeyDown(SHIFT) = TRUE;
++ gfKeyState.set(SHIFT);
+ break;
+
+ case SDLK_LCTRL:
+ case SDLK_RCTRL:
+- _KeyDown(CTRL) = TRUE;
++ gfKeyState.set(CTRL);
+ break;
+
+ case SDLK_LALT:
+ case SDLK_RALT:
+- _KeyDown(ALT) = TRUE;
++ gfKeyState.set(ALT);
+ break;
+
+- case SDLK_PRINT:
+- case SDLK_SCROLLOCK:
++ case SDLK_PRINTSCREEN:
++ case SDLK_SCROLLLOCK:
+ break;
+
+ default:
+@@ -268,34 +325,35 @@ void KeyDown(const SDL_keysym* KeySym)
+ }
+
+
+-void KeyUp(const SDL_keysym* KeySym)
++void KeyUp(const SDL_Keysym* KeySym)
+ {
+ switch (KeySym->sym)
+ {
+ case SDLK_LSHIFT:
+ case SDLK_RSHIFT:
+- _KeyDown(SHIFT) = FALSE;
++ gfKeyState.reset(SHIFT);
+ break;
+
+ case SDLK_LCTRL:
+ case SDLK_RCTRL:
+- _KeyDown(CTRL) = FALSE;
++ gfKeyState.reset(CTRL);
+ break;
+
+ case SDLK_LALT:
+ case SDLK_RALT:
+- _KeyDown(ALT) = FALSE;
++ gfKeyState.reset(ALT);
+ break;
+
+- case SDLK_PRINT:
+- if (KeySym->mod & KMOD_CTRL) VideoCaptureToggle(); else PrintScreen();
++ case SDLK_PRINTSCREEN:
++ PrintScreen();
+ break;
+
+- case SDLK_SCROLLOCK:
+- SDL_WM_GrabInput
++ case SDLK_SCROLLLOCK:
++ SDL_SetWindowGrab
+ (
+- SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF ?
+- SDL_GRAB_ON : SDL_GRAB_OFF
++ GAME_WINDOW,
++ SDL_GetWindowGrab(GAME_WINDOW) == SDL_FALSE ?
++ SDL_TRUE : SDL_FALSE
+ );
+ break;
+
+@@ -313,14 +371,28 @@ void KeyUp(const SDL_keysym* KeySym)
+ }
+ }
+
++void TextInput(const SDL_TextInputEvent* TextEv) {
++ try {
++ UTF8String utf8String = UTF8String(TextEv->text);
++ QueueKeyEvent(TEXT_INPUT, SDLK_UNKNOWN, KMOD_NONE, utf8String.getUTF16()[0]);
++ }
++ catch (const InvalidEncodingException&)
++ {
++ // ignore invalid inputs
++ static bool warn = true;
++ if (warn)
++ {
++ SLOGW("SGP", "Received invalid utf-8 character.");
++ warn = false;
++ }
++ }
++}
++
+
+ void GetMousePos(SGPPoint* Point)
+ {
+- int x;
+- int y;
+- SDL_GetMouseState(&x, &y);
+- Point->iX = x;
+- Point->iY = y;
++ Point->iX = gusMouseXPos;
++ Point->iY = gusMouseYPos;
+ }
+
+
+@@ -356,20 +428,6 @@ void FreeMouseCursor(void)
+ fCursorWasClipped = FALSE;
+ }
+
+-
+-void RestoreCursorClipRect(void)
+-{
+-#if 1 // XXX TODO0000
+- UNIMPLEMENTED
+-#else
+- if (fCursorWasClipped)
+- {
+- ClipCursor(&gCursorClipRect);
+- }
+-#endif
+-}
+-
+-
+ void GetRestrictedClipCursor(SGPRect* pRectangle)
+ {
+ #if 1 // XXX TODO0000
+@@ -391,26 +449,27 @@ BOOLEAN IsCursorRestricted(void)
+
+ void SimulateMouseMovement( UINT32 uiNewXPos, UINT32 uiNewYPos )
+ {
+- // Wizardry NOTE: This function currently doesn't quite work right for in any Windows resolution other than 640x480.
+- // mouse_event() uses your current Windows resolution to calculate the resulting x,y coordinates. So in order to get
+- // the right coordinates, you'd have to find out the current Windows resolution through a system call, and then do:
+- // uiNewXPos = uiNewXPos * SCREEN_WIDTH / WinScreenResX;
+- // uiNewYPos = uiNewYPos * SCREEN_HEIGHT / WinScreenResY;
+- //
+- // JA2 doesn't have this problem, 'cause they use DirectDraw calls that change the Windows resolution properly.
+- //
+- // Alex Meduna, Dec. 3, 1997
+-
+-#if 1
+- FIXME
+- SDL_WarpMouse(uiNewXPos, uiNewYPos);
+-#else
+- // Adjust coords based on our resolution
+- FLOAT flNewXPos = (FLOAT)uiNewXPos / SCREEN_WIDTH * 65536;
+- FLOAT flNewYPos = (FLOAT)uiNewYPos / SCREEN_HEIGHT * 65536;
++ int windowWidth, windowHeight;
++ SDL_GetWindowSize(GAME_WINDOW, &windowWidth, &windowHeight);
+
+- mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, (UINT32)flNewXPos, (UINT32)flNewYPos, 0, 0);
+-#endif
++ double windowWidthD = windowWidth;
++ double windowHeightD = windowHeight;
++ double screenWidthD = SCREEN_WIDTH;
++ double screenHeightD = SCREEN_HEIGHT;
++
++ double scaleFactorX = windowWidthD / screenWidthD;
++ double scaleFactorY = windowHeightD / screenHeightD;
++ double scaleFactor = windowWidth > windowHeight ? scaleFactorY : scaleFactorX;
++
++ double scaledWindowWidth = scaleFactor * screenWidthD;
++ double scaledWindowHeight = scaleFactor * screenHeightD;
++
++ double paddingX = (windowWidthD - scaledWindowWidth) / 2.0;
++ double paddingY = (windowHeight - scaledWindowHeight) / 2.0;
++ int windowPositionX = paddingX + (double)uiNewXPos * scaledWindowWidth / screenWidthD;
++ int windowPositionY = paddingY + (double)uiNewYPos * scaledWindowHeight / screenHeightD;
++
++ SDL_WarpMouseInWindow(GAME_WINDOW, windowPositionX, windowPositionY);
+ }
+
+
+@@ -465,4 +524,18 @@ static void HandleSingleClicksAndButtonRepeats(void)
+ {
+ guiRightButtonRepeatTimer = 0;
+ }
++
++ // Is there a MIDDLE mouse button repeat
++ if (gfMiddleButtonState)
++ {
++ if ((guiMiddleButtonRepeatTimer > 0)&&(guiMiddleButtonRepeatTimer <= uiTimer))
++ {
++ QueueMouseEvent(MIDDLE_BUTTON_REPEAT);
++ guiMiddleButtonRepeatTimer = uiTimer + BUTTON_REPEAT_TIME;
++ }
++ }
++ else
++ {
++ guiMiddleButtonRepeatTimer = 0;
++ }
+ }
+diff --git a/sgp/Input.h b/sgp/Input.h
+index 84d7cd185..0da0517a6 100644
+--- a/sgp/Input.h
++++ b/sgp/Input.h
+@@ -8,6 +8,7 @@
+ #define KEY_DOWN 0x0001
+ #define KEY_UP 0x0002
+ #define KEY_REPEAT 0x0004
++#define TEXT_INPUT 0x0006
+ #define LEFT_BUTTON_DOWN 0x0008
+ #define LEFT_BUTTON_UP 0x0010
+ #define LEFT_BUTTON_DBL_CLK 0x0020
+@@ -18,7 +19,11 @@
+ #define MOUSE_POS 0x0400
+ #define MOUSE_WHEEL_UP 0x0800
+ #define MOUSE_WHEEL_DOWN 0x1000
+-#define MOUSE_EVENTS 0x1FF8
++#define MOUSE_EVENTS 0xFFF8
++
++#define MIDDLE_BUTTON_DOWN 0x2000
++#define MIDDLE_BUTTON_UP 0x4000
++#define MIDDLE_BUTTON_REPEAT 0x8000
+
+ #define SHIFT_DOWN 0x01
+ #define CTRL_DOWN 0x02
+@@ -41,9 +46,11 @@ extern BOOLEAN DequeueEvent(InputAtom *Event);
+
+ void MouseButtonDown(const SDL_MouseButtonEvent*);
+ void MouseButtonUp(const SDL_MouseButtonEvent*);
++void MouseWheelScroll(const SDL_MouseWheelEvent*);
+
+-void KeyDown(const SDL_keysym*);
+-void KeyUp( const SDL_keysym*);
++void KeyDown(const SDL_Keysym*);
++void KeyUp( const SDL_Keysym*);
++void TextInput( const SDL_TextInputEvent*);
+
+ extern void GetMousePos(SGPPoint *Point);
+
+@@ -51,10 +58,10 @@ extern BOOLEAN DequeueSpecificEvent(InputAtom *Event, UINT32 uiMaskFlags );
+
+ extern void RestrictMouseToXYXY(UINT16 usX1, UINT16 usY1, UINT16 usX2, UINT16 usY2);
+ void RestrictMouseCursor(const SGPRect* pRectangle);
++extern void SetSafeMousePosition(int x, int y);
+ extern void FreeMouseCursor(void);
+ extern BOOLEAN IsCursorRestricted( void );
+ extern void GetRestrictedClipCursor( SGPRect *pRectangle );
+-extern void RestoreCursorClipRect( void );
+
+
+ void SimulateMouseMovement( UINT32 uiNewXPos, UINT32 uiNewYPos );
+@@ -62,16 +69,15 @@ void SimulateMouseMovement( UINT32 uiNewXPos, UINT32 uiNewYPos );
+ void DequeueAllKeyBoardEvents(void);
+
+
+-extern BOOLEAN gfKeyState[SDLK_LAST]; // TRUE = Pressed, FALSE = Not Pressed
+-
+ extern UINT16 gusMouseXPos; // X position of the mouse on screen
+ extern UINT16 gusMouseYPos; // y position of the mouse on screen
+ extern BOOLEAN gfLeftButtonState; // TRUE = Pressed, FALSE = Not Pressed
+ extern BOOLEAN gfRightButtonState; // TRUE = Pressed, FALSE = Not Pressed
++extern BOOLEAN gfMiddleButtonState;
+
+-
+-#define _KeyDown(a) gfKeyState[(a)]
++bool _KeyDown(SDL_Keycode);
+ #define _LeftButtonDown gfLeftButtonState
+ #define _RightButtonDown gfRightButtonState
++#define _MiddleButtonDown gfMiddleButtonState
+
+ #endif
+diff --git a/sgp/PCX.cc b/sgp/PCX.cc
+index cf1719bad..bfea6df43 100644
+--- a/sgp/PCX.cc
++++ b/sgp/PCX.cc
+@@ -75,7 +75,7 @@ SGPImage* LoadPCXFileToImage(char const* const filename, UINT16 const contents)
+ dst[i].r = palette[i * 3 + 0];
+ dst[i].g = palette[i * 3 + 1];
+ dst[i].b = palette[i * 3 + 2];
+- dst[i].unused = 0;
++ dst[i].a = 0;
+ }
+ img->pui16BPPPalette = Create16BPPPalette(dst);
+ }
+diff --git a/sgp/Platform.h b/sgp/Platform.h
+index 263f9b1b6..c232813e2 100644
+--- a/sgp/Platform.h
++++ b/sgp/Platform.h
+@@ -43,11 +43,9 @@
+ #define __func__ __FUNCTION__
+ #endif
+
+-#if !defined(_WIN32)
+- /* Not Visual Studio, not MINGW */
+- #define __max(a, b) ((a) > (b) ? (a) : (b))
+- #define __min(a, b) ((a) < (b) ? (a) : (b))
+-#endif
++/* Not Visual Studio, not MINGW */
++#define __max(a, b) ((a) > (b) ? (a) : (b))
++#define __min(a, b) ((a) < (b) ? (a) : (b))
+
+ /**************************************************************
+ *
+diff --git a/sgp/SGP.cc b/sgp/SGP.cc
+index 7b9b71048..1c43c4c61 100644
+--- a/sgp/SGP.cc
++++ b/sgp/SGP.cc
+@@ -212,28 +212,28 @@ static void MainLoop(int msPerGameCycle)
+ {
+ switch (event.type)
+ {
+- case SDL_ACTIVEEVENT:
+- if (event.active.state & SDL_APPACTIVE)
+- {
+- s_doGameCycles = (event.active.gain != 0);
+- break;
+- }
++ case SDL_APP_WILLENTERBACKGROUND:
++ s_doGameCycles = false;
++ break;
++
++ case SDL_APP_WILLENTERFOREGROUND:
++ s_doGameCycles = true;
+ break;
+
+ case SDL_KEYDOWN: KeyDown(&event.key.keysym); break;
+ case SDL_KEYUP: KeyUp( &event.key.keysym); break;
++ case SDL_TEXTINPUT: TextInput(&event.text); break;
+
+ case SDL_MOUSEBUTTONDOWN: MouseButtonDown(&event.button); break;
+ case SDL_MOUSEBUTTONUP: MouseButtonUp(&event.button); break;
+
+ case SDL_MOUSEMOTION:
+- gusMouseXPos = event.motion.x;
+- gusMouseYPos = event.motion.y;
++ SetSafeMousePosition(event.motion.x, event.motion.y);
+ break;
+
+- case SDL_QUIT:
+- deinitGameAndExit();
+- break;
++ case SDL_MOUSEWHEEL: MouseWheelScroll(&event.wheel); break;
++
++ case SDL_QUIT: deinitGameAndExit(); break;
+ }
+ }
+ else
+@@ -358,7 +358,6 @@ try
+ ////////////////////////////////////////////////////////////
+
+ SDL_Init(SDL_INIT_VIDEO);
+- SDL_EnableUNICODE(SDL_ENABLE);
+
+ #ifdef __APPLE__
+ // Enable 3-button mouse support if the user haven't instructed
+diff --git a/sgp/STCI.cc b/sgp/STCI.cc
+index c5d958ec8..bc200f7da 100644
+--- a/sgp/STCI.cc
++++ b/sgp/STCI.cc
+@@ -113,7 +113,7 @@ static SGPImage* STCILoadIndexed(UINT16 const contents, HWFILE const f, STCIHead
+ palette[i].r = pSTCIPalette[i].ubRed;
+ palette[i].g = pSTCIPalette[i].ubGreen;
+ palette[i].b = pSTCIPalette[i].ubBlue;
+- palette[i].unused = 0;
++ palette[i].a = 0;
+ }
+
+ img->fFlags |= IMAGE_PALETTE;
+diff --git a/sgp/SoundMan.cc b/sgp/SoundMan.cc
+index e37d632e7..aff29801e 100644
+--- a/sgp/SoundMan.cc
++++ b/sgp/SoundMan.cc
+@@ -14,22 +14,18 @@
+ #include "Timer.h"
+ #include <SDL.h>
+ #include <assert.h>
++#include <stdexcept>
+
+ #include "ContentManager.h"
+ #include "GameInstance.h"
++#include "slog/slog.h"
+
++#define DEBUG_TAG_SOUND "Sound"
++#define DEBUG_TAG_ASSERTS "Asserts"
+
+ // Uncomment this to disable the startup of sound hardware
+ //#define SOUND_DISABLE
+
+-
+-#ifdef WITH_SOUND_DEBUG
+-# define SNDDBG(fmt, ...) (void)fprintf(stderr, ">>>> SND: " fmt, __VA_ARGS__)
+-#else
+-# define SNDDBG(fmt, ...) (void)0
+-#endif
+-
+-
+ /*
+ * from\to FREE PLAY STOP DEAD
+ * FREE M
+@@ -59,25 +55,17 @@ enum
+ SAMPLE_ALLOCATED = 1U << 0,
+ SAMPLE_LOCKED = 1U << 1,
+ SAMPLE_RANDOM = 1U << 2,
+- SAMPLE_STEREO = 1U << 3,
+- SAMPLE_16BIT = 1U << 4
++ SAMPLE_STEREO = 1U << 3
+ };
+
+
+ #define SOUND_MAX_CACHED 128 // number of cache slots
++#define SOUND_MAX_CHANNELS 16 // number of mixer channels
+
+-#ifdef JA2
+-# define SOUND_MAX_CHANNELS 16 // number of mixer channels
+-#else
+-# define SOUND_MAX_CHANNELS 32 // number of mixer channels
+-#endif
+-
+-
+-#define SOUND_DEFAULT_MEMORY (16 * 1024 * 1024) // default memory limit
++#define SOUND_DEFAULT_MEMORY (32 * 1024 * 1024) // default memory limit
+ #define SOUND_DEFAULT_THRESH ( 2 * 1024 * 1024) // size for sample to be double-buffered
+ #define SOUND_DEFAULT_STREAM (64 * 1024) // double-buffered buffer size
+
+-
+ // Struct definition for sample slots in the cache
+ // Holds the regular sample data, as well as the data for the random samples
+ struct SAMPLETAG
+@@ -85,7 +73,6 @@ struct SAMPLETAG
+ CHAR8 pName[128]; // Path to sample data
+ UINT32 n_samples;
+ UINT32 uiFlags; // Status flags
+- UINT32 uiSpeed; // Playback frequency
+ PTR pData; // pointer to sample data memory
+ UINT32 uiCacheHits;
+
+@@ -121,15 +108,19 @@ struct SOUNDTAG
+ UINT32 Pan;
+ };
+
+-
++static size_t GetSampleSize(const SAMPLETAG* const s);
+ static const UINT32 guiSoundDefaultVolume = MAXVOLUME;
+ static const UINT32 guiSoundMemoryLimit = SOUND_DEFAULT_MEMORY; // Maximum memory used for sounds
+ static UINT32 guiSoundMemoryUsed = 0; // Memory currently in use
+ static const UINT32 guiSoundCacheThreshold = SOUND_DEFAULT_THRESH; // Double-buffered threshold
++static void IncreaseSoundMemoryUsedBySample(SAMPLETAG *sample) { guiSoundMemoryUsed += sample->n_samples * GetSampleSize(sample); }
++static void DecreaseSoundMemoryUsedBySample(SAMPLETAG *sample) { guiSoundMemoryUsed -= sample->n_samples * GetSampleSize(sample); }
+
+ static BOOLEAN fSoundSystemInit = FALSE; // Startup called
+ static BOOLEAN gfEnableStartup = TRUE; // Allow hardware to start up
+
++SDL_AudioSpec gTargetAudioSpec;
++
+ // Sample cache list for files loaded
+ static SAMPLETAG pSampleList[SOUND_MAX_CACHED];
+ // Sound channel list for output channels
+@@ -188,13 +179,7 @@ UINT32 SoundPlay(const char* pFilename, UINT32 volume, UINT32 pan, UINT32 loop,
+ if (SoundPlayStreamed(pFilename))
+ {
+ //Trying to play a sound which is bigger then the 'guiSoundCacheThreshold'
+-
+- // This line was causing a page fault in the Wiz 8 project, so
+- // I changed it to the second line, which works OK. -- DB
+-
+- //DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("\n*******\nSoundPlay(): ERROR: trying to play %s which is bigger then the 'guiSoundCacheThreshold', use SoundPlayStreamedFile() instead\n", pFilename));
+-
+- FastDebugMsg(String("SoundPlay: ERROR: Trying to play %s sound is too lardge to load into cache, use SoundPlayStreamedFile() instead\n", pFilename));
++ SLOGE(DEBUG_TAG_SOUND, "Trying to play %s sound is too large to load into cache, use SoundPlayStreamedFile() instead", pFilename));
+ return SOUND_ERROR;
+ }
+ #endif
+@@ -211,7 +196,6 @@ UINT32 SoundPlay(const char* pFilename, UINT32 volume, UINT32 pan, UINT32 loop,
+ static SAMPLETAG* SoundGetEmptySample(void);
+ static BOOLEAN SoundCleanCache(void);
+ static SAMPLETAG* SoundGetEmptySample(void);
+-static size_t GetSampleSize(const SAMPLETAG* const s);
+
+ UINT32 SoundPlayFromBuffer(INT16* pbuffer, UINT32 size, UINT32 volume, UINT32 pan, UINT32 loop, void (*end_callback)(void*), void* data)
+ {
+@@ -223,14 +207,13 @@ UINT32 SoundPlayFromBuffer(INT16* pbuffer, UINT32 size, UINT32 volume, UINT32 pa
+ SoundCleanCache();
+ buffertag = SoundGetEmptySample();
+ }
+- sprintf(buffertag->pName, "SmackBuff %p - SampleSize %u", pbuffer, size);
+- buffertag->uiSpeed=22050;
++ sprintf(buffertag->pName, "SmackBuff %p - SampleSize %u", pbuffer, size);
+ buffertag->n_samples = size;
+ buffertag->pData = pbuffer;
+- buffertag->uiFlags = SAMPLE_16BIT | SAMPLE_STEREO | SAMPLE_ALLOCATED;
++ buffertag->uiFlags = SAMPLE_STEREO | SAMPLE_ALLOCATED;
+ buffertag->uiPanMax = 64;
+ buffertag->uiMaxInstances = 1;
+- guiSoundMemoryUsed += size * GetSampleSize(buffertag);
++ IncreaseSoundMemoryUsedBySample(buffertag);
+
+ SOUNDTAG* const channel = SoundGetFreeChannel();
+ if (channel == NULL) return SOUND_ERROR;
+@@ -263,7 +246,7 @@ try
+ FILE* hRealFileHandle = GetRealFileHandleFromFileManFileHandle(hFile);
+ if (hRealFileHandle == NULL)
+ {
+- FastDebugMsg(String("\n*******\nSoundPlayStreamedFile(): ERROR: Couldnt get a real file handle for '%s' in SoundPlayStreamedFile()\n", pFilename ) );
++ SLOGE(DEBUG_TAG_SOUND, "SoundPlayStreamedFile(): Couldnt get a real file handle for '%s' in SoundPlayStreamedFile()", pFilename );
+ return SOUND_ERROR;
+ }
+
+@@ -285,14 +268,14 @@ try
+ }
+ catch (...)
+ {
+- FastDebugMsg(String("\n*******\nSoundPlayStreamedFile(): ERROR: Failed to play '%s'\n", pFilename));
++ SLOGE(DEBUG_TAG_SOUND, "SoundPlayStreamedFile(): Failed to play '%s'", pFilename);
+ return SOUND_ERROR;
+ }
+
+
+ UINT32 SoundPlayRandom(const char* pFilename, UINT32 time_min, UINT32 time_max, UINT32 vol_min, UINT32 vol_max, UINT32 pan_min, UINT32 pan_max, UINT32 max_instances)
+ {
+- SNDDBG("RAND \"%s\"\n", pFilename);
++ SLOGD(DEBUG_TAG_SOUND, "playing random Sound: \"%s\"", pFilename);
+
+ if (!fSoundSystemInit) return SOUND_ERROR;
+
+@@ -479,7 +462,7 @@ void SoundServiceStreams(void)
+ SOUNDTAG* Sound = &pSoundList[i];
+ if (Sound->State == CHANNEL_DEAD)
+ {
+- SNDDBG("DEAD channel %u file \"%s\" (refcount %u)\n", i, Sound->pSample->pName, Sound->pSample->uiInstances);
++ SLOGD(DEBUG_TAG_SOUND, "DEAD channel %u file \"%s\" (refcount %u)", i, Sound->pSample->pName, Sound->pSample->uiInstances);
+ if (Sound->EOSCallback != NULL) Sound->EOSCallback(Sound->pCallbackData);
+ assert(Sound->pSample->uiInstances != 0);
+ Sound->pSample->uiInstances--;
+@@ -556,274 +539,37 @@ static SAMPLETAG* SoundGetCached(const char* pFilename)
+
+ static size_t GetSampleSize(const SAMPLETAG* const s)
+ {
+- return
+- (s->uiFlags & SAMPLE_16BIT ? 2 : 1) *
+- (s->uiFlags & SAMPLE_STEREO ? 2 : 1);
+-}
+-
+-
+-static BOOLEAN HalfSampleRate(SAMPLETAG* const s)
+-{
+- SNDDBG("SMPL \"%s\" from %uHz to %uHz\n", s->pName, s->uiSpeed, s->uiSpeed / 2);
+-
+- UINT32 const n_samples = s->n_samples / 2;
+- void* const ndata = malloc(n_samples * GetSampleSize(s));
+- if (ndata == NULL) return FALSE;
+- void* const odata = s->pData;
+- if (s->uiFlags & SAMPLE_16BIT)
+- {
+- INT16* const dst = (INT16*)ndata;
+- const INT16* const src = (const INT16*)odata;
+- if (s->uiFlags & SAMPLE_STEREO)
+- {
+- for (size_t i = 0; i < n_samples; ++i)
+- {
+- dst[2 * i + 0] = (src[4 * i + 0] + src[4 * i + 2]) / 2;
+- dst[2 * i + 1] = (src[4 * i + 1] + src[4 * i + 3]) / 2;
+- }
+- }
+- else
+- {
+- for (size_t i = 0; i < n_samples; ++i)
+- {
+- dst[i] = (src[2 * i] + src[2 * i + 1]) / 2;
+- }
+- }
+- }
+- else
+- {
+- UINT8* const dst = (UINT8*)ndata;
+- const UINT8* const src = (const UINT8*)odata;
+- if (s->uiFlags & SAMPLE_STEREO)
+- {
+- for (size_t i = 0; i < n_samples; ++i)
+- {
+- dst[2 * i + 0] = (src[4 * i + 0] + src[4 * i + 2]) / 2;
+- dst[2 * i + 1] = (src[4 * i + 1] + src[4 * i + 3]) / 2;
+- }
+- }
+- else
+- {
+- for (size_t i = 0; i < n_samples; ++i)
+- {
+- dst[i] = (src[2 * i] + src[2 * i + 1]) / 2;
+- }
+- }
+- }
+- s->pData = ndata;
+- free(odata);
+-
+- s->n_samples = n_samples;
+- s->uiSpeed /= 2;
+- return TRUE;
+-}
+-
+-static BOOLEAN DoubleSampleRate(SAMPLETAG* const s)
+-{
+- UINT8 bitcount = s->uiFlags & SAMPLE_16BIT ? 16 : 8;
+-
+- SNDDBG("SMPL \"%s\" %dbit from %uHz to %uHz\n", s->pName, bitcount, s->uiSpeed, s->uiSpeed * 2);
+-
+- UINT32 const n_samples = s->n_samples * 2;
+- void* const ndata = malloc(n_samples * GetSampleSize(s));
+- if (ndata == NULL) return FALSE;
+- void* const odata = s->pData;
+- if (bitcount == 16)
+- {
+- INT16* const dst = (INT16*)ndata;
+- const INT16* const src = (const INT16*)odata;
+- if (s->uiFlags & SAMPLE_STEREO)
+- {
+- for (size_t i = 0; i < s->n_samples; ++i)
+- {
+- INT16 i1c1 = src[2 * i + 0];
+- INT16 i1c2 = src[2 * i + 1];
+- INT16 i2c1 = i != s->n_samples-1 ? src[2 * i + 2] : i1c1;
+- INT16 i2c2 = i != s->n_samples-1 ? src[2 * i + 3] : i1c2;
+-
+- dst[4 * i + 0] = i1c1;
+- dst[4 * i + 1] = i1c2;
+- dst[4 * i + 2] = (i1c1 + i2c1) / 2;
+- dst[4 * i + 3] = (i1c2 + i2c2) / 2;
+- }
+- }
+- else
+- {
+- for (size_t i = 0; i < s->n_samples; ++i)
+- {
+- INT16 i1 = src[i];
+- INT16 i2 = i != s->n_samples-1 ? src[i+1] : i1;
+- dst[i*2] = i1;
+- dst[i*2+1] = (i1 + i2) / 2;
+- }
+- }
+- }
+- else
+- {
+- UINT8* const dst = (UINT8*)ndata;
+- const UINT8* const src = (const UINT8*)odata;
+- if (s->uiFlags & SAMPLE_STEREO)
+- {
+- for (size_t i = 0; i < s->n_samples; ++i)
+- {
+- UINT8 i1c1 = src[2 * i + 0];
+- UINT8 i1c2 = src[2 * i + 1];
+- UINT8 i2c1 = i != s->n_samples-1 ? src[2 * i + 2] : i1c1;
+- UINT8 i2c2 = i != s->n_samples-1 ? src[2 * i + 3] : i1c2;
+-
+- dst[4 * i + 0] = i1c1;
+- dst[4 * i + 1] = i1c2;
+- dst[4 * i + 2] = (i1c1 + i2c1) / 2;
+- dst[4 * i + 3] = (i1c2 + i2c2) / 2;
+- }
+- }
+- else
+- {
+- for (size_t i = 0; i < s->n_samples; ++i)
+- {
+- UINT8 i1 = src[i];
+- UINT8 i2 = i != s->n_samples-1 ? src[i+1] : i1;
+- dst[i*2] = i1;
+- dst[i*2+1] = (i1 + i2) / 2;
+- }
+- }
+- }
+- s->pData = ndata;
+- free(odata);
+-
+- s->n_samples = n_samples;
+- s->uiSpeed *= 2;
+- return TRUE;
+-}
+-
+-
+-#define FOURCC(a, b, c, d) ((UINT8)(d) << 24 | (UINT8)(c) << 16 | (UINT8)(b) << 8 | (UINT8)(a))
+-
+-
+-enum WaveFormatTag
+-{
+- WAVE_FORMAT_UNKNOWN = 0x0000,
+- WAVE_FORMAT_PCM = 0x0001,
+- WAVE_FORMAT_DVI_ADPCM = 0x0011
+-};
+-
+-
+-static void LoadPCM(SAMPLETAG* const s, HWFILE const file, UINT32 const size)
+-{
+- SGP::Buffer<UINT8> data(size);
+- FileRead(file, data, size);
+-
+- s->n_samples = size / GetSampleSize(s);
+- s->pData = data.Release();
+-}
+-
+-
+-static inline int Clamp(int min, int x, int max)
+-{
+- if (x < min) return min;
+- if (x > max) return max;
+- return x;
++ return 2u * (s->uiFlags & SAMPLE_STEREO ? 2 : 1);
+ }
+
+-
+-static void LoadDVIADPCM(SAMPLETAG* const s, HWFILE const file, UINT16 const block_align)
+-{
+- s->uiFlags |= SAMPLE_16BIT;
+-
+- size_t CountSamples = s->n_samples;
+- INT16* const Data = (INT16*)malloc(CountSamples * GetSampleSize(s));
+- INT16* D = Data;
+-
+- for (;;)
+- {
+- INT16 CurSample_;
+- FileRead(file, &CurSample_, sizeof(CurSample_));
+-
+- UINT8 StepIndex_;
+- FileRead(file, &StepIndex_, sizeof(StepIndex_));
+-
+- FileSeek(file, 1 , FILE_SEEK_FROM_CURRENT); // reserved byte
+-
+- INT32 CurSample = CurSample_;
+- INT32 StepIndex = StepIndex_;
+-
+- *D++ = CurSample;
+- if (--CountSamples == 0)
+- {
+- s->pData = Data;
+- return;
+- }
+-
+- UINT DataCount = block_align / 4;
+- while (--DataCount != 0)
+- {
+- UINT32 DataWord;
+- FileRead(file, &DataWord, sizeof(DataWord));
+- for (UINT i = 0; i < 8; i++)
+- {
+- static const INT16 StepTable[] =
+- {
+- 7, 8, 9, 10, 11, 12, 13, 14,
+- 16, 17, 19, 21, 23, 25, 28, 31,
+- 34, 37, 41, 45, 50, 55, 60, 66,
+- 73, 80, 88, 97, 107, 118, 130, 143,
+- 157, 173, 190, 209, 230, 253, 279, 307,
+- 337, 371, 408, 449, 494, 544, 598, 658,
+- 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+- 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+- 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
+- 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+- 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
+- 32767
+- };
+-
+- static const INT8 IndexTable[] =
+- {
+- -1, -1, -1, -1, 2, 4, 6, 8
+- };
+-
+-#if 1
+- INT32 Diff = ((DataWord & 7) * 2 + 1) * StepTable[StepIndex] >> 3;
+-#else
+- INT32 Diff = 0;
+- if (DataWord & 4) Diff += StepTable[StepIndex];
+- if (DataWord & 2) Diff += StepTable[StepIndex] >> 1;
+- if (DataWord & 1) Diff += StepTable[StepIndex] >> 2;
+- Diff += StepTable[StepIndex] >> 3;
+-#endif
+- if (DataWord & 8) Diff = -Diff;
+- CurSample = Clamp(-32768, CurSample + Diff, 32767);
+- StepIndex = Clamp(0, StepIndex + IndexTable[DataWord & 7], 88);
+- DataWord >>= 4;
+-
+- *D++ = CurSample;
+- if (--CountSamples == 0)
+- {
+- s->pData = Data;
+- return;
+- }
+- }
+- }
+- }
+-}
+-
+-
+-
+-
+-
+ /* Loads a sound file from disk into the cache, allocating memory and a slot
+ * for storage.
+ *
+ * Returns: The sample index if successful, NO_SAMPLE if the file wasn't found
+ * in the cache. */
+ static SAMPLETAG* SoundLoadDisk(const char* pFilename)
+-try
+ {
+ Assert(pFilename != NULL);
+
+- AutoSGPFile hFile(GCM->openGameResForReading(pFilename));
++ if(pFilename[0] == '\0') {
++ SLOGE(DEBUG_TAG_ASSERTS, "SoundLoadDisk Error: pFilename is an empty string.");
++ return NULL;
++ }
+
+- UINT32 uiSize = FileGetSize(hFile);
++ AutoSGPFile hFile;
++
++ try
++ {
++ hFile = GCM->openGameResForReading(pFilename);
++ }
++ catch (const std::runtime_error& err)
++ {
++ SLOGE(DEBUG_TAG_ASSERTS, "SoundLoadDisk Error: %s", err.what());
++ return NULL;
++ }
++
++ // A pessimistic approach as we dont know the decoded size yet
++ UINT32 uiSize = FileGetSize(hFile) * 2;
+
+ // if insufficient memory, start unloading old samples until either
+ // there's nothing left to unload, or we fit
+@@ -831,8 +577,8 @@ try
+ {
+ if (!SoundCleanCache())
+ {
+- SNDDBG("Not enough memory. Size: %u, Used: %u, Max: %u\n", uiSize, guiSoundMemoryUsed, guiSoundMemoryLimit);
+- FastDebugMsg(String("SoundLoadDisk: ERROR: trying to play %s, not enough memory\n", pFilename));
++ SLOGE(DEBUG_TAG_SOUND, "SoundLoadDisk: trying to play %s, not enough memory\nSize: %u, Used: %u, Max: %u",
++ pFilename, uiSize, guiSoundMemoryUsed, guiSoundMemoryLimit);
+ return NULL;
+ }
+ }
+@@ -848,107 +594,63 @@ try
+ // if we still don't have a sample slot
+ if (s == NULL)
+ {
+- FastDebugMsg(String("SoundLoadDisk: ERROR: Trying to play %s, sound channels are full\n", pFilename));
++ SLOGE(DEBUG_TAG_SOUND, "SoundLoadDisk: Trying to play %s, sound channels are full", pFilename);
+ return NULL;
+ }
+
+ memset(s, 0, sizeof(*s));
+
+- FileSeek(hFile, 12, FILE_SEEK_FROM_CURRENT);
+-
+- UINT16 FormatTag = WAVE_FORMAT_UNKNOWN;
+- UINT16 BlockAlign = 0;
+- for (;;)
+- {
+- UINT32 Tag;
+- UINT32 Size;
+-
+- FileRead(hFile, &Tag, sizeof(Tag));
+- FileRead(hFile, &Size, sizeof(Size));
++ SDL_RWops* rwOps = FileGetRWOps(hFile);
++ SDL_AudioSpec wavSpec;
++ Uint32 wavLength;
++ Uint8 *wavBuffer;
++ SDL_AudioCVT cvt;
+
+- switch (Tag)
+- {
+- case FOURCC('f', 'm', 't', ' '):
+- {
+- UINT16 Channels;
+- UINT32 Rate;
+- UINT16 BitsPerSample;
+-
+- FileRead(hFile, &FormatTag, sizeof(FormatTag));
+- FileRead(hFile, &Channels, sizeof(Channels));
+- FileRead(hFile, &Rate, sizeof(Rate));
+- FileSeek(hFile, 4 , FILE_SEEK_FROM_CURRENT);
+- FileRead(hFile, &BlockAlign, sizeof(BlockAlign));
+- FileRead(hFile, &BitsPerSample, sizeof(BitsPerSample));
+- SNDDBG("LOAD file \"%s\" format %u channels %u rate %u bits %u to slot %u\n", pFilename, FormatTag, Channels, Rate, BitsPerSample, s - pSampleList);
+- switch (FormatTag)
+- {
+- case WAVE_FORMAT_PCM: break;
++ if (SDL_LoadWAV_RW(rwOps, 0, &wavSpec, &wavBuffer, &wavLength) == NULL) {
++ SLOGE(DEBUG_TAG_SOUND, "Error loading sound file: %s", SDL_GetError());
++ return NULL;
++ }
+
+- case WAVE_FORMAT_DVI_ADPCM:
+- FileSeek(hFile, 4 , FILE_SEEK_FROM_CURRENT);
+- break;
++ SDL_BuildAudioCVT(&cvt, wavSpec.format, wavSpec.channels, wavSpec.freq, gTargetAudioSpec.format, wavSpec.channels, gTargetAudioSpec.freq);
++ cvt.len = wavLength;
++ cvt.buf = MALLOCN(UINT8, cvt.len * cvt.len_mult);
++ memcpy(cvt.buf, wavBuffer, wavLength);
++ SDL_FreeWAV(wavBuffer);
++ SDL_FreeRW(rwOps);
+
+- default: return NULL;
+- }
++ if (cvt.needed) {
++ if (SDL_ConvertAudio(&cvt) != 0) {
++ SLOGE(DEBUG_TAG_SOUND, "Error converting sound file: %s", SDL_GetError());
++ return NULL;
++ };
++ }
+
+- s->uiSpeed = Rate;
+- if (Channels != 1) s->uiFlags |= SAMPLE_STEREO;
+- if (BitsPerSample == 16) s->uiFlags |= SAMPLE_16BIT;
+- break;
+- }
++ UINT32 convertedSize = cvt.len * cvt.len_ratio;
+
+- case FOURCC('f', 'a', 'c', 't'):
+- {
+- UINT32 Samples;
+- FileRead(hFile, &Samples, sizeof(Samples));
+- s->n_samples = Samples;
+- break;
+- }
++ strcpy(s->pName, pFilename);
++ s->n_samples = UINT32(convertedSize / (wavSpec.channels * 2));
++ s->uiFlags |= SAMPLE_ALLOCATED;
++ if (wavSpec.channels != 1) {
++ s->uiFlags |= SAMPLE_STEREO;
++ }
+
+- case FOURCC('d', 'a', 't', 'a'):
+- {
+- switch (FormatTag)
+- {
+- case WAVE_FORMAT_PCM:
+- LoadPCM(s, hFile, Size);
+- goto sound_loaded;
++ s->uiInstances = 0;
++ s->pData = MALLOCN(UINT8, convertedSize);
++ memcpy(s->pData, cvt.buf, convertedSize);
+
+- case WAVE_FORMAT_DVI_ADPCM:
+- LoadDVIADPCM(s, hFile, BlockAlign);
+- goto sound_loaded;
++ free(cvt.buf);
+
+- default: return NULL;
+- }
+- }
+-
+- default:
+- FileSeek(hFile, Size, FILE_SEEK_FROM_CURRENT);
+- break;
+- }
+- }
++ IncreaseSoundMemoryUsedBySample(s);
+
+-sound_loaded:
+- strcpy(s->pName, pFilename);
+- if (s->uiSpeed == 44100 && !HalfSampleRate(s))
+- {
+- free(s->pData);
+- return NULL;
+- }
+- if (s->uiSpeed == 11025 && !DoubleSampleRate(s))
+- {
+- free(s->pData);
+- return NULL;
+- }
+- guiSoundMemoryUsed += s->n_samples * GetSampleSize(s);
+- s->uiFlags |= SAMPLE_ALLOCATED;
+- s->uiInstances = 0;
+ return s;
+ }
+-catch (...) { return 0; }
+
+
+-static BOOLEAN SoundSampleIsPlaying(const SAMPLETAG* s);
++// Returns TRUE/FALSE that a sample is currently in use for playing a sound.
++static BOOLEAN SoundSampleIsPlaying(const SAMPLETAG* s)
++{
++ return s->uiInstances > 0;
++}
+
+
+ /* Removes the least-used sound from the cache to make room.
+@@ -970,7 +672,7 @@ static BOOLEAN SoundCleanCache(void)
+
+ if (candidate != NULL)
+ {
+- SNDDBG("FREE sample %u \"%s\" with %u hits\n", candidate - pSampleList, candidate->pName, candidate->uiCacheHits);
++ SLOGD(DEBUG_TAG_SOUND, "freeing sample %u \"%s\" with %u hits", candidate - pSampleList, candidate->pName, candidate->uiCacheHits);
+ SoundFreeSample(candidate);
+ return TRUE;
+ }
+@@ -979,13 +681,6 @@ static BOOLEAN SoundCleanCache(void)
+ }
+
+
+-// Returns TRUE/FALSE that a sample is currently in use for playing a sound.
+-static BOOLEAN SoundSampleIsPlaying(const SAMPLETAG* s)
+-{
+- return s->uiInstances > 0;
+-}
+-
+-
+ /* Returns an available sample.
+ *
+ * Returns: A free sample or NULL if none are left. */
+@@ -1007,8 +702,8 @@ static void SoundFreeSample(SAMPLETAG* s)
+
+ assert(s->uiInstances == 0);
+
+- guiSoundMemoryUsed -= s->n_samples * GetSampleSize(s);
+- free(s->pData);
++ DecreaseSoundMemoryUsedBySample(s);
++ MemFree(s->pData);
+ memset(s, 0, sizeof(*s));
+ }
+
+@@ -1030,6 +725,8 @@ static SOUNDTAG* SoundGetChannelByID(UINT32 uiSoundID)
+
+ static void SoundCallback(void* userdata, Uint8* stream, int len)
+ {
++ SDL_memset(stream, 0, len);
++
+ UINT16* Stream = (UINT16*)stream;
+
+ // XXX TODO proper mixing, mainly clipping
+@@ -1058,48 +755,23 @@ static void SoundCallback(void* userdata, Uint8* stream, int len)
+
+ mixing:
+ amount = MIN(samples, s->n_samples - Sound->pos);
+- if (s->uiFlags & SAMPLE_16BIT)
++ if (s->uiFlags & SAMPLE_STEREO)
+ {
+- if (s->uiFlags & SAMPLE_STEREO)
++ const INT16* const src = (const INT16*)s->pData + Sound->pos * 2;
++ for (UINT32 i = 0; i < amount; ++i)
+ {
+- const INT16* const src = (const INT16*)s->pData + Sound->pos * 2;
+- for (UINT32 i = 0; i < amount; ++i)
+- {
+- Stream[2 * i + 0] += src[2 * i + 0] * vol_l >> 7;
+- Stream[2 * i + 1] += src[2 * i + 1] * vol_r >> 7;
+- }
+- }
+- else
+- {
+- const INT16* const src = (const INT16*)s->pData + Sound->pos;
+- for (UINT32 i = 0; i < amount; i++)
+- {
+- const INT data = src[i];
+- Stream[2 * i + 0] += data * vol_l >> 7;
+- Stream[2 * i + 1] += data * vol_r >> 7;
+- }
++ Stream[2 * i + 0] += src[2 * i + 0] * vol_l >> 7;
++ Stream[2 * i + 1] += src[2 * i + 1] * vol_r >> 7;
+ }
+ }
+ else
+ {
+- if (s->uiFlags & SAMPLE_STEREO)
+- {
+- const UINT8* const src = (const UINT8*)s->pData + Sound->pos * 2;
+- for (UINT32 i = 0; i < amount; ++i)
+- {
+- Stream[2 * i + 0] += (src[2 * i + 0] - 128) * vol_l << 1;
+- Stream[2 * i + 1] += (src[2 * i + 1] - 128) * vol_r << 1;
+- }
+- }
+- else
++ const INT16* const src = (const INT16*)s->pData + Sound->pos;
++ for (UINT32 i = 0; i < amount; i++)
+ {
+- const UINT8* const src = (const UINT8*)s->pData + Sound->pos;
+- for (UINT32 i = 0; i < amount; ++i)
+- {
+- const INT data = (src[i] - 128) << 1;
+- Stream[2 * i + 0] += data * vol_l;
+- Stream[2 * i + 1] += data * vol_r;
+- }
++ const INT data = src[i];
++ Stream[2 * i + 0] += data * vol_l >> 7;
++ Stream[2 * i + 1] += data * vol_r >> 7;
+ }
+ }
+
+@@ -1128,15 +800,14 @@ static BOOLEAN SoundInitHardware(void)
+ {
+ SDL_InitSubSystem(SDL_INIT_AUDIO);
+
+- SDL_AudioSpec spec;
+- spec.freq = 22050;
+- spec.format = AUDIO_S16SYS;
+- spec.channels = 2;
+- spec.samples = 1024;
+- spec.callback = SoundCallback;
+- spec.userdata = NULL;
++ gTargetAudioSpec.freq = 44100;
++ gTargetAudioSpec.format = AUDIO_S16SYS;
++ gTargetAudioSpec.channels = 2;
++ gTargetAudioSpec.samples = 1024;
++ gTargetAudioSpec.callback = SoundCallback;
++ gTargetAudioSpec.userdata = NULL;
+
+- if (SDL_OpenAudio(&spec, NULL) != 0) return FALSE;
++ if (SDL_OpenAudio(&gTargetAudioSpec, NULL) != 0) return FALSE;
+
+ memset(pSoundList, 0, sizeof(pSoundList));
+ SDL_PauseAudio(0);
+@@ -1174,7 +845,7 @@ static UINT32 SoundGetUniqueID(void);
+ * Returns: Unique sound ID if successful, SOUND_ERROR if not. */
+ static UINT32 SoundStartSample(SAMPLETAG* sample, SOUNDTAG* channel, UINT32 volume, UINT32 pan, UINT32 loop, void (*end_callback)(void*), void* data)
+ {
+- SNDDBG("PLAY channel %u sample %u file \"%s\"\n", channel - pSoundList, sample - pSampleList, sample->pName);
++ SLOGD(DEBUG_TAG_SOUND, "playing channel %u sample %u file \"%s\"", channel - pSoundList, sample - pSampleList, sample->pName);
+
+ if (!fSoundSystemInit) return SOUND_ERROR;
+
+@@ -1220,7 +891,7 @@ static BOOLEAN SoundStopChannel(SOUNDTAG* channel)
+
+ if (channel->pSample == NULL) return FALSE;
+
+- SNDDBG("STOP channel %u\n", channel - pSoundList);
++ SLOGD(DEBUG_TAG_SOUND, "stopping channel channel %u", channel - pSoundList);
+ channel->State = CHANNEL_STOP;
+ return TRUE;
+ }
+diff --git a/sgp/VSurface.cc b/sgp/VSurface.cc
+index 9147259b8..4c68143dd 100644
+--- a/sgp/VSurface.cc
++++ b/sgp/VSurface.cc
+@@ -28,13 +28,13 @@ SGPVSurface::SGPVSurface(UINT16 const w, UINT16 const h, UINT8 const bpp) :
+ switch (bpp)
+ {
+ case 8:
+- s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bpp, 0, 0, 0, 0);
++ s = SDL_CreateRGBSurface(0, w, h, bpp, 0, 0, 0, 0);
+ break;
+
+ case 16:
+ {
+- SDL_PixelFormat const* f = SDL_GetVideoSurface()->format;
+- s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bpp, f->Rmask, f->Gmask, f->Bmask, 0);
++ SDL_PixelFormat const* f = SDL_AllocFormat(SDL_PIXELFORMAT_RGB565);
++ s = SDL_CreateRGBSurface(0, w, h, bpp, f->Rmask, f->Gmask, f->Bmask, f->Amask);
+ break;
+ }
+
+@@ -112,7 +112,7 @@ void SGPVSurface::SetTransparency(const COLORVAL colour)
+
+ default: abort(); // HACK000E
+ }
+- SDL_SetColorKey(surface_, SDL_SRCCOLORKEY, colour_key);
++ SDL_SetColorKey(surface_, SDL_TRUE, colour_key);
+ }
+
+
+@@ -370,9 +370,10 @@ void BltStretchVideoSurface(SGPVSurface* const dst, SGPVSurface const* const src
+ UINT const dx = src_rect->w;
+ UINT const dy = src_rect->h;
+ UINT py = 0;
+- if (ssurface->flags & SDL_SRCCOLORKEY)
++ if (ssurface->flags & SDL_TRUE)
+ {
+- const UINT16 key = ssurface->format->colorkey;
++// const UINT16 key = ssurface->format->colorkey;
++ const UINT16 key = 0;
+ for (UINT iy = 0; iy < height; ++iy)
+ {
+ const UINT16* s = os;
+diff --git a/sgp/Video.cc b/sgp/Video.cc
+index 1c6c2170b..57fab03c8 100644
+--- a/sgp/Video.cc
++++ b/sgp/Video.cc
+@@ -22,13 +22,13 @@
+ #include <stdarg.h>
+ #include "UILayout.h"
+ #include "PlatformIO.h"
+-#include "PlatformSDL.h"
+ #include "Font.h"
+ #include "Icon.h"
+
+ #include "ContentManager.h"
+ #include "GameInstance.h"
+
++#include "slog/slog.h"
+
+ #define BUFFER_READY 0x00
+ #define BUFFER_DIRTY 0x02
+@@ -44,6 +44,12 @@
+
+ #define MAX_NUM_FRAMES 25
+
++#define RED_MASK 0xF800
++#define GREEN_MASK 0x07E0
++#define BLUE_MASK 0x001F
++#define ALPHA_MASK 0
++
++#define DEBUG_TAG_VIDEO "Video"
+
+ static BOOLEAN gfVideoCapture = FALSE;
+ static UINT32 guiFramePeriod = 1000 / 15;
+@@ -80,9 +86,12 @@ static UINT32 guiPrintFrameBufferIndex;
+
+ static SDL_Surface* MouseCursor;
+ static SDL_Surface* FrameBuffer;
+-static SDL_Surface* ScreenBuffer;
+-static Uint32 g_video_flags = SDL_SWSURFACE | SDL_HWPALETTE;
++static SDL_Renderer* GameRenderer;
++SDL_Window* g_game_window;
+
++static SDL_Surface* ScreenBuffer;
++static SDL_Texture* ScreenTexture;
++static Uint32 g_window_flags = 0;
+
+ static void RecreateBackBuffer();
+ static void DeletePrimaryVideoSurfaces(void);
+@@ -91,48 +100,25 @@ void VideoSetFullScreen(const BOOLEAN enable)
+ {
+ if (enable)
+ {
+- g_video_flags |= SDL_FULLSCREEN;
++ g_window_flags |= SDL_WINDOW_FULLSCREEN;
+ }
+ else
+ {
+- g_video_flags &= ~SDL_FULLSCREEN;
++ g_window_flags &= ~SDL_WINDOW_FULLSCREEN;
+ }
+ }
+
+
+ void VideoToggleFullScreen(void)
+ {
+- SDL_Surface* const scr = ScreenBuffer;
+-
+- // First try using SDL magic to toggle fullscreen
+- if (SDL_WM_ToggleFullScreen(scr))
++ if (SDL_GetWindowFlags(g_game_window) & SDL_WINDOW_FULLSCREEN)
+ {
+- g_video_flags ^= SDL_FULLSCREEN;
+- return;
++ SDL_SetWindowFullscreen(g_game_window, 0);
++ }
++ else
++ {
++ SDL_SetWindowFullscreen(g_game_window, SDL_WINDOW_FULLSCREEN);
+ }
+-
+- // Fallback to manual toggling
+- SDL_PixelFormat const& fmt = *scr->format;
+- int const w = scr->w;
+- int const h = scr->h;
+- Uint8 const bpp = fmt.BitsPerPixel;
+-
+- SGP::AutoObj<SDL_Surface, SDL_FreeSurface> tmp(SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bpp, fmt.Rmask, fmt.Gmask, fmt.Bmask, fmt.Amask));
+- if (!tmp) return;
+-
+- SDL_BlitSurface(scr, 0, tmp, 0);
+-
+- Uint32 const new_vflags = g_video_flags ^ SDL_FULLSCREEN;
+- SDL_Surface* const new_scr = SDL_SetVideoMode(w, h, bpp, new_vflags);
+- if (!new_scr) return;
+-
+- g_video_flags = new_vflags;
+-
+- ScreenBuffer = new_scr;
+- RecreateBackBuffer();
+-
+- SDL_BlitSurface(tmp, 0, new_scr, 0);
+- SDL_UpdateRect(new_scr, 0, 0, 0, 0);
+ }
+
+
+@@ -141,9 +127,21 @@ static void GetRGBDistribution();
+
+ void InitializeVideoManager(void)
+ {
+- DebugMsg(TOPIC_VIDEO, DBG_LEVEL_0, "Initializing the video manager");
++ SLOGD(DEBUG_TAG_VIDEO, "Initializing the video manager");
++ SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
++ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
++
++ g_window_flags |= SDL_WINDOW_RESIZABLE;
++
++ g_game_window = SDL_CreateWindow(APPLICATION_NAME,
++ SDL_WINDOWPOS_UNDEFINED,
++ SDL_WINDOWPOS_UNDEFINED,
++ SCREEN_WIDTH, SCREEN_HEIGHT,
++ g_window_flags);
++
++ GameRenderer = SDL_CreateRenderer(g_game_window, -1, 0);
++ SDL_RenderSetLogicalSize(GameRenderer, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+- SDL_WM_SetCaption(APPLICATION_NAME, NULL);
+ SDL_Surface* windowIcon = SDL_CreateRGBSurfaceFrom(
+ (void*)gWindowIconData.pixel_data,
+ gWindowIconData.width,
+@@ -151,29 +149,56 @@ void InitializeVideoManager(void)
+ gWindowIconData.bytes_per_pixel*8,
+ gWindowIconData.bytes_per_pixel*gWindowIconData.width,
+ 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
+- SDL_WM_SetIcon(windowIcon, NULL);
++ SDL_SetWindowIcon(g_game_window, windowIcon);
+ SDL_FreeSurface(windowIcon);
+
++
+ ClippingRect.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+- ScreenBuffer = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, PIXEL_DEPTH, g_video_flags);
+- if (!ScreenBuffer) throw std::runtime_error("Failed to set up video mode");
++ ScreenBuffer = SDL_CreateRGBSurface(
++ 0,
++ SCREEN_WIDTH,
++ SCREEN_HEIGHT,
++ PIXEL_DEPTH,
++ RED_MASK,
++ GREEN_MASK,
++ BLUE_MASK,
++ ALPHA_MASK
++ );
++
++ if (ScreenBuffer == NULL) {
++ SLOGE(DEBUG_TAG_VIDEO, "SDL_CreateRGBSurface for ScreenBuffer failed: %s\n", SDL_GetError());
++ }
+
+- Uint32 Rmask = ScreenBuffer->format->Rmask;
+- Uint32 Gmask = ScreenBuffer->format->Gmask;
+- Uint32 Bmask = ScreenBuffer->format->Bmask;
+- Uint32 Amask = ScreenBuffer->format->Amask;
++ ScreenTexture = SDL_CreateTexture(GameRenderer,
++ SDL_PIXELFORMAT_RGB565,
++ SDL_TEXTUREACCESS_STREAMING,
++ SCREEN_WIDTH, SCREEN_HEIGHT);
++
++ if (ScreenTexture == NULL) {
++ SLOGE(DEBUG_TAG_VIDEO, "SDL_CreateTexture for ScreenTexture failed: %s\n", SDL_GetError());
++ }
+
+ FrameBuffer = SDL_CreateRGBSurface(
+ SDL_SWSURFACE, SCREEN_WIDTH, SCREEN_HEIGHT, PIXEL_DEPTH,
+- Rmask, Gmask, Bmask, Amask
++ RED_MASK, GREEN_MASK, BLUE_MASK, ALPHA_MASK
+ );
+
++ if (FrameBuffer == NULL)
++ {
++ SLOGE(DEBUG_TAG_VIDEO, "SDL_CreateRGBSurface for FrameBuffer failed: %s\n", SDL_GetError());
++ }
++
+ MouseCursor = SDL_CreateRGBSurface(
+- SDL_SWSURFACE, MAX_CURSOR_WIDTH, MAX_CURSOR_HEIGHT, PIXEL_DEPTH,
+- Rmask, Gmask, Bmask, Amask
++ 0, MAX_CURSOR_WIDTH, MAX_CURSOR_HEIGHT, PIXEL_DEPTH,
++ RED_MASK, GREEN_MASK, BLUE_MASK, ALPHA_MASK
+ );
+- SDL_SetColorKey(MouseCursor, SDL_SRCCOLORKEY, 0);
++ SDL_SetColorKey(MouseCursor, SDL_TRUE, 0);
++
++ if (MouseCursor == NULL)
++ {
++ SLOGE(DEBUG_TAG_VIDEO, "SDL_CreateRGBSurface for MouseCursor failed: %s\n", SDL_GetError());
++ }
+
+ SDL_ShowCursor(SDL_DISABLE);
+
+@@ -192,8 +217,7 @@ void InitializeVideoManager(void)
+
+ void ShutdownVideoManager(void)
+ {
+- DebugMsg(TOPIC_VIDEO, DBG_LEVEL_0, "Shutting down the video manager");
+-
++ SLOGD(DEBUG_TAG_VIDEO, "Shutting down the video manager");
+ /* Toggle the state of the video manager to indicate to the refresh thread
+ * that it needs to shut itself down */
+
+@@ -211,32 +235,6 @@ void SuspendVideoManager(void)
+ guiVideoManagerState = VIDEO_SUSPENDED;
+ }
+
+-
+-BOOLEAN RestoreVideoManager(void)
+-{
+-#if 1 // XXX TODO
+- UNIMPLEMENTED;
+- return false;
+-#else
+- // Make sure the video manager is indeed suspended before moving on
+-
+- if (guiVideoManagerState == VIDEO_SUSPENDED)
+- {
+- // Set the video state to VIDEO_ON
+-
+- guiFrameBufferState = BUFFER_DIRTY;
+- gfForceFullScreenRefresh = TRUE;
+- guiVideoManagerState = VIDEO_ON;
+- return TRUE;
+- }
+- else
+- {
+- return FALSE;
+- }
+-#endif
+-}
+-
+-
+ void InvalidateRegion(INT32 iLeft, INT32 iTop, INT32 iRight, INT32 iBottom)
+ {
+ if (gfForceFullScreenRefresh)
+@@ -346,7 +344,7 @@ void InvalidateScreen(void)
+ static void ScrollJA2Background(INT16 sScrollXIncrement, INT16 sScrollYIncrement)
+ {
+ SDL_Surface* Frame = FrameBuffer;
+- SDL_Surface* Source = ScreenBuffer; // Primary
++ SDL_Surface* Source = SDL_CreateRGBSurface(0, ScreenBuffer->w, ScreenBuffer->h, PIXEL_DEPTH, RED_MASK, GREEN_MASK, BLUE_MASK, ALPHA_MASK);
+ SDL_Surface* Dest = ScreenBuffer; // Back
+ SDL_Rect SrcRect;
+ SDL_Rect DstRect;
+@@ -356,6 +354,8 @@ static void ScrollJA2Background(INT16 sScrollXIncrement, INT16 sScrollYIncrement
+ const UINT16 usWidth = SCREEN_WIDTH;
+ const UINT16 usHeight = gsVIEWPORT_WINDOW_END_Y - gsVIEWPORT_WINDOW_START_Y;
+
++ SDL_BlitSurface(ScreenBuffer, NULL, Source, NULL);
++
+ if (sScrollXIncrement < 0)
+ {
+ SrcRect.x = 0;
+@@ -444,14 +444,17 @@ static void ScrollJA2Background(INT16 sScrollXIncrement, INT16 sScrollYIncrement
+ // BLIT NEW
+ ExecuteVideoOverlaysToAlternateBuffer(BACKBUFFER);
+
+- SDL_UpdateRect
+- (
+- Dest,
+- gsVIEWPORT_START_X,
+- gsVIEWPORT_WINDOW_START_Y,
+- gsVIEWPORT_END_X - gsVIEWPORT_START_X,
+- gsVIEWPORT_WINDOW_END_Y - gsVIEWPORT_WINDOW_START_Y
+- );
++ SDL_Texture* screenTexture = SDL_CreateTextureFromSurface(GameRenderer, ScreenBuffer);
++
++ SDL_Rect r;
++ r.x = gsVIEWPORT_START_X;
++ r.y = gsVIEWPORT_WINDOW_START_Y;
++ r.w = gsVIEWPORT_END_X - gsVIEWPORT_START_X;
++ r.h = gsVIEWPORT_WINDOW_END_Y - gsVIEWPORT_WINDOW_START_Y;
++ SDL_RenderCopy(GameRenderer, screenTexture, &r, &r);
++
++ SDL_FreeSurface(Source);
++ SDL_DestroyTexture(screenTexture);
+ }
+
+
+@@ -481,8 +484,8 @@ static void WriteTGAHeader(FILE* const f)
+ 0,
+ 0, 0,
+ 0, 0,
+- SCREEN_WIDTH % 256, SCREEN_WIDTH / 256,
+- SCREEN_HEIGHT % 256, SCREEN_HEIGHT / 256,
++ (UINT8) (SCREEN_WIDTH % 256), (UINT8) (SCREEN_WIDTH / 256),
++ (UINT8) (SCREEN_HEIGHT % 256), (UINT8) (SCREEN_HEIGHT / 256),
+ PIXEL_DEPTH,
+ 0
+ };
+@@ -550,29 +553,6 @@ static void TakeScreenshot()
+
+ static void SnapshotSmall(void);
+
+-#if EXPENSIVE_SDL_UPDATE_RECT
+-static void joinInRectangle(SDL_Rect &result, const SDL_Rect &newRect)
+-{
+- if((newRect.w != 0) && (newRect.h != 0))
+- {
+- if((result.w == 0) && (result.h == 0))
+- {
+- // special case: empty rectangle
+- result = newRect;
+- }
+- else
+- {
+- int16_t X2 = std::max(result.x + result.w, newRect.x + newRect.w);
+- int16_t Y2 = std::max(result.y + result.h, newRect.y + newRect.h);
+- result.x = std::min(result.x, newRect.x);
+- result.y = std::min(result.y, newRect.y);
+- result.w = X2 - result.x;
+- result.h = Y2 - result.y;
+- }
+- }
+-}
+-#endif
+-
+ void RefreshScreen(void)
+ {
+ if (guiVideoManagerState != VIDEO_ON) return;
+@@ -660,10 +640,6 @@ void RefreshScreen(void)
+ gfPrintFrameBuffer = FALSE;
+ }
+
+-#if EXPENSIVE_SDL_UPDATE_RECT
+- SDL_Rect combinedRect = {0, 0, 0, 0};
+-#endif
+-
+ SGPPoint MousePos;
+ GetMousePos(&MousePos);
+ SDL_Rect src;
+@@ -675,56 +651,13 @@ void RefreshScreen(void)
+ dst.x = MousePos.iX - gsMouseCursorXOffset;
+ dst.y = MousePos.iY - gsMouseCursorYOffset;
+ SDL_BlitSurface(MouseCursor, &src, ScreenBuffer, &dst);
+-
+-#if EXPENSIVE_SDL_UPDATE_RECT
+- joinInRectangle(combinedRect, dst);
+- joinInRectangle(combinedRect, MouseBackground);
+-#else
+- SDL_UpdateRects(ScreenBuffer, 1, &dst);
+- SDL_UpdateRects(ScreenBuffer, 1, &MouseBackground);
+-#endif
+-
+ MouseBackground = dst;
+
+- if (gfForceFullScreenRefresh)
+- {
+- SDL_UpdateRect(ScreenBuffer, 0, 0, 0, 0);
+- }
+- else
+- {
+-#if EXPENSIVE_SDL_UPDATE_RECT
+- for(int i = 0; i < guiDirtyRegionCount; i++)
+- {
+- joinInRectangle(combinedRect, DirtyRegions[i]);
+- }
+-#else
+- SDL_UpdateRects(ScreenBuffer, guiDirtyRegionCount, DirtyRegions);
+-#endif
+-
+- for (UINT32 i = 0; i < guiDirtyRegionExCount; i++)
+- {
+- SDL_Rect* r = &DirtyRegionsEx[i];
+- if (scrolling)
+- {
+- if (r->y <= gsVIEWPORT_WINDOW_END_Y && r->y + r->h <= gsVIEWPORT_WINDOW_END_Y)
+- {
+- continue;
+- }
+- }
+-#if EXPENSIVE_SDL_UPDATE_RECT
+- joinInRectangle(combinedRect, *r);
+-#else
+- SDL_UpdateRects(ScreenBuffer, 1, r);
+-#endif
+- }
+- }
++ SDL_UpdateTexture(ScreenTexture, NULL, ScreenBuffer->pixels, ScreenBuffer->pitch);
+
+-#if EXPENSIVE_SDL_UPDATE_RECT
+- if((combinedRect.w != 0) && (combinedRect.h != 0))
+- {
+- SDL_UpdateRects(ScreenBuffer, 1, &combinedRect);
+- }
+-#endif
++ SDL_RenderClear(GameRenderer);
++ SDL_RenderCopy(GameRenderer, ScreenTexture, NULL, NULL);
++ SDL_RenderPresent(GameRenderer);
+
+ gfForceFullScreenRefresh = FALSE;
+ guiDirtyRegionCount = 0;
+@@ -815,33 +748,6 @@ static void SnapshotSmall(void)
+ if (giNumFrames == MAX_NUM_FRAMES) RefreshMovieCache();
+ }
+
+-
+-void VideoCaptureToggle(void)
+-{
+-#ifdef JA2TESTVERSION
+- gfVideoCapture = !gfVideoCapture;
+- if (gfVideoCapture)
+- {
+- for (INT32 cnt = 0; cnt < MAX_NUM_FRAMES; cnt++)
+- {
+- gpFrameData[cnt] = MALLOCN(UINT16, SCREEN_WIDTH * SCREEN_HEIGHT);
+- }
+- guiLastFrame = GetClock();
+- }
+- else
+- {
+- RefreshMovieCache();
+-
+- for (INT32 cnt = 0; cnt < MAX_NUM_FRAMES; cnt++)
+- {
+- if (gpFrameData[cnt] != NULL) MemFree(gpFrameData[cnt]);
+- }
+- }
+- giNumFrames = 0;
+-#endif
+-}
+-
+-
+ static void RefreshMovieCache(void)
+ {
+ static UINT32 uiPicNum = 0;
+@@ -935,7 +841,7 @@ void InitializeVideoSurfaceManager(void)
+
+ void ShutdownVideoSurfaceManager(void)
+ {
+- DebugMsg(TOPIC_VIDEOSURFACE, DBG_LEVEL_0, "Shutting down the Video Surface manager");
++ SLOGD(DEBUG_TAG_VIDEO, "Shutting down the Video Surface manager");
+
+ // Delete primary viedeo surfaces
+ DeletePrimaryVideoSurfaces();
+diff --git a/sgp/Video.h b/sgp/Video.h
+index fbb980b63..2158ef3ee 100644
+--- a/sgp/Video.h
++++ b/sgp/Video.h
+@@ -6,13 +6,14 @@
+
+
+ #define VIDEO_NO_CURSOR 0xFFFF
++#define GAME_WINDOW g_game_window
+
++extern SDL_Window* g_game_window;
+
+ void VideoSetFullScreen(BOOLEAN enable);
+ void InitializeVideoManager(void);
+ void ShutdownVideoManager(void);
+ void SuspendVideoManager(void);
+-BOOLEAN RestoreVideoManager(void);
+ void InvalidateRegion(INT32 iLeft, INT32 iTop, INT32 iRight, INT32 iBottom);
+ void InvalidateScreen(void);
+ void GetPrimaryRGBDistributionMasks(UINT32* RedBitMask, UINT32* GreenBitMask, UINT32* BlueBitMask);
+@@ -25,8 +26,6 @@ void VideoToggleFullScreen(void);
+
+ void SetMouseCursorProperties(INT16 sOffsetX, INT16 sOffsetY, UINT16 usCursorHeight, UINT16 usCursorWidth);
+
+-void VideoCaptureToggle(void);
+-
+ void InvalidateRegionEx(INT32 iLeft, INT32 iTop, INT32 iRight, INT32 iBottom);
+
+ void RefreshScreen(void);