diff --git a/.circleci/config.yml b/.circleci/config.yml
index 711be39d76fdfb80c4680bbae19c8dd135af0afb..3faca372cd6fb0f92d05fdea87de12a297705395 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -50,7 +50,7 @@ jobs:
             - v1-SRB2-APT
       - run:
           name: Install SDK
-          command: apt-get -o Dir::Cache="/root/.cache/apt" -qq -y --no-install-recommends install git build-essential nasm libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client
+          command: apt-get -o Dir::Cache="/root/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client
       - run:
           name: make md5sum
           command: find /root/.cache/apt/archives -type f -print0 | sort -z | xargs -r0 md5sum > /root/.cache/apt_archives.md5
diff --git a/.gitignore b/.gitignore
index cd828dc116957ceda70d6c5b8a2b929b158f1f80..268e3632906a9b840747e6c015b32848ba45b50f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,4 +22,5 @@ Win32_LIB_ASM_Release
 /make
 /bin
 /build
-/build.*
+/build/*
+/CMakeUserPresets.json
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 915912af5e8689b782e62b30c8eafbee66804b97..80a3bdcd6798a48d10ea928d60ca3c4fb77bf353 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -53,11 +53,15 @@ else()
 	set(SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT OFF)
 endif()
 
+# Clang tidy options will be ignored if CMAKE_<LANG>_CLANG_TIDY are set.
+option(SRB2_CONFIG_ENABLE_CLANG_TIDY_C "Enable default clang-tidy check configuration for C" OFF)
+option(SRB2_CONFIG_ENABLE_CLANG_TIDY_CXX "Enable default clang-tidy check configuration for C++" OFF)
 option(
 	SRB2_CONFIG_SYSTEM_LIBRARIES
 	"Link dependencies using CMake's find_package and do not use internal builds"
 	${SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT}
 )
+option(SRB2_CONFIG_ENABLE_TESTS "Build the test suite" ON)
 # This option isn't recommended for distribution builds and probably won't work (yet).
 cmake_dependent_option(
 	SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES
@@ -76,6 +80,25 @@ option(SRB2_CONFIG_ZDEBUG "Compile with ZDEBUG defined." OFF)
 option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF)
 set(SRB2_CONFIG_ASSET_DIRECTORY "" CACHE PATH "Path to directory that contains all asset files for the installer. If set, assets will be part of installation and cpack.")
 
+if(SRB2_CONFIG_ENABLE_TESTS)
+	# https://github.com/catchorg/Catch2
+	CPMAddPackage(
+		NAME Catch2
+		VERSION 3.4.0
+		GITHUB_REPOSITORY catchorg/Catch2
+		OPTIONS
+			"CATCH_INSTALL_DOCS OFF"
+	)
+	list(APPEND CMAKE_MODULE_PATH "${Catch2_SOURCE_DIR}/extras")
+	include(CTest)
+	include(Catch)
+	add_executable(srb2tests)
+	# To add tests, use target_sources to add individual test files to the target in subdirs.
+	target_link_libraries(srb2tests PRIVATE Catch2::Catch2 Catch2::Catch2WithMain)
+	target_compile_features(srb2tests PRIVATE c_std_11 cxx_std_17)
+	catch_discover_tests(srb2tests)
+endif()
+
 # Enable CCache
 # (Set USE_CCACHE=ON to use, CCACHE_OPTIONS for options)
 if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL Windows)
@@ -108,7 +131,11 @@ if("${SRB2_CONFIG_SYSTEM_LIBRARIES}")
 	find_package(SDL2_mixer REQUIRED)
 	find_package(CURL REQUIRED)
 	find_package(OPENMPT REQUIRED)
-	find_package(GME REQUIRED)
+
+	# libgme defaults to "Nuked" YM2612 emulator, which is
+	# very SLOW. The system library probably uses the
+	# default so just always build it.
+	#find_package(GME REQUIRED)
 endif()
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
@@ -119,13 +146,6 @@ if ((${SRB2_USE_CCACHE}) AND (${CMAKE_C_COMPILER} MATCHES "clang"))
 	message(WARNING "Using clang and CCache: You may want to set environment variable CCACHE_CPP2=yes to prevent include errors during compile.")
 endif()
 
-# Add sources from Sourcefile
-function(target_sourcefile type)
-	file(STRINGS Sourcefile list
-		REGEX "[-0-9A-Za-z_]+\.${type}")
-	target_sources(SRB2SDL2 PRIVATE ${list})
-endfunction()
-
 # bitness check
 set(SRB2_SYSTEM_BITS 0)
 if(CMAKE_SIZEOF_VOID_P EQUAL 8)
@@ -144,7 +164,8 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
 set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
 
 # Set EXE names so the assets CMakeLists can refer to its target
-set(SRB2_SDL2_EXE_NAME srb2 CACHE STRING "Executable binary output name")
+set(SRB2_SDL2_EXE_NAME "" CACHE STRING "Override executable binary output name")
+set(SRB2_SDL2_EXE_SUFFIX "" CACHE STRING "Optional executable suffix, separated by an underscore")
 
 include_directories(${CMAKE_CURRENT_BINARY_DIR}/src)
 
@@ -152,11 +173,37 @@ add_subdirectory(src)
 add_subdirectory(assets)
 
 
-## config.h generation
 set(GIT_EXECUTABLE "git" CACHE FILEPATH "Path to git binary")
 include(GitUtilities)
-git_latest_commit(SRB2_COMP_COMMIT "${CMAKE_SOURCE_DIR}")
-git_current_branch(SRB2_GIT_BRANCH "${CMAKE_SOURCE_DIR}")
-set(SRB2_COMP_BRANCH "${SRB2_GIT_BRANCH}")
-set(SRB2_COMP_REVISION "${SRB2_COMP_COMMIT}")
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/config.h)
+
+if("${SRB2_SDL2_EXE_NAME}" STREQUAL "")
+	# cause a reconfigure if the branch changes
+	get_git_dir(SRB2_GIT_DIR)
+	configure_file("${SRB2_GIT_DIR}/HEAD" HEAD COPYONLY)
+
+	git_current_branch(SRB2_GIT_REVISION)
+
+	if("${SRB2_GIT_REVISION}" STREQUAL "")
+		# use abbreviated commit hash if on detached HEAD
+		git_latest_commit(SRB2_GIT_REVISION)
+	endif()
+
+	if("${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
+		list(APPEND EXE_NAME_PARTS "srb2win")
+	elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
+		list(APPEND EXE_NAME_PARTS "lsdlsrb2")
+	else()
+		list(APPEND EXE_NAME_PARTS "srb2")
+	endif()
+
+	if(NOT "${SRB2_GIT_REVISION}" STREQUAL "master")
+		list(APPEND EXE_NAME_PARTS ${SRB2_GIT_REVISION})
+	endif()
+else()
+	list(APPEND EXE_NAME_PARTS ${SRB2_SDL2_EXE_NAME})
+endif()
+
+list(APPEND EXE_NAME_PARTS ${SRB2_SDL2_EXE_SUFFIX})
+
+list(JOIN EXE_NAME_PARTS "_" EXE_NAME)
+set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME ${EXE_NAME})
diff --git a/CMakePresets.json b/CMakePresets.json
new file mode 100644
index 0000000000000000000000000000000000000000..7713bb38516877d5e8e50cc46914a7f58c9c6d73
--- /dev/null
+++ b/CMakePresets.json
@@ -0,0 +1,29 @@
+{
+	"version": 3,
+	"configurePresets": [
+		{
+			"name": "default",
+			"description": "Build using default generator",
+			"binaryDir": "build",
+			"cacheVariables": {
+				"CMAKE_C_FLAGS": "-fdiagnostics-color",
+				"CMAKE_CXX_FLAGS": "-fdiagnostics-color",
+				"CMAKE_BUILD_TYPE": "RelWithDebInfo"
+			}
+		},
+		{
+			"name": "debug",
+			"description": "Build for development (no optimizations)",
+			"inherits": "default",
+			"cacheVariables": {
+				"CMAKE_BUILD_TYPE": "Debug"
+			}
+		}
+	],
+	"buildPresets": [
+		{
+			"name": "default",
+			"configurePreset": "default"
+		}
+	]
+}
diff --git a/README.md b/README.md
index 49a3cc36d167169467a2d65bec7527610691694d..56ff2d02d24e39fe6f8038a0770e7b1b265c7ae7 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,6 @@
 [Sonic Robo Blast 2](https://srb2.org/) is a 3D Sonic the Hedgehog fangame based on a modified version of [Doom Legacy](http://doomlegacy.sourceforge.net/).
 
 ## Dependencies
-- NASM (x86 builds only)
 - SDL2 (Linux/OS X only)
 - SDL2-Mixer (Linux/OS X only)
 - libupnp (Linux/OS X only)
diff --git a/SRB2.cbp b/SRB2.cbp
index 2a1eb87b8565b3d2d9c13216c23d39dceecd6f92..9e887bf859c05ab821b3936f29cb3089daef2b2d 100644
--- a/SRB2.cbp
+++ b/SRB2.cbp
@@ -1992,24 +1992,6 @@ HW3SOUND for 3D hardware sound  support
 			<Option compilerVar="CC" />
 		</Unit>
 		<Unit filename="src/v_video.h" />
-		<Unit filename="src/vid_copy.s">
-			<Option compilerVar="CC" />
-			<Option compiler="avrgcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="gnu_gcc_compiler_for_mingw32" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="gnu_gcc_compiler_for_mingw64" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="armelfgcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="tricoregcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="ppcgcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="gcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option target="Debug Native/SDL" />
-			<Option target="Release Native/SDL" />
-			<Option target="Debug Linux/SDL" />
-			<Option target="Release Linux/SDL" />
-			<Option target="Debug Mingw/SDL" />
-			<Option target="Release Mingw/SDL" />
-			<Option target="Debug Mingw/DirectX" />
-			<Option target="Release Mingw/DirectX" />
-		</Unit>
 		<Unit filename="src/w_wad.c">
 			<Option compilerVar="CC" />
 		</Unit>
diff --git a/SRB2_common.props b/SRB2_common.props
index 0f80ceb174874e682f0205de06733cb25e2b247a..6a0d53484f10106bc254851b1e1056dc7b24a86a 100644
--- a/SRB2_common.props
+++ b/SRB2_common.props
@@ -25,9 +25,6 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(PlatformTarget)'=='x86'">
-    <ClCompile>
-      <PreprocessorDefinitions>USEASM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-    </ClCompile>
     <Link>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
     </Link>
diff --git a/Srb2.dev b/Srb2.dev
index 21683e7c3c5e055393d91778fba3405bfae240de..8bd36cf490cc84dae69d25399113d346392e4fc8 100644
--- a/Srb2.dev
+++ b/Srb2.dev
@@ -5,7 +5,7 @@ Ver=3
 IsCpp=0
 Type=0
 UnitCount=279
-Folders=A_Asm,B_Bot,BLUA,D_Doom,F_Frame,G_Game,H_Hud,Hw_Hardware,Hw_Hardware/r_opengl,I_Interface,I_Interface/Dummy,I_Interface/SDL,I_Interface/Win32,LUA,M_Misc,P_Play,R_Rend,S_Sounds,W_Wad
+Folders=B_Bot,BLUA,D_Doom,F_Frame,G_Game,H_Hud,Hw_Hardware,Hw_Hardware/r_opengl,I_Interface,I_Interface/Dummy,I_Interface/SDL,I_Interface/Win32,LUA,M_Misc,P_Play,R_Rend,S_Sounds,W_Wad
 CommandLine=
 CompilerSettings=00000000000100000111e1
 PchHead=-1
@@ -1473,36 +1473,6 @@ Priority=1000
 OverrideBuildCmd=0
 BuildCmd=
 
-[Unit149]
-FileName=src\tmap.nas
-Folder=A_Asm
-Compile=0
-CompileCpp=0
-Link=0
-Priority=1000
-OverrideBuildCmd=1
-BuildCmd=nasm.exe -g -o $@ -f win32 src/tmap.nas
-
-[Unit150]
-FileName=src\asm_defs.inc
-Folder=A_Asm
-Compile=0
-CompileCpp=0
-Link=0
-Priority=1000
-OverrideBuildCmd=0
-BuildCmd=
-
-[Unit151]
-FileName=src\vid_copy.s
-Folder=A_Asm
-Compile=1
-CompileCpp=0
-Link=1
-Priority=1000
-OverrideBuildCmd=1
-BuildCmd=$(CC) $(CFLAGS) -x assembler-with-cpp -c src/vid_copy.s -o $@
-
 [Unit152]
 FileName=src\y_inter.h
 Folder=H_Hud
@@ -1543,26 +1513,6 @@ Priority=1000
 OverrideBuildCmd=0
 BuildCmd=
 
-[Unit156]
-FileName=src\p5prof.h
-Folder=A_Asm
-Compile=1
-CompileCpp=0
-Link=1
-Priority=1000
-OverrideBuildCmd=0
-BuildCmd=
-
-[Unit157]
-FileName=src\tmap_mmx.nas
-Folder=A_Asm
-Compile=0
-CompileCpp=0
-Link=0
-Priority=1000
-OverrideBuildCmd=1
-BuildCmd=nasm.exe -g -o $@ -f win32 src/tmap_mmx.nas
-
 [Unit159]
 FileName=src\lzf.h
 Folder=W_Wad
diff --git a/alias-bootstrap.sh b/alias-bootstrap.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f1a6ac4ed918db4391a39c9a3076dfcc38d2861b
--- /dev/null
+++ b/alias-bootstrap.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env sh
+
+# All these commands can be run from anywhere in the git
+# tree, not just the top level.
+
+# Usage: git cmake
+#
+# Same usage as standard CMake command.
+#
+git config 'alias.cmake' '!cmake'
+
+# Usage: git build <build preset> [options]
+# Usage: git build [options]
+#
+# In the second usage, when no preset is given, the
+# "default" build preset is used.
+#
+# Available options can be found by running:
+#
+#     git cmake --build
+#
+git config 'alias.build' '!p="${1##-*}"; [ "$p" ] && shift; git cmake --build --preset "${p:-default}"'
+
+# Usage: git crossmake
+#
+# Shortcut to i686-w64-mingw32-cmake (CMake cross
+# compiler)
+#
+git config 'alias.crossmake' '!i686-w64-mingw32-cmake'
diff --git a/appveyor.yml b/appveyor.yml
index e3348d35cb2b001fba3162792867548b278d63f5..63d801b734719bf4ba47dfb42b1b7849c3a23189 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.11.{branch}-{build}
+version: 2.2.13.{branch}-{build}
 os: MinGW
 
 environment:
@@ -7,8 +7,6 @@ environment:
  # c:\mingw-w64 i686 has gcc 6.3.0, so use c:\msys64 7.3.0 instead
  MINGW_SDK: c:\msys64\mingw32
  CFLAGS: -Wno-implicit-fallthrough
- NASM_ZIP: nasm-2.12.01
- NASM_URL: http://www.nasm.us/pub/nasm/releasebuilds/2.12.01/win64/nasm-2.12.01-win64.zip
  UPX_ZIP: upx391w
  UPX_URL: http://upx.sourceforge.net/download/upx391w.zip
  CCACHE_EXE: ccache.exe
@@ -40,17 +38,12 @@ environment:
  ASSET_CLEAN: 0
 
 cache:
-- nasm-2.12.01.zip
 - upx391w.zip
 - ccache.exe
 - C:\Users\appveyor\.ccache
 - C:\Users\appveyor\srb2_cache
 
 install:
-- if not exist "%NASM_ZIP%.zip" appveyor DownloadFile "%NASM_URL%" -FileName "%NASM_ZIP%.zip"
-- 7z x -y "%NASM_ZIP%.zip" -o%TMP% >null
-- robocopy /S /xx /ns /nc /nfl /ndl /np /njh /njs "%TMP%\%NASM_ZIP%" "%MINGW_SDK%\bin" nasm.exe || exit 0
-
 - if not exist "%UPX_ZIP%.zip" appveyor DownloadFile "%UPX_URL%" -FileName "%UPX_ZIP%.zip"
 - 7z x -y "%UPX_ZIP%.zip" -o%TMP% >null
 - robocopy /S /xx /ns /nc /nfl /ndl /np /njh /njs "%TMP%\%UPX_ZIP%" "%MINGW_SDK%\bin" upx.exe || exit 0
@@ -65,7 +58,6 @@ configuration:
 before_build:
 - set "Path=%MINGW_SDK%\bin;%Path%"
 - mingw32-make --version
-- nasm -v
 - if not [%NOUPX%] == [1] ( upx -V )
 - ccache -V
 - ccache -s
diff --git a/cmake/Comptime.cmake b/cmake/Comptime.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..8388aed9ece077229a35d601ffd0679cb2ecd035
--- /dev/null
+++ b/cmake/Comptime.cmake
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
+
+set(CMAKE_BINARY_DIR "${BINARY_DIR}")
+set(CMAKE_CURRENT_BINARY_DIR "${BINARY_DIR}")
+
+# Set up CMAKE path
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
+
+include(GitUtilities)
+
+git_current_branch(SRB2_COMP_BRANCH)
+git_working_tree_dirty(SRB2_COMP_UNCOMMITTED)
+
+git_latest_commit(SRB2_COMP_REVISION)
+git_subject(subject)
+string(REGEX REPLACE "([\"\\])" "\\\\\\1" SRB2_COMP_NOTE "${subject}")
+
+if("${CMAKE_BUILD_TYPE}" STREQUAL "")
+	set(CMAKE_BUILD_TYPE None)
+endif()
+
+# These build types enable optimizations of some kind by default.
+set(optimized_build_types "MINSIZEREL;RELEASE;RELWITHDEBINFO")
+
+string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
+if("${build_type}" IN_LIST optimized_build_types)
+	set(SRB2_COMP_OPTIMIZED TRUE)
+else()
+	set(SRB2_COMP_OPTIMIZED FALSE)
+endif()
+
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h")
diff --git a/cmake/Modules/CMakeASM_YASMInformation.cmake b/cmake/Modules/CMakeASM_YASMInformation.cmake
deleted file mode 100644
index 1765180853bb2d23217a1eb785f97737411e68b1..0000000000000000000000000000000000000000
--- a/cmake/Modules/CMakeASM_YASMInformation.cmake
+++ /dev/null
@@ -1,46 +0,0 @@
-
-#=============================================================================
-# Copyright 2010 Kitware, Inc.
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-#  License text for the above reference.)
-
-# support for the yasm assembler
-
-set(CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS nasm yasm asm)
-
-if(NOT CMAKE_ASM_YASM_OBJECT_FORMAT)
-  if(WIN32)
-    if(CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT win64)
-    else()
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT win32)
-    endif()
-  elseif(APPLE)
-    if(CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT macho64)
-    else()
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT macho)
-    endif()
-  else()
-    if(CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT elf64)
-    else()
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT elf)
-    endif()
-  endif()
-endif()
-
-set(CMAKE_ASM_YASM_COMPILE_OBJECT "<CMAKE_ASM_YASM_COMPILER> <FLAGS> -f ${CMAKE_ASM_YASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
-
-# Load the generic ASMInformation file:
-set(ASM_DIALECT "_YASM")
-include(CMakeASMInformation)
-set(ASM_DIALECT)
diff --git a/cmake/Modules/CMakeDetermineASM_YASMCompiler.cmake b/cmake/Modules/CMakeDetermineASM_YASMCompiler.cmake
deleted file mode 100644
index a5e7c9e5801121f04411e5f1b1c6efa98736bcc1..0000000000000000000000000000000000000000
--- a/cmake/Modules/CMakeDetermineASM_YASMCompiler.cmake
+++ /dev/null
@@ -1,27 +0,0 @@
-
-#=============================================================================
-# Copyright 2010 Kitware, Inc.
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-#  License text for the above reference.)
-
-# Find the nasm assembler. yasm (http://www.tortall.net/projects/yasm/) is nasm compatible
-
-set(CMAKE_ASM_YASM_COMPILER_LIST nasm yasm)
-
-if(NOT CMAKE_ASM_YASM_COMPILER)
-  find_program(CMAKE_ASM_YASM_COMPILER yasm
-    "$ENV{ProgramFiles}/YASM")
-endif()
-
-# Load the generic DetermineASM compiler file with the DIALECT set properly:
-set(ASM_DIALECT "_YASM")
-include(CMakeDetermineASMCompiler)
-set(ASM_DIALECT)
diff --git a/cmake/Modules/CMakeTestASM_YASMCompiler.cmake b/cmake/Modules/CMakeTestASM_YASMCompiler.cmake
deleted file mode 100644
index 745f7125c4a2f7a003c488b89d977b75a8eb3ebc..0000000000000000000000000000000000000000
--- a/cmake/Modules/CMakeTestASM_YASMCompiler.cmake
+++ /dev/null
@@ -1,23 +0,0 @@
-
-#=============================================================================
-# Copyright 2010 Kitware, Inc.
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-#  License text for the above reference.)
-
-# This file is used by EnableLanguage in cmGlobalGenerator to
-# determine that the selected ASM_NASM "compiler" works.
-# For assembler this can only check whether the compiler has been found,
-# because otherwise there would have to be a separate assembler source file
-# for each assembler on every architecture.
-
-set(ASM_DIALECT "_YASM")
-include(CMakeTestASMCompiler)
-set(ASM_DIALECT)
diff --git a/cmake/Modules/GitUtilities.cmake b/cmake/Modules/GitUtilities.cmake
index d29e6b509dd27015f429c9e8f4de12e20fcc5b12..586c7b433ff31476fe2a4cf0d5634d36d2c997be 100644
--- a/cmake/Modules/GitUtilities.cmake
+++ b/cmake/Modules/GitUtilities.cmake
@@ -6,38 +6,54 @@ endif()
 
 set(__GitUtilities ON)
 
-function(git_describe variable path)
-	execute_process(COMMAND "${GIT_EXECUTABLE}" "describe"
-		WORKING_DIRECTORY "${path}"
-		RESULT_VARIABLE result
+macro(_git_command)
+	execute_process(
+		COMMAND "${GIT_EXECUTABLE}" ${ARGN}
+		WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
 		OUTPUT_VARIABLE output
 		ERROR_QUIET
 		OUTPUT_STRIP_TRAILING_WHITESPACE
 	)
+endmacro()
 
+macro(_git_easy_command)
+	_git_command(${ARGN})
 	set(${variable} "${output}" PARENT_SCOPE)
-endfunction()
+endmacro()
 
-function(git_current_branch variable path)
-	execute_process(COMMAND ${GIT_EXECUTABLE} "symbolic-ref" "--short" "HEAD"
-		WORKING_DIRECTORY "${path}"
-		RESULT_VARIABLE result
-		OUTPUT_VARIABLE output
-		ERROR_QUIET
-		OUTPUT_STRIP_TRAILING_WHITESPACE
-	)
+function(git_current_branch variable)
+	_git_command(symbolic-ref -q --short HEAD)
+
+	# If a detached head, a ref could still be resolved.
+	if("${output}" STREQUAL "")
+		_git_command(describe --all --exact-match)
+
+		# Get the ref, in the form heads/master or
+		# remotes/origin/master so isolate the final part.
+		string(REGEX REPLACE ".*/" "" output "${output}")
+	endif()
 
 	set(${variable} "${output}" PARENT_SCOPE)
 endfunction()
 
-function(git_latest_commit variable path)
-	execute_process(COMMAND ${GIT_EXECUTABLE} "rev-parse" "--short" "HEAD"
-		WORKING_DIRECTORY "${path}"
-		RESULT_VARIABLE result
-		OUTPUT_VARIABLE output
-		ERROR_QUIET
-		OUTPUT_STRIP_TRAILING_WHITESPACE
-	)
+function(git_latest_commit variable)
+	_git_easy_command(rev-parse --short HEAD)
+endfunction()
 
-	set(${variable} "${output}" PARENT_SCOPE)
-endfunction()
\ No newline at end of file
+function(git_working_tree_dirty variable)
+	_git_command(status --porcelain -uno)
+
+	if(output STREQUAL "")
+		set(${variable} FALSE PARENT_SCOPE)
+	else()
+		set(${variable} TRUE PARENT_SCOPE)
+	endif()
+endfunction()
+
+function(git_subject variable)
+	_git_easy_command(log -1 --format=%s)
+endfunction()
+
+function(get_git_dir variable)
+	_git_easy_command(rev-parse --git-dir)
+endfunction()
diff --git a/cmake/Modules/clang-tidy-default.cmake b/cmake/Modules/clang-tidy-default.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..2be3af10d961229bac835c7083a0d81f677f7144
--- /dev/null
+++ b/cmake/Modules/clang-tidy-default.cmake
@@ -0,0 +1,21 @@
+find_program(CLANG_TIDY clang-tidy)
+
+# Note: Apple Clang does not ship with clang tools. If you want clang-tidy on
+# macOS, it's best to install the Homebrew llvm bottle and set CLANG_TIDY
+# in your build directory. The llvm package is keg-only, so it will not
+# collide with Apple Clang.
+
+function(target_set_default_clang_tidy target lang checks)
+    if("${CLANG_TIDY}" STREQUAL "CLANG_TIDY-NOTFOUND")
+        return()
+    endif()
+
+    get_target_property(c_clang_tidy_prop SRB2SDL2 C_CLANG_TIDY)
+    if(NOT ("${c_clang_tidy_prop}" STREQUAL "c_clang_tidy_prop-NOTFOUND"))
+        return()
+    endif()
+
+    set_target_properties("${target}" PROPERTIES
+	    ${lang}_CLANG_TIDY "${CLANG_TIDY};-checks=${checks}"
+    )
+endfunction()
diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
index dd5cdb46b2c2338fd1b2728f877ab903389054ad..41ad998891154f56fe850cdfe457a48189dd648f 100644
--- a/extras/conf/SRB2-22.cfg
+++ b/extras/conf/SRB2-22.cfg
@@ -4367,7 +4367,6 @@ thingtypes
 	{
 		color = 14; // Yellow
 		title = "Rings and Weapon Panels";
-		width = 24;
 		height = 24;
 		flags8height = 24;
 		flags8text = "[8] Float";
@@ -4377,7 +4376,6 @@ thingtypes
 		{
 			title = "Ring";
 			sprite = "RINGA0";
-			width = 16;
 		}
 		301
 		{
@@ -4393,6 +4391,7 @@ thingtypes
 		{
 			title = "Infinity Ring";
 			sprite = "RNGIA0";
+			width = 24;
 		}
 		304
 		{
@@ -4418,43 +4417,53 @@ thingtypes
 		{
 			title = "CTF Team Ring (Red)";
 			sprite = "internal:TRNGA0R";
-			width = 16;
 		}
 		309
 		{
 			title = "CTF Team Ring (Blue)";
 			sprite = "internal:TRNGA0B";
-			width = 16;
 		}
 		330
 		{
 			title = "Bounce Ring Panel";
 			sprite = "PIKBA0";
+			width = 24;
+			height = 40;
 		}
 		331
 		{
 			title = "Rail Ring Panel";
 			sprite = "PIKRA0";
+			width = 24;
+			height = 40;
 		}
 		332
 		{
 			title = "Automatic Ring Panel";
 			sprite = "PIKAA0";
+			width = 24;
+			height = 40;
 		}
 		333
 		{
 			title = "Explosion Ring Panel";
 			sprite = "PIKEA0";
+			width = 24;
+			height = 40;
 		}
 		334
 		{
 			title = "Scatter Ring Panel";
 			sprite = "PIKSA0";
+			width = 24;
+			height = 40;
 		}
 		335
 		{
 			title = "Grenade Ring Panel";
 			sprite = "PIKGA0";
+			width = 24;
+			height = 40;
 		}
 	}
 
@@ -4463,7 +4472,7 @@ thingtypes
 		color = 10; // Light Green
 		title = "Other Collectibles";
 		width = 16;
-		height = 32;
+		height = 24;
 		sort = 1;
 		sprite = "CEMGA0";
 
@@ -4529,6 +4538,7 @@ thingtypes
 		{
 			title = "Emerald Hunt Location";
 			sprite = "SHRDA0";
+			height = 32;
 			flags8height = 24;
 			flags8text = "[8] Float";
 		}
diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg
index df08e3ac50fb47c80b412da2966e10cc2cec5f79..9eb227974dfd0b33de593a7ced83b42fab7d6738 100644
--- a/extras/conf/udb/Includes/SRB222_things.cfg
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -1185,7 +1185,7 @@ udmf
 	{
 		color = 14; // Yellow
 		title = "Rings and Weapon Panels";
-		width = 24;
+		width = 16;
 		height = 24;
 		sprite = "RINGA0";
 
@@ -1193,7 +1193,6 @@ udmf
 		{
 			title = "Ring";
 			sprite = "RINGA0";
-			width = 16;
 			arg0
 			{
 				title = "Float?";
@@ -1227,6 +1226,7 @@ udmf
 		{
 			title = "Infinity Ring";
 			sprite = "RNGIA0";
+			width = 24;
 			arg0
 			{
 				title = "Float?";
@@ -1282,7 +1282,6 @@ udmf
 		{
 			title = "CTF Team Ring (Red)";
 			sprite = "internal:TRNGA0R";
-			width = 16;
 			arg0
 			{
 				title = "Float?";
@@ -1294,7 +1293,6 @@ udmf
 		{
 			title = "CTF Team Ring (Blue)";
 			sprite = "internal:TRNGA0B";
-			width = 16;
 			arg0
 			{
 				title = "Float?";
@@ -1306,6 +1304,8 @@ udmf
 		{
 			title = "Bounce Ring Panel";
 			sprite = "PIKBA0";
+			width = 24;
+			height = 40;
 			arg0
 			{
 				title = "Float?";
@@ -1317,6 +1317,8 @@ udmf
 		{
 			title = "Rail Ring Panel";
 			sprite = "PIKRA0";
+			width = 24;
+			height = 40;
 			arg0
 			{
 				title = "Float?";
@@ -1328,6 +1330,8 @@ udmf
 		{
 			title = "Automatic Ring Panel";
 			sprite = "PIKAA0";
+			width = 24;
+			height = 40;
 			arg0
 			{
 				title = "Float?";
@@ -1339,6 +1343,8 @@ udmf
 		{
 			title = "Explosion Ring Panel";
 			sprite = "PIKEA0";
+			width = 24;
+			height = 40;
 			arg0
 			{
 				title = "Float?";
@@ -1350,6 +1356,8 @@ udmf
 		{
 			title = "Scatter Ring Panel";
 			sprite = "PIKSA0";
+			width = 24;
+			height = 40;
 			arg0
 			{
 				title = "Float?";
@@ -1361,6 +1369,8 @@ udmf
 		{
 			title = "Grenade Ring Panel";
 			sprite = "PIKGA0";
+			width = 24;
+			height = 40;
 			arg0
 			{
 				title = "Float?";
@@ -1375,7 +1385,7 @@ udmf
 		color = 10; // Light_Green
 		title = "Other Collectibles";
 		width = 16;
-		height = 32;
+		height = 24;
 		sort = 1;
 		sprite = "CEMGA0";
 
@@ -1445,6 +1455,7 @@ udmf
 		{
 			title = "Emerald Hunt Location";
 			sprite = "SHRDA0";
+			height = 32;
 			arg0
 			{
 				title = "Float?";
diff --git a/src/Android.mk b/src/Android.mk
index a461da2242c7ab813831c95c1d442353756b0907..035d48887727c2a6d6b63a6acb38fc7ec65a9342 100644
--- a/src/Android.mk
+++ b/src/Android.mk
@@ -76,7 +76,7 @@ LOCAL_SRC_FILES :=      am_map.c \
                         android/i_system.c \
                         android/i_video.c
 
-LOCAL_CFLAGS += -DPLATFORM_ANDROID -DNONX86 -DLINUX -DDEBUGMODE -DNOASM -DNOPIX -DUNIXCOMMON -DNOTERMIOS
+LOCAL_CFLAGS += -DPLATFORM_ANDROID -DNONX86 -DLINUX -DDEBUGMODE -DNOPIX -DUNIXCOMMON -DNOTERMIOS
 
 LOCAL_MODULE := libsrb2
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2f4467a322026aa73b4281b97daeae8800855deb..b926b3b7a3372d206df781fd65bbf7a947ddaef3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,23 +1,150 @@
-add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32)
+include(clang-tidy-default)
+
+add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
+	comptime.c
+	md5.c
+	config.h.in
+	string.c
+	d_main.c
+	d_clisrv.c
+	d_net.c
+	d_netfil.c
+	d_netcmd.c
+	dehacked.c
+	deh_soc.c
+	deh_lua.c
+	deh_tables.c
+	z_zone.c
+	f_finale.c
+	f_wipe.c
+	g_demo.c
+	g_game.c
+	g_input.c
+	am_map.c
+	command.c
+	console.c
+	hu_stuff.c
+	i_time.c
+	y_inter.c
+	st_stuff.c
+	m_aatree.c
+	m_anigif.c
+	m_argv.c
+	m_bbox.c
+	m_cheat.c
+	m_cond.c
+	m_easing.c
+	m_fixed.c
+	m_menu.c
+	m_misc.c
+	m_perfstats.c
+	m_random.c
+	m_queue.c
+	info.c
+	p_ceilng.c
+	p_enemy.c
+	p_floor.c
+	p_inter.c
+	p_lights.c
+	p_map.c
+	p_maputl.c
+	p_mobj.c
+	p_polyobj.c
+	p_saveg.c
+	p_setup.c
+	p_sight.c
+	p_spec.c
+	p_telept.c
+	p_tick.c
+	p_user.c
+	p_slopes.c
+	tables.c
+	r_bsp.c
+	r_data.c
+	r_draw.c
+	r_fps.c
+	r_main.c
+	r_plane.c
+	r_segs.c
+	r_skins.c
+	r_sky.c
+	r_splats.c
+	r_things.c
+	r_bbox.c
+	r_textures.c
+	r_patch.c
+	r_patchrotation.c
+	r_picformats.c
+	r_portal.c
+	screen.c
+	taglist.c
+	v_video.c
+	s_sound.c
+	sounds.c
+	w_wad.c
+	filesrch.c
+	mserv.c
+	http-mserv.c
+	i_tcp.c
+	lzf.c
+	b_bot.c
+	u_list.c
+	lua_script.c
+	lua_baselib.c
+	lua_mathlib.c
+	lua_hooklib.c
+	lua_consolelib.c
+	lua_infolib.c
+	lua_mobjlib.c
+	lua_playerlib.c
+	lua_skinlib.c
+	lua_thinkerlib.c
+	lua_maplib.c
+	lua_taglib.c
+	lua_polyobjlib.c
+	lua_blockmaplib.c
+	lua_hudlib.c
+	lua_hudlib_drawlist.c
+	lua_inputlib.c
+)
 
-if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows" AND NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}")
-	# On MinGW with internal libraries, link the standard library statically
-	target_link_options(SRB2SDL2 PRIVATE "-static")
-endif()
+# This updates the modification time for comptime.c at the
+# end of building so when the build system is ran next time,
+# that file gets flagged. comptime.c will always be rebuilt.
+#
+# This begs the question, why always rebuild comptime.c?
+# Some things like the git commit must be checked each time
+# the program is built. But the build system determines which
+# files should be rebuilt before anything else. So
+# comptime.c, which only needs to be rebuilt based on
+# information known at build time, must be told to rebuild
+# before that information can be ascertained.
+add_custom_command(
+	TARGET SRB2SDL2
+	POST_BUILD
+	COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CMAKE_CURRENT_SOURCE_DIR}/comptime.c
+)
 
-# Core sources
-target_sourcefile(c)
-target_sources(SRB2SDL2 PRIVATE comptime.c md5.c config.h.in)
+# config.h is generated by this command. It should be done at
+# build time for accurate git information and before anything
+# that needs it, obviously.
+add_custom_target(_SRB2_reconf ALL
+	COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake
+	WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.."
+)
+add_dependencies(SRB2SDL2 _SRB2_reconf)
 
-set(SRB2_ASM_SOURCES vid_copy.s)
+if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
+	target_link_options(SRB2SDL2 PRIVATE "-Wl,--disable-dynamicbase")
+	if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}")
+		# On MinGW with internal libraries, link the standard library statically
+		target_link_options(SRB2SDL2 PRIVATE "-static")
+	endif()
+endif()
 
-set(SRB2_NASM_SOURCES tmap_mmx.nas tmap.nas)
+target_compile_features(SRB2SDL2 PRIVATE c_std_11 cxx_std_17)
 
 ### Configuration
-set(SRB2_CONFIG_USEASM OFF CACHE BOOL
-	"Enable NASM tmap implementation for software mode speedup.")
-set(SRB2_CONFIG_YASM OFF CACHE BOOL
-	"Use YASM in place of NASM.")
 set(SRB2_CONFIG_DEV_BUILD OFF CACHE BOOL
 	"Compile a development build of SRB2.")
 
@@ -74,33 +201,6 @@ if("${SRB2_CONFIG_HWRENDER}")
 	endif()
 endif()
 
-if(${SRB2_CONFIG_USEASM})
-	#SRB2_ASM_FLAGS can be used to pass flags to either nasm or yasm.
-	if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
-		set(SRB2_ASM_FLAGS "-DLINUX ${SRB2_ASM_FLAGS}")
-	endif()
-
-	if(${SRB2_CONFIG_YASM})
-		set(CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS} nas)
-		set(CMAKE_ASM_YASM_FLAGS "${SRB2_ASM_FLAGS}" CACHE STRING "Flags used by the assembler during all build types.")
-		enable_language(ASM_YASM)
-	else()
-		set(CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS} nas)
-		set(CMAKE_ASM_NASM_FLAGS "${SRB2_ASM_FLAGS}" CACHE STRING "Flags used by the assembler during all build types.")
-		enable_language(ASM_NASM)
-	endif()
-
-	set(SRB2_USEASM ON)
-	target_compile_definitions(SRB2SDL2 PRIVATE -DUSEASM)
-	target_compile_options(SRB2SDL2 PRIVATE -msse3 -mfpmath=sse)
-
-	target_sources(SRB2SDL2 PRIVATE ${SRB2_ASM_SOURCES}
-		${SRB2_NASM_SOURCES})
-else()
-	set(SRB2_USEASM OFF)
-	target_compile_definitions(SRB2SDL2 PRIVATE -DNONX86 -DNORUSEASM)
-endif()
-
 # Targets
 
 # If using CCACHE, then force it.
@@ -289,6 +389,9 @@ if(SRB2_CONFIG_PROFILEMODE AND "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
 endif()
 
 add_subdirectory(sdl)
+if(SRB2_CONFIG_ENABLE_TESTS)
+	add_subdirectory(tests)
+endif()
 
 # strip debug symbols into separate file when using gcc.
 # to be consistent with Makefile, don't generate for OS X.
@@ -329,3 +432,11 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND NOT "${SRB2_CONFIG_INTERNAL_LIBRA
 		COMMENT "Copying runtime DLLs"
 	)
 endif()
+
+# Setup clang-tidy
+if(SRB2_CONFIG_ENABLE_CLANG_TIDY_C)
+	target_set_default_clang_tidy(SRB2SDL2 C "-*,clang-analyzer-*,-clang-analyzer-cplusplus-*")
+endif()
+if(SRB2_CONFIG_ENABLE_CLANG_TIDY_CXX)
+	target_set_default_clang_tidy(SRB2SDL2 CXX "-*,clang-analyzer-*,modernize-*")
+endif()
diff --git a/src/Makefile b/src/Makefile
index 36b1a7efabea7416f42c624fc03ab93d2a15c05f..41cef2a179383670247847bcd572a651c1b8aad6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -47,8 +47,6 @@
 # HAVE_MINIUPNPC=1 - Enable automated port forwarding.
 #                    Already enabled by default for 32-bit
 #                    Windows.
-# NOASM=1 - Disable hand optimized assembly code for the
-#           Software renderer.
 # NOPNG=1 - Disable PNG graphics support. (TODO: double
 #           check netplay compatible.)
 # NOCURL=1 - Disable libcurl--HTTP capability.
@@ -88,7 +86,6 @@
 #        executable.
 # WINDOWSHELL=1 - Use Windows commands.
 # PREFIX= - Prefix to many commands, for cross compiling.
-# YASM=1 - Use Yasm instead of NASM assembler.
 # STABS=1 - ?
 # ECHO=1 - Print out each command in the build process.
 # NOECHOFILENAMES=1 - Don't print out each that is being
@@ -148,22 +145,6 @@ OBJCOPY:=$(call Prefix,objcopy)
 OBJDUMP:=$(call Prefix,objdump)
 WINDRES:=$(call Prefix,windres)
 
-ifdef YASM
-NASM?=yasm
-else
-NASM?=nasm
-endif
-
-ifdef YASM
-ifdef STABS
-NASMOPTS?=-g stabs
-else
-NASMOPTS?=-g dwarf2
-endif
-else
-NASMOPTS?=-g
-endif
-
 GZIP?=gzip
 GZIP_OPTS?=-9 -f -n
 ifdef WINDOWSHELL
@@ -187,8 +168,6 @@ makedir:=../make
 opts:=-DCOMPVERSION -g
 libs:=
 
-nasm_format:=
-
 # This is a list of variables names, of which if defined,
 # also defines the name as a macro to the compiler.
 passthru_opts:=
@@ -316,7 +295,6 @@ endif
 
 LD:=$(CC)
 cc:=$(cc) $(opts)
-nasm=$(NASM) $(NASMOPTS) -f $(nasm_format)
 ifdef UPX
 upx=$(UPX) $(UPX_OPTS)
 endif
@@ -393,7 +371,6 @@ $(objdir)/%.$(1) : %.$(2) | $$$$(@D)/
 endef
 
 $(eval $(call _recipe,o,c,$(cc) -c -o $$@ $$<))
-$(eval $(call _recipe,o,nas,$(nasm) -o $$@ $$<))
 $(eval $(call _recipe,o,s,$(cc) $(asflags) -c -o $$@ $$<))
 $(eval $(call _recipe,res,rc,$(windres) -i $$< -o $$@))
 
@@ -414,3 +391,5 @@ ifdef WINDOWSHELL
 else
 	@:
 endif
+
+#$(warning The handwritten GNU Makefile for SRB2 is deprecated, and may be removed in the future. Please consider switching to CMake.)
diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk
index 8ba33383bb2f0c8169e92f91c6c8fea25c6c852f..1787f94cb8988e6b27d5bb334c5a978ef2b31947 100644
--- a/src/Makefile.d/features.mk
+++ b/src/Makefile.d/features.mk
@@ -18,13 +18,6 @@ opts+=-DHWRENDER
 sources+=$(call List,hardware/Sourcefile)
 endif
 
-ifndef NOASM
-ifndef NONX86
-sources+=tmap.nas tmap_mmx.nas
-opts+=-DUSEASM
-endif
-endif
-
 ifndef NOMD5
 sources+=md5.c
 endif
diff --git a/src/Makefile.d/nix.mk b/src/Makefile.d/nix.mk
index 767b64c12be4bf42fede8e07e80cba68151ef92a..9adf3f0f14fdc2b052e48053cabedee48a1a7edd 100644
--- a/src/Makefile.d/nix.mk
+++ b/src/Makefile.d/nix.mk
@@ -9,10 +9,6 @@ opts+=-DUNIXCOMMON -DLUA_USE_POSIX
 # instead of addresses
 libs+=-lm -rdynamic
 
-ifndef nasm_format
-nasm_format:=elf -DLINUX
-endif
-
 ifndef NOHW
 opts+=-I/usr/X11R6/include
 libs+=-L/usr/X11R6/lib
@@ -29,13 +25,12 @@ endif
 # Tested by Steel, as of release 2.2.8.
 ifdef FREEBSD
 opts+=-I/usr/X11R6/include -DLINUX -DFREEBSD
-libs+=-L/usr/X11R6/lib -lipx -lkvm
+libs+=-L/usr/X11R6/lib -lkvm -lexecinfo
 endif
 
 # FIXME: UNTESTED
 #ifdef SOLARIS
 #NOIPX=1
-#NOASM=1
 #opts+=-I/usr/local/include -I/opt/sfw/include \
 #		-DSOLARIS -DINADDR_NONE=INADDR_ANY -DBSD_COMP
 #libs+=-L/opt/sfw/lib -lsocket -lnsl
diff --git a/src/Makefile.d/platform.mk b/src/Makefile.d/platform.mk
index c5ac71a20adc24766a961cb544af54cf3a1b59b1..d19143e4cf6040dc161b201553db3942b123ee39 100644
--- a/src/Makefile.d/platform.mk
+++ b/src/Makefile.d/platform.mk
@@ -39,7 +39,6 @@ else ifdef SOLARIS # FIXME: UNTESTED
 UNIX=1
 platform=solaris
 else ifdef CYGWIN32 # FIXME: UNTESTED
-nasm_format=win32
 platform=cygwin
 else ifdef MINGW
 ifdef MINGW64
diff --git a/src/Makefile.d/sdl.mk b/src/Makefile.d/sdl.mk
index 99ca624e69f2f18c10625c93585f14681636f36e..a1bfa33038bbacebada85790a7565fceb9440985 100644
--- a/src/Makefile.d/sdl.mk
+++ b/src/Makefile.d/sdl.mk
@@ -56,13 +56,6 @@ SDL_LDFLAGS?=$(shell $(SDL_CONFIG) \
 $(eval $(call Propogate_flags,SDL))
 endif
 
-# use the x86 asm code
-ifndef CYGWIN32
-ifndef NOASM
-USEASM=1
-endif
-endif
-
 ifdef MINGW
 ifndef NOSDLMAIN
 SDLMAIN=1
diff --git a/src/Makefile.d/win32.mk b/src/Makefile.d/win32.mk
index 0e48ed68359523e70b4ba0a6d8eade4b81d1d9ca..73a3d9e453ecaa0a01e32e15f71326bd7be57920 100644
--- a/src/Makefile.d/win32.mk
+++ b/src/Makefile.d/win32.mk
@@ -17,8 +17,6 @@ sources+=win32/Srb2win.rc
 opts+=-DSTDC_HEADERS
 libs+=-ladvapi32 -lkernel32 -lmsvcrt -luser32
 
-nasm_format:=win32
-
 SDL?=1
 
 ifndef NOHW
diff --git a/src/Sourcefile b/src/Sourcefile
index 7c530500052e7379fa503ef888445dde79d0d350..f2b408c665d28306ce9c778646f6139b6874099a 100644
--- a/src/Sourcefile
+++ b/src/Sourcefile
@@ -81,7 +81,6 @@ mserv.c
 http-mserv.c
 i_tcp.c
 lzf.c
-vid_copy.s
 b_bot.c
 u_list.c
 lua_script.c
diff --git a/src/android/i_system.c b/src/android/i_system.c
index ff8b88de539353bfd93ae612a3739770b267c416..9d798d452fb1b36460553fd35870894be2a3e406 100644
--- a/src/android/i_system.c
+++ b/src/android/i_system.c
@@ -278,4 +278,26 @@ char *I_ClipboardPaste(void)
 
 void I_RegisterSysCommands(void) {}
 
+// This is identical to the SDL implementation.
+size_t I_GetRandomBytes(char *destination, size_t count)
+{
+  FILE *rndsource;
+  size_t actual_bytes;
+
+  if (!(rndsource = fopen("/dev/urandom", "r")))
+	  if (!(rndsource = fopen("/dev/random", "r")))
+		  actual_bytes = 0;
+
+  if (rndsource)
+  {
+	  actual_bytes = fread(destination, 1, count, rndsource);
+	  fclose(rndsource);
+  }
+
+  if (actual_bytes == 0)
+    I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes");
+
+  return actual_bytes;
+}
+
 #include "../sdl/dosstr.c"
diff --git a/src/asm_defs.inc b/src/asm_defs.inc
deleted file mode 100644
index 48f8da0d8f582f28ad09674eec97b4af840f40b9..0000000000000000000000000000000000000000
--- a/src/asm_defs.inc
+++ /dev/null
@@ -1,43 +0,0 @@
-// SONIC ROBO BLAST 2
-//-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2023 by Sonic Team Junior.
-//
-// This program is free software distributed under the
-// terms of the GNU General Public License, version 2.
-// See the 'LICENSE' file for more details.
-//-----------------------------------------------------------------------------
-/// \file  asm_defs.inc
-/// \brief must match the C structures
-
-#ifndef __ASM_DEFS__
-#define __ASM_DEFS__
-
-// this makes variables more noticable,
-// and make the label match with C code
-
-// Linux, unlike DOS, has no "_" 19990119 by Kin
-// and nasm needs .data code segs under linux 20010210 by metzgermeister
-// FIXME: nasm ignores these settings, so I put the macros into the makefile
-#ifdef __ELF__
-#define C(label) label
-#define CODE_SEG .data
-#else
-#define C(label) _##label
-#define CODE_SEG .text
-#endif
-
-/* This is a more readable way to access the arguments passed from C code   */
-/* PLEASE NOTE: it is supposed that all arguments passed from C code are    */
-/*              32bit integer (INT32, long, and most *pointers)               */
-#define ARG1      8(%ebp)
-#define ARG2      12(%ebp)
-#define ARG3      16(%ebp)
-#define ARG4      20(%ebp)
-#define ARG5      24(%ebp)
-#define ARG6      28(%ebp)
-#define ARG7      32(%ebp)
-#define ARG8      36(%ebp)
-#define ARG9      40(%ebp)      //(c)tm ... Allegro by Shawn Hargreaves.
-
-#endif
diff --git a/src/blua/CMakeLists.txt b/src/blua/CMakeLists.txt
index 4e9c67d2f348a8bfed899e4002d25136284b031f..892bf534addc810434bdd00960ebe7a92ef7bde4 100644
--- a/src/blua/CMakeLists.txt
+++ b/src/blua/CMakeLists.txt
@@ -1 +1,28 @@
-target_sourcefile(c)
+target_sources(SRB2SDL2 PRIVATE
+	lapi.c
+	lbaselib.c
+	ldo.c
+	lfunc.c
+	linit.c
+	liolib.c
+	llex.c
+	lmem.c
+	lobject.c
+	lstate.c
+	lstrlib.c
+	ltablib.c
+	lundump.c
+	lzio.c
+	lauxlib.c
+	lcode.c
+	ldebug.c
+	ldump.c
+	lgc.c
+	lopcodes.c
+	lparser.c
+	lstring.c
+	ltable.c
+	ltm.c
+	lvm.c
+	loslib.c
+)
diff --git a/src/comptime.c b/src/comptime.c
index 398eda0743706cecb4a22d1996f8949564f1fe07..386b53f46904df87fc5f64749cc11db6e6821e3f 100644
--- a/src/comptime.c
+++ b/src/comptime.c
@@ -11,6 +11,9 @@
 #include "config.h"
 const char *compbranch = SRB2_COMP_BRANCH;
 const char *comprevision = SRB2_COMP_REVISION;
+const char *compnote = SRB2_COMP_NOTE;
+const char *comptype = CMAKE_BUILD_TYPE;
+const int compoptimized = SRB2_COMP_OPTIMIZED;
 
 #elif (defined(COMPVERSION))
 #include "comptime.h"
@@ -21,5 +24,12 @@ const char *comprevision = "illegal";
 
 #endif
 
+const int compuncommitted =
+#if (defined(COMPVERSION_UNCOMMITTED))
+1;
+#else
+0;
+#endif
+
 const char *compdate = __DATE__;
 const char *comptime = __TIME__;
diff --git a/src/config.h.in b/src/config.h.in
index 3d6d98375693b220329ebd7b8fe1619def9fe792..6d49a698934bf8b498f6bfb402bbaa7f8ff93191 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -11,8 +11,18 @@
 
 #ifdef CMAKECONFIG
 
-#define SRB2_COMP_REVISION    "${SRB2_COMP_REVISION}"
-#define SRB2_COMP_BRANCH      "${SRB2_COMP_BRANCH}"
+#define SRB2_COMP_REVISION       "${SRB2_COMP_REVISION}"
+#define SRB2_COMP_BRANCH         "${SRB2_COMP_BRANCH}"
+#define SRB2_COMP_NOTE           "${SRB2_COMP_NOTE}"
+// This is done with configure_file instead of defines in order to avoid
+// recompiling the whole target whenever the working directory state changes
+#cmakedefine SRB2_COMP_UNCOMMITTED
+#ifdef SRB2_COMP_UNCOMMITTED
+#define COMPVERSION_UNCOMMITTED
+#endif
+
+#define CMAKE_BUILD_TYPE         "${CMAKE_BUILD_TYPE}"
+#cmakedefine01 SRB2_COMP_OPTIMIZED
 
 #endif
 
@@ -28,12 +38,14 @@
  * Last updated 2021 / 05 / 06 - v2.2.9 - patch.pk3 & zones.pk3
  * Last updated 2022 / 03 / 06 - v2.2.10 - main assets
  * Last updated 2023 / 05 / 02 - v2.2.11 - patch.pk3 & zones.pk3
+ * Last updated 2023 / 09 / 06 - v2.2.12 - patch.pk3
+ * Last updated 2023 / 09 / 09 - v2.2.13 - none
  */
 #define ASSET_HASH_SRB2_PK3   "ad911f29a28a18968ee5b2d11c2acb39"
 #define ASSET_HASH_ZONES_PK3  "1c8adf8d079ecb87d00081f158acf3c7"
 #define ASSET_HASH_PLAYER_DTA "2e7aaae8a6b1b77d90ffe7606ceadb6c"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_PK3  "2e69558bce3b9610624549a75e29e19b"
+#define ASSET_HASH_PATCH_PK3  "3c7b73f34af7e9a7bceb2d5260f76172"
 #endif
 
 #endif
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 13dab45390dfa4b5f8c125465b1a61f8eadf67be..83482b527c3354f5c2b6f81cc2e47f66b108c2b1 100755
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1295,6 +1295,7 @@ static boolean CL_AskFileList(INT32 firstfile)
 static boolean CL_SendJoin(void)
 {
 	UINT8 localplayers = 1;
+	char const *player2name;
 	if (netgame)
 		CONS_Printf(M_GetText("Sending join request...\n"));
 	netbuffer->packettype = PT_CLIENTJOIN;
@@ -1311,9 +1312,14 @@ static boolean CL_SendJoin(void)
 	CleanupPlayerName(consoleplayer, cv_playername.zstring);
 	if (splitscreen)
 		CleanupPlayerName(1, cv_playername2.zstring);/* 1 is a HACK? oh no */
+	// Avoid empty string on bots to avoid softlocking in singleplayer
+	if (botingame)
+		player2name = strcmp(cv_playername.zstring, "Tails") == 0 ? "Tail" : "Tails";
+	else
+		player2name = cv_playername2.zstring;
 
 	strncpy(netbuffer->u.clientcfg.names[0], cv_playername.zstring, MAXPLAYERNAME);
-	strncpy(netbuffer->u.clientcfg.names[1], cv_playername2.zstring, MAXPLAYERNAME);
+	strncpy(netbuffer->u.clientcfg.names[1], player2name, MAXPLAYERNAME);
 
 	return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
 }
@@ -4578,7 +4584,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 			// If we've alredy received a ticcmd for this tic, just submit it for the next one.
 			tic_t faketic = maketic;
 			if ((!!(netcmds[maketic % BACKUPTICS][netconsole].angleturn & TICCMD_RECEIVED))
-				&& (maketic - firstticstosend < BACKUPTICS))
+				&& (maketic - firstticstosend < BACKUPTICS - 1))
 				faketic++;
 
 			// Copy ticcmd
diff --git a/src/d_main.c b/src/d_main.c
index 2db4002580151c622395e2ed78756793221457b6..24c70843a3bd03d383651d7423914a7fd4d3f0e5 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -70,6 +70,8 @@
 #include "filesrch.h" // refreshdirmenu
 #include "g_input.h" // tutorial mode control scheming
 #include "m_perfstats.h"
+#include "m_random.h"
+#include "command.h"
 
 #ifdef CMAKECONFIG
 #include "config.h"
@@ -1215,6 +1217,15 @@ D_ConvertVersionNumbers (void)
 #endif
 }
 
+static void Command_assert(void)
+{
+#if !defined(NDEBUG) || defined(PARANOIA)
+	CONS_Printf("Yes, assertions are enabled.\n");
+#else
+	CONS_Printf("No, assertions are NOT enabled.\n");
+#endif
+}
+
 //
 // D_SRB2Main
 //
@@ -1228,6 +1239,11 @@ void D_SRB2Main(void)
 	/* break the version string into version numbers, for netplay */
 	D_ConvertVersionNumbers();
 
+	if (!strcmp(compbranch, ""))
+	{
+		compbranch = "detached HEAD";
+	}
+
 	// Print GPL notice for our console users (Linux)
 	CONS_Printf(
 	"\n\nSonic Robo Blast 2\n"
@@ -1341,11 +1357,12 @@ void D_SRB2Main(void)
 	snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons");
 	I_mkdir(addonsdir, 0755);
 
-	// rand() needs seeded regardless of password
-	srand((unsigned int)time(NULL));
-	rand();
-	rand();
-	rand();
+	// seed M_Random because it is necessary; seed P_Random for scripts that
+	// might want to use random numbers immediately at start
+	if (!M_RandomSeedFromOS())
+		M_RandomSeed((UINT32)time(NULL)); // less good but serviceable
+
+	P_SetRandSeed(M_RandomizedSeed());
 
 	if (M_CheckParm("-password") && M_IsNextParm())
 		D_SetPassword(M_GetNextParm());
@@ -1363,6 +1380,8 @@ void D_SRB2Main(void)
 	// Do this up here so that WADs loaded through the command line can use ExecCfg
 	COM_Init();
 
+	COM_AddCommand("assert", Command_assert, COM_LUA);
+
 	// Add any files specified on the command line with
 	// "-file <file>" or "-folder <folder>" to the add-on list
 	if (!((M_GetUrlProtocolArg() || M_CheckParm("-connect")) && !M_CheckParm("-server")))
@@ -1718,14 +1737,15 @@ void D_SRB2Main(void)
 			// Prevent warping to nonexistent levels
 			if (W_CheckNumForName(G_BuildMapName(pstartmap)) == LUMPERROR)
 				I_Error("Could not warp to %s (map not found)\n", G_BuildMapName(pstartmap));
-			// Prevent warping to locked levels
-			// ... unless you're in a dedicated server.  Yes, technically this means you can view any level by
-			// running a dedicated server and joining it yourself, but that's better than making dedicated server's
-			// lives hell.
-			else if (!dedicated && M_MapLocked(pstartmap, serverGamedata))
-				I_Error("You need to unlock this level before you can warp to it!\n");
 			else
 			{
+				if (M_CampaignWarpIsCheat(gametype, pstartmap, serverGamedata))
+				{
+					// If you're warping via command line, you know what you're doing.
+					// No need to I_Error over this.
+					G_SetUsedCheats(false);
+				}
+
 				D_MapChange(pstartmap, gametype, ultimatemode, true, 0, false, false);
 			}
 		}
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index f81a6abde0a7e30dff0b0c69c2a05484fde2b223..be0d03af2243f2aebb8f1d7cf700cb7af31a5f4c 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -776,6 +776,8 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_showfocuslost);
 	CV_RegisterVar(&cv_pauseifunfocused);
 
+	CV_RegisterVar(&cv_instantretry);
+
 	// g_input.c
 	CV_RegisterVar(&cv_sideaxis);
 	CV_RegisterVar(&cv_sideaxis2);
@@ -1643,9 +1645,14 @@ static void Command_Playdemo_f(void)
 {
 	char name[256];
 
-	if (COM_Argc() != 2)
+	if (COM_Argc() < 2)
 	{
-		CONS_Printf(M_GetText("playdemo <demoname>: playback a demo\n"));
+		CONS_Printf("playdemo <demoname> [-addfiles / -force]:\n");
+		CONS_Printf(M_GetText(
+					"Play back a demo file. The full path from your SRB2 directory must be given.\n\n"
+
+					"* With \"-addfiles\", any required files are added from a list contained within the demo file.\n"
+					"* With \"-force\", the demo is played even if the necessary files have not been added.\n"));
 		return;
 	}
 
@@ -1667,6 +1674,16 @@ static void Command_Playdemo_f(void)
 
 	CONS_Printf(M_GetText("Playing back demo '%s'.\n"), name);
 
+	demofileoverride = DFILE_OVERRIDE_NONE;
+	if (strcmp(COM_Argv(2), "-addfiles") == 0)
+	{
+		demofileoverride = DFILE_OVERRIDE_LOAD;
+	}
+	else if (strcmp(COM_Argv(2), "-force") == 0)
+	{
+		demofileoverride = DFILE_OVERRIDE_SKIP;
+	}
+
 	// Internal if no extension, external if one exists
 	// If external, convert the file name to a path in SRB2's home directory
 	if (FIL_CheckExtension(name))
@@ -1887,8 +1904,8 @@ static void Command_Map_f(void)
 	size_t option_gametype;
 	const char *gametypename;
 	boolean newresetplayers;
-
-	boolean wouldSetCheats;
+	boolean prevent_cheat;
+	boolean set_cheated;
 
 	INT32 newmapnum;
 
@@ -1909,22 +1926,34 @@ static void Command_Map_f(void)
 	option_gametype =   COM_CheckPartialParm("-g");
 	newresetplayers = ! COM_CheckParm("-noresetplayers");
 
-	wouldSetCheats =
-		!( netgame || multiplayer ) &&
-		!( usedCheats );
+	prevent_cheat = !( usedCheats ) && !( option_force || cv_debug );
+	set_cheated = false;
 
-	if (wouldSetCheats && !option_force
-	&& !M_SecretUnlocked(SECRET_LEVELSELECT, serverGamedata))
+	if (!( netgame || multiplayer ))
 	{
-		/* May want to be more descriptive? */
-		CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
-		return;
+		if (prevent_cheat)
+		{
+			/* May want to be more descriptive? */
+			CONS_Printf(M_GetText("Cheats must be enabled to level change in single player.\n"));
+			return;
+		}
+		else
+		{
+			set_cheated = true;
+		}
 	}
 
-	if (!newresetplayers && !cv_debug)
+	if (!newresetplayers)
 	{
-		CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
-		return;
+		if (prevent_cheat)
+		{
+			CONS_Printf(M_GetText("Cheats must be enabled to use -noresetplayers.\n"));
+			return;
+		}
+		else
+		{
+			set_cheated = true;
+		}
 	}
 
 	if (option_gametype)
@@ -1932,7 +1961,7 @@ static void Command_Map_f(void)
 		if (!multiplayer)
 		{
 			CONS_Printf(M_GetText(
-						"You can't switch gametypes in single player!\n"));
+				"You can't switch gametypes in single player!\n"));
 			return;
 		}
 		else if (COM_Argc() < option_gametype + 2)/* no argument after? */
@@ -1945,7 +1974,9 @@ static void Command_Map_f(void)
 	}
 
 	if (!( first_option = COM_FirstOption() ))
+	{
 		first_option = COM_Argc();
+	}
 
 	if (first_option < 2)
 	{
@@ -1968,11 +1999,6 @@ static void Command_Map_f(void)
 		return;
 	}
 
-	if (wouldSetCheats)
-	{
-		G_SetUsedCheats(false);
-	}
-
 	// new gametype value
 	// use current one by default
 	if (option_gametype)
@@ -2014,15 +2040,13 @@ static void Command_Map_f(void)
 	}
 
 	// don't use a gametype the map doesn't support
-	if (cv_debug || option_force || cv_skipmapcheck.value)
-		fromlevelselect = false; // The player wants us to trek on anyway.  Do so.
 	// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
-	else
+	if (!(
+			mapheaderinfo[newmapnum-1] &&
+			mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)
+	))
 	{
-		if (!(
-					mapheaderinfo[newmapnum-1] &&
-					mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)
-		))
+		if (prevent_cheat && !cv_skipmapcheck.value)
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum),
 				(multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player"));
@@ -2032,23 +2056,33 @@ static void Command_Map_f(void)
 		}
 		else
 		{
-			fromlevelselect =
-				( netgame || multiplayer ) &&
-				newgametype == gametype    &&
-				gametypedefaultrules[newgametype] & GTR_CAMPAIGN;
+			// The player wants us to trek on anyway.  Do so.
+			fromlevelselect = false;
+			set_cheated = ((gametypedefaultrules[newgametype] & GTR_CAMPAIGN) == GTR_CAMPAIGN);
 		}
 	}
+	else
+	{
+		fromlevelselect =
+			( netgame || multiplayer ) &&
+			newgametype == gametype    &&
+			(gametypedefaultrules[newgametype] & GTR_CAMPAIGN);
+	}
 
 	// Prevent warping to locked levels
-	// ... unless you're in a dedicated server.  Yes, technically this means you can view any level by
-	// running a dedicated server and joining it yourself, but that's better than making dedicated server's
-	// lives hell.
-	if (!dedicated && M_MapLocked(newmapnum, serverGamedata))
+	if (M_CampaignWarpIsCheat(newgametype, newmapnum, serverGamedata))
 	{
-		CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
-		Z_Free(realmapname);
-		Z_Free(mapname);
-		return;
+		if (prevent_cheat)
+		{
+			CONS_Alert(CONS_NOTICE, M_GetText("Cheats must be enabled to warp to a locked level!\n"));
+			Z_Free(realmapname);
+			Z_Free(mapname);
+			return;
+		}
+		else
+		{
+			set_cheated = true;
+		}
 	}
 
 	// Ultimate Mode only in SP via menu
@@ -2065,6 +2099,11 @@ static void Command_Map_f(void)
 	}
 	tutorialmode = false; // warping takes us out of tutorial mode
 
+	if (set_cheated && !usedCheats)
+	{
+		G_SetUsedCheats(false);
+	}
+
 	D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
 
 	Z_Free(realmapname);
@@ -2106,11 +2145,13 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 
 	lastgametype = gametype;
 	gametype = READUINT8(*cp);
-	G_SetGametype(gametype); // I fear putting that macro as an argument
 
 	if (gametype < 0 || gametype >= gametypecount)
 		gametype = lastgametype;
-	else if (gametype != lastgametype)
+	else
+		G_SetGametype(gametype);
+
+	if (gametype != lastgametype)
 		D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype
 
 	skipprecutscene = ((flags & (1<<2)) != 0);
@@ -2132,12 +2173,6 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	if (demoplayback && !timingdemo)
 		precache = false;
 
-	if (resetplayer && !FLS)
-	{
-		emeralds = 0;
-		memset(&luabanks, 0, sizeof(luabanks));
-	}
-
 	if (modeattacking)
 	{
 		SetPlayerSkinByNum(0, cv_chooseskin.value-1);
@@ -3845,7 +3880,7 @@ static void Command_ListWADS_f(void)
 static void Command_Version_f(void)
 {
 #ifdef DEVELOP
-	CONS_Printf("Sonic Robo Blast 2 %s-%s (%s %s) ", compbranch, comprevision, compdate, comptime);
+	CONS_Printf("Sonic Robo Blast 2 %s %s %s (%s %s) ", compbranch, comprevision, compnote, compdate, comptime);
 #else
 	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision, compbranch);
 #endif
@@ -3879,11 +3914,6 @@ static void Command_Version_f(void)
 	else // 16-bit? 128-bit?
 		CONS_Printf("Bits Unknown ");
 
-	// No ASM?
-#ifdef NOASM
-	CONS_Printf("\x85" "NOASM " "\x80");
-#endif
-
 	// Debug build
 #ifdef _DEBUG
 	CONS_Printf("\x85" "DEBUG " "\x80");
@@ -4249,9 +4279,6 @@ void D_GameTypeChanged(INT32 lastgametype)
 	else if (!multiplayer && !netgame)
 	{
 		G_SetGametype(GT_COOP);
-		// These shouldn't matter anymore
-		//CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue);
-		//CV_SetValue(&cv_itemrespawn, 0);
 	}
 
 	// reset timelimit and pointlimit in race/coop, prevent stupid cheats
@@ -4552,25 +4579,37 @@ static void Command_Mapmd5_f(void)
 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
 }
 
+void D_SendExitLevel(boolean cheat)
+{
+	UINT8 buf[8];
+	UINT8 *buf_p = buf;
+
+	WRITEUINT8(buf_p, cheat);
+
+	SendNetXCmd(XD_EXITLEVEL, &buf, buf_p - buf);
+}
+
 static void Command_ExitLevel_f(void)
 {
-	if (!(netgame || (multiplayer && gametype != GT_COOP)) && !cv_debug)
-		CONS_Printf(M_GetText("This only works in a netgame.\n"));
-	else if (!(server || (IsPlayerAdmin(consoleplayer))))
+	if (!(server || (IsPlayerAdmin(consoleplayer))))
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 	else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demoplayback)
 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
 	else
-		SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+		D_SendExitLevel(true);
 }
 
 static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
 {
-	(void)cp;
+	boolean cheat = false;
+
+	cheat = (boolean)READUINT8(*cp);
 
 	// Ignore duplicate XD_EXITLEVEL commands.
 	if (gameaction == ga_completed)
+	{
 		return;
+	}
 
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
@@ -4580,6 +4619,11 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
+	if (G_CoopGametype() && cheat)
+	{
+		G_SetUsedCheats(false);
+	}
+
 	G_ExitLevel();
 }
 
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 26bf4d5c6bfbcfe9e481b35b84f38c44360ec3fa..8bbc801d0ef700868482fd982346dff5296c5e14 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -201,6 +201,7 @@ void D_SendPlayerConfig(void);
 void Command_ExitGame_f(void);
 void Command_Retry_f(void);
 void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore
+void D_SendExitLevel(boolean cheat);
 void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pultmode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect);
 boolean IsPlayerAdmin(INT32 playernum);
 void SetAdminPlayer(INT32 playernum);
diff --git a/src/d_think.h b/src/d_think.h
index bdb5db3f54135545d27b0018943f0c995fffdcd4..efc1589bf62e277a67ab309f05d33d66af741865 100644
--- a/src/d_think.h
+++ b/src/d_think.h
@@ -17,6 +17,8 @@
 #ifndef __D_THINK__
 #define __D_THINK__
 
+#include "doomdef.h"
+
 #ifdef __GNUG__
 #pragma interface
 #endif
@@ -49,6 +51,11 @@ typedef struct thinker_s
 	// killough 11/98: count of how many other objects reference
 	// this one using pointers. Used for garbage collection.
 	INT32 references;
+
+#ifdef PARANOIA
+	INT32 debug_mobjtype;
+	tic_t debug_time;
+#endif
 } thinker_t;
 
 #endif
diff --git a/src/deh_lua.c b/src/deh_lua.c
index 1b177eb25724e38eb42f81f08ff9919177372c29..0b789547b266aeb238701ca6b34c4f8702c334fb 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -277,8 +277,8 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word)
 	}
 	else if (fastncmp("MTF_", word, 4)) {
 		p = word+4;
-		for (i = 0; i < 4; i++)
-			if (MAPTHINGFLAG_LIST[i] && fastcmp(p, MAPTHINGFLAG_LIST[i])) {
+		for (i = 0; MAPTHINGFLAG_LIST[i]; i++)
+			if (fastcmp(p, MAPTHINGFLAG_LIST[i])) {
 				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
diff --git a/src/deh_tables.c b/src/deh_tables.c
index 6a815b6ea54adb20a4bec570b86046b369b41597..0801cf935a184d5a30d0edd215fe90e2b68db737 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -4410,11 +4410,12 @@ const char *const MOBJEFLAG_LIST[] = {
 	NULL
 };
 
-const char *const MAPTHINGFLAG_LIST[4] = {
+const char *const MAPTHINGFLAG_LIST[] = {
 	"EXTRA", // Extra flag for objects.
 	"OBJECTFLIP", // Reverse gravity flag for objects.
 	"OBJECTSPECIAL", // Special flag used with certain objects.
-	"AMBUSH" // Deaf monsters/do not react to sound.
+	"AMBUSH", // Deaf monsters/do not react to sound.
+	"ABSOLUTEZ" // Absolute spawn height flag for objects.
 };
 
 const char *const PLAYERFLAG_LIST[] = {
@@ -4551,6 +4552,7 @@ const char *const MSF_LIST[] = {
 const char *const SSF_LIST[] = {
 	"OUTERSPACE",
 	"DOUBLESTEPUP",
+	"NOSTEPDOWN",
 	"WINDCURRENT",
 	"CONVEYOR",
 	"SPEEDPAD",
@@ -4567,6 +4569,8 @@ const char *const SSF_LIST[] = {
 	"ZOOMTUBEEND",
 	"FINISHLINE",
 	"ROPEHANG",
+	"JUMPFLIP",
+	"GRAVITYOVERRIDE",
 	NULL
 };
 
@@ -4610,8 +4614,7 @@ const char *COLOR_ENUMS[] = {
 	// Desaturated
 	"AETHER",     	// SKINCOLOR_AETHER,
 	"SLATE",     	// SKINCOLOR_SLATE,
-	"METEORITE",   	// SKINCOLOR_METEORITE,
-	"MERCURY",     	// SKINCOLOR_MERCURY,
+	"MOONSTONE",   	// SKINCOLOR_MOONSTONE,
 	"BLUEBELL",   	// SKINCOLOR_BLUEBELL,
 	"PINK",     	// SKINCOLOR_PINK,
 	"ROSEWOOD",   	// SKINCOLOR_ROSEWOOD,
@@ -4648,10 +4651,10 @@ const char *COLOR_ENUMS[] = {
 	"COPPER",     	// SKINCOLOR_COPPER,
 	"APRICOT",     	// SKINCOLOR_APRICOT,
 	"ORANGE",     	// SKINCOLOR_ORANGE,
-	"PUMPKIN",     	// SKINCOLOR_PUMPKIN,
 	"RUST",     	// SKINCOLOR_RUST,
-	"GOLD",     	// SKINCOLOR_GOLD,
+	"TANGERINE",   	// SKINCOLOR_TANGERINE,
 	"TOPAZ",     	// SKINCOLOR_TOPAZ,
+	"GOLD",     	// SKINCOLOR_GOLD,
 	"SANDY",     	// SKINCOLOR_SANDY,
 	"GOLDENROD",   	// SKINCOLOR_GOLDENROD,
 	"YELLOW",     	// SKINCOLOR_YELLOW,
@@ -4661,20 +4664,21 @@ const char *COLOR_ENUMS[] = {
 	"LIME",     	// SKINCOLOR_LIME,
 	"PERIDOT",     	// SKINCOLOR_PERIDOT,
 	"APPLE",     	// SKINCOLOR_APPLE,
+	"HEADLIGHT",	// SKINCOLOR_HEADLIGHT,
 	"CHARTREUSE",   // SKINCOLOR_CHARTREUSE,
 	"GREEN",     	// SKINCOLOR_GREEN,
 	"FOREST",     	// SKINCOLOR_FOREST,
 	"SHAMROCK",    	// SKINCOLOR_SHAMROCK,
 	"JADE",     	// SKINCOLOR_JADE,
-	"HEADLIGHT",	// SKINCOLOR_HEADLIGHT,
 	"MINT",     	// SKINCOLOR_MINT,
 	"MASTER",     	// SKINCOLOR_MASTER,
 	"EMERALD",     	// SKINCOLOR_EMERALD,
-	"BOTTLE",     	// SKINCOLOR_BOTTLE,
 	"SEAFOAM",     	// SKINCOLOR_SEAFOAM,
 	"ISLAND",     	// SKINCOLOR_ISLAND,
+	"BOTTLE",     	// SKINCOLOR_BOTTLE,
 	"AQUA",     	// SKINCOLOR_AQUA,
 	"TEAL",     	// SKINCOLOR_TEAL,
+	"OCEAN",     	// SKINCOLOR_OCEAN,
 	"WAVE",     	// SKINCOLOR_WAVE,
 	"CYAN",     	// SKINCOLOR_CYAN,
 	"TURQUOISE",    // SKINCOLOR_TURQUOISE,
@@ -4700,7 +4704,7 @@ const char *COLOR_ENUMS[] = {
 	"NOBLE",     	// SKINCOLOR_NOBLE,
 	"FUCHSIA",     	// SKINCOLOR_FUCHSIA,
 	"BUBBLEGUM",   	// SKINCOLOR_BUBBLEGUM,
-	"CRYSTAL",    	// SKINCOLOR_CRYSTAL,
+	"SIBERITE",   	// SKINCOLOR_SIBERITE,
 	"MAGENTA",     	// SKINCOLOR_MAGENTA,
 	"NEON",     	// SKINCOLOR_NEON,
 	"VIOLET",     	// SKINCOLOR_VIOLET,
diff --git a/src/deh_tables.h b/src/deh_tables.h
index 6731f171d867b2e2c796d55a38879b6a428c3129..42716f9b4bd271c98aca83737f419670d862d7e5 100644
--- a/src/deh_tables.h
+++ b/src/deh_tables.h
@@ -62,7 +62,7 @@ extern const char *const MOBJTYPE_LIST[];
 extern const char *const MOBJFLAG_LIST[];
 extern const char *const MOBJFLAG2_LIST[]; // \tMF2_(\S+).*// (.+) --> \t"\1", // \2
 extern const char *const MOBJEFLAG_LIST[];
-extern const char *const MAPTHINGFLAG_LIST[4];
+extern const char *const MAPTHINGFLAG_LIST[];
 extern const char *const PLAYERFLAG_LIST[];
 extern const char *const GAMETYPERULE_LIST[];
 extern const char *const ML_LIST[]; // Linedef flags
diff --git a/src/doomdata.h b/src/doomdata.h
index 4c5bdefaf968f073462ab37b295cf85b5185954e..276e03297b6f0d453ad0d0c65fc8bd9196d9680c 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -62,6 +62,10 @@ enum
 #define MTF_AMBUSH 8
 
 // Do not use bit five or after, as they are used for object z-offsets.
+// Unless it's exclusive to UDMF.
+
+// Flag to use Z as absolute spawn height, ignoring the floor and ceiling.
+#define MTF_ABSOLUTEZ 16
 
 #if defined(_MSC_VER)
 #pragma pack(1)
@@ -211,6 +215,7 @@ typedef struct
 	UINT8 extrainfo;
 	taglist_t tags;
 	fixed_t scale;
+	fixed_t spritexscale, spriteyscale;
 	INT32 args[NUMMAPTHINGARGS];
 	char *stringargs[NUMMAPTHINGSTRINGARGS];
 	struct mobj_s *mobj;
diff --git a/src/doomdef.h b/src/doomdef.h
index 84404d6edb62d82633b3f2643b9133a14d6b7ed3..45d6645faa0bc0ff884e11cb5722e4967f679560 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -108,6 +108,14 @@ FILE *fopenfile(const char*, const char*);
 
 //#define NOMD5
 
+// If you don't disable ALL debug first, you get ALL debug enabled
+#if !defined (NDEBUG)
+#define PACKETDROP
+#define PARANOIA
+#define RANGECHECK
+#define ZDEBUG
+#endif
+
 // Uncheck this to compile debugging code
 //#define RANGECHECK
 //#ifndef PARANOIA
@@ -261,8 +269,7 @@ typedef enum
 	// Desaturated
 	SKINCOLOR_AETHER,
 	SKINCOLOR_SLATE,
-	SKINCOLOR_METEORITE,
-	SKINCOLOR_MERCURY,
+	SKINCOLOR_MOONSTONE,
 	SKINCOLOR_BLUEBELL,
 	SKINCOLOR_PINK,
 	SKINCOLOR_ROSEWOOD,
@@ -299,10 +306,10 @@ typedef enum
 	SKINCOLOR_COPPER,
 	SKINCOLOR_APRICOT,
 	SKINCOLOR_ORANGE,
-	SKINCOLOR_PUMPKIN,
 	SKINCOLOR_RUST,
-	SKINCOLOR_GOLD,
+	SKINCOLOR_TANGERINE,
 	SKINCOLOR_TOPAZ,
+	SKINCOLOR_GOLD,
 	SKINCOLOR_SANDY,
 	SKINCOLOR_GOLDENROD,
 	SKINCOLOR_YELLOW,
@@ -312,20 +319,21 @@ typedef enum
 	SKINCOLOR_LIME,
 	SKINCOLOR_PERIDOT,
 	SKINCOLOR_APPLE,
+	SKINCOLOR_HEADLIGHT,
 	SKINCOLOR_CHARTREUSE,
 	SKINCOLOR_GREEN,
 	SKINCOLOR_FOREST,
 	SKINCOLOR_SHAMROCK,
 	SKINCOLOR_JADE,
-	SKINCOLOR_HEADLIGHT,
 	SKINCOLOR_MINT,
 	SKINCOLOR_MASTER,
 	SKINCOLOR_EMERALD,
-	SKINCOLOR_BOTTLE,
 	SKINCOLOR_SEAFOAM,
 	SKINCOLOR_ISLAND,
+	SKINCOLOR_BOTTLE,
 	SKINCOLOR_AQUA,
 	SKINCOLOR_TEAL,
+	SKINCOLOR_OCEAN,
 	SKINCOLOR_WAVE,
 	SKINCOLOR_CYAN,
 	SKINCOLOR_TURQUOISE,
@@ -351,7 +359,7 @@ typedef enum
 	SKINCOLOR_NOBLE,
 	SKINCOLOR_FUCHSIA,
 	SKINCOLOR_BUBBLEGUM,
-	SKINCOLOR_CRYSTAL,
+	SKINCOLOR_SIBERITE,
 	SKINCOLOR_MAGENTA,
 	SKINCOLOR_NEON,
 	SKINCOLOR_VIOLET,
@@ -637,7 +645,16 @@ UINT32 quickncasehash (const char *p, size_t n)
 #define PUNCTUATION "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
 
 // Compile date and time and revision.
-extern const char *compdate, *comptime, *comprevision, *compbranch;
+extern const char
+	*compdate,
+	*comptime,
+	*comprevision,
+	*compbranch,
+	*compnote,
+	*comptype;
+extern int
+	compuncommitted,
+	compoptimized;
 
 // Disabled code and code under testing
 // None of these that are disabled in the normal build are guaranteed to work perfectly
diff --git a/src/doomstat.h b/src/doomstat.h
index a812cc304f6e0b19cab8f2fecf37868264ed9b16..fdd0d0b834ff58601cce67881e399709f9277d8b 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -249,6 +249,7 @@ extern textprompt_t *textprompts[MAX_PROMPTS];
 // For the Custom Exit linedef.
 extern INT16 nextmapoverride;
 extern UINT8 skipstats;
+extern INT16 nextgametype;
 
 extern UINT32 ssspheres; //  Total # of spheres in a level
 
diff --git a/src/doomtype.h b/src/doomtype.h
index f6c236e20bf3979ec9f1ef2353ed80b647e45b73..4070e346a1b13dd516a91504f056cebc4defb20d 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -17,6 +17,10 @@
 #ifndef __DOOMTYPE__
 #define __DOOMTYPE__
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #ifdef _WIN32
 //#define WIN32_LEAN_AND_MEAN
 #define RPC_NO_WINDOWS_H
@@ -78,7 +82,9 @@ typedef long ssize_t;
 #endif
 	#define strncasecmp             strnicmp
 	#define strcasecmp              stricmp
+#ifndef __cplusplus
 	#define inline                  __inline
+#endif
 #elif defined (__WATCOMC__)
 	#include <dos.h>
 	#include <sys\types.h>
@@ -94,32 +100,26 @@ typedef long ssize_t;
 	#define strnicmp(x,y,n) strncasecmp(x,y,n)
 #endif
 
-char *strcasestr(const char *in, const char *what);
+char *nongnu_strcasestr(const char *in, const char *what);
+#ifndef _GNU_SOURCE
+#define strcasestr nongnu_strcasestr
+#endif
 #define stristr strcasestr
 
 int startswith (const char *base, const char *tag);
 int endswith (const char *base, const char *tag);
 
-#if defined (macintosh) //|| defined (__APPLE__) //skip all boolean/Boolean crap
-	#define true 1
-	#define false 0
-	#define min(x,y) (((x)<(y)) ? (x) : (y))
-	#define max(x,y) (((x)>(y)) ? (x) : (y))
-
-#ifdef macintosh
-	#define stricmp strcmp
-	#define strnicmp strncmp
+#if defined (_WIN32) || defined (__HAIKU__)
+#define HAVE_DOSSTR_FUNCS
 #endif
 
-	#define boolean INT32
-
-	#ifndef O_BINARY
-	#define O_BINARY 0
+#if defined (__APPLE__)
+	#define SRB2_HAVE_STRLCPY
+#elif defined (__GLIBC_PREREQ)
+	// glibc 2.38: added strlcpy and strlcat to _DEFAULT_SOURCE
+	#if __GLIBC_PREREQ(2, 38)
+		#define SRB2_HAVE_STRLCPY
 	#endif
-#endif //macintosh
-
-#if defined (_WIN32) || defined (__HAIKU__)
-#define HAVE_DOSSTR_FUNCS
 #endif
 
 #ifndef HAVE_DOSSTR_FUNCS
@@ -129,7 +129,7 @@ int strlwr(char *n); // from dosstr.c
 
 #include <stddef.h> // for size_t
 
-#ifndef __APPLE__
+#ifndef SRB2_HAVE_STRLCPY
 size_t strlcat(char *dst, const char *src, size_t siz);
 size_t strlcpy(char *dst, const char *src, size_t siz);
 #endif
@@ -144,22 +144,24 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 
 /* Boolean type definition */
 
-// \note __BYTEBOOL__ used to be set above if "macintosh" was defined,
-// if macintosh's version of boolean type isn't needed anymore, then isn't this macro pointless now?
-#ifndef __BYTEBOOL__
-	#define __BYTEBOOL__
-
-	//faB: clean that up !!
-	#if defined( _MSC_VER)  && (_MSC_VER >= 1800) // MSVC 2013 and forward
-		#include "stdbool.h"
-	#elif defined (_WIN32)
-		#define false   FALSE           // use windows types
-		#define true    TRUE
-		#define boolean BOOL
-	#else
-		typedef enum {false, true} boolean;
-	#endif
-#endif // __BYTEBOOL__
+// Note: C++ bool and C99/C11 _Bool are NOT compatible.
+// Historically, boolean was win32 BOOL on Windows. For equivalence, it's now
+// int32_t. "true" and "false" are only declared for C code; in C++, conversion
+// between "bool" and "int32_t" takes over.
+#ifndef _WIN32
+typedef int32_t boolean;
+#else
+#define boolean BOOL
+#endif
+
+#ifndef __cplusplus
+#ifndef _WIN32
+enum {false = 0, true = 1};
+#else
+#define false FALSE
+#define true TRUE
+#endif
+#endif
 
 /* 7.18.2.1  Limits of exact-width integer types */
 
@@ -387,4 +389,8 @@ unset_bit_array (bitarray_t * const array, const int value)
 
 typedef UINT64 precise_t;
 
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
 #endif //__DOOMTYPE__
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index 8556c0248651d04469b4c1114a780bba47824421..125d2e8aec897f288995649aec2476989b4136d6 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -180,6 +180,11 @@ const char *I_ClipboardPaste(void)
 	return NULL;
 }
 
+size_t I_GetRandomBytes(char *destination, size_t amount)
+{
+	return 0;
+}
+
 void I_RegisterSysCommands(void) {}
 
 void I_GetCursorPosition(INT32 *x, INT32 *y)
diff --git a/src/f_finale.c b/src/f_finale.c
index e59b43472087eb2f03a6240eadff652cbbe99609..91c06b31652ab2902668fdb82009519ee6811108 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1062,12 +1062,14 @@ static const char *credits[] = {
 	"\"Golden\"",
 	"Vivian \"toaster\" Grannell",
 	"Julio \"Chaos Zero 64\" Guir",
+	"\"Hanicef\"",
 	"\"Hannu_Hanhi\"", // For many OpenGL performance improvements!
 	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
 	"Iestyn \"Monster Iestyn\" Jealous",
 	"\"Kaito Sinclaire\"",
 	"\"Kalaron\"", // Coded some of Sryder13's collection of OpenGL fixes, especially fog
+	"\"katsy\"",
 	"Ronald \"Furyhunter\" Kinard", // The SDL2 port
 	"\"Lat'\"", // SRB2-CHAT, the chat window from Kart
 	"\"LZA\"",
@@ -1090,6 +1092,7 @@ static const char *credits[] = {
 	"Ben \"Cue\" Woodford",
 	"Lachlan \"Lach\" Wright",
 	"Marco \"mazmazz\" Zafra",
+	"\"Zwip-Zwap Zapony\"",
 	"",
 	"\1Art",
 	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D:
@@ -1197,6 +1200,7 @@ static const char *credits[] = {
 	"FreeDoom Project", // Used some of the mancubus and rocket launcher sprites for Brak
 	"Kart Krew",
 	"Alex \"MistaED\" Fuller",
+	"Howard Drossin", // Virtual Sonic - Sonic & Knuckles Theme
 	"Pascal \"CodeImp\" vd Heiden", // Doom Builder developer
 	"Randi Heit (<!>)", // For their MSPaint <!> sprite that we nicked
 	"Simon \"sirjuddington\" Judd", // SLADE developer
@@ -1644,7 +1648,7 @@ void F_GameEvaluationTicker(void)
 		sparklloop = 0;
 	}
 
-	if (finalecount == 5*TICRATE)
+	if (G_CoopGametype() && !stagefailed && finalecount == 5*TICRATE)
 	{
 		serverGamedata->timesBeaten++;
 		clientGamedata->timesBeaten++;
@@ -2256,7 +2260,7 @@ void F_InitMenuPresValues(void)
 	curfadevalue = 16;
 	curbgcolor = -1;
 	curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
-	curbgyspeed = (gamestate == GS_TIMEATTACK) ? 22 : titlescrollyspeed;
+	curbgyspeed = (gamestate == GS_TIMEATTACK) ? 18 : titlescrollyspeed;
 	curbghide = (gamestate == GS_TIMEATTACK) ? false : true;
 
 	curhidepics = hidetitlepics;
@@ -3509,6 +3513,7 @@ void F_TitleScreenTicker(boolean run)
 		}
 
 		titledemo = true;
+		demofileoverride = DFILE_OVERRIDE_NONE;
 		G_DoPlayDemo(dname);
 	}
 }
diff --git a/src/g_demo.c b/src/g_demo.c
index 0403da16da95c5d4e41a614fa2640797ece217f2..4b9ff56e80f5711faabd18b53a9474f2c0a60c5e 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -39,6 +39,7 @@
 #include "v_video.h"
 #include "lua_hook.h"
 #include "md5.h" // demo checksums
+#include "d_netfil.h" // G_CheckDemoExtraFiles
 
 boolean timingdemo; // if true, exit with report on completion
 boolean nodrawers; // for comparative timing purposes
@@ -49,6 +50,7 @@ static char demoname[64];
 boolean demorecording;
 boolean demoplayback;
 boolean titledemo; // Title Screen demo can be cancelled by any key
+demo_file_override_e demofileoverride;
 static UINT8 *demobuffer = NULL;
 static UINT8 *demo_p, *demotime_p;
 static UINT8 *demoend;
@@ -56,6 +58,7 @@ static UINT8 demoflags;
 static UINT16 demoversion;
 boolean singledemo; // quit after playing a demo from cmdline
 boolean demo_start; // don't start playing demo right away
+boolean demo_forwardmove_rng; // old demo backwards compatibility
 boolean demosynced = true; // console warning message
 
 boolean metalrecording; // recording as metal sonic
@@ -95,7 +98,7 @@ demoghost *ghosts = NULL;
 // DEMO RECORDING
 //
 
-#define DEMOVERSION 0x000f
+#define DEMOVERSION 0x0010
 #define DEMOHEADER  "\xF0" "SRB2Replay" "\x0F"
 
 #define DF_GHOST        0x01 // This demo contains ghost data too!
@@ -1413,6 +1416,10 @@ void G_BeginRecording(void)
 	char name[MAXCOLORNAME+1];
 	player_t *player = &players[consoleplayer];
 
+	char *filename;
+	UINT16 totalfiles;
+	UINT8 *m;
+
 	if (demo_p)
 		return;
 	memset(name,0,sizeof(name));
@@ -1435,23 +1442,43 @@ void G_BeginRecording(void)
 	M_Memcpy(demo_p, mapmd5, 16); demo_p += 16;
 
 	WRITEUINT8(demo_p,demoflags);
+
+	// file list
+	m = demo_p;/* file count */
+	demo_p += 2;
+
+	totalfiles = 0;
+	for (i = mainwads; ++i < numwadfiles; )
+	{
+		if (wadfiles[i]->important)
+		{
+			nameonly(( filename = va("%s", wadfiles[i]->filename) ));
+			WRITESTRINGL(demo_p, filename, MAX_WADPATH);
+			WRITEMEM(demo_p, wadfiles[i]->md5sum, 16);
+
+			totalfiles++;
+		}
+	}
+
+	WRITEUINT16(m, totalfiles);
+
 	switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
 	{
-	case ATTACKING_NONE: // 0
-		break;
-	case ATTACKING_RECORD: // 1
-		demotime_p = demo_p;
-		WRITEUINT32(demo_p,UINT32_MAX); // time
-		WRITEUINT32(demo_p,0); // score
-		WRITEUINT16(demo_p,0); // rings
-		break;
-	case ATTACKING_NIGHTS: // 2
-		demotime_p = demo_p;
-		WRITEUINT32(demo_p,UINT32_MAX); // time
-		WRITEUINT32(demo_p,0); // score
-		break;
-	default: // 3
-		break;
+		case ATTACKING_NONE: // 0
+			break;
+		case ATTACKING_RECORD: // 1
+			demotime_p = demo_p;
+			WRITEUINT32(demo_p,UINT32_MAX); // time
+			WRITEUINT32(demo_p,0); // score
+			WRITEUINT16(demo_p,0); // rings
+			break;
+		case ATTACKING_NIGHTS: // 2
+			demotime_p = demo_p;
+			WRITEUINT32(demo_p,UINT32_MAX); // time
+			WRITEUINT32(demo_p,0); // score
+			break;
+		default: // 3
+			break;
 	}
 
 	WRITEUINT32(demo_p,P_GetInitSeed());
@@ -1483,18 +1510,18 @@ void G_BeginRecording(void)
 	// Stats
 	WRITEUINT8(demo_p,player->charability);
 	WRITEUINT8(demo_p,player->charability2);
-	WRITEUINT8(demo_p,player->actionspd>>FRACBITS);
-	WRITEUINT8(demo_p,player->mindash>>FRACBITS);
-	WRITEUINT8(demo_p,player->maxdash>>FRACBITS);
-	WRITEUINT8(demo_p,player->normalspeed>>FRACBITS);
-	WRITEUINT8(demo_p,player->runspeed>>FRACBITS);
+	WRITEFIXED(demo_p,player->actionspd);
+	WRITEFIXED(demo_p,player->mindash);
+	WRITEFIXED(demo_p,player->maxdash);
+	WRITEFIXED(demo_p,player->normalspeed);
+	WRITEFIXED(demo_p,player->runspeed);
 	WRITEUINT8(demo_p,player->thrustfactor);
 	WRITEUINT8(demo_p,player->accelstart);
 	WRITEUINT8(demo_p,player->acceleration);
 	WRITEFIXED(demo_p,player->height);
 	WRITEFIXED(demo_p,player->spinheight);
-	WRITEUINT8(demo_p,player->camerascale>>FRACBITS);
-	WRITEUINT8(demo_p,player->shieldscale>>FRACBITS);
+	WRITEFIXED(demo_p,player->camerascale);
+	WRITEFIXED(demo_p,player->shieldscale);
 
 	// Trying to convert it back to % causes demo desync due to precision loss.
 	// Don't do it.
@@ -1590,6 +1617,183 @@ void G_BeginMetal(void)
 	oldmetal.angle = mo->angle>>24;
 }
 
+static void G_LoadDemoExtraFiles(UINT8 **pp, UINT16 this_demo_version)
+{
+	UINT16 totalfiles;
+	char filename[MAX_WADPATH];
+	UINT8 md5sum[16];
+	filestatus_t ncs;
+	boolean toomany = false;
+	boolean alreadyloaded;
+	UINT16 i, j;
+
+	if (this_demo_version < 0x0010)
+	{
+		// demo has no file list
+		return;
+	}
+
+	totalfiles = READUINT16((*pp));
+	for (i = 0; i < totalfiles; ++i)
+	{
+		if (toomany)
+			SKIPSTRING((*pp));
+		else
+		{
+			strlcpy(filename, (char *)(*pp), sizeof filename);
+			SKIPSTRING((*pp));
+		}
+		READMEM((*pp), md5sum, 16);
+
+		if (!toomany)
+		{
+			alreadyloaded = false;
+
+			for (j = 0; j < numwadfiles; ++j)
+			{
+				if (memcmp(md5sum, wadfiles[j]->md5sum, 16) == 0)
+				{
+					alreadyloaded = true;
+					break;
+				}
+			}
+
+			if (alreadyloaded)
+				continue;
+
+			if (numwadfiles >= MAX_WADFILES)
+				toomany = true;
+			else
+				ncs = findfile(filename, md5sum, false);
+
+			if (toomany)
+			{
+				CONS_Alert(CONS_WARNING, M_GetText("Too many files loaded to add anymore for demo playback\n"));
+				if (!CON_Ready())
+					M_StartMessage(M_GetText("There are too many files loaded to add this demo's addons.\n\nDemo playback may desync.\n\nPress ESC\n"), NULL, MM_NOTHING);
+			}
+			else if (ncs != FS_FOUND)
+			{
+				if (ncs == FS_NOTFOUND)
+					CONS_Alert(CONS_NOTICE, M_GetText("You do not have a copy of %s\n"), filename);
+				else if (ncs == FS_MD5SUMBAD)
+					CONS_Alert(CONS_NOTICE, M_GetText("Checksum mismatch on %s\n"), filename);
+				else
+					CONS_Alert(CONS_NOTICE, M_GetText("Unknown error finding file %s\n"), filename);
+
+				if (!CON_Ready())
+					M_StartMessage(M_GetText("There were errors trying to add this demo's addons. Check the console for more information.\n\nDemo playback may desync.\n\nPress ESC\n"), NULL, MM_NOTHING);
+			}
+			else
+			{
+				P_AddWadFile(filename);
+			}
+		}
+	}
+}
+
+static void G_SkipDemoExtraFiles(UINT8 **pp, UINT16 this_demo_version)
+{
+	UINT16 totalfiles;
+	UINT16 i;
+
+	if (this_demo_version < 0x0010)
+	{
+		// demo has no file list
+		return;
+	}
+
+	totalfiles = READUINT16((*pp));
+	for (i = 0; i < totalfiles; ++i)
+	{
+		SKIPSTRING((*pp));// file name
+		(*pp) += 16;// md5
+	}
+}
+
+// G_CheckDemoExtraFiles: checks if our loaded WAD list matches the demo's.
+// Enabling quick prevents filesystem checks to see if needed files are available to load.
+static UINT8 G_CheckDemoExtraFiles(UINT8 **pp, boolean quick, UINT16 this_demo_version)
+{
+	UINT16 totalfiles, filesloaded, nmusfilecount;
+	char filename[MAX_WADPATH];
+	UINT8 md5sum[16];
+	boolean toomany = false;
+	boolean alreadyloaded;
+	UINT16 i, j;
+	UINT8 error = DFILE_ERROR_NONE;
+
+	if (this_demo_version < 0x0010)
+	{
+		// demo has no file list
+		return DFILE_ERROR_NONE;
+	}
+
+	totalfiles = READUINT16((*pp));
+	filesloaded = 0;
+	for (i = 0; i < totalfiles; ++i)
+	{
+		if (toomany)
+			SKIPSTRING((*pp));
+		else
+		{
+			strlcpy(filename, (char *)(*pp), sizeof filename);
+			SKIPSTRING((*pp));
+		}
+		READMEM((*pp), md5sum, 16);
+
+		if (!toomany)
+		{
+			alreadyloaded = false;
+			nmusfilecount = 0;
+
+			for (j = 0; j < numwadfiles; ++j)
+			{
+				if (wadfiles[j]->important && j > mainwads)
+					nmusfilecount++;
+				else
+					continue;
+
+				if (memcmp(md5sum, wadfiles[j]->md5sum, 16) == 0)
+				{
+					alreadyloaded = true;
+
+					if (i != nmusfilecount-1 && error < DFILE_ERROR_OUTOFORDER)
+						error |= DFILE_ERROR_OUTOFORDER;
+
+					break;
+				}
+			}
+
+			if (alreadyloaded)
+			{
+				filesloaded++;
+				continue;
+			}
+
+			if (numwadfiles >= MAX_WADFILES)
+				error = DFILE_ERROR_CANNOTLOAD;
+			else if (!quick && findfile(filename, md5sum, false) != FS_FOUND)
+				error = DFILE_ERROR_CANNOTLOAD;
+			else if (error < DFILE_ERROR_INCOMPLETEOUTOFORDER)
+				error |= DFILE_ERROR_NOTLOADED;
+		} else
+			error = DFILE_ERROR_CANNOTLOAD;
+	}
+
+	// Get final file count
+	nmusfilecount = 0;
+
+	for (j = 0; j < numwadfiles; ++j)
+		if (wadfiles[j]->important && j > mainwads)
+			nmusfilecount++;
+
+	if (!error && filesloaded < nmusfilecount)
+		error = DFILE_ERROR_EXTRAFILES;
+
+	return error;
+}
+
 void G_SetDemoTime(UINT32 ptime, UINT32 pscore, UINT16 prings)
 {
 	if (!demorecording || !demotime_p)
@@ -1618,10 +1822,9 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	UINT8 *buffer,*p;
 	UINT8 flags;
 	UINT32 oldtime, newtime, oldscore, newscore;
-	UINT16 oldrings, newrings, oldversion;
+	UINT16 oldrings, newrings, oldversion, newversion;
 	size_t bufsize ATTRUNUSED;
 	UINT8 c;
-	UINT16 s ATTRUNUSED;
 	UINT8 aflags = 0;
 
 	// load the new file
@@ -1637,15 +1840,15 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	I_Assert(c == VERSION);
 	c = READUINT8(p); // SUBVERSION
 	I_Assert(c == SUBVERSION);
-	s = READUINT16(p);
-	I_Assert(s >= 0x000c);
+	newversion = READUINT16(p);
+	I_Assert(newversion == DEMOVERSION);
 	p += 16; // demo checksum
 	I_Assert(!memcmp(p, "PLAY", 4));
 	p += 4; // PLAY
 	p += 2; // gamemap
 	p += 16; // map md5
 	flags = READUINT8(p); // demoflags
-
+	G_SkipDemoExtraFiles(&p, newversion);
 	aflags = flags & (DF_RECORDATTACK|DF_NIGHTSATTACK);
 	I_Assert(aflags);
 	if (flags & DF_RECORDATTACK)
@@ -1687,7 +1890,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	switch(oldversion) // demoversion
 	{
 	case DEMOVERSION: // latest always supported
-	case 0x000e: // The previous demoversions also supported
+	case 0x000f: // The previous demoversions also supported 
+	case 0x000e:
 	case 0x000d: // all that changed between then and now was longer color name
 	case 0x000c:
 		break;
@@ -1710,6 +1914,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 		p += 2; // gamemap
 	p += 16; // mapmd5
 	flags = READUINT8(p);
+	G_SkipDemoExtraFiles(&p, oldversion);
 	if (!(flags & aflags))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' not from same game mode. It will be overwritten.\n"), oldname);
@@ -1829,8 +2034,10 @@ void G_DoPlayDemo(char *defdemoname)
 	version = READUINT8(demo_p);
 	subversion = READUINT8(demo_p);
 	demoversion = READUINT16(demo_p);
+	demo_forwardmove_rng = (demoversion < 0x0010);
 	switch(demoversion)
 	{
+	case 0x000f:
 	case 0x000d:
 	case 0x000e:
 	case DEMOVERSION: // latest always supported
@@ -1871,6 +2078,69 @@ void G_DoPlayDemo(char *defdemoname)
 	demo_p += 16; // mapmd5
 
 	demoflags = READUINT8(demo_p);
+
+	if (titledemo)
+	{
+		// Titledemos should always play and ought to always be compatible with whatever wadlist is running.
+		G_SkipDemoExtraFiles(&demo_p, demoversion);
+	}
+	else if (demofileoverride == DFILE_OVERRIDE_LOAD)
+	{
+		G_LoadDemoExtraFiles(&demo_p, demoversion);
+	}
+	else if (demofileoverride == DFILE_OVERRIDE_SKIP)
+	{
+		G_SkipDemoExtraFiles(&demo_p, demoversion);
+	}
+	else
+	{
+		UINT8 error = G_CheckDemoExtraFiles(&demo_p, false, demoversion);
+
+		if (error)
+		{
+			switch (error)
+			{
+				case DFILE_ERROR_NOTLOADED:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo are not loaded.\n\nUse\n\"playdemo %s -addfiles\"\nto load them and play the demo.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_OUTOFORDER:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo are loaded out of order.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_INCOMPLETEOUTOFORDER:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo are not loaded, and some are out of order.\n\nUse\n\"playdemo %s -addfiles\"\nto load needed files and play the demo.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_CANNOTLOAD:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo cannot be loaded.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_EXTRAFILES:
+					snprintf(msg, 1024,
+						M_GetText("You have additional files loaded beyond the demo's file list.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"),
+					pdemoname);
+					break;
+			}
+
+			CONS_Alert(CONS_ERROR, "%s", msg);
+			M_StartMessage(msg, NULL, MM_NOTHING);
+			Z_Free(pdemoname);
+			Z_Free(demobuffer);
+			demoplayback = false;
+			titledemo = false;
+			return;
+		}
+	}
+
 	modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT;
 	CON_ToggleOff();
 
@@ -1913,18 +2183,18 @@ void G_DoPlayDemo(char *defdemoname)
 
 	charability = READUINT8(demo_p);
 	charability2 = READUINT8(demo_p);
-	actionspd = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	mindash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	maxdash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	normalspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	runspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	actionspd = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	mindash = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	maxdash = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	normalspeed = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	runspeed = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	thrustfactor = READUINT8(demo_p);
 	accelstart = READUINT8(demo_p);
 	acceleration = READUINT8(demo_p);
 	height = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	spinheight = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
-	camerascale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	shieldscale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	camerascale = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	shieldscale = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	jumpfactor = READFIXED(demo_p);
 	followitem = READUINT32(demo_p);
 
@@ -2026,6 +2296,88 @@ void G_DoPlayDemo(char *defdemoname)
 	demo_start = true;
 }
 
+//
+// Check if a replay can be loaded from the menu
+//
+UINT8 G_CheckDemoForError(char *defdemoname)
+{
+	lumpnum_t l;
+	char *n,*pdemoname;
+	UINT16 our_demo_version;
+
+	if (titledemo)
+	{
+		// Don't do anything with files for these.
+		return DFILE_ERROR_NONE;
+	}
+
+	n = defdemoname+strlen(defdemoname);
+	while (*n != '/' && *n != '\\' && n != defdemoname)
+		n--;
+	if (n != defdemoname)
+		n++;
+	pdemoname = ZZ_Alloc(strlen(n)+1);
+	strcpy(pdemoname,n);
+
+	// Internal if no extension, external if one exists
+	if (FIL_CheckExtension(defdemoname))
+	{
+		//FIL_DefaultExtension(defdemoname, ".lmp");
+		if (!FIL_ReadFile(defdemoname, &demobuffer))
+		{
+			return DFILE_ERROR_NOTDEMO;
+		}
+		demo_p = demobuffer;
+	}
+	// load demo resource from WAD
+	else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
+	{
+		return DFILE_ERROR_NOTDEMO;
+	}
+	else // it's an internal demo
+	{
+		demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC);
+	}
+
+	// read demo header
+	if (memcmp(demo_p, DEMOHEADER, 12))
+	{
+		return DFILE_ERROR_NOTDEMO;
+	}
+	demo_p += 12; // DEMOHEADER
+
+	demo_p++; // version
+	demo_p++; // subversion
+	our_demo_version = READUINT16(demo_p);
+	switch(our_demo_version)
+	{
+	case 0x000d:
+	case 0x000e:
+	case 0x000f:
+	case DEMOVERSION: // latest always supported
+		break;
+#ifdef OLD22DEMOCOMPAT
+	case 0x000c:
+		break;
+#endif
+	// too old, cannot support.
+	default:
+		return DFILE_ERROR_NOTDEMO;
+	}
+	demo_p += 16; // demo checksum
+	if (memcmp(demo_p, "PLAY", 4))
+	{
+		return DFILE_ERROR_NOTDEMO;
+	}
+	demo_p += 4; // "PLAY"
+	demo_p += 2; // gamemap
+	demo_p += 16; // mapmd5
+
+	demo_p++; // demoflags
+
+	return G_CheckDemoExtraFiles(&demo_p, true, our_demo_version);
+}
+
 void G_AddGhost(char *defdemoname)
 {
 	INT32 i;
@@ -2085,6 +2437,7 @@ void G_AddGhost(char *defdemoname)
 	ghostversion = READUINT16(p);
 	switch(ghostversion)
 	{
+	case 0x000f:
 	case 0x000d:
 	case 0x000e:
 	case DEMOVERSION: // latest always supported
@@ -2130,6 +2483,9 @@ void G_AddGhost(char *defdemoname)
 		Z_Free(buffer);
 		return;
 	}
+
+	G_SkipDemoExtraFiles(&p, ghostversion); // Don't wanna modify the file list for ghosts.
+
 	switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
 	{
 	case ATTACKING_NONE: // 0
@@ -2161,17 +2517,12 @@ void G_AddGhost(char *defdemoname)
 	// Ghosts do not have a player structure to put this in.
 	p++; // charability
 	p++; // charability2
-	p++; // actionspd
-	p++; // mindash
-	p++; // maxdash
-	p++; // normalspeed
-	p++; // runspeed
+	p += (ghostversion < 0x0010) ? 5 : 5 * sizeof(fixed_t); // actionspd, mindash, maxdash, normalspeed, and runspeed
 	p++; // thrustfactor
 	p++; // accelstart
 	p++; // acceleration
 	p += (ghostversion < 0x000e) ? 2 : 2 * sizeof(fixed_t); // height and spinheight
-	p++; // camerascale
-	p++; // shieldscale
+	p += (ghostversion < 0x0010) ? 2 : 2 * sizeof(fixed_t); // camerascale and shieldscale
 	p += 4; // jumpfactor
 	p += 4; // followitem
 
@@ -2347,6 +2698,7 @@ void G_DoPlayMetal(void)
 	switch(metalversion)
 	{
 	case DEMOVERSION: // latest always supported
+	case 0x000f:
 	case 0x000e: // There are checks wheter the momentum is from older demo versions or not
 	case 0x000d: // all that changed between then and now was longer color name
 	case 0x000c:
diff --git a/src/g_demo.h b/src/g_demo.h
index f25315a58c9b6040f38baca3d7f50abc9050a69e..379c57428a6db9daeef1b5d6ab6368436224c828 100644
--- a/src/g_demo.h
+++ b/src/g_demo.h
@@ -26,9 +26,19 @@
 extern boolean demoplayback, titledemo, demorecording, timingdemo;
 extern tic_t demostarttime;
 
+typedef enum
+{
+	DFILE_OVERRIDE_NONE = 0, // Show errors normally
+	DFILE_OVERRIDE_LOAD, // Forcefully load demo, add files beforehand
+	DFILE_OVERRIDE_SKIP, // Forcefully load demo, skip file list
+} demo_file_override_e;
+
+extern demo_file_override_e demofileoverride;
+
 // Quit after playing a demo from cmdline.
 extern boolean singledemo;
 extern boolean demo_start;
+extern boolean demo_forwardmove_rng;
 extern boolean demosynced;
 
 extern mobj_t *metalplayback;
@@ -53,6 +63,18 @@ typedef enum
 	GHC_RETURNSKIN // ditto
 } ghostcolor_t;
 
+// G_CheckDemoExtraFiles: checks if our loaded WAD list matches the demo's.
+typedef enum
+{
+	DFILE_ERROR_NONE = 0, // No file error
+	DFILE_ERROR_NOTLOADED, // Files are not loaded, but can be without a restart.
+	DFILE_ERROR_OUTOFORDER, // Files are loaded, but out of order.
+	DFILE_ERROR_INCOMPLETEOUTOFORDER, // Some files are loaded out of order, but others are not.
+	DFILE_ERROR_CANNOTLOAD, // Files are missing and cannot be loaded.
+	DFILE_ERROR_EXTRAFILES, // Extra files outside of the replay's file list are loaded.
+	DFILE_ERROR_NOTDEMO = UINT8_MAX, // This replay isn't even a replay...
+} demo_file_error_e;
+
 // Record/playback tics
 void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
 void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
@@ -83,5 +105,6 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill);
 void G_StopDemo(void);
 boolean G_CheckDemoStatus(void);
 INT32 G_ConvertOldFrameFlags(INT32 frame);
+UINT8 G_CheckDemoForError(char *defdemoname);
 
 #endif // __G_DEMO__
diff --git a/src/g_game.c b/src/g_game.c
index b8c43499850cd49ca39fd6498b37a4d91b95c8ed..619ed8c89d722280375a30fe3059c58785ae61b2 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -156,6 +156,7 @@ textprompt_t *textprompts[MAX_PROMPTS];
 
 INT16 nextmapoverride;
 UINT8 skipstats;
+INT16 nextgametype = -1;
 
 // Pointers to each CTF flag
 mobj_t *redflag;
@@ -315,6 +316,8 @@ consvar_t cv_consolechat = CVAR_INIT ("chatmode", "Window", CV_SAVE, consolechat
 // Pause game upon window losing focus
 consvar_t cv_pauseifunfocused = CVAR_INIT ("pauseifunfocused", "Yes", CV_SAVE, CV_YesNo, NULL);
 
+consvar_t cv_instantretry = CVAR_INIT ("instantretry", "No", CV_SAVE, CV_YesNo, NULL);
+
 consvar_t cv_crosshair = CVAR_INIT ("crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL);
 consvar_t cv_crosshair2 = CVAR_INIT ("crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL);
 consvar_t cv_invertmouse = CVAR_INIT ("invertmouse", "Off", CV_SAVE, CV_OnOff, NULL);
@@ -2079,7 +2082,7 @@ boolean G_Responder(event_t *ev)
 	if (gameaction == ga_nothing && !singledemo &&
 		((demoplayback && !modeattacking && !titledemo) || gamestate == GS_TITLESCREEN))
 	{
-		if (ev->type == ev_keydown && ev->key != 301 && !(gamestate == GS_TITLESCREEN && finalecount < TICRATE))
+		if (ev->type == ev_keydown && ev->key != 301 && !(gamestate == GS_TITLESCREEN && finalecount < (cv_tutorialprompt.value ? TICRATE : 0)))
 		{
 			M_StartControlPanel();
 			return true;
@@ -2137,7 +2140,7 @@ boolean G_Responder(event_t *ev)
 			if (! netgame)
 				F_StartGameEvaluation();
 			else if (server || IsPlayerAdmin(consoleplayer))
-				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+				D_SendExitLevel(false);
 			return true;
 		}
 	}
@@ -2172,9 +2175,9 @@ boolean G_Responder(event_t *ev)
 					if (menuactive || pausedelay < 0 || leveltime < 2)
 						return true;
 
-					if (pausedelay < 1+(NEWTICRATE/2))
+					if (!cv_instantretry.value && pausedelay < 1+(NEWTICRATE/2))
 						pausedelay = 1+(NEWTICRATE/2);
-					else if (++pausedelay > 1+(NEWTICRATE/2)+(NEWTICRATE/3))
+					else if (cv_instantretry.value || ++pausedelay > 1+(NEWTICRATE/2)+(NEWTICRATE/3))
 					{
 						G_SetModeAttackRetryFlag();
 						return true;
@@ -2523,7 +2526,6 @@ static inline void G_PlayerFinishLevel(INT32 player)
 
 	memset(p->powers, 0, sizeof (p->powers));
 	p->ringweapons = 0;
-	p->recordscore = 0;
 
 	p->mo->flags2 &= ~MF2_SHADOW; // cancel invisibility
 	P_FlashPal(p, 0, 0); // Resets
@@ -3462,9 +3464,7 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] =
 };
 
 //
-// G_SetGametype
-//
-// Set a new gametype, also setting gametype rules accordingly. Yay!
+// Sets a new gametype.
 //
 void G_SetGametype(INT16 gtype)
 {
@@ -3862,7 +3862,7 @@ static INT16 RandMap(UINT32 tolflags, INT16 pprevmap)
 	for (ix = 0; ix < NUMMAPS; ix++)
 		if (mapheaderinfo[ix] && (mapheaderinfo[ix]->typeoflevel & tolflags) == tolflags
 		 && ix != pprevmap // Don't pick the same map.
-		 && (dedicated || !M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps.
+		 && (!M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps.
 		)
 			okmaps[numokmaps++] = ix;
 
@@ -4054,6 +4054,13 @@ static void G_DoCompleted(void)
 			nextmap = 1100-1; // No infinite loop for you
 	}
 
+	INT16 gametype_to_use;
+
+	if (nextgametype >= 0 && nextgametype < gametypecount)
+		gametype_to_use = nextgametype;
+	else
+		gametype_to_use = gametype;
+
 	// If nextmap is actually going to get used, make sure it points to
 	// a map of the proper gametype -- skip levels that don't support
 	// the current gametype. (Helps avoid playing boss levels in Race,
@@ -4062,8 +4069,8 @@ static void G_DoCompleted(void)
 	{
 		if (nextmap >= 0 && nextmap < NUMMAPS)
 		{
-			register INT16 cm = nextmap;
-			UINT32 tolflag = G_TOLFlag(gametype);
+			INT16 cm = nextmap;
+			UINT32 tolflag = G_TOLFlag(gametype_to_use);
 			UINT8 visitedmap[(NUMMAPS+7)/8];
 
 			memset(visitedmap, 0, sizeof (visitedmap));
@@ -4118,7 +4125,7 @@ static void G_DoCompleted(void)
 	{
 		token--;
 
-		if (!nextmapoverride)
+//		if (!nextmapoverride) // Having a token should pull the player into the special stage before going to the overridden map (Issue #933)
 			for (i = 0; i < 7; i++)
 				if (!(emeralds & (1<<i)))
 				{
@@ -4143,7 +4150,7 @@ static void G_DoCompleted(void)
 		if (cv_advancemap.value == 0) // Stay on same map.
 			nextmap = prevmap;
 		else if (cv_advancemap.value == 2) // Go to random map.
-			nextmap = RandMap(G_TOLFlag(gametype), prevmap);
+			nextmap = RandMap(G_TOLFlag(gametype_to_use), prevmap);
 	}
 
 	// We are committed to this map now.
@@ -4152,7 +4159,6 @@ static void G_DoCompleted(void)
 	if (nextmap < NUMMAPS && !mapheaderinfo[nextmap])
 		P_AllocMapHeader(nextmap);
 
-	// If the current gametype has no intermission screen set, then don't start it.
 	Y_DetermineIntermissionType();
 
 	if ((skipstats && !modeattacking) || (modeattacking && stagefailed) || (intertype == int_none))
@@ -4218,12 +4224,21 @@ static void G_DoWorldDone(void)
 {
 	if (server)
 	{
+		INT16 gametype_to_use;
+
+		if (nextgametype >= 0 && nextgametype < gametypecount)
+			gametype_to_use = nextgametype;
+		else
+			gametype_to_use = gametype;
+
 		if (gametyperules & GTR_CAMPAIGN)
 			// don't reset player between maps
-			D_MapChange(nextmap+1, gametype, ultimatemode, false, 0, false, false);
+			D_MapChange(nextmap+1, gametype_to_use, ultimatemode, false, 0, false, false);
 		else
 			// resetplayer in match/chaos/tag/CTF/race for more equality
-			D_MapChange(nextmap+1, gametype, ultimatemode, true, 0, false, false);
+			D_MapChange(nextmap+1, gametype_to_use, ultimatemode, true, 0, false, false);
+
+		nextgametype = -1;
 	}
 
 	gameaction = ga_nothing;
@@ -4266,7 +4281,7 @@ static void G_DoContinued(void)
 {
 	player_t *pl = &players[consoleplayer];
 	I_Assert(!netgame && !multiplayer);
-	I_Assert(pl->continues > 0);
+	//I_Assert(pl->continues > 0);
 
 	if (pl->continues)
 		pl->continues--;
@@ -5048,6 +5063,12 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 		numgameovers = tokenlist = token = sstimer = redscore = bluescore = lastmap = 0;
 		countdown = countdown2 = exitfadestarted = 0;
 
+		if (!FLS)
+		{
+			emeralds = 0;
+			memset(&luabanks, 0, sizeof(luabanks));
+		}
+
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			players[i].playerstate = PST_REBORN;
@@ -5055,6 +5076,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 			players[i].starpostx = players[i].starposty = players[i].starpostz = 0;
 			players[i].recordscore = 0;
 
+			// default lives, continues and score
 			if (netgame || multiplayer)
 			{
 				if (!FLS || (players[i].lives < 1))
@@ -5114,6 +5136,19 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	automapactive = false;
 	imcontinuing = false;
 
+	// fetch saved data if available
+	if (savedata.lives > 0)
+	{
+		numgameovers = savedata.numgameovers;
+		players[consoleplayer].continues = savedata.continues;
+		players[consoleplayer].lives = savedata.lives;
+		players[consoleplayer].score = savedata.score;
+		if ((botingame = ((botskin = savedata.botskin) != 0)))
+			botcolor = skins[botskin-1].prefcolor;
+		emeralds = savedata.emeralds;
+		savedata.lives = 0;
+	}
+
 	if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene.
 		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer, FLS);
 	else
diff --git a/src/g_game.h b/src/g_game.h
index a8c285f79f442b04a266f7e4ccbdd34997029833..9873430b936fd4b79f89d0adbcf714a303fae2ce 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -49,6 +49,8 @@ extern boolean promptactive;
 
 extern consvar_t cv_pauseifunfocused;
 
+extern consvar_t cv_instantretry;
+
 // used in game menu
 extern consvar_t cv_tutorialprompt;
 extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_compactscoreboard;
diff --git a/src/hardware/CMakeLists.txt b/src/hardware/CMakeLists.txt
index 4e9c67d2f348a8bfed899e4002d25136284b031f..e7819aba97e2065d36f6f920d4725d7b294505f3 100644
--- a/src/hardware/CMakeLists.txt
+++ b/src/hardware/CMakeLists.txt
@@ -1 +1,14 @@
-target_sourcefile(c)
+target_sources(SRB2SDL2 PRIVATE
+	hw_bsp.c
+	hw_draw.c
+	hw_light.c
+	hw_main.c
+	hw_clip.c
+	hw_md2.c
+	hw_cache.c
+	hw_md2load.c
+	hw_md3load.c
+	hw_model.c
+	hw_batching.c
+	r_opengl/r_opengl.c
+)
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 36ff86abd7d0770351234913b03d837e2086b62d..f2022bcea337558ca983e9f7e83a688303fe8dfb 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1699,7 +1699,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend)
 					{
 						blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent;
-						Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
+						Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255));
 					}
 
 					if (gl_frontsector->numlights)
@@ -1822,7 +1822,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend)
 					{
 						blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent;
-						Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
+						Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255));
 					}
 
 					if (gl_backsector->numlights)
@@ -3095,7 +3095,7 @@ static void HWR_Subsector(size_t num)
 										   false,
 					                       *rover->bottomheight,
 					                       *gl_frontsector->lightlist[light].lightlevel,
-					                       rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector,
+					                       max(0, min(rover->alpha, 255)), rover->master->frontsector,
 					                       HWR_RippleBlend(gl_frontsector, rover, false) | (rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent),
 					                       false, *gl_frontsector->lightlist[light].extra_colormap);
 				}
@@ -3141,7 +3141,7 @@ static void HWR_Subsector(size_t num)
 											true,
 					                        *rover->topheight,
 					                        *gl_frontsector->lightlist[light].lightlevel,
-					                        rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector,
+					                        max(0, min(rover->alpha, 255)), rover->master->frontsector,
 					                        HWR_RippleBlend(gl_frontsector, rover, false) | (rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent),
 					                        false, *gl_frontsector->lightlist[light].extra_colormap);
 				}
@@ -3595,7 +3595,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 			return;
 	}
 
-	floordiff = abs((flip < 0 ? thing->height : 0) + interp.z - groundz);
+	floordiff = abs((flip < 0 ? interp.height : 0) + interp.z - groundz);
 
 	alpha = floordiff / (4*FRACUNIT) + 75;
 	if (alpha >= 255) return;
@@ -3606,9 +3606,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	HWR_GetPatch(gpatch);
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
-	scalemul = FixedMul(scalemul, (thing->radius*2) / gpatch->height);
-	if ((thing->scale != thing->old_scale) && (thing->scale >= FRACUNIT/1024)) // Interpolate shadows when scaling mobjs
-		scalemul = FixedMul(scalemul, FixedDiv(interp.scale, thing->scale));
+	scalemul = FixedMul(scalemul, (interp.radius*2) / gpatch->height);
 
 	fscale = FIXED_TO_FLOAT(scalemul);
 	fx = FIXED_TO_FLOAT(interp.x);
@@ -3720,7 +3718,7 @@ static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts
 
 		if (P_MobjFlip(spr->mobj) == -1)
 		{
-			basey = FIXED_TO_FLOAT(interp.z + spr->mobj->height);
+			basey = FIXED_TO_FLOAT(interp.z + interp.height);
 		}
 		else
 		{
@@ -4055,32 +4053,32 @@ static void HWR_DrawBoundingBox(gl_vissprite_t *vis)
 	// repeat this 4 times (overhead)
 	//
 	//
-	// 17    20  21    11
-	//    16 15  14 10
-	// 27 22  *--*  07 12
+	// 15    16  17    09
+	//    14 13  12 08
+	// 23 18  *--*  07 10
 	//        |  |
-	// 26 23  *--*  06 13
-	//    24 00  01 02
-	// 25    05  04    03
+	// 22 19  *--*  06 11
+	//    20 00  01 02
+	// 21    05  04    03
 	//
 
-	v[000].x = v[005].x = v[015].x = v[016].x = v[017].x = v[020].x =
-		v[022].x = v[023].x = v[024].x = v[025].x = v[026].x = v[027].x = vis->x1; // west
+	v[ 0].x = v[ 5].x = v[13].x = v[14].x = v[15].x = v[16].x =
+		v[18].x = v[19].x = v[20].x = v[21].x = v[22].x = v[23].x = vis->x1; // west
 
-	v[001].x = v[002].x = v[003].x = v[004].x = v[006].x = v[007].x =
-		v[010].x = v[011].x = v[012].x = v[013].x = v[014].x = v[021].x = vis->x2; // east
+	v[ 1].x = v[ 2].x = v[ 3].x = v[ 4].x = v[ 6].x = v[ 7].x =
+		v[ 8].x = v[ 9].x = v[10].x = v[11].x = v[12].x = v[17].x = vis->x2; // east
 
-	v[000].z = v[001].z = v[002].z = v[003].z = v[004].z = v[005].z =
-		v[006].z = v[013].z = v[023].z = v[024].z = v[025].z = v[026].z = vis->z1; // south
+	v[ 0].z = v[ 1].z = v[ 2].z = v[ 3].z = v[ 4].z = v[ 5].z =
+		v[ 6].z = v[11].z = v[19].z = v[20].z = v[21].z = v[22].z = vis->z1; // south
 
-	v[007].z = v[010].z = v[011].z = v[012].z = v[014].z = v[015].z =
-		v[016].z = v[017].z = v[020].z = v[021].z = v[022].z = v[027].z = vis->z2; // north
+	v[ 7].z = v[ 8].z = v[ 9].z = v[10].z = v[12].z = v[13].z =
+		v[14].z = v[15].z = v[16].z = v[17].z = v[18].z = v[23].z = vis->z2; // north
 
-	v[000].y = v[001].y = v[002].y = v[006].y = v[007].y = v[010].y =
-		v[014].y = v[015].y = v[016].y = v[022].y = v[023].y = v[024].y = vis->gz; // bottom
+	v[ 0].y = v[ 1].y = v[ 2].y = v[ 6].y = v[ 7].y = v[ 8].y =
+		v[12].y = v[13].y = v[14].y = v[18].y = v[19].y = v[20].y = vis->gz; // bottom
 
-	v[003].y = v[004].y = v[005].y = v[011].y = v[012].y = v[013].y =
-		v[017].y = v[020].y = v[021].y = v[025].y = v[026].y = v[027].y = vis->gzt; // top
+	v[ 3].y = v[ 4].y = v[ 5].y = v[ 9].y = v[10].y = v[11].y =
+		v[15].y = v[16].y = v[17].y = v[21].y = v[22].y = v[23].y = vis->gzt; // top
 
 	Surf.PolyColor = V_GetColor(R_GetBoundingBoxColor(vis->mobj));
 	
@@ -5326,7 +5324,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 			}
 
 			groundz = R_GetShadowZ(thing, NULL);
-			floordiff = abs(((thing->eflags & MFE_VERTICALFLIP) ? caster->height : 0) + casterinterp.z - groundz);
+			floordiff = abs(((thing->eflags & MFE_VERTICALFLIP) ? casterinterp.height : 0) + casterinterp.z - groundz);
 
 			shadowheight = FIXED_TO_FLOAT(floordiff);
 			shadowscale = FIXED_TO_FLOAT(FixedMul(FRACUNIT - floordiff/640, casterinterp.scale));
@@ -5378,7 +5376,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 		if (vflip)
 		{
-			gz = FIXED_TO_FLOAT(interp.z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale);
+			gz = FIXED_TO_FLOAT(interp.z + interp.height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale);
 			gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale);
 		}
 		else
@@ -5684,7 +5682,6 @@ static void HWR_ProjectBoundingBox(mobj_t *thing)
 	gl_vissprite_t *vis;
 	float tr_x, tr_y;
 	float tz;
-	float rad;
 
 	if (!thing)
 		return;
@@ -5719,15 +5716,13 @@ static void HWR_ProjectBoundingBox(mobj_t *thing)
 	tr_x += gl_viewx;
 	tr_y += gl_viewy;
 
-	rad = FIXED_TO_FLOAT(thing->radius);
-
 	vis = HWR_NewVisSprite();
-	vis->x1 = tr_x - rad;
-	vis->x2 = tr_x + rad;
-	vis->z1 = tr_y - rad;
-	vis->z2 = tr_y + rad;
+	vis->x1 = tr_x - FIXED_TO_FLOAT(interp.radius);
+	vis->x2 = tr_x + FIXED_TO_FLOAT(interp.radius);
+	vis->z1 = tr_y - FIXED_TO_FLOAT(interp.radius);
+	vis->z2 = tr_y + FIXED_TO_FLOAT(interp.radius);
 	vis->gz = FIXED_TO_FLOAT(interp.z);
-	vis->gzt = vis->gz + FIXED_TO_FLOAT(thing->height);
+	vis->gzt = vis->gz + FIXED_TO_FLOAT(interp.height);
 	vis->mobj = thing;
 
 	vis->precip = false;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index fca3af217ececcf3a5d95c7df8559da24240f01b..6123eb9a932b4858e423343d917eaa606f658b1c 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1146,7 +1146,7 @@ static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 ski
 static boolean HWR_AllowModel(mobj_t *mobj)
 {
 	// Signpost overlay. Not needed.
-	if (mobj->state-states == S_PLAY_SIGN)
+	if (mobj->sprite2 == SPR2_SIGN || mobj->state-states == S_PLAY_SIGN)
 		return false;
 
 	// Otherwise, render the model.
@@ -1585,7 +1585,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		p.y = FIXED_TO_FLOAT(interp.y)+md2->offset;
 
 		if (flip)
-			p.z = FIXED_TO_FLOAT(interp.z + spr->mobj->height);
+			p.z = FIXED_TO_FLOAT(interp.z + interp.height);
 		else
 			p.z = FIXED_TO_FLOAT(interp.z);
 
@@ -1621,8 +1621,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 				p.roll = true;
 
 				// rotation pivot
-				p.centerx = FIXED_TO_FLOAT(spr->mobj->radius / 2);
-				p.centery = FIXED_TO_FLOAT(spr->mobj->height / 2);
+				p.centerx = FIXED_TO_FLOAT(interp.radius / 2);
+				p.centery = FIXED_TO_FLOAT(interp.height / 2);
 
 				// rotation axes relative to camera
 				p.rollx = FIXED_TO_FLOAT(FINECOSINE(FixedAngle(camAngleDiff) >> ANGLETOFINESHIFT));
diff --git a/src/http-mserv.c b/src/http-mserv.c
index b7032e89a3d65ecec15f195d1d46334435a411d9..df9a71a5c5d6cfcb8f73ecd3eeff371170e76c8e 100644
--- a/src/http-mserv.c
+++ b/src/http-mserv.c
@@ -159,7 +159,7 @@ HMS_connect (const char *format, ...)
 		return NULL;
 	}
 
-	if (cv_masterserver_token.string[0])
+	if (cv_masterserver_token.string && cv_masterserver_token.string[0])
 	{
 		quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0);
 		token_length = ( sizeof "?token="-1 )+ strlen(quack_token);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 091e2b2fba50e46638aa6ee52bff2d7e6c81f9f0..e223d320814c6dff885886d6d57a434c0d120493 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2025,7 +2025,7 @@ void HU_Drawer(void)
 		V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP | V_ALLOWLOWERCASE, resynch_text);
 	}
 
-	if (modeattacking && pausedelay > 0 && !pausebreakkey)
+	if (modeattacking && pausedelay > 0 && !(pausebreakkey || cv_instantretry.value))
 	{
 		INT32 strength = ((pausedelay - 1 - NEWTICRATE/2)*10)/(NEWTICRATE/3);
 		INT32 y = hudinfo[HUD_LIVES].y - 13;
diff --git a/src/i_system.h b/src/i_system.h
index deea9f8a8de8b0a5128336a03c4ca4d6069b0867..834dd4091487b295fd1faed9d11018e498d79b12 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -49,6 +49,10 @@ size_t I_GetFreeMem(size_t *total);
   */
 precise_t I_GetPreciseTime(void);
 
+/**	\brief	Fills a buffer with random data, returns amount of data obtained.
+  */
+size_t I_GetRandomBytes(char *destination, size_t count);
+
 /** \brief  Get the precision of precise_t in units per second. Invocations of
             this function for the program's duration MUST return the same value.
   */
diff --git a/src/info.c b/src/info.c
index abcf4b49998e827c42d8abb6f80b494bec8eb68c..36389d849a38e21064e0f4e94b803cccfc9f7ef0 100644
--- a/src/info.c
+++ b/src/info.c
@@ -7194,7 +7194,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_cgot,       // deathsound
 		EMERALD1,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -7220,7 +7220,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_cgot,       // deathsound
 		EMERALD2,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -7246,7 +7246,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_cgot,       // deathsound
 		EMERALD3,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -7272,7 +7272,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_cgot,       // deathsound
 		EMERALD4,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -7298,7 +7298,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_cgot,       // deathsound
 		EMERALD5,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -7324,7 +7324,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_cgot,       // deathsound
 		EMERALD6,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -7350,7 +7350,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_cgot,       // deathsound
 		EMERALD7,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -18344,7 +18344,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_itemup,     // deathsound
 		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
+		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		pw_bouncering,  // mass
@@ -18371,7 +18371,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_itemup,     // deathsound
 		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
+		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		pw_railring,    // mass
@@ -18425,7 +18425,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_itemup,     // deathsound
 		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
+		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		pw_automaticring, // mass
@@ -18452,7 +18452,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_itemup,     // deathsound
 		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
+		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		pw_explosionring, // mass
@@ -18479,7 +18479,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_itemup,     // deathsound
 		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
+		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		pw_scatterring, // mass
@@ -18506,7 +18506,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_itemup,     // deathsound
 		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
+		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		pw_grenadering, // mass
@@ -18535,7 +18535,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_ncitem,     // deathsound
 		60*FRACUNIT,    // speed
 		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		pw_bouncering,  // mass
 		2*TICRATE,      // damage
@@ -18562,7 +18562,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_ncitem,     // deathsound
 		60*FRACUNIT,    // speed
 		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		pw_railring,    // mass
 		2*TICRATE,      // damage
@@ -18589,7 +18589,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_ncitem,     // deathsound
 		60*FRACUNIT,    // speed
 		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		pw_automaticring, // mass
 		2*TICRATE,      // damage
@@ -18616,7 +18616,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_ncitem,     // deathsound
 		60*FRACUNIT,    // speed
 		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		pw_explosionring, // mass
 		2*TICRATE,      // damage
@@ -18643,7 +18643,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_ncitem,     // deathsound
 		60*FRACUNIT,    // speed
 		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		pw_scatterring, // mass
 		2*TICRATE,      // damage
@@ -18670,7 +18670,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_ncitem,     // deathsound
 		60*FRACUNIT,    // speed
 		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		pw_grenadering, // mass
 		2*TICRATE,      // damage
@@ -21584,10 +21584,9 @@ skincolor_t skincolors[MAXSKINCOLORS] = {
 	{"Black",  {0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1e, 0x1f, 0x1f}, SKINCOLOR_WHITE,  7,  V_GRAYMAP, true}, // SKINCOLOR_BLACK
 
 	// Desaturated
-	{"Aether",    {0x00, 0x00, 0x01, 0x02, 0x02, 0x03, 0x91, 0x91, 0x91, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xaf}, SKINCOLOR_GREY,      15, 0,           true}, // SKINCOLOR_AETHER
+	{"Aether",    {0x00, 0x00, 0x01, 0x01, 0x90, 0x90, 0x91, 0x91, 0x92, 0xaa, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xae}, SKINCOLOR_GREY,      15, 0,           true}, // SKINCOLOR_AETHER
 	{"Slate",     {0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0xaa, 0xaa, 0xaa, 0xab, 0xac, 0xac, 0xad, 0xad, 0xae, 0xaf}, SKINCOLOR_SILVER,    12, 0,           true}, // SKINCOLOR_SLATE
-	{"Meteorite", {   0,    4,    8,    9,   11,   12,   14,   15,  171,  172,  173,  174,  175,   27,   29,   31}, SKINCOLOR_TOPAZ,     15, V_GRAYMAP,   true}, // SKINCOLOR_METEORITE
-	{"Mercury",   {   0,    3,    4,    7,   11,   12,   14,   15,  171,  172,  173,  155,  157,  159,  253,  254}, SKINCOLOR_ECRU,      15, V_AZUREMAP,  true}, // SKINCOLOR_MERCURY
+	{"Moonstone", {   0,    4,    8,    9,   11,   12,   14,   15,  171,  172,  173,  174,  175,   27,   29,   31}, SKINCOLOR_TOPAZ,     15, V_GRAYMAP,   true}, // SKINCOLOR_MOONSTONE
 	{"Bluebell",  {0x90, 0x91, 0x92, 0x93, 0x94, 0x94, 0x95, 0xac, 0xac, 0xad, 0xad, 0xa8, 0xa8, 0xa9, 0xfd, 0xfe}, SKINCOLOR_COPPER,    4,  V_BLUEMAP,   true}, // SKINCOLOR_BLUEBELL
 	{"Pink",      {0xd0, 0xd0, 0xd1, 0xd1, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0x2b, 0x2c, 0x2e}, SKINCOLOR_AZURE,     9,  V_REDMAP,    true}, // SKINCOLOR_PINK
 	{"Rosewood",  { 209,  210,  211,  212,  213,  214,  228,  230,  232,  234,  235,  237,   26,   27,   28,   29}, SKINCOLOR_SEPIA,     5,  V_BROWNMAP,  true}, // SKINCOLOR_ROSEWOOD
@@ -21597,37 +21596,37 @@ skincolor_t skincolors[MAXSKINCOLORS] = {
 	{"Boulder",   {0xde, 0xe0, 0xe1, 0xe4, 0xe7, 0xe9, 0xeb, 0xec, 0xed, 0xed, 0xed, 0x19, 0x19, 0x1b, 0x1d, 0x1e}, SKINCOLOR_KETCHUP,   0,  V_BROWNMAP,  true}, // SKINCOLOR_BOULDER
 	{"Bronze",    {  82,   84,   50,   51,  223,  228,  230,  232,  234,  236,  237,  238,  239,  239,   30,   31}, SKINCOLOR_VOLCANIC,  9,  V_BROWNMAP,  true}, // SKINCOLOR_BRONZE
 	{"Sepia",     {  88,   84,   85,   86,  224,  226,  228,  230,  232,  235,  236,  237,  238,  239,   28,   28}, SKINCOLOR_ROSEWOOD,  5,  V_BROWNMAP,  true}, // SKINCOLOR_SEPIA
-	{"Ecru",      {  80,   83,   84,   85,   86,  242,  243,  245,  230,  232,  234,  236,  238,  239,   47,   47}, SKINCOLOR_MERCURY,   15, V_BROWNMAP,  true}, // SKINCOLOR_ECRU
+	{"Ecru",      {  80,   83,   84,   85,   86,  242,  243,  245,  230,  232,  234,  236,  238,  239,   47,   47}, SKINCOLOR_ARCTIC,    12, V_BROWNMAP,  true}, // SKINCOLOR_ECRU
 	{"Tan",       {0x51, 0x51, 0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0xf5, 0xf5, 0xf9, 0xf9, 0xed, 0xed}, SKINCOLOR_BROWN,     12, V_BROWNMAP,  true}, // SKINCOLOR_TAN
 	{"Beige",     {0x54, 0x55, 0x56, 0x56, 0xf2, 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xed, 0xed}, SKINCOLOR_MOSS,      5,  V_BROWNMAP,  true}, // SKINCOLOR_BEIGE
 	{"Rosebush",  { 208,  216,  209,   85,   90,   91,   91,   92,  191,   93,   94,  107,  109,  110,  111,  111}, SKINCOLOR_EGGPLANT,  5,  V_GREENMAP,  true}, // SKINCOLOR_ROSEBUSH
 	{"Moss",      {0x58, 0x58, 0x59, 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5c, 0x5d, 0x5d, 0x5e, 0x5e, 0x5f, 0x5f}, SKINCOLOR_BEIGE,     13, V_GREENMAP,  true}, // SKINCOLOR_MOSS
 	{"Azure",     {0x90, 0x90, 0x91, 0x91, 0xaa, 0xaa, 0xab, 0xab, 0xab, 0xac, 0xad, 0xad, 0xae, 0xae, 0xaf, 0xaf}, SKINCOLOR_PINK,      5,  V_AZUREMAP,  true}, // SKINCOLOR_AZURE
 	{"Eggplant",  {   4,   8,    11,   11,   16,  195,  195,  195,  196,  186,  187,  187,  254,  254,   30,   31}, SKINCOLOR_ROSEBUSH,  5,  V_PURPLEMAP, true}, // SKINCOLOR_EGGPLANT
-	{"Lavender",  {0xc0, 0xc0, 0xc1, 0xc1, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xc7, 0xc7}, SKINCOLOR_HEADLIGHT, 8,  V_PURPLEMAP, true}, // SKINCOLOR_LAVENDER
+	{"Lavender",  {0xc0, 0xc0, 0xc1, 0xc1, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xc7, 0xc7}, SKINCOLOR_GOLD,      4,  V_PURPLEMAP, true}, // SKINCOLOR_LAVENDER
 
 	// Viv's vivid colours (toast 21/07/17)
-	// Tweaks & additions (Lach, sphere, Alice, MotorRoach 26/10/22)
+	// Tweaks & additions (Lach, Chrispy, sphere, Alice, MotorRoach & Saneko 26/10/22)
 	{"Ruby",       {0xb0, 0xb0, 0xc9, 0xca, 0xcc, 0x26, 0x27, 0x28, 0x29, 0x2a, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xfd}, SKINCOLOR_EMERALD,    10, V_REDMAP,     true}, // SKINCOLOR_RUBY
 	{"Cherry",     { 202,  203,  204,  205,  206,   40,   41,   42,   43,   44,  186,  187,   28,   29,   30,   31}, SKINCOLOR_MIDNIGHT,   10, V_REDMAP,     true}, // SKINCOLOR_CHERRY
 	{"Salmon",     {0xd0, 0xd0, 0xd1, 0xd2, 0x20, 0x21, 0x24, 0x25, 0x26, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e}, SKINCOLOR_FOREST,     6,  V_REDMAP,     true}, // SKINCOLOR_SALMON
-	{"Pepper",     { 210,   32,   33,   34,   35,   35,   36,   37,   38,   39,   41,   43,   45,   45,   46,   47}, SKINCOLOR_GREEN,      10, V_REDMAP,     true}, // SKINCOLOR_PEPPER
-	{"Red",        {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x47, 0x2e, 0x2f}, SKINCOLOR_MASTER,     8,  V_REDMAP,     true}, // SKINCOLOR_RED
+	{"Pepper",     { 210,   32,   33,   34,   35,   35,   36,   37,   38,   39,   41,   43,   45,   45,   46,   47}, SKINCOLOR_MASTER,     8,  V_REDMAP,     true}, // SKINCOLOR_PEPPER
+	{"Red",        {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x47, 0x2e, 0x2f}, SKINCOLOR_GREEN,      10, V_REDMAP,     true}, // SKINCOLOR_RED
 	{"Crimson",    {0x27, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x1f}, SKINCOLOR_ICY,        10, V_REDMAP,     true}, // SKINCOLOR_CRIMSON
 	{"Flame",      {0x31, 0x32, 0x33, 0x36, 0x22, 0x22, 0x25, 0x25, 0x25, 0xcd, 0xcf, 0xcf, 0xc5, 0xc5, 0xc7, 0xc7}, SKINCOLOR_PURPLE,     8,  V_REDMAP,     true}, // SKINCOLOR_FLAME
 	{"Garnet",     {   0,   83,   50,   53,   34,   35,   37,   38,   39,   40,   42,   44,   45,   46,   47,   47}, SKINCOLOR_AQUAMARINE, 6,  V_REDMAP,     true}, // SKINCOLOR_GARNET
 	{"Ketchup",    {0x48, 0x49, 0x40, 0x33, 0x34, 0x36, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2b, 0x2c, 0x47, 0x2e, 0x2f}, SKINCOLOR_BOULDER,    8,  V_REDMAP,     true}, // SKINCOLOR_KETCHUP
 	{"Peachy",     {0xd0, 0x30, 0x31, 0x31, 0x32, 0x32, 0xdc, 0xdc, 0xdc, 0xd3, 0xd4, 0xd4, 0xcc, 0xcd, 0xce, 0xcf}, SKINCOLOR_TEAL,       7,  V_ROSYMAP,    true}, // SKINCOLOR_PEACHY
 	{"Quail",      {0xd8, 0xd9, 0xdb, 0xdc, 0xde, 0xdf, 0xd5, 0xd5, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0x1d, 0x1f}, SKINCOLOR_WAVE,       5,  V_BROWNMAP,   true}, // SKINCOLOR_QUAIL
-	{"Foundation", {  80,   81,   82,   84,  219,  221,  221,  212,  213,  214,  215,  195,  196,  186,  187,   30}, SKINCOLOR_DREAM,      6,  V_ORANGEMAP,  true}, // SKINCOLOR_FOUNDATION
+	{"Foundation", {  80,   81,   82,   84,  219,  221,  221,  212,  213,  214,  215,  197,  186,  187,  187,   30}, SKINCOLOR_DREAM,      6,  V_ORANGEMAP,  true}, // SKINCOLOR_FOUNDATION
 	{"Sunset",     {0x51, 0x52, 0x40, 0x40, 0x34, 0x36, 0xd5, 0xd5, 0xd6, 0xd7, 0xcf, 0xcf, 0xc6, 0xc6, 0xc7, 0xfe}, SKINCOLOR_SAPPHIRE,   5,  V_ORANGEMAP,  true}, // SKINCOLOR_SUNSET
 	{"Copper",     {0x58, 0x54, 0x40, 0x34, 0x35, 0x38, 0x3a, 0x3c, 0x3d, 0x2a, 0x2b, 0x2c, 0x2c, 0xba, 0xba, 0xbb}, SKINCOLOR_BLUEBELL,   5,  V_ORANGEMAP,  true}, // SKINCOLOR_COPPER
 	{"Apricot",    {0x00, 0xd8, 0xd9, 0xda, 0xdb, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e}, SKINCOLOR_CYAN,       4,  V_ORANGEMAP,  true}, // SKINCOLOR_APRICOT
 	{"Orange",     {  49,   50,   51,   52,   53,   54,   55,   57,   58,   59,   60,   42,   44,   45,   46,   46}, SKINCOLOR_BLUE,       4,  V_ORANGEMAP,  true}, // SKINCOLOR_ORANGE
-	{"Pumpkin",    {  51,   52,   53,   54,   56,   58,   59,   59,   61,   61,   63,   45,   46,   47,   47,   31}, SKINCOLOR_ARCTIC,     12, V_ORANGEMAP,  true}, // SKINCOLOR_PUMPKIN
 	{"Rust",       {0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3f, 0x2c, 0x2d, 0x47, 0x2e, 0x2f, 0x2f}, SKINCOLOR_YOGURT,     8,  V_ORANGEMAP,  true}, // SKINCOLOR_RUST
-	{"Gold",       {0x51, 0x51, 0x54, 0x54, 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x3f, 0x2d, 0x2e, 0x2f, 0x2f}, SKINCOLOR_MAUVE,      8,  V_YELLOWMAP,  true}, // SKINCOLOR_GOLD
-	{"Topaz",      {   0,   81,   83,   73,   74,   74,   65,   52,   53,   54,   56,   58,   60,   42,   43,   45}, SKINCOLOR_METEORITE,  10, V_YELLOWMAP,  true}, // SKINCOLOR_TOPAZ
+	{"Tangerine",  {  81,   83,   64,   64,   51,   52,   53,   54,   56,   58,   60,   61,   63,   45,   46,   47}, SKINCOLOR_OCEAN,      12, V_ORANGEMAP,  true}, // SKINCOLOR_TANGERINE
+	{"Topaz",      {   0,   81,   83,   73,   74,   74,   65,   52,   53,   54,   56,   58,   60,   42,   43,   45}, SKINCOLOR_MOONSTONE,  10, V_YELLOWMAP,  true}, // SKINCOLOR_TOPAZ
+	{"Gold",       {0x51, 0x51, 0x54, 0x54, 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x3f, 0x2d, 0x2e, 0x2f, 0x2f}, SKINCOLOR_LAVENDER,   10, V_YELLOWMAP,  true}, // SKINCOLOR_GOLD
 	{"Sandy",      {0x53, 0x40, 0x41, 0x42, 0x43, 0xe6, 0xe9, 0xe9, 0xea, 0xec, 0xec, 0xc6, 0xc6, 0xc7, 0xc7, 0xfe}, SKINCOLOR_SKY,        8,  V_YELLOWMAP,  true}, // SKINCOLOR_SANDY
 	{"Goldenrod",  {   0,   80,   81,   81,   83,   73,   73,   64,   65,   66,   67,   68,   69,   62,   44,   45}, SKINCOLOR_MAJESTY,    8,  V_YELLOWMAP,  true}, // SKINCOLOR_GOLDENROD
 	{"Yellow",     {0x52, 0x53, 0x49, 0x49, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4d, 0x4d, 0x4e, 0x4e, 0x4f, 0xed}, SKINCOLOR_CORNFLOWER, 8,  V_YELLOWMAP,  true}, // SKINCOLOR_YELLOW
@@ -21637,20 +21636,21 @@ skincolor_t skincolors[MAXSKINCOLORS] = {
 	{"Lime",       {0x50, 0x51, 0x52, 0x53, 0x48, 0xbc, 0xbd, 0xbe, 0xbe, 0xbf, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, SKINCOLOR_MAGENTA,    9,  V_PERIDOTMAP, true}, // SKINCOLOR_LIME
 	{"Peridot",    {0x58, 0x58, 0xbc, 0xbc, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0x5e, 0x5e, 0x5f, 0x5f, 0x77, 0x77}, SKINCOLOR_COBALT,     2,  V_PERIDOTMAP, true}, // SKINCOLOR_PERIDOT
 	{"Apple",      {0x49, 0x49, 0xbc, 0xbd, 0xbe, 0xbe, 0xbe, 0x67, 0x69, 0x6a, 0x6b, 0x6b, 0x6c, 0x6d, 0x6d, 0x6d}, SKINCOLOR_RASPBERRY,  13, V_PERIDOTMAP, true}, // SKINCOLOR_APPLE
+	{"Headlight",  {   0,   80,   81,   82,   73,   84,   64,   65,   91,   91,  124,  125,  126,  137,  138,  139}, SKINCOLOR_MAUVE,      8,  V_YELLOWMAP,  true}, // SKINCOLOR_HEADLIGHT
 	{"Chartreuse", {  80,   82,   72,   73,  188,  188,  113,  114,  114,  125,  126,  137,  138,  139,  253,  254}, SKINCOLOR_NOBLE,      9,  V_PERIDOTMAP, true}, // SKINCOLOR_CHARTREUSE
-	{"Green",      {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, SKINCOLOR_PEPPER,     8,  V_GREENMAP,   true}, // SKINCOLOR_GREEN
+	{"Green",      {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, SKINCOLOR_RED,        6,  V_GREENMAP,   true}, // SKINCOLOR_GREEN
 	{"Forest",     {0x65, 0x66, 0x67, 0x68, 0x69, 0x69, 0x6a, 0x6b, 0x6b, 0x6c, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f}, SKINCOLOR_SALMON,     9,  V_GREENMAP,   true}, // SKINCOLOR_FOREST
-	{"Shamrock",   {0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77}, SKINCOLOR_CRYSTAL,    10, V_GREENMAP,   true}, // SKINCOLOR_SHAMROCK
-	{"Jade",       { 128,  120,  121,  122,  122,  113,  114,  114,  115,  116,  117,  118,  119,  110,  111,   30}, SKINCOLOR_ROSY,       7,  V_GREENMAP,   true}, // SKINCOLOR_JADE
-	{"Headlight",  {   0,   80,   81,   82,   73,   84,   64,   65,   91,   91,  124,  125,  126,  137,  138,  139}, SKINCOLOR_LAVENDER,   10, V_YELLOWMAP,  true}, // SKINCOLOR_HEADLIGHT
+	{"Shamrock",   {0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77}, SKINCOLOR_SIBERITE,   10, V_GREENMAP,   true}, // SKINCOLOR_SHAMROCK
+	{"Jade",       { 128,  120,  121,  122,  122,  113,  114,  114,  115,  116,  117,  118,  119,  110,  111,   30}, SKINCOLOR_TAFFY,      10, V_GREENMAP,   true}, // SKINCOLOR_JADE
 	{"Mint",       {0x00, 0x00, 0x58, 0x58, 0x59, 0x62, 0x62, 0x62, 0x64, 0x67, 0x7e, 0x7e, 0x8f, 0x8f, 0x8a, 0x8a}, SKINCOLOR_VIOLET,     5,  V_GREENMAP,   true}, // SKINCOLOR_MINT
-	{"Master",     {   0,   80,   88,   96,  112,  113,   99,  100,  124,  125,  126,  117,  107,  118,  119,  111}, SKINCOLOR_RED,        6,  V_GREENMAP,   true}, // SKINCOLOR_MASTER
+	{"Master",     {   0,   80,   88,   96,  112,  113,   99,  100,  124,  125,  126,  117,  107,  118,  119,  111}, SKINCOLOR_PEPPER,     8,  V_GREENMAP,   true}, // SKINCOLOR_MASTER
 	{"Emerald",    {  80,   96,  112,  113,  114,  114,  125,  125,  126,  126,  137,  137,  138,  138,  139,  139}, SKINCOLOR_RUBY,       9,  V_GREENMAP,   true}, // SKINCOLOR_EMERALD
-	{"Bottle",     {   0,    1,    3,    4,    5,  140,  141,  141,  124,  125,  126,  127,  118,  119,  111,  111}, SKINCOLOR_LATTE,      14, V_AQUAMAP,    true}, // SKINCOLOR_BOTTLE
 	{"Seafoam",    {0x01, 0x58, 0x59, 0x5a, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0x8f, 0x8f, 0x8a, 0x8a, 0x8b, 0xfd, 0xfd}, SKINCOLOR_PLUM,       6,  V_AQUAMAP,    true}, // SKINCOLOR_SEAFOAM
 	{"Island",     {  96,   97,  113,  113,  114,  124,  142,  136,  136,  150,  151,  153,  168,  168,  169,  169}, SKINCOLOR_GALAXY,     7,  V_AQUAMAP,    true}, // SKINCOLOR_ISLAND
-	{"Aqua",       {0x78, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x76, 0x77}, SKINCOLOR_TAFFY,      10, V_AQUAMAP,    true}, // SKINCOLOR_AQUA
+	{"Bottle",     {   0,    1,    3,    4,    5,  140,  141,  141,  124,  125,  126,  127,  118,  119,  111,  111}, SKINCOLOR_LATTE,      14, V_AQUAMAP,    true}, // SKINCOLOR_BOTTLE
+	{"Aqua",       {0x78, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x76, 0x77}, SKINCOLOR_ROSY,       7,  V_AQUAMAP,    true}, // SKINCOLOR_AQUA
 	{"Teal",       {0x78, 0x78, 0x8c, 0x8c, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x8a, 0x8a, 0x8a, 0x8a}, SKINCOLOR_PEACHY,     7,  V_SKYMAP,     true}, // SKINCOLOR_TEAL
+	{"Ocean",      { 120,  121,  122,  122,  123,  141,  142,  142,  136,  137,  138,  138,  139,  139,  253,  253}, SKINCOLOR_TANGERINE,  4,  V_AQUAMAP,    true}, // SKINCOLOR_OCEAN
 	{"Wave",       {0x00, 0x78, 0x78, 0x79, 0x8d, 0x87, 0x88, 0x89, 0x89, 0xae, 0xa8, 0xa8, 0xa9, 0xa9, 0xfd, 0xfd}, SKINCOLOR_QUAIL,      5,  V_SKYMAP,     true}, // SKINCOLOR_WAVE
 	{"Cyan",       {0x80, 0x81, 0xff, 0xff, 0x83, 0x83, 0x8d, 0x8d, 0x8d, 0x8e, 0x7e, 0x7f, 0x76, 0x76, 0x77, 0x6e}, SKINCOLOR_APRICOT,    6,  V_SKYMAP,     true}, // SKINCOLOR_CYAN
 	{"Turquoise",  {  0,   120,  121,  122,  123,  141,  141,  135,  136,  136,  150,  153,  155,  157,  159,  253}, SKINCOLOR_SANGRIA,    12, V_SKYMAP,     true}, // SKINCOLOR_TURQUOISE
@@ -21661,12 +21661,12 @@ skincolor_t skincolors[MAXSKINCOLORS] = {
 	{"Dream",      {  80,  208,  200,  200,  146,  146,  133,  134,  135,  136,  137,  138,  139,  139,  254,  254}, SKINCOLOR_FOUNDATION, 9,  V_SKYMAP,     true}, // SKINCOLOR_DREAM
 	{"Icy",        {0x00, 0x00, 0x00, 0x00, 0x80, 0x81, 0x83, 0x83, 0x86, 0x87, 0x95, 0x95, 0xad, 0xad, 0xae, 0xaf}, SKINCOLOR_CRIMSON,    0,  V_SKYMAP,     true}, // SKINCOLOR_ICY
 	{"Daybreak",   {  80,   81,   82,   72,   64,    9,   11,  171,  149,  150,  151,  153,  156,  157,  159,  253}, SKINCOLOR_EVENTIDE,   12, V_BLUEMAP,    true}, // SKINCOLOR_DAYBREAK
-	{"Sapphire",   {0x80, 0x82, 0x86, 0x87, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, SKINCOLOR_SUNSET,     5,  V_SKYMAP,     true}, // SKINCOLOR_SAPPHIRE
-	{"Arctic",     {   0,    1,    3,    4,  146,  146,  147,  148,  148,  149,  150,  153,  156,  159,  253,  254}, SKINCOLOR_PUMPKIN,    6,  V_BLUEMAP,    true}, // SKINCOLOR_ARCTIC
+	{"Sapphire",   {0x80, 0x82, 0x86, 0x87, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, SKINCOLOR_SUNSET,     5,  V_BLUEMAP,    true}, // SKINCOLOR_SAPPHIRE
+	{"Arctic",     {   0,    1,    3,    4,  145,  146,  147,  148,  148,  149,  150,  153,  156,  159,  253,  254}, SKINCOLOR_ECRU,       15, V_BLUEMAP,    true}, // SKINCOLOR_ARCTIC
 	{"Cornflower", {0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x9a, 0x9c, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e}, SKINCOLOR_YELLOW,     4,  V_BLUEMAP,    true}, // SKINCOLOR_CORNFLOWER
 	{"Blue",       {0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, SKINCOLOR_ORANGE,     5,  V_BLUEMAP,    true}, // SKINCOLOR_BLUE
 	{"Cobalt",     { 145,  147,  149,  150,  151,  153,  154,  155,  156,  157,  158,  159,  253,  253,  254,  254}, SKINCOLOR_PERIDOT,    5,  V_BLUEMAP,    true}, // SKINCOLOR_COBALT
-	{"Midnight",   { 171,  171,  172,  173,  173,  174,  155,  156,  157,  159,  253,  253,  254,  254,   31,   31}, SKINCOLOR_CHERRY,     10, V_GRAYMAP,    true}, // SKINCOLOR_MIDNIGHT
+	{"Midnight",   { 171,  171,  172,  173,  173,  174,  175,  157,  158,  159,  253,  253,  254,  254,   31,   31}, SKINCOLOR_CHERRY,     10, V_GRAYMAP,    true}, // SKINCOLOR_MIDNIGHT
 	{"Galaxy",     { 160,  161,  162,  163,  164,  165,  166,  166,  154,  155,  156,  157,  159,  253,  254,   31}, SKINCOLOR_ISLAND,     7,  V_PURPLEMAP,  true}, // SKINCOLOR_GALAXY
 	{"Vapor",      {0x80, 0x81, 0x83, 0x86, 0x94, 0x94, 0xa3, 0xa3, 0xa4, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa9, 0xa9}, SKINCOLOR_LILAC,      4,  V_SKYMAP,     true}, // SKINCOLOR_VAPOR
 	{"Dusk",       {0x92, 0x93, 0x94, 0x94, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xaf, 0xaf, 0xa9, 0xa9, 0xfd, 0xfd}, SKINCOLOR_OLIVE,      0,  V_BLUEMAP,    true}, // SKINCOLOR_DUSK
@@ -21676,21 +21676,21 @@ skincolor_t skincolors[MAXSKINCOLORS] = {
 	{"Noble",      { 144,  146,  147,  148,  149,  164,  164,  165,  166,  185,  186,  186,  187,  187,   28,   29}, SKINCOLOR_CHARTREUSE, 12, V_PURPLEMAP,  true}, // SKINCOLOR_NOBLE
 	{"Fuchsia",    { 200,  201,  203,  204,  204,  183,  184,  184,  165,  166,  167,  168,  169,  159,  253,  254}, SKINCOLOR_LEMON,      10, V_PURPLEMAP,  true}, // SKINCOLOR_FUCHSIA
 	{"Bubblegum",  {   0,  208,  208,  176,  177,  178,  179,  180,  181,  182,  164,  166,  167,  168,  169,  253}, SKINCOLOR_PASTEL,     8,  V_MAGENTAMAP, true}, // SKINCOLOR_BUBBLEGUM
-	{"Crystal",    { 252,  177,  179,  180,  181,  181,  182,  182,  183,  164,  166,  167,  167,  168,  169,  159}, SKINCOLOR_EMERALD,    8,  V_MAGENTAMAP, true}, // SKINCOLOR_CRYSTAL
+	{"Siberite",   { 252,  177,  179,  180,  181,  181,  182,  182,  183,  164,  166,  167,  167,  168,  169,  159}, SKINCOLOR_EMERALD,    8,  V_MAGENTAMAP, true}, // SKINCOLOR_SIBERITE
 	{"Magenta",    {0xb3, 0xb3, 0xb4, 0xb5, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbb}, SKINCOLOR_LIME,       6,  V_MAGENTAMAP, true}, // SKINCOLOR_MAGENTA
 	{"Neon",       {0xb3, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbb, 0xc7, 0xc7, 0x1d, 0x1d, 0x1e}, SKINCOLOR_CERULEAN,   2,  V_MAGENTAMAP, true}, // SKINCOLOR_NEON
 	{"Violet",     {0xd0, 0xd1, 0xd2, 0xca, 0xcc, 0xb8, 0xb9, 0xb9, 0xba, 0xa8, 0xa8, 0xa9, 0xa9, 0xfd, 0xfe, 0xfe}, SKINCOLOR_MINT,       6,  V_MAGENTAMAP, true}, // SKINCOLOR_VIOLET
 	{"Royal",      { 208,  209,  192,  192,  192,  193,  193,  194,  194,  172,  173,  174,  175,  175,  139,  139}, SKINCOLOR_FANCY,      9,  V_PURPLEMAP,  true}, // SKINCOLOR_ROYAL
 	{"Lilac",      {0x00, 0xd0, 0xd1, 0xd2, 0xd3, 0xc1, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xfe, 0x1f}, SKINCOLOR_VAPOR,      4,  V_ROSYMAP,    true}, // SKINCOLOR_LILAC
-	{"Mauve",      { 176,  177,  178,  192,  193,  194,  195,  195,  196,  185,  185,  186,  186,  187,  187,  253}, SKINCOLOR_GOLD,       4,  V_PURPLEMAP,  true}, // SKINCOLOR_MAUVE
+	{"Mauve",      { 176,  177,  178,  192,  193,  194,  195,  195,  196,  185,  185,  186,  186,  187,  187,  253}, SKINCOLOR_HEADLIGHT,  8,  V_PURPLEMAP,  true}, // SKINCOLOR_MAUVE
 	{"Eventide",   {  51,   52,   53,   33,   34,  204,  183,  183,  184,  184,  166,  167,  168,  169,  253,  254}, SKINCOLOR_DAYBREAK,   13, V_MAGENTAMAP, true}, // SKINCOLOR_EVENTIDE
 	{"Plum",       {0xc8, 0xd3, 0xd5, 0xd6, 0xd7, 0xce, 0xcf, 0xb9, 0xb9, 0xba, 0xba, 0xa9, 0xa9, 0xa9, 0xfd, 0xfe}, SKINCOLOR_MINT,       7,  V_ROSYMAP,    true}, // SKINCOLOR_PLUM
 	{"Raspberry",  {0xc8, 0xc9, 0xca, 0xcb, 0xcb, 0xcc, 0xcd, 0xcd, 0xce, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xfe, 0xfe}, SKINCOLOR_APPLE,      13, V_ROSYMAP,    true}, // SKINCOLOR_RASPBERRY
-	{"Taffy",      {   1,  176,  176,  177,  178,  179,  202,  203,  204,  204,  205,  206,  207,   44,   45,   46}, SKINCOLOR_AQUA,       1,  V_ROSYMAP,    true}, // SKINCOLOR_TAFFY
-	{"Rosy",       {0xfc, 0xc8, 0xc8, 0xc9, 0xc9, 0xca, 0xca, 0xcb, 0xcb, 0xcc, 0xcc, 0xcd, 0xcd, 0xce, 0xce, 0xcf}, SKINCOLOR_JADE,       8,  V_ROSYMAP,    true}, // SKINCOLOR_ROSY
+	{"Taffy",      {   1,  176,  176,  177,  178,  179,  202,  203,  204,  204,  205,  206,  207,   44,   45,   46}, SKINCOLOR_JADE,       8,  V_ROSYMAP,    true}, // SKINCOLOR_TAFFY
+	{"Rosy",       {0xfc, 0xc8, 0xc8, 0xc9, 0xc9, 0xca, 0xca, 0xcb, 0xcb, 0xcc, 0xcc, 0xcd, 0xcd, 0xce, 0xce, 0xcf}, SKINCOLOR_AQUA,       1,  V_ROSYMAP,    true}, // SKINCOLOR_ROSY
 	{"Fancy",      {   0,  208,   49,  210,  210,  202,  202,  203,  204,  204,  205,  206,  207,  207,  186,  186}, SKINCOLOR_ROYAL,      9,  V_ROSYMAP,    true}, // SKINCOLOR_FANCY
 	{"Sangria",    { 210,   32,   33,   34,   34,  215,  215,  207,  207,  185,  186,  186,  186,  169,  169,  253}, SKINCOLOR_TURQUOISE,  12, V_ROSYMAP,    true}, // SKINCOLOR_SANGRIA
-	{"Volcanic",   {  35,   38,   41,   42,   44,   46,   46,  169,  169,  159,  253,  254,   30,   30,   31,   31}, SKINCOLOR_BRONZE,     9,  V_REDMAP,     true}, // SKINCOLOR_VOLCANIC
+	{"Volcanic",   {  54,   36,   42,   44,   45,   46,   46,   47,   28,  253,  253,  254,  254,   30,   31,   31}, SKINCOLOR_BRONZE,     9,  V_REDMAP,     true}, // SKINCOLOR_VOLCANIC
 
 	// super
 	{"Super Silver 1", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03}, SKINCOLOR_BLACK, 15, 0,         false}, // SKINCOLOR_SUPERSILVER1
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 838e2f53ba6088e599bbedc4fbd7b20bfa1edfcb..4af5066aeb8df5a75b6091c205e932c72f6f2349 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1079,7 +1079,8 @@ static int lib_pZMovement(lua_State *L)
 	if (!actor)
 		return LUA_ErrInvalid(L, "mobj_t");
 	lua_pushboolean(L, P_ZMovement(actor));
-	P_CheckPosition(actor, actor->x, actor->y);
+	if (!P_MobjWasRemoved(actor))
+		P_CheckPosition(actor, actor->x, actor->y);
 	P_SetTarget(&tmthing, ptmthing);
 	return 1;
 }
@@ -1107,7 +1108,8 @@ static int lib_pSceneryZMovement(lua_State *L)
 	if (!actor)
 		return LUA_ErrInvalid(L, "mobj_t");
 	lua_pushboolean(L, P_SceneryZMovement(actor));
-	P_CheckPosition(actor, actor->x, actor->y);
+	if (!P_MobjWasRemoved(actor))
+		P_CheckPosition(actor, actor->x, actor->y);
 	P_SetTarget(&tmthing, ptmthing);
 	return 1;
 }
@@ -2454,7 +2456,7 @@ static int lib_pFadeLight(lua_State *L)
 static int lib_pIsFlagAtBase(lua_State *L)
 {
 	mobjtype_t flag = luaL_checkinteger(L, 1);
-	NOHUD
+	//HUDSAFE
 	INLEVEL
 	if (flag >= NUMMOBJTYPES)
 		return luaL_error(L, "mobj type %d out of range (0 - %d)", flag, NUMMOBJTYPES-1);
@@ -3824,7 +3826,7 @@ static int lib_gDoReborn(lua_State *L)
 }
 
 // Another Lua function that doesn't actually exist!
-// Sets nextmapoverride & skipstats without instantly ending the level, for instances where other sources should be exiting the level, like normal signposts.
+// Sets nextmapoverride, skipstats and nextgametype without instantly ending the level, for instances where other sources should be exiting the level, like normal signposts.
 static int lib_gSetCustomExitVars(lua_State *L)
 {
 	int n = lua_gettop(L); // Num arguments
@@ -3833,18 +3835,21 @@ static int lib_gSetCustomExitVars(lua_State *L)
 
 	// LUA EXTENSION: Custom exit like support
 	// Supported:
-	//	G_SetCustomExitVars();			[reset to defaults]
-	//	G_SetCustomExitVars(int)		[nextmap override only]
-	//	G_SetCustomExitVars(nil, int)	[skipstats only]
-	//	G_SetCustomExitVars(int, int)	[both of the above]
+	//	G_SetCustomExitVars();               [reset to defaults]
+	//	G_SetCustomExitVars(int)             [nextmap override only]
+	//	G_SetCustomExitVars(nil, int)        [skipstats only]
+	//	G_SetCustomExitVars(int, int)        [both of the above]
+	//	G_SetCustomExitVars(int, int, int)   [nextmapoverride, skipstats and nextgametype]
 
 	nextmapoverride = 0;
 	skipstats = 0;
+	nextgametype = -1;
 
 	if (n >= 1)
 	{
 		nextmapoverride = (INT16)luaL_optinteger(L, 1, 0);
 		skipstats = (INT16)luaL_optinteger(L, 2, 0);
+		nextgametype = (INT16)luaL_optinteger(L, 3, -1);
 	}
 
 	return 0;
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 9e5d1e2ee03dfe94144c1eec79757fb2d3a2f064..3783b8f7b6dac036a2e169c807e8e1079f130c76 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -615,7 +615,7 @@ static int cvar_get(lua_State *L)
 		break;
 	default:
 		if (devparm)
-			return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS, field);
+			return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS ".", lua_tostring(L, 2));
 		else
 			return 0;
 	}
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index c7f67e93ac07d194a25a2ce365726219d52a3ee4..6eec91273352db4adc598a80b7e0138795589640 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -282,7 +282,6 @@ static int patch_get(lua_State *L)
 	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
 	enum patch field = Lua_optoption(L, 2, -1, patch_fields_ref);
 
-	// patches are invalidated when switching renderers
 	if (!patch) {
 		if (field == patch_valid) {
 			lua_pushboolean(L, 0);
@@ -436,7 +435,7 @@ static int camera_set(lua_State *L)
 		cam->momz = luaL_checkfixed(L, 3);
 		break;
 	default:
-		return luaL_error(L, LUA_QL("camera_t") " has no field named " LUA_QS, camera_opt[field]);
+		return luaL_error(L, LUA_QL("camera_t") " has no field named " LUA_QS ".", lua_tostring(L, 2));
 	}
 	return 0;
 }
@@ -1256,8 +1255,6 @@ static int libd_RandomKey(lua_State *L)
 	INT32 a = (INT32)luaL_checkinteger(L, 1);
 
 	HUDONLY
-	if (a > 65536)
-		LUA_UsageWarning(L, "v.RandomKey: range > 65536 is undefined behavior");
 	lua_pushinteger(L, M_RandomKey(a));
 	return 1;
 }
@@ -1268,13 +1265,6 @@ static int libd_RandomRange(lua_State *L)
 	INT32 b = (INT32)luaL_checkinteger(L, 2);
 
 	HUDONLY
-	if (b < a) {
-		INT32 c = a;
-		a = b;
-		b = c;
-	}
-	if ((b-a+1) > 65536)
-		LUA_UsageWarning(L, "v.RandomRange: range > 65536 is undefined behavior");
 	lua_pushinteger(L, M_RandomRange(a, b));
 	return 1;
 }
@@ -1496,7 +1486,6 @@ void LUA_SetHudHook(int hook, huddrawlist_h list)
 			break;
 
 		case HUD_HOOK(intermission):
-			lua_pushboolean(gL, intertype == int_spec &&
-					stagefailed);
+			lua_pushboolean(gL, stagefailed);
 	}
 }
diff --git a/src/lua_hudlib_drawlist.c b/src/lua_hudlib_drawlist.c
index 6f83094ac0ee1d256947c9e78090489de0387937..c518ba52540ff874480d7cda2c8314a31987e993 100644
--- a/src/lua_hudlib_drawlist.c
+++ b/src/lua_hudlib_drawlist.c
@@ -177,9 +177,18 @@ static const char *CopyString(huddrawlist_h list, const char* str)
 	lenstr = strlen(str);
 	if (list->strbuf_capacity <= list->strbuf_len + lenstr + 1)
 	{
+		const char *old_offset = list->strbuf;
+		size_t i;
 		if (list->strbuf_capacity == 0) list->strbuf_capacity = 256;
 		else list->strbuf_capacity *= 2;
 		list->strbuf = (char*) Z_Realloc(list->strbuf, sizeof(char) * list->strbuf_capacity, PU_STATIC, NULL);
+
+		// align the string pointers to make sure old pointers don't point towards invalid addresses
+		// this is necessary since Z_ReallocAlign might actually move the string buffer in memory
+		for (i = 0; i < list->items_len; i++)
+		{
+			list->items[i].str += list->strbuf - old_offset;
+		}
 	}
 	const char *result = (const char *) &list->strbuf[list->strbuf_len];
 	strncpy(&list->strbuf[list->strbuf_len], str, lenstr + 1);
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 10baa3c6727f4840234c50c162727838e9286c40..3764acf6a40945489506ce8d94b1b976fa771752 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -508,8 +508,6 @@ static int pivotlist_get(lua_State *L)
 	const char *field = luaL_checkstring(L, 2);
 	UINT8 frame;
 
-	I_Assert(framepivot != NULL);
-
 	frame = R_Char2Frame(field[0]);
 	if (frame == 255)
 		luaL_error(L, "invalid frame %s", field);
@@ -539,8 +537,6 @@ static int pivotlist_set(lua_State *L)
 	if (hook_cmd_running)
 		return luaL_error(L, "Do not alter spriteframepivot_t in CMD building code!");
 
-	I_Assert(pivotlist != NULL);
-
 	frame = R_Char2Frame(field[0]);
 	if (frame == 255)
 		luaL_error(L, "invalid frame %s", field);
@@ -1750,7 +1746,7 @@ static int lib_setSkinColor(lua_State *L)
 		else if (i == 6 || (str && fastcmp(str,"accessible"))) {
 			boolean v = lua_toboolean(L, 3);
 			if (cnum < FIRSTSUPERCOLOR && v != skincolors[cnum].accessible)
-				return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
+				CONS_Alert(CONS_WARNING, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
 			else
 				info->accessible = v;
 		}
@@ -1845,7 +1841,7 @@ static int skincolor_set(lua_State *L)
 	else if (fastcmp(field,"accessible")) {
 		boolean v = lua_toboolean(L, 3);
 		if (cnum < FIRSTSUPERCOLOR && v != skincolors[cnum].accessible)
-			return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
+			CONS_Alert(CONS_WARNING, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
 		else
 			info->accessible = v;
 	} else
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 3d95cdb751f8ae349ed026591ede5cd55438b189..e343979939056ec3c18b482a3bfd3423c446d70e 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -800,8 +800,9 @@ static int sector_set(lua_State *L)
 	case sector_fslope: // f_slope
 	case sector_cslope: // c_slope
 	case sector_friction: // friction
-	default:
 		return luaL_error(L, "sector_t field " LUA_QS " cannot be set.", sector_opt[field]);
+	default:
+		return luaL_error(L, "sector_t has no field named " LUA_QS ".", lua_tostring(L, 2));
 	case sector_floorheight: { // floorheight
 		boolean flag;
 		mobj_t *ptmthing = tmthing;
@@ -1279,8 +1280,9 @@ static int side_set(lua_State *L)
 	case side_sector:
 	case side_special:
 	case side_text:
-	default:
 		return luaL_error(L, "side_t field " LUA_QS " cannot be set.", side_opt[field]);
+	default:
+		return luaL_error(L, "side_t has no field named " LUA_QS ".", lua_tostring(L, 2));
 	case side_textureoffset:
 		side->textureoffset = luaL_checkfixed(L, 3);
 		break;
@@ -2291,8 +2293,9 @@ static int ffloor_set(lua_State *L)
 	case ffloor_target: // target
 	case ffloor_next: // next
 	case ffloor_prev: // prev
-	default:
 		return luaL_error(L, "ffloor_t field " LUA_QS " cannot be set.", ffloor_opt[field]);
+	default:
+		return luaL_error(L, "ffloor_t has no field named " LUA_QS ".", lua_tostring(L, 2));
 	case ffloor_topheight: { // topheight
 		boolean flag;
 		fixed_t lastpos = *ffloor->topheight;
@@ -2426,8 +2429,9 @@ static int slope_set(lua_State *L)
 	case slope_d: // d
 	case slope_flags: // flags
 	case slope_normal: // normal
-	default:
 		return luaL_error(L, "pslope_t field " LUA_QS " cannot be set.", slope_opt[field]);
+	default:
+		return luaL_error(L, "pslope_t has no field named " LUA_QS ".", lua_tostring(L, 2));
 	case slope_o: { // o
 		luaL_checktype(L, 3, LUA_TTABLE);
 
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index fd8dcec92599b81c8e0cefbc0f5a095181a64d84..fddf958beb783e27671cb966c3afbc3e20b0c620 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -925,6 +925,8 @@ enum mapthing_e {
 	mapthing_type,
 	mapthing_options,
 	mapthing_scale,
+	mapthing_spritexscale,
+	mapthing_spriteyscale,
 	mapthing_z,
 	mapthing_extrainfo,
 	mapthing_tag,
@@ -944,6 +946,8 @@ const char *const mapthing_opt[] = {
 	"type",
 	"options",
 	"scale",
+	"spritexscale",
+	"spriteyscale",
 	"z",
 	"extrainfo",
 	"tag",
@@ -999,7 +1003,13 @@ static int mapthing_get(lua_State *L)
 			lua_pushinteger(L, mt->options);
 			break;
 		case mapthing_scale:
-			lua_pushinteger(L, mt->scale);
+			lua_pushfixed(L, mt->scale);
+			break;
+		case mapthing_spritexscale:
+			lua_pushfixed(L, mt->spritexscale);
+			break;
+		case mapthing_spriteyscale:
+			lua_pushfixed(L, mt->spriteyscale);
 			break;
 		case mapthing_z:
 			lua_pushinteger(L, mt->z);
@@ -1072,6 +1082,12 @@ static int mapthing_set(lua_State *L)
 		case mapthing_scale:
 			mt->scale = luaL_checkfixed(L, 3);
 			break;
+		case mapthing_spritexscale:
+			mt->spritexscale = luaL_checkfixed(L, 3);
+			break;
+		case mapthing_spriteyscale:
+			mt->spriteyscale = luaL_checkfixed(L, 3);
+			break;
 		case mapthing_z:
 			mt->z = (INT16)luaL_checkinteger(L, 3);
 			break;
diff --git a/src/lua_script.c b/src/lua_script.c
index a8263ea5fe040272530bae660c1c3bee882164d9..6a5982006379d3e32e9694009e73fe67111a7f40 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -225,6 +225,18 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	} else if (fastcmp(word,"pointlimit")) {
 		lua_pushinteger(L, cv_pointlimit.value);
 		return 1;
+	} else if (fastcmp(word, "redflag")) {
+		LUA_PushUserdata(L, redflag, META_MOBJ);
+		return 1;
+	} else if (fastcmp(word, "blueflag")) {
+		LUA_PushUserdata(L, blueflag, META_MOBJ);
+		return 1;
+	} else if (fastcmp(word, "rflagpoint")) {
+		LUA_PushUserdata(L, rflagpoint, META_MAPTHING);
+		return 1;
+	} else if (fastcmp(word, "bflagpoint")) {
+		LUA_PushUserdata(L, bflagpoint, META_MAPTHING);
+		return 1;
 	// begin map vars
 	} else if (fastcmp(word,"spstage_start")) {
 		lua_pushinteger(L, spstage_start);
@@ -977,6 +989,7 @@ enum
 	ARCH_MAPHEADER,
 	ARCH_SKINCOLOR,
 	ARCH_MOUSE,
+	ARCH_SKIN,
 
 	ARCH_TEND=0xFF,
 };
@@ -1005,6 +1018,7 @@ static const struct {
 	{META_MAPHEADER,   ARCH_MAPHEADER},
 	{META_SKINCOLOR,   ARCH_SKINCOLOR},
 	{META_MOUSE,    ARCH_MOUSE},
+	{META_SKIN,     ARCH_SKIN},
 	{NULL,          ARCH_NULL}
 };
 
@@ -1326,6 +1340,13 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			WRITEUINT8(save_p, m == &mouse ? 1 : 2);
 			break;
 		}
+		case ARCH_SKIN:
+		{
+			skin_t *skin = *((skin_t **)lua_touserdata(gL, myindex));
+			WRITEUINT8(save_p, ARCH_SKIN);
+			WRITEUINT8(save_p, skin - skins); // UINT8 because MAXSKINS is only 32
+			break;
+		}
 		default:
 			WRITEUINT8(save_p, ARCH_NULL);
 			return 2;
@@ -1572,6 +1593,9 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 	case ARCH_MOUSE:
 		LUA_PushUserdata(gL, READUINT16(save_p) == 1 ? &mouse : &mouse2, META_MOUSE);
 		break;
+	case ARCH_SKIN:
+		LUA_PushUserdata(gL, &skins[READUINT8(save_p)], META_SKIN);
+		break;
 	case ARCH_TEND:
 		return 1;
 	}
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index 13e0dd98715462fa9c63696aa87c307957162d4e..041c5d59851f3669b03d45c899c20b3440e193f0 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -25,6 +25,7 @@ enum skin {
 	skin_flags,
 	skin_realname,
 	skin_hudname,
+	skin_supername,
 	skin_ability,
 	skin_ability2,
 	skin_thokitem,
@@ -63,6 +64,7 @@ static const char *const skin_opt[] = {
 	"flags",
 	"realname",
 	"hudname",
+	"supername",
 	"ability",
 	"ability2",
 	"thokitem",
@@ -126,6 +128,9 @@ static int skin_get(lua_State *L)
 	case skin_hudname:
 		lua_pushstring(L, skin->hudname);
 		break;
+	case skin_supername:
+		lua_pushstring(L, skin->supername);
+		break;
 	case skin_ability:
 		lua_pushinteger(L, skin->ability);
 		break;
diff --git a/src/m_cheat.c b/src/m_cheat.c
index e370335f8332d65fd7f5824270f99a81d9ba7a4b..7ad86353ac86916b167d3e0768f9a8ad6f23444e 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1102,6 +1102,8 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 
 	mt->options = (mt->z << ZSHIFT) | (UINT16)cv_opflags.value;
 	mt->scale = player->mo->scale;
+	mt->spritexscale = player->mo->spritexscale;
+	mt->spriteyscale = player->mo->spriteyscale;
 	memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 	memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 	mt->pitch = mt->roll = 0;
diff --git a/src/m_cond.c b/src/m_cond.c
index 86bae4933137df3e633950038f19603d121db03b..18018f20751d209dbe839a649ae9936352c3a5a5 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -113,7 +113,7 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1
 	condition_t *cond;
 	UINT32 num, wnum;
 
-	I_Assert(set && set <= MAXCONDITIONSETS);
+	I_Assert(set < MAXCONDITIONSETS);
 
 	wnum = conditionSets[set - 1].numconditions;
 	num = ++conditionSets[set - 1].numconditions;
@@ -467,8 +467,19 @@ UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data)
 
 UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data)
 {
-	if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0
-	|| cv_debug || devparm)
+	if (dedicated)
+	{
+		// If you're in a dedicated server, every level is unlocked.
+		// Yes, technically this means you can view any level by
+		// running a dedicated server and joining it yourself, but
+		// that's better than making dedicated server's lives hell.
+		return false;
+	}
+	
+	if (cv_debug || devparm)
+		return false; // Unlock every level when in devmode.
+
+	if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0)
 	{
 		return false;
 	}
@@ -481,6 +492,48 @@ UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data)
 	return false;
 }
 
+UINT8 M_CampaignWarpIsCheat(INT32 gt, INT32 mapnum, gamedata_t *data)
+{
+	if (M_MapLocked(mapnum, data) == true)
+	{
+		// Warping to locked maps is definitely always a cheat
+		return true;
+	}
+
+	if ((gametypedefaultrules[gt] & GTR_CAMPAIGN) == 0)
+	{
+		// Not a campaign, do whatever you want.
+		return false;
+	}
+
+	if (G_IsSpecialStage(mapnum))
+	{
+		// Warping to special stages is a cheat
+		return true;
+	}
+
+	if (mapheaderinfo[mapnum-1]->menuflags & LF2_HIDEINMENU)
+	{
+		// You're never allowed to warp to this level.
+		return true;
+	}
+
+	if (mapheaderinfo[mapnum-1]->menuflags & LF2_NOVISITNEEDED)
+	{
+		// You're always allowed to warp to this level.
+		return false;
+	}
+
+	if (mapnum == spstage_start)
+	{
+		// Warping to the first level is never a cheat
+		return false;
+	}
+
+	// It's only a cheat if you've never been there.
+	return (!(data->mapvisited[mapnum-1]));
+}
+
 INT32 M_CountEmblems(gamedata_t *data)
 {
 	INT32 found = 0, i;
diff --git a/src/m_cond.h b/src/m_cond.h
index 2be8d564be46f0c6bba94a427555fc2a50c08d05..2491a384c02aa5f34ba47933eba689811ac599d0 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -252,6 +252,7 @@ void M_SilentUpdateSkinAvailabilites(void);
 UINT8 M_AnySecretUnlocked(gamedata_t *data);
 UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data);
 UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data);
+UINT8 M_CampaignWarpIsCheat(INT32 gt, INT32 mapnum, gamedata_t *data);
 INT32 M_CountEmblems(gamedata_t *data);
 
 // Emblem shit
diff --git a/src/m_fixed.c b/src/m_fixed.c
index ad283119646b75abf514360bcf64d96972e86214..b674e3b2c8e230524d57ea540dada83b8bf75eb6 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -21,50 +21,6 @@
 #include "doomdef.h"
 #include "m_fixed.h"
 
-#ifdef __USE_C_FIXEDMUL__
-
-/**	\brief	The FixedMul function
-
-	\param	a	fixed_t number
-	\param	b	fixed_t number
-
-	\return	a*b>>FRACBITS
-
-*/
-fixed_t FixedMul(fixed_t a, fixed_t b)
-{
-	// Need to cast to unsigned before shifting to avoid undefined behaviour
-	// for negative integers
-	return (fixed_t)(((UINT64)((INT64)a * b)) >> FRACBITS);
-}
-
-#endif //__USE_C_FIXEDMUL__
-
-#ifdef __USE_C_FIXEDDIV__
-/**	\brief	The FixedDiv2 function
-
-	\param	a	fixed_t number
-	\param	b	fixed_t number
-
-	\return	a/b * FRACUNIT
-
-*/
-fixed_t FixedDiv2(fixed_t a, fixed_t b)
-{
-	INT64 ret;
-
-	if (b == 0)
-		I_Error("FixedDiv: divide by zero");
-
-	ret = (((INT64)a * FRACUNIT)) / b;
-
-	if ((ret > INT32_MAX) || (ret < INT32_MIN))
-		I_Error("FixedDiv: divide by zero");
-	return (fixed_t)ret;
-}
-
-#endif // __USE_C_FIXEDDIV__
-
 fixed_t FixedSqrt(fixed_t x)
 {
 #ifdef HAVE_SQRT
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 4a5b7ce2adf863571000b2134948d0c8e4e3e86f..94bd6a16bbf86e70c4f38c86a4758424da325db1 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -53,127 +53,35 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FloatToFixed(float f)
 #define FIXED_TO_FLOAT(x) FixedToFloat(x) // (((float)(x)) / ((float)FRACUNIT))
 #define FLOAT_TO_FIXED(f) FloatToFixed(f) // (fixed_t)((f) * ((float)FRACUNIT))
 
+/**	\brief	The FixedMul function
 
-#if defined (__WATCOMC__) && FRACBITS == 16
-	#pragma aux FixedMul =  \
-		"imul ebx",         \
-		"shrd eax,edx,16"   \
-		parm    [eax] [ebx] \
-		value   [eax]       \
-		modify exact [eax edx]
-
-	#pragma aux FixedDiv2 = \
-		"cdq",              \
-		"shld edx,eax,16",  \
-		"sal eax,16",       \
-		"idiv ebx"          \
-		parm    [eax] [ebx] \
-		value   [eax]       \
-		modify exact [eax edx]
-#elif defined (__GNUC__) && defined (__i386__) && !defined (NOASM)
-	// i386 linux, cygwin or mingw
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // asm
-	{
-		fixed_t ret;
-		asm
-		(
-			 "imull %2;"           // a*b
-			 "shrdl %3,%%edx,%0;"  // shift logical right FRACBITS bits
-			:"=a" (ret)            // eax is always the result and the first operand (%0,%1)
-			:"0" (a), "r" (b)      // and %2 is what we use imull on with what in %1
-			, "I" (FRACBITS)       // %3 holds FRACBITS (normally 16)
-			:"cc", "%edx"         // edx and condition codes clobbered
-		);
-		return ret;
-	}
+	\param	a	fixed_t number
+	\param	b	fixed_t number
 
-	FUNCMATH FUNCINLINE static inline fixed_t FixedDiv2(fixed_t a, fixed_t b)
-	{
-		fixed_t ret;
-		asm
-		(
-			  "movl  %1,%%edx;"    // these two instructions allow the next two to pair, on the Pentium processor.
-			  "sarl  $31,%%edx;"   // shift arithmetic right 31 on EDX
-			  "shldl %3,%1,%%edx;" // DP shift logical left FRACBITS on EDX
-			  "sall  %3,%0;"       // shift arithmetic left FRACBITS on EAX
-			  "idivl %2;"          // EDX/b = EAX
-			: "=a" (ret)
-			: "0" (a), "r" (b)
-			, "I" (FRACBITS)
-			: "%edx"
-		);
-		return ret;
-	}
-#elif defined (__GNUC__) && defined (__arm__) && !defined(__thumb__) && !defined(NOASM) //ARMv4 ASM
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // let abuse smull
-	{
-		fixed_t ret;
-		asm
-		(
-			  "smull %[lo], r1, %[a], %[b];"
-			  "mov %[lo], %[lo], lsr %3;"
-			  "orr %[lo], %[lo], r1, lsl %3;"
-			: [lo] "=&r" (ret) // rhi, rlo and rm must be distinct registers
-			: [a] "r" (a), [b] "r" (b)
-			, "i" (FRACBITS)
-			: "r1"
-		);
-		return ret;
-	}
+	\return	a*b>>FRACBITS
 
-	#define __USE_C_FIXEDDIV__ // no double or asm div in ARM land
-#elif defined (__GNUC__) && defined (__ppc__) && !defined(NOASM) && 0 // WII: PPC CPU
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // asm
-	{
-		fixed_t ret, hi, lo;
-		asm
-		(
-			  "mullw %0, %2, %3;"
-			  "mulhw %1, %2, %3"
-			: "=r" (hi), "=r" (lo)
-			: "r" (a), "r" (b)
-			, "I" (FRACBITS)
-		);
-		ret = (INT64)((hi>>FRACBITS)+lo)<<FRACBITS;
-		return ret;
-	}
+*/
+FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedMul(fixed_t a, fixed_t b)
+{
+	// Need to cast to unsigned before shifting to avoid undefined behaviour
+	// for negative integers
+	return (fixed_t)(((UINT64)((INT64)a * b)) >> FRACBITS);
+}
 
-	#define __USE_C_FIXEDDIV__// Alam: I am lazy
-#elif defined (__GNUC__) && defined (__mips__) && !defined(NOASM) && 0 // PSP: MIPS CPU
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // asm
-	{
-		fixed_t ret;
-		asm
-		(
-			  "mult %3, %4;"    // a*b=h<32+l
-			: "=r" (ret), "=l" (a), "=h" (b) //todo: abuse shr opcode
-			: "0" (a), "r" (b)
-			, "I" (FRACBITS)
-			//: "+l", "+h"
-		);
-		ret = (INT64)((a>>FRACBITS)+b)<<FRACBITS;
-		return ret;
-	}
+/**	\brief	The FixedDiv2 function
 
-	#define __USE_C_FIXEDDIV__ // no 64b asm div in MIPS land
-#elif defined (__GNUC__) && defined (__sh__) && 0 // DC: SH4 CPU
-#elif defined (__GNUC__) && defined (__m68k__) && 0 // DEAD: Motorola 6800 CPU
-#elif defined (_MSC_VER) && defined(USEASM) && FRACBITS == 16
-	// Microsoft Visual C++ (no asm inline)
-	fixed_t __cdecl FixedMul(fixed_t a, fixed_t b);
-	fixed_t __cdecl FixedDiv2(fixed_t a, fixed_t b);
-#else
-	#define __USE_C_FIXEDMUL__
-	#define __USE_C_FIXEDDIV__
-#endif
+	\param	a	fixed_t number
+	\param	b	fixed_t number
 
-#ifdef __USE_C_FIXEDMUL__
-FUNCMATH fixed_t FixedMul(fixed_t a, fixed_t b);
-#endif
+	\return	a/b * FRACUNIT
 
-#ifdef __USE_C_FIXEDDIV__
-FUNCMATH fixed_t FixedDiv2(fixed_t a, fixed_t b);
-#endif
+*/
+FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedDiv2(fixed_t a, fixed_t b)
+{
+	// This does not check for division overflow or division by 0!
+	// That is the caller's responsibility.
+	return (fixed_t)(((INT64)a * FRACUNIT) / b);
+}
 
 /**	\brief	The FixedInt function
 
diff --git a/src/m_menu.c b/src/m_menu.c
index 7618a2c4afe3cb72efab63e51acac50a922d3f09..aa1d4b5bd2c421fa5b2ff819adb2d60570411ae2 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2649,7 +2649,7 @@ static boolean MIT_SetCurBackground(UINT32 menutype, INT32 level, INT32 *retval,
 		{
 			strncpy(curbgname, defaultname, 9);
 			curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
-			curbgyspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollyspeed;
+			curbgyspeed = (gamestate == GS_TIMEATTACK) ? 18 : titlescrollyspeed;
 		}
 	}
 	return false;
@@ -2843,8 +2843,8 @@ static void M_HandleMenuPresState(menu_t *newMenu)
 	curfadevalue = 16;
 	curhidepics = hidetitlepics;
 	curbgcolor = -1;
-	curbgxspeed = titlescrollxspeed;
-	curbgyspeed = titlescrollyspeed;
+	curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
+	curbgyspeed = (gamestate == GS_TIMEATTACK) ? 18 : titlescrollyspeed;
 	curbghide = (gamestate != GS_TIMEATTACK); // show in time attack, hide in other menus
 
 	curttmode = ttmode;
@@ -3196,7 +3196,7 @@ boolean M_Responder(event_t *ev)
 	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_GAMEEND)
 		return false;
 
-	if (gamestate == GS_TITLESCREEN && finalecount < TICRATE)
+	if (gamestate == GS_TITLESCREEN && finalecount < (cv_tutorialprompt.value ? TICRATE : 0))
 		return false;
 
 	if (CON_Ready() && gamestate != GS_WAITINGPLAYERS)
@@ -3500,10 +3500,10 @@ boolean M_Responder(event_t *ev)
 #ifndef DEVELOP
 					// TODO: Replays are scary, so I left the remaining instances of this alone.
 					// It'd be nice to get rid of this once and for all though!
-					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && (modifiedgame && !savemoddata) && !usedCheats)
+					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && usedCheats)
 					{
 						S_StartSound(NULL, sfx_skid);
-						M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+						M_StartMessage(M_GetText("This cannot be done in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
 						return true;
 					}
 #endif
@@ -7108,9 +7108,6 @@ static void M_DestroyRobots(INT32 choice)
 
 static void M_LevelSelectWarp(INT32 choice)
 {
-	boolean fromloadgame = (currentMenu == &SP_LevelSelectDef);
-	boolean frompause = (currentMenu == &SP_PauseLevelSelectDef);
-
 	(void)choice;
 
 	if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR)
@@ -7122,13 +7119,11 @@ static void M_LevelSelectWarp(INT32 choice)
 	startmap = (INT16)(cv_nextmap.value);
 	fromlevelselect = true;
 
-	if (fromloadgame)
-		G_LoadGame((UINT32)cursaveslot, startmap);
-	else
+	if (currentMenu == &SP_LevelSelectDef || currentMenu == &SP_PauseLevelSelectDef)
 	{
-		cursaveslot = 0;
-
-		if (frompause)
+		if (cursaveslot > 0) // do we have a save slot to load?
+			G_LoadGame((UINT32)cursaveslot, startmap); // reload from SP save data: this is needed to keep score/lives/continues from reverting to defaults
+		else // no save slot, start new game but keep the current skin
 		{
 			M_ClearMenus(true);
 
@@ -7139,8 +7134,11 @@ static void M_LevelSelectWarp(INT32 choice)
 				Z_Free(levelselect.rows);
 			levelselect.rows = NULL;
 		}
-		else
-			M_SetupChoosePlayer(0);
+	}
+	else // start new game
+	{
+		cursaveslot = 0;
+		M_SetupChoosePlayer(0);
 	}
 }
 
@@ -10460,13 +10458,23 @@ static void M_ChooseTimeAttack(INT32 choice)
 	G_DeferedInitNew(false, G_BuildMapName(cv_nextmap.value), (UINT8)(cv_chooseskin.value-1), false, false);
 }
 
+static char ra_demoname[1024];
+
+static void M_StartTimeAttackReplay(INT32 choice)
+{
+	if (choice == 'y' || choice == KEY_ENTER)
+	{
+		M_ClearMenus(true);
+		modeattacking = ATTACKING_RECORD; // set modeattacking before G_DoPlayDemo so the map loader knows
+		G_DoPlayDemo(ra_demoname);
+	}
+}
+
 // Player has selected the "REPLAY" from the time attack screen
 static void M_ReplayTimeAttack(INT32 choice)
 {
 	const char *which;
-	char *demoname;
-	M_ClearMenus(true);
-	modeattacking = ATTACKING_RECORD; // set modeattacking before G_DoPlayDemo so the map loader knows
+	UINT8 error = DFILE_ERROR_NONE;
 
 	if (currentMenu == &SP_ReplayDef)
 	{
@@ -10486,11 +10494,15 @@ static void M_ReplayTimeAttack(INT32 choice)
 			break;
 		case 4: // guest
 			// srb2/replay/main/map01-guest.lmp
-			G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
-			return;
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value));
+			break;
+		}
+
+		if (choice != 4)
+		{
+			// srb2/replay/main/map01-sonic-time-best.lmp
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which);
 		}
-		// srb2/replay/main/map01-sonic-time-best.lmp
-		G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which));
 	}
 	else if (currentMenu == &SP_NightsReplayDef)
 	{
@@ -10506,19 +10518,78 @@ static void M_ReplayTimeAttack(INT32 choice)
 			which = "last";
 			break;
 		case 3: // guest
-			G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
-			return;
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value));
+			break;
 		}
 
-		demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which);
+		if (choice != 3)
+		{
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which);
 
 #ifdef OLDNREPLAYNAME // Check for old style named NiGHTS replay if a new style replay doesn't exist.
-		if (!FIL_FileExists(demoname))
-			demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which);
+			if (!FIL_FileExists(ra_demoname))
+			{
+				snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which);
+			}
 #endif
+		}
+	}
+
+	demofileoverride = DFILE_OVERRIDE_NONE;
+	error = G_CheckDemoForError(ra_demoname);
+
+	if (error)
+	{
+		S_StartSound(NULL, sfx_skid);
+
+		switch (error)
+		{
+			case DFILE_ERROR_NOTDEMO:
+				M_StartMessage(M_GetText("An error occurred loading this replay.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+
+			case DFILE_ERROR_NOTLOADED:
+				demofileoverride = DFILE_OVERRIDE_LOAD;
+				M_StartMessage(M_GetText("Add-ons for this replay\nhave not been loaded.\n\nAttempt to load files?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				break;
+
+			case DFILE_ERROR_OUTOFORDER:
+				/*
+				demofileoverride = DFILE_OVERRIDE_SKIP;
+				M_StartMessage(M_GetText("Add-ons for this replay\nwere loaded out of order.\n\nAttempt to playback anyway?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("Add-ons for this replay\nwere loaded out of order.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+
+			case DFILE_ERROR_INCOMPLETEOUTOFORDER:
+				/*
+				demofileoverride = DFILE_OVERRIDE_LOAD;
+				M_StartMessage(M_GetText("Add-ons for this replay\nhave not been loaded,\nand some are in the wrong order.\n\nAttempt to load files?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("Add-ons for this replay\nhave not been loaded,\nand some are in the wrong order.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+
+			case DFILE_ERROR_CANNOTLOAD:
+				/*
+				demofileoverride = DFILE_OVERRIDE_SKIP;
+				M_StartMessage(M_GetText("Add-ons for this replay\ncould not be loaded.\n\nAttempt to playback anyway?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("Add-ons for this replay\ncould not be loaded.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
 
-		G_DoPlayDemo(demoname);
+			case DFILE_ERROR_EXTRAFILES:
+				/*
+				demofileoverride = DFILE_OVERRIDE_SKIP;
+				M_StartMessage(M_GetText("You have more files loaded\nthan the replay does.\n\nAttempt to playback anyway?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("You have more files loaded\nthan the replay does.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+		}
+
+		return;
 	}
+
+	M_StartTimeAttackReplay(KEY_ENTER);
 }
 
 static void M_EraseGuest(INT32 choice)
@@ -11994,6 +12065,136 @@ static INT32        setupm_fakeskin;
 static menucolor_t *setupm_fakecolor;
 static boolean      colorgrid;
 
+#define COLOR_GRID_ROW_SIZE (16)
+
+static UINT16 M_GetColorGridIndex(UINT16 color)
+{
+	menucolor_t *look;
+	UINT16 i = 0;
+
+	if (!skincolors[color].accessible)
+	{
+		return 0;
+	}
+
+	for (look = menucolorhead; ; i++, look = look->next)
+	{
+		while (!skincolors[look->color].accessible) // skip inaccessible colors
+		{
+			if (look == menucolortail)
+			{
+				return 0;
+			}
+
+			look = look->next;
+		}
+
+		if (look->color == color)
+		{
+			return i;
+		}
+
+		if (look == menucolortail)
+		{
+			return 0;
+		}
+	}
+}
+
+static INT32 M_GridIndexToX(UINT16 index)
+{
+	return (index % COLOR_GRID_ROW_SIZE);
+}
+
+static INT32 M_GridIndexToY(UINT16 index)
+{
+	return (index / COLOR_GRID_ROW_SIZE);
+}
+
+static UINT16 M_ColorGridLen(void)
+{
+	menucolor_t *look;
+	UINT16 i = 0;
+
+	for (look = menucolorhead; ; i++)
+	{
+		do
+		{
+			if (look == menucolortail)
+			{
+				return i;
+			}
+
+			look = look->next;
+		}
+		while (!skincolors[look->color].accessible); // skip inaccessible colors
+	}
+}
+
+static UINT16 M_GridPosToGridIndex(INT32 x, INT32 y)
+{
+	const UINT16 grid_len = M_ColorGridLen();
+	const UINT16 grid_height = ((grid_len - 1) / COLOR_GRID_ROW_SIZE) + 1;
+	const UINT16 last_row_len = COLOR_GRID_ROW_SIZE - ((grid_height * COLOR_GRID_ROW_SIZE) - grid_len);
+
+	UINT16 row_len = COLOR_GRID_ROW_SIZE;
+	UINT16 new_index = 0;
+
+	while (y < 0)
+	{
+		y += grid_height;
+	}
+	y = (y % grid_height);
+
+	if (y >= grid_height-1 && last_row_len > 0)
+	{
+		row_len = last_row_len;
+	}
+
+	while (x < 0)
+	{
+		x += row_len;
+	}
+	x = (x % row_len);
+
+	new_index = (y * COLOR_GRID_ROW_SIZE) + x;
+	if (new_index >= grid_len)
+	{
+		new_index = grid_len - 1;
+	}
+
+	return new_index;
+}
+
+static menucolor_t *M_GridIndexToMenuColor(UINT16 index)
+{
+	menucolor_t *look = menucolorhead;
+	UINT16 i = 0;
+
+	for (look = menucolorhead; ; i++, look = look->next)
+	{
+		while (!skincolors[look->color].accessible) // skip inaccessible colors
+		{
+			if (look == menucolortail)
+			{
+				return menucolorhead;
+			}
+
+			look = look->next;
+		}
+
+		if (i == index)
+		{
+			return look;
+		}
+
+		if (look == menucolortail)
+		{
+			return menucolorhead;
+		}
+	}
+}
+
 static void M_DrawSetupMultiPlayerMenu(void)
 {
 	INT32 x, y, cursory = 0, flags = 0;
@@ -12112,32 +12313,54 @@ colordraw:
 		boolean stoprow = false;
 		menucolor_t *mc; // Last accessed color
 
+		const UINT16 grid_len = M_ColorGridLen();
+		const UINT16 grid_end_y = M_GridIndexToY(grid_len - 1);
+
+		INT32 grid_select = M_GetColorGridIndex(setupm_fakecolor->color);
+		INT32 grid_select_y = M_GridIndexToY(grid_select);
+
 		x = 132;
 		y = 66;
-		pos = min(max(0, 16*((M_GetColorIndex(setupm_fakecolor->color)-1)/16) - 64), 16*(M_GetColorIndex(menucolortail->color)/16-1) - 128);
-		mc = M_GetColorFromIndex(pos);
+
+		pos = M_GridPosToGridIndex(0, max(0, min(grid_select_y - 3, grid_end_y - 7)));
+		mc = M_GridIndexToMenuColor(pos);
 
 		// Draw grid
 		V_DrawFill(x-2, y-2, 132, 132, 159);
 		for (j = 0; j < 8; j++)
 		{
-			for (i = 0; i < 16; i++)
+			for (i = 0; i < COLOR_GRID_ROW_SIZE; i++)
 			{
-				if (skincolors[mc->color].accessible && !stoprow)
+				if (skincolors[mc->color].accessible)
 				{
 					M_DrawColorRamp(x + i*w, y + j*16, w, 1, skincolors[mc->color]);
-					if (mc->color == setupm_fakecolor->color) // store current color position
-						{
-							cx = x + i*w;
-							cy = y + j*16;
-						}
+
+					if (mc == setupm_fakecolor) // store current color position
+					{
+						cx = x + i*w;
+						cy = y + j*16;
+					}
 				}
-				mc = mc->next;
-				while (!skincolors[mc->color].accessible && !stoprow) // Find accessible color after this one
+
+				if (stoprow)
 				{
-					mc = mc->next;
-					if (mc == menucolortail) stoprow = true;
+					break;
 				}
+
+				// Find accessible color after this one
+				do
+				{
+					mc = mc->next;
+					if (mc == menucolortail)
+					{
+						stoprow = true;
+					}
+				} while (!skincolors[mc->color].accessible && !stoprow);
+			}
+
+			if (stoprow)
+			{
+				break;
 			}
 		}
 
@@ -12258,15 +12481,16 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 		case KEY_DOWNARROW:
 		case KEY_UPARROW:
 			{
-				UINT8 i;
 				if (itemOn == 2 && colorgrid)
 				{
-					for (i = 0; i < 16; i++)
-					{
-						setupm_fakecolor = (choice == KEY_UPARROW) ? setupm_fakecolor->prev : setupm_fakecolor->next;
-						while (!skincolors[setupm_fakecolor->color].accessible) // skip inaccessible colors
-							setupm_fakecolor = (choice == KEY_UPARROW) ? setupm_fakecolor->prev : setupm_fakecolor->next;
-					}
+					UINT16 index = M_GetColorGridIndex(setupm_fakecolor->color);
+					INT32 x = M_GridIndexToX(index);
+					INT32 y = M_GridIndexToY(index);
+
+					y += (choice == KEY_UPARROW) ? -1 : 1;
+
+					index = M_GridPosToGridIndex(x, y);
+					setupm_fakecolor = M_GridIndexToMenuColor(index);
 				}
 				else if (choice == KEY_UPARROW)
 					M_PrevOpt();
@@ -12293,8 +12517,8 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 			}
 			else if (itemOn == 2) // player color
 			{
-				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakecolor = setupm_fakecolor->prev;
+				S_StartSound(NULL,sfx_menu1); // Tails
 			}
 			break;
 
@@ -12333,8 +12557,8 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 			}
 			else if (itemOn == 2) // player color
 			{
-				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakecolor = setupm_fakecolor->next;
+				S_StartSound(NULL,sfx_menu1); // Tails
 			}
 			break;
 
@@ -12344,13 +12568,28 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 				UINT8 i;
 				if (itemOn == 2) // player color
 				{
-					S_StartSound(NULL,sfx_menu1);
-					for (i = 0; i < (colorgrid ? 64 : 13); i++) // or (282-charw)/(2*indexwidth)
+					if (colorgrid)
 					{
-						setupm_fakecolor = (choice == KEY_PGUP) ? setupm_fakecolor->prev : setupm_fakecolor->next;
-						while (!skincolors[setupm_fakecolor->color].accessible) // skip inaccessible colors
+						UINT16 index = M_GetColorGridIndex(setupm_fakecolor->color);
+						INT32 x = M_GridIndexToX(index);
+						INT32 y = M_GridIndexToY(index);
+
+						y += (choice == KEY_UPARROW) ? -4 : 4;
+
+						index = M_GridPosToGridIndex(x, y);
+						setupm_fakecolor = M_GridIndexToMenuColor(index);
+					}
+					else
+					{
+						for (i = 0; i < 13; i++) // or (282-charw)/(2*indexwidth)
+						{
 							setupm_fakecolor = (choice == KEY_PGUP) ? setupm_fakecolor->prev : setupm_fakecolor->next;
+							while (!skincolors[setupm_fakecolor->color].accessible) // skip inaccessible colors
+								setupm_fakecolor = (choice == KEY_PGUP) ? setupm_fakecolor->prev : setupm_fakecolor->next;
+						}
 					}
+
+					S_StartSound(NULL, sfx_menu1); // Tails
 				}
 			}
 			break;
@@ -12380,7 +12619,6 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 				}
 			}
 			break;
-			break;
 
 		case KEY_DEL:
 			if (itemOn == 0 && (l = strlen(setupm_name))!=0)
diff --git a/src/m_random.c b/src/m_random.c
index 8b5138b9c86f77e6fa4a0d0867e4e62198fb2dca..536fbfbbd1077abf6ae400bd4d8773a7574e4b08 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -3,6 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
+// Copyright (C) 2022-2023 by tertu marybig.
 // Copyright (C) 1999-2023 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -14,11 +15,122 @@
 
 #include "doomdef.h"
 #include "doomtype.h"
+#include "i_system.h" // I_GetRandomBytes
 
 #include "m_random.h"
 #include "m_fixed.h"
 
-#include "m_cond.h" // totalplaytime
+// SFC32 random number generator implementation
+
+typedef struct rnstate_s {
+	UINT32 data[3];
+	UINT32 counter;
+} rnstate_t;
+
+/** Generate a raw uniform random number using a particular state.
+  *
+  * \param state The RNG state to use.
+  * \return A random UINT32.
+  */
+static inline UINT32 RandomState_Get32(rnstate_t *state) {
+	UINT32 result, b, c;
+
+	b = state->data[1];
+	c = state->data[2];
+	result = state->data[0] + b + state->counter++;
+
+	state->data[0] = b ^ (b >> 9);
+	state->data[1] = c * 9;
+	state->data[2] = ((c << 21) | (c >> 11)) + result;
+
+	return result;
+}
+
+/** Seed an SFC32 RNG state with up to 96 bits of seed data.
+  *
+  * \param state The RNG state to seed.
+  * \param seeds A pointer to up to 3 UINT32s to use as seed data.
+  * \param seed_count The number of seed words.
+  */
+static inline void RandomState_Seed(rnstate_t *state, UINT32 *seeds, size_t seed_count)
+{
+	size_t i;
+
+	state->counter = 1;
+
+	for(i = 0; i < 3; i++)
+	{
+		UINT32 seed_word;
+
+		if(i < seed_count)
+			seed_word = seeds[i];
+		else
+			seed_word = 0;
+
+		// For SFC32, seed data should be stored in the state in reverse order.
+		state->data[2-i] = seed_word;
+	}
+
+	for(i = 0; i < 16; i++)
+		RandomState_Get32(state);
+}
+
+/** Gets a uniform number in the range [0, limit).
+  * Technique is based on a combination of scaling and rejection sampling
+  * and is adapted from Daniel Lemire.
+  *
+  * \note Any UINT32 is a valid argument for limit.
+  *
+  * \param state The RNG state to use.
+  * \param limit The upper limit of the range.
+  * \return A UINT32 in the range [0, limit).
+  */
+static inline UINT32 RandomState_GetKey32(rnstate_t *state, const UINT32 limit)
+{
+	UINT32 raw_random, scaled_lower_word;
+	UINT64 scaled_random;
+
+	// This algorithm won't work correctly if passed a 0.
+	if (limit == 0) return 0;
+
+	raw_random = RandomState_Get32(state);
+	scaled_random = (UINT64)raw_random * (UINT64)limit;
+
+	/*The high bits of scaled_random now contain the number we want, but it is
+	possible, depending on the number we generated and the value of limit,
+	that there is bias in the result. The rest of this code is for ensuring
+	that does not happen.
+	*/
+	scaled_lower_word = (UINT32)scaled_random;
+
+	// If we're lucky, we can bail out now and avoid the division
+	if (scaled_lower_word < limit)
+	{
+		// Scale the limit to improve the chance of success.
+		// After this, the first result might turn out to be good enough.
+		UINT32 scaled_limit;
+		// An explanation for this trick: scaled_limit should be
+		// (UINT32_MAX+1)%range, but if that was computed directly the result
+		// would need to be computed as a UINT64. This trick allows it to be
+		// computed using 32-bit arithmetic.
+		scaled_limit = (-limit) % limit;
+
+		while (scaled_lower_word < scaled_limit)
+		{
+			raw_random = RandomState_Get32(state);
+			scaled_random = (UINT64)raw_random * (UINT64)limit;
+			scaled_lower_word = (UINT32)scaled_random;
+		}
+	}
+
+	return scaled_random >> 32;
+}
+
+// The default seed is the hexadecimal digits of pi, though it will be overwritten.
+static rnstate_t m_randomstate = {
+	.data = {0x4A3B6035U, 0x99555606U, 0x6F603421U},
+	.counter = 16
+};
 
 // ---------------------------
 // RNG functions (not synched)
@@ -31,13 +143,7 @@
   */
 fixed_t M_RandomFixed(void)
 {
-#if RAND_MAX < 65535
-	// Compensate for insufficient randomness.
-	fixed_t rndv = (rand()&1)<<15;
-	return rand()^rndv;
-#else
-	return (rand() & 0xFFFF);
-#endif
+	return RandomState_Get32(&m_randomstate) >> (32-FRACBITS);
 }
 
 /** Provides a random byte. Distribution is uniform.
@@ -47,7 +153,7 @@ fixed_t M_RandomFixed(void)
   */
 UINT8 M_RandomByte(void)
 {
-	return (rand() & 0xFF);
+	return RandomState_Get32(&m_randomstate) >> 24;
 }
 
 /** Provides a random integer for picking random elements from an array.
@@ -59,7 +165,22 @@ UINT8 M_RandomByte(void)
   */
 INT32 M_RandomKey(INT32 a)
 {
-	return (INT32)((rand()/((float)RAND_MAX+1.0f))*a);
+	boolean range_is_negative;
+	INT64 range;
+	INT32 random_result;
+
+	range = a;
+	range_is_negative = range < 0;
+
+	if(range_is_negative)
+		range = -range;
+
+	random_result = RandomState_GetKey32(&m_randomstate, (UINT32)range);
+
+	if(range_is_negative)
+		random_result = -random_result;
+
+	return random_result;
 }
 
 /** Provides a random integer in a given range.
@@ -72,7 +193,46 @@ INT32 M_RandomKey(INT32 a)
   */
 INT32 M_RandomRange(INT32 a, INT32 b)
 {
-	return (INT32)((rand()/((float)RAND_MAX+1.0f))*(b-a+1))+a;
+  	if (b < a)
+	{
+    	INT32 temp;
+
+		temp = a;
+		a = b;
+		b = temp;
+	}
+
+	const UINT32 spread = b-a+1;
+	return (INT32)((INT64)RandomState_GetKey32(&m_randomstate, spread) + a);
+}
+
+/** Attempts to seed the unsynched RNG from a good random number source
+  * provided by the operating system.
+  * \return true on success, false on failure.
+  */
+boolean M_RandomSeedFromOS(void)
+{
+	UINT32 complete_word_count;
+
+	union {
+		UINT32 words[3];
+		char bytes[sizeof(UINT32[3])];
+	} seed_data;
+
+	complete_word_count = I_GetRandomBytes((char *)&seed_data.bytes, sizeof(seed_data)) / sizeof(UINT32);
+
+	// If we get even 1 word of seed, it's fine, but any less probably is not fine.
+	if (complete_word_count == 0)
+		return false;
+
+	RandomState_Seed(&m_randomstate, (UINT32 *)&seed_data.words, complete_word_count);
+
+	return true;
+}
+
+void M_RandomSeed(UINT32 seed)
+{
+	RandomState_Seed(&m_randomstate, &seed, 1);
 }
 
 
@@ -246,10 +406,18 @@ void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed)
 }
 
 /** Gets a randomized seed for setting the random seed.
+  * This function will never return 0, as the current P_Random implementation
+  * cannot handle a zero seed. Any other seed is equally likely.
   *
   * \sa P_GetRandSeed
   */
 UINT32 M_RandomizedSeed(void)
 {
-	return ((serverGamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed();
+	UINT32 seed;
+
+	do {
+		seed = RandomState_Get32(&m_randomstate);
+	} while(seed == 0);
+
+	return seed;
 }
diff --git a/src/m_random.h b/src/m_random.h
index 824287e27d486906c60d976e60817cb5ac9ffb73..a7c07a46b5e2c951548d67f409287e80345c20dd 100644
--- a/src/m_random.h
+++ b/src/m_random.h
@@ -3,6 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
+// Copyright (C) 2022-2023 by tertu marybig.
 // Copyright (C) 1999-2023 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -29,6 +30,8 @@ fixed_t M_RandomFixed(void);
 UINT8   M_RandomByte(void);
 INT32   M_RandomKey(INT32 a);
 INT32   M_RandomRange(INT32 a, INT32 b);
+boolean M_RandomSeedFromOS(void);
+void    M_RandomSeed(UINT32 a);
 
 // PRNG functions
 #ifdef DEBUGRANDOM
diff --git a/src/p5prof.h b/src/p5prof.h
deleted file mode 100644
index a9ed3965e9691f9931cd360da7cc41efc2ee5c55..0000000000000000000000000000000000000000
--- a/src/p5prof.h
+++ /dev/null
@@ -1,278 +0,0 @@
-/*********************************************************
- *
- * File:  p5prof.h
- * By:    Kevin Baca
- *
- * MODIFIED BY Fab SO THAT RDMSR(...) WRITES EDX : EAX TO A LONG LONG
- * (WHICH MEANS WRITE THE LOW DWORD FIRST)
- *
- * Now in yer code do:
- *   INT64 count,total;
- *
- *   ...
- *   RDMSR(0x10,&count);        //inner loop count
- *   total += count;
- *   ...
- *
- *   printf("0x%x %x", (INT32)total, *((INT32 *)&total+1));
- *   //                  HIGH        LOW
- *
- *********************************************************/
-/**\file
-	\brief  This file provides macros to profile your code.
-
- Here's how they work...
-
- As you may or may not know, the Pentium class of
- processors provides extremely fine grained profiling
- capabilities through the use of what are called
- Machine Specific Registers (MSRs). These registers
- can provide information about almost any aspect of
- CPU performance down to a single cycle.
-
- The MSRs of interest for profiling are specified by
- indices 0x10, 0x11, 0x12, and 0x13.  Here is a brief
- description of each of these registers:
-
- MSR 0x10
-    This register is simple a cycle counter.
-
- MSR 0x11
-    This register controls what type of profiling data
- will be gathered.
-
- MSRs 0x12 and 0x13
-    These registers gather the profiling data specified in
- MSR 0x11.
-
- Each MSR is 64 bits wide.  For the Pentium processor,
- only the lower 32 bits of MSR 0x11 are valid.  Bits 0-15
- specify what data will be gathered in MSR 0x12.  Bits 16-31
- specify what data will be gathered in MSR 0x13.  Both sets
- of bits have the same format:
-
- Bits 0-5 specify which hardware event will be tracked.
- Bit 6, if set, indicates events will be tracked in
- rings 0-2.
- Bit 7, if set, indicates events will be tracked in
- ring 3.
- Bit 8, if set, indicates cycles should be counted for
- the specified event.  If clear, it indicates the
- number of events should be counted.
-
- Two instructions are provided for manupulating the MSRs.
- RDMSR (Read Machine Specific Register) and WRMSR
- (Write Machine Specific Register).  These opcodes were
- originally undocumented and therefore most assemblers don't
- recognize them.  Their byte codes are provided in the
- macros below.
-
- RDMSR takes the MSR index in ecx and the profiling criteria
- in edx : eax.
-
- WRMSR takes the MSR index in ecx and returns the profile data
- in edx : eax.
-
- Two profiling registers limits profiling capability to
- gathering only two types of information.  The register
- usage can, however, be combined in interesting ways.
- For example, you can set one register to gather the
- number of a specific type of event while the other gathers
- the number of cycles for the same event.  Or you can
- gather the number of two separate events while using
- MSR 0x10 to gather the number of cycles.
-
- The enumerated list provides somewhat readable labels for
- the types of events that can be tracked.
-
- For more information, get ahold of appendix H from the
- Intel Pentium programmer's manual (I don't remember the
- order number) or go to
- http://green.kaist.ac.kr/jwhahn/art3.htm.
- That's an article by Terje Mathisen where I got most of
- my information.
-
- You may use this code however you wish.  I hope it's
- useful and I hope I got everything right.
-
- -Kevin
-
- kbaca@skygames.com
-
-*/
-
-#ifdef __GNUC__
-
-#define RDTSC(_dst) \
-__asm__("
-     .byte 0x0F,0x31
-     movl %%edx,(%%edi)
-     movl %%eax,4(%%edi)"\
-: : "D" (_dst) : "eax", "edx", "edi")
-
-// the old code... swapped it
-//     movl %%edx,(%%edi)
-//     movl %%eax,4(%%edi)"
-#define RDMSR(_msri, _msrd) \
-__asm__("
-     .byte 0x0F,0x32
-     movl %%eax,(%%edi)
-     movl %%edx,4(%%edi)"\
-: : "c" (_msri), "D" (_msrd) : "eax", "ecx", "edx", "edi")
-
-#define WRMSR(_msri, _msrd) \
-__asm__("
-     xorl %%edx,%%edx
-     .byte 0x0F,0x30"\
-: : "c" (_msri), "a" (_msrd) : "eax", "ecx", "edx")
-
-#define RDMSR_0x12_0x13(_msr12, _msr13) \
-__asm__("
-     movl $0x12,%%ecx
-     .byte 0x0F,0x32
-     movl %%edx,(%%edi)
-     movl %%eax,4(%%edi)
-     movl $0x13,%%ecx
-     .byte 0x0F,0x32
-     movl %%edx,(%%esi)
-     movl %%eax,4(%%esi)"\
-: : "D" (_msr12), "S" (_msr13) : "eax", "ecx", "edx", "edi")
-
-#define ZERO_MSR_0x12_0x13() \
-__asm__("
-     xorl %%edx,%%edx
-     xorl %%eax,%%eax
-     movl $0x12,%%ecx
-     .byte 0x0F,0x30
-     movl $0x13,%%ecx
-     .byte 0x0F,0x30"\
-: : : "eax", "ecx", "edx")
-
-#elif defined (__WATCOMC__)
-
-extern void RDTSC(UINT32 *dst);
-#pragma aux RDTSC =\
-   "db 0x0F,0x31"\
-   "mov [edi],edx"\
-   "mov [4+edi],eax"\
-   parm [edi]\
-   modify [eax edx edi];
-
-extern void RDMSR(UINT32 msri, UINT32 *msrd);
-#pragma aux RDMSR =\
-   "db 0x0F,0x32"\
-   "mov [edi],edx"\
-   "mov [4+edi],eax"\
-   parm [ecx] [edi]\
-   modify [eax ecx edx edi];
-
-extern void WRMSR(UINT32 msri, UINT32 msrd);
-#pragma aux WRMSR =\
-   "xor edx,edx"\
-   "db 0x0F,0x30"\
-   parm [ecx] [eax]\
-   modify [eax ecx edx];
-
-extern void RDMSR_0x12_0x13(UINT32 *msr12, UINT32 *msr13);
-#pragma aux RDMSR_0x12_0x13 =\
-   "mov ecx,0x12"\
-   "db 0x0F,0x32"\
-   "mov [edi],edx"\
-   "mov [4+edi],eax"\
-   "mov ecx,0x13"\
-   "db 0x0F,0x32"\
-   "mov [esi],edx"\
-   "mov [4+esi],eax"\
-   parm [edi] [esi]\
-   modify [eax ecx edx edi esi];
-
-extern void ZERO_MSR_0x12_0x13(void);
-#pragma aux ZERO_MSR_0x12_0x13 =\
-   "xor edx,edx"\
-   "xor eax,eax"\
-   "mov ecx,0x12"\
-   "db 0x0F,0x30"\
-   "mov ecx,0x13"\
-   "db 0x0F,0x30"\
-   modify [eax ecx edx];
-
-#endif
-
-typedef enum
-{
-   DataRead,
-     DataWrite,
-     DataTLBMiss,
-     DataReadMiss,
-     DataWriteMiss,
-     WriteHitEM,
-     DataCacheLinesWritten,
-     DataCacheSnoops,
-     DataCacheSnoopHit,
-     MemAccessBothPipes,
-     BankConflict,
-     MisalignedDataRef,
-     CodeRead,
-     CodeTLBMiss,
-     CodeCacheMiss,
-     SegRegLoad,
-     RESERVED0,
-     RESERVED1,
-     Branch,
-     BTBHit,
-     TakenBranchOrBTBHit,
-     PipelineFlush,
-     InstructionsExeced,
-     InstructionsExecedVPipe,
-     BusUtilizationClocks,
-     PipelineStalledWriteBackup,
-     PipelineStalledDateMemRead,
-     PipeLineStalledWriteEM,
-     LockedBusCycle,
-     IOReadOrWriteCycle,
-     NonCacheableMemRef,
-     AGI,
-     RESERVED2,
-     RESERVED3,
-     FPOperation,
-     Breakpoint0Match,
-     Breakpoint1Match,
-     Breakpoint2Match,
-     Breakpoint3Match,
-     HWInterrupt,
-     DataReadOrWrite,
-     DataReadOrWriteMiss
-};
-
-#define PROF_CYCLES (0x100)
-#define PROF_EVENTS (0x000)
-#define RING_012    (0x40)
-#define RING_3      (0x80)
-#define RING_0123   (RING_012 | RING_3)
-
-/*void ProfSetProfiles(UINT32 msr12, UINT32 msr13);*/
-#define ProfSetProfiles(_msr12, _msr13)\
-{\
-   UINT32 prof;\
-\
-   prof = (_msr12) | ((_msr13) << 16);\
-   WRMSR(0x11, prof);\
-}
-
-/*void ProfBeginProfiles(void);*/
-#define ProfBeginProfiles()\
-   ZERO_MSR_0x12_0x13();
-
-/*void ProfGetProfiles(UINT32 msr12[2], UINT32 msr13[2]);*/
-#define ProfGetProfiles(_msr12, _msr13)\
-   RDMSR_0x12_0x13(_msr12, _msr13);
-
-/*void ProfZeroTimer(void);*/
-#define ProfZeroTimer()\
-   WRMSR(0x10, 0);
-
-/*void ProfReadTimer(UINT32 timer[2]);*/
-#define ProfReadTimer(timer)\
-   RDMSR(0x10, timer);
-
-/*EOF*/
diff --git a/src/p_enemy.c b/src/p_enemy.c
index b468c230f4591ac2be6c9f6286000cf0ed3ac596..eebb65f3cb146f229183b014acdc89e0042f8da2 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -829,7 +829,7 @@ static boolean P_LookForShield(mobj_t *actor)
 			continue;
 
 		if ((player->powers[pw_shield] & SH_PROTECTELECTRIC)
-			&& (P_AproxDistance(P_AproxDistance(actor->x-player->mo->x, actor->y-player->mo->y), actor->z-player->mo->z) < FixedMul(RING_DIST, player->mo->scale)))
+			&& (R_PointToDist2(0, 0, R_PointToDist2(0, 0, actor->x-player->mo->x, actor->y-player->mo->y), actor->z-player->mo->z) < FixedMul(RING_DIST, player->mo->scale)))
 		{
 			P_SetTarget(&actor->tracer, player->mo);
 
@@ -12636,8 +12636,7 @@ void A_MineRange(mobj_t *actor)
 void A_ConnectToGround(mobj_t *actor)
 {
 	mobj_t *work;
-	fixed_t workz;
-	fixed_t workh;
+	fixed_t endz;
 	angle_t ang;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
@@ -12648,38 +12647,42 @@ void A_ConnectToGround(mobj_t *actor)
 	if (actor->subsector->sector->ffloors)
 		P_AdjustMobjFloorZ_FFloors(actor, actor->subsector->sector, 2);
 
+	endz = actor->z;
 	if (actor->flags2 & MF2_OBJECTFLIP)
-		workz = (actor->z + actor->height) - actor->ceilingz;
+		actor->z = actor->ceilingz - actor->height; // Ensures perfect ceiling connection
 	else
-		workz = actor->floorz - actor->z;
+		actor->z = actor->floorz; // Ensures perfect floor connection
 
 	if (locvar2)
 	{
-		workh = FixedMul(mobjinfo[locvar2].height, actor->scale);
-		if (actor->flags2 & MF2_OBJECTFLIP)
-			workz += workh;
-		work = P_SpawnMobjFromMobj(actor, 0, 0, workz, locvar2);
-		workz += workh;
-	}
+		work = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar2);
+		if (work)
+			work->old_z = work->z; // Don't copy old_z from the actor
 
-	if (!locvar1)
-		return;
+		actor->z += P_MobjFlip(actor) * FixedMul(mobjinfo[locvar2].height, actor->scale);
+	}
 
-	if (!(workh = FixedMul(mobjinfo[locvar1].height, actor->scale)))
+	if (!locvar1 || !mobjinfo[locvar1].height) // Can't tile the middle object?
+	{
+		actor->z = endz;
 		return;
+	}
 
 	ang = actor->angle + ANGLE_45;
-	while (workz < 0)
+	while ((actor->flags2 & MF2_OBJECTFLIP) ? (actor->z > endz) : (actor->z < endz))
 	{
-		work = P_SpawnMobjFromMobj(actor, 0, 0, workz, locvar1);
+		work = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
 		if (work)
-			work->angle = ang;
+		{
+			work->angle = work->old_angle = ang;
+			work->old_z = work->z; // Don't copy old_z from the actor
+		}
+
 		ang += ANGLE_90;
-		workz += workh;
+		actor->z += P_MobjFlip(actor) * FixedMul(mobjinfo[locvar1].height, actor->scale);
 	}
 
-	if (workz != 0)
-		actor->z += P_MobjFlip(actor)*workz;
+	actor->old_z = actor->z; // Reset Z interpolation - the spawned objects intentionally don't have any Z interpolation either, after all
 }
 
 // Function: A_SpawnParticleRelative
diff --git a/src/p_inter.c b/src/p_inter.c
index 4d22ba343893cc1245df77da80f9a973c201ab49..271b6ebc45a5bf42d048b39ace8db61d212741c8 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1144,7 +1144,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					if (!(mo2->type == MT_RING || mo2->type == MT_COIN
 						|| mo2->type == MT_BLUESPHERE || mo2->type == MT_BOMBSPHERE
 						|| mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR
-						|| ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL))))
+						|| ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL) && P_CanPickupEmblem(player, mo2->health - 1) && !P_EmblemWasCollected(mo2->health - 1))))
 						continue;
 
 					// Yay! The thing's in reach! Pull it in!
@@ -2235,7 +2235,7 @@ void P_CheckTimeLimit(void)
 		}
 
 		if (server)
-			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+			D_SendExitLevel(false);
 	}
 
 	//Optional tie-breaker for Match/CTF
@@ -2298,11 +2298,11 @@ void P_CheckTimeLimit(void)
 			}
 		}
 		if (server)
-			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+			D_SendExitLevel(false);
 	}
 
 	if (server)
-		SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+		D_SendExitLevel(false);
 }
 
 /** Checks if a player's score is over the pointlimit and the round should end.
@@ -2331,7 +2331,7 @@ void P_CheckPointLimit(void)
 		if ((UINT32)cv_pointlimit.value <= redscore || (UINT32)cv_pointlimit.value <= bluescore)
 		{
 			if (server)
-				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+				D_SendExitLevel(false);
 		}
 	}
 	else
@@ -2344,7 +2344,7 @@ void P_CheckPointLimit(void)
 			if ((UINT32)cv_pointlimit.value <= players[i].score)
 			{
 				if (server)
-					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+					D_SendExitLevel(false);
 				return;
 			}
 		}
@@ -2388,7 +2388,7 @@ void P_CheckSurvivors(void)
 		{
 			CONS_Printf(M_GetText("The IT player has left the game.\n"));
 			if (server)
-				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+				D_SendExitLevel(false);
 
 			return;
 		}
@@ -2408,7 +2408,7 @@ void P_CheckSurvivors(void)
 			{
 				CONS_Printf(M_GetText("All players have been tagged!\n"));
 				if (server)
-					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+					D_SendExitLevel(false);
 			}
 
 			return;
@@ -2420,7 +2420,7 @@ void P_CheckSurvivors(void)
 		{
 			CONS_Printf(M_GetText("There are no players able to become IT.\n"));
 			if (server)
-				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+				D_SendExitLevel(false);
 		}
 
 		return;
@@ -2432,7 +2432,7 @@ void P_CheckSurvivors(void)
 	{
 		CONS_Printf(M_GetText("All players have been tagged!\n"));
 		if (server)
-			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+			D_SendExitLevel(false);
 	}
 }
 
diff --git a/src/p_map.c b/src/p_map.c
index a8170269cb909d595855d10061a0790f5ed82723..80135db7471058b85f7aadda2fef0c37e4ddba57 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2524,7 +2524,6 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam)
 {
 	subsector_t *s = R_PointInSubsector(x, y);
-	boolean retval = true;
 	boolean itsatwodlevel = false;
 
 	floatok = false;
@@ -2539,8 +2538,8 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam)
 		fixed_t tryx = thiscam->x;
 		fixed_t tryy = thiscam->y;
 
-		if ((thiscam == &camera && (players[displayplayer].pflags & PF_NOCLIP))
-		|| (thiscam == &camera2 && (players[secondarydisplayplayer].pflags & PF_NOCLIP)))
+		if ((thiscam == &camera && (players[displayplayer].pflags & PF_NOCLIP || players[displayplayer].powers[pw_carry] == CR_NIGHTSMODE))
+		|| (thiscam == &camera2 && (players[secondarydisplayplayer].pflags & PF_NOCLIP || players[secondarydisplayplayer].powers[pw_carry] == CR_NIGHTSMODE)))
 		{ // Noclipping player camera noclips too!!
 			floatok = true;
 			thiscam->floorz = thiscam->z;
@@ -2608,7 +2607,7 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam)
 	thiscam->y = y;
 	thiscam->subsector = s;
 
-	return retval;
+	return true;
 }
 
 //
@@ -4009,131 +4008,135 @@ void P_BounceMove(mobj_t *mo)
 	slidemo = mo;
 	hitcount = 0;
 
-retry:
-	if (++hitcount == 3)
-		goto bounceback; // don't loop forever
-
-	if (mo->player)
-	{
-		mmomx = mo->player->rmomx;
-		mmomy = mo->player->rmomy;
-	}
-	else
+	do
 	{
-		mmomx = mo->momx;
-		mmomy = mo->momy;
-	}
+		if (++hitcount == 3)
+			goto bounceback; // don't loop forever
 
-	// trace along the three leading corners
-	if (mo->momx > 0)
-	{
-		leadx = mo->x + mo->radius;
-		trailx = mo->x - mo->radius;
-	}
-	else
-	{
-		leadx = mo->x - mo->radius;
-		trailx = mo->x + mo->radius;
-	}
+		if (mo->player)
+		{
+			mmomx = mo->player->rmomx;
+			mmomy = mo->player->rmomy;
+		}
+		else
+		{
+			mmomx = mo->momx;
+			mmomy = mo->momy;
+		}
 
-	if (mo->momy > 0)
-	{
-		leady = mo->y + mo->radius;
-		traily = mo->y - mo->radius;
-	}
-	else
-	{
-		leady = mo->y - mo->radius;
-		traily = mo->y + mo->radius;
-	}
+		// trace along the three leading corners
+		if (mo->momx > 0)
+		{
+			leadx = mo->x + mo->radius;
+			trailx = mo->x - mo->radius;
+		}
+		else
+		{
+			leadx = mo->x - mo->radius;
+			trailx = mo->x + mo->radius;
+		}
+
+		if (mo->momy > 0)
+		{
+			leady = mo->y + mo->radius;
+			traily = mo->y - mo->radius;
+		}
+		else
+		{
+			leady = mo->y - mo->radius;
+			traily = mo->y + mo->radius;
+		}
 
-	bestslidefrac = FRACUNIT + 1;
+		bestslidefrac = FRACUNIT + 1;
 
-	P_PathTraverse(leadx, leady, leadx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
-	P_PathTraverse(trailx, leady, trailx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
-	P_PathTraverse(leadx, traily, leadx + mmomx, traily + mmomy, PT_ADDLINES, PTR_SlideTraverse);
+		P_PathTraverse(leadx, leady, leadx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
+		P_PathTraverse(trailx, leady, trailx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
+		P_PathTraverse(leadx, traily, leadx + mmomx, traily + mmomy, PT_ADDLINES, PTR_SlideTraverse);
 
-	// move up to the wall
-	if (bestslidefrac == FRACUNIT + 1)
-	{
-		// the move must have hit the middle, so bounce straight back
-bounceback:
-		if (P_TryMove(mo, mo->x - mmomx, mo->y - mmomy, true))
+		// move up to the wall
+		if (bestslidefrac == FRACUNIT + 1)
 		{
-			mo->momx *= -1;
-			mo->momy *= -1;
-			mo->momx = FixedMul(mo->momx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-			mo->momy = FixedMul(mo->momy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-
-			if (mo->player)
+			// the move must have hit the middle, so bounce straight back
+bounceback:
+			if (P_TryMove(mo, mo->x - mmomx, mo->y - mmomy, true))
 			{
-				mo->player->cmomx *= -1;
-				mo->player->cmomy *= -1;
-				mo->player->cmomx = FixedMul(mo->player->cmomx,
-					(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-				mo->player->cmomy = FixedMul(mo->player->cmomy,
-					(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+				mo->momx *= -1;
+				mo->momy *= -1;
+				mo->momx = FixedMul(mo->momx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+				mo->momy = FixedMul(mo->momy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+
+				if (mo->player)
+				{
+					mo->player->cmomx *= -1;
+					mo->player->cmomy *= -1;
+					mo->player->cmomx = FixedMul(mo->player->cmomx,
+						(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+					mo->player->cmomy = FixedMul(mo->player->cmomy,
+						(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+				}
 			}
+			return;
 		}
-		return;
-	}
 
-	// fudge a bit to make sure it doesn't hit
-	bestslidefrac -= 0x800;
-	if (bestslidefrac > 0)
-	{
-		newx = FixedMul(mmomx, bestslidefrac);
-		newy = FixedMul(mmomy, bestslidefrac);
+		// fudge a bit to make sure it doesn't hit
+		bestslidefrac -= 0x800;
+		if (bestslidefrac > 0)
+		{
+			newx = FixedMul(mmomx, bestslidefrac);
+			newy = FixedMul(mmomy, bestslidefrac);
 
-		if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
-			goto bounceback;
-	}
+			if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
+			{
+				if (P_MobjWasRemoved(mo))
+					return;
+				goto bounceback;
+			}
+		}
 
-	// Now continue along the wall.
-	// First calculate remainder.
-	bestslidefrac = FRACUNIT - bestslidefrac;
+		// Now continue along the wall.
+		// First calculate remainder.
+		bestslidefrac = FRACUNIT - bestslidefrac;
 
-	if (bestslidefrac > FRACUNIT)
-		bestslidefrac = FRACUNIT;
+		if (bestslidefrac > FRACUNIT)
+			bestslidefrac = FRACUNIT;
 
-	if (bestslidefrac <= 0)
-		return;
+		if (bestslidefrac <= 0)
+			return;
 
-	if (mo->type == MT_SHELL)
-	{
-		tmxmove = mmomx;
-		tmymove = mmomy;
-	}
-	else if (mo->type == MT_THROWNBOUNCE)
-	{
-		tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
-		tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
-	}
-	else if (mo->type == MT_THROWNGRENADE || mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE)
-	{
-		// Quickly decay speed as it bounces
-		tmxmove = FixedDiv(mmomx, 2*FRACUNIT);
-		tmymove = FixedDiv(mmomy, 2*FRACUNIT);
-	}
-	else
-	{
-		tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-		tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-	}
+		if (mo->type == MT_SHELL)
+		{
+			tmxmove = mmomx;
+			tmymove = mmomy;
+		}
+		else if (mo->type == MT_THROWNBOUNCE)
+		{
+			tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
+			tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
+		}
+		else if (mo->type == MT_THROWNGRENADE || mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE)
+		{
+			// Quickly decay speed as it bounces
+			tmxmove = FixedDiv(mmomx, 2*FRACUNIT);
+			tmymove = FixedDiv(mmomy, 2*FRACUNIT);
+		}
+		else
+		{
+			tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+			tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+		}
 
-	P_HitBounceLine(bestslideline); // clip the moves
+		P_HitBounceLine(bestslideline); // clip the moves
 
-	mo->momx = tmxmove;
-	mo->momy = tmymove;
+		mo->momx = tmxmove;
+		mo->momy = tmymove;
 
-	if (mo->player)
-	{
-		mo->player->cmomx = tmxmove;
-		mo->player->cmomy = tmymove;
+		if (mo->player)
+		{
+			mo->player->cmomx = tmxmove;
+			mo->player->cmomy = tmymove;
+		}
 	}
-
-	if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true))
-		goto retry;
+	while (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true) && !P_MobjWasRemoved(mo));
 }
 
 //
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 4ca59285f7fc46bbfeb6e2373b50eb4eae33637d..686f08478e8d7006e3d01c7a69ea3f38b4e1534a 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3688,7 +3688,7 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled
 			dummy.y = thiscam->y;
 			dummy.z = thiscam->z;
 			dummy.height = thiscam->height;
-			if (!resetcalled && !(player->pflags & PF_NOCLIP) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead.
+			if (!resetcalled && !(player->pflags & PF_NOCLIP || player->powers[pw_carry] == CR_NIGHTSMODE) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead.
 				P_ResetCamera(player, thiscam);
 			else
 			{
@@ -3719,7 +3719,7 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled
 		// adjust height
 		thiscam->z += thiscam->momz + player->mo->pmomz;
 
-		if (!itsatwodlevel && !(player->pflags & PF_NOCLIP))
+		if (!itsatwodlevel && !(player->pflags & PF_NOCLIP || player->powers[pw_carry] == CR_NIGHTSMODE))
 		{
 			// clip movement
 			if (thiscam->z <= thiscam->floorz) // hit the floor
@@ -6882,7 +6882,6 @@ void P_RunOverlays(void)
 		mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP);
 		mo->scale = mo->destscale = mo->target->scale;
 		mo->angle = (mo->target->player ? mo->target->player->drawangle : mo->target->angle) + mo->movedir;
-		P_SetTarget(&mo->dontdrawforviewmobj, mo->target->dontdrawforviewmobj); // Hide the overlay from the view that its target is hidden from - But don't copy drawonlyforplayer!
 
 		if (!(mo->state->frame & FF_ANIMATE))
 			zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale);
@@ -9228,7 +9227,7 @@ static void P_DragonbomberThink(mobj_t *mobj)
 		else
 		{
 			fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
-			fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->flags & MFE_VERTICALFLIP ? -128*mobj->scale : 128*mobj->scale + mobj->target->height);
+			fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->eflags & MFE_VERTICALFLIP ? -128*mobj->scale : (128*mobj->scale + mobj->target->height));
 			angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
 			if (diff > ANGLE_180)
 				mobj->angle -= DRAGONTURNSPEED;
@@ -10564,6 +10563,29 @@ static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
 		case MT_REDFLAG:
 		case MT_BLUEFLAG:
 
+		case MT_BOUNCERING:
+		case MT_AUTOMATICRING:
+		case MT_INFINITYRING:
+		case MT_RAILRING:
+		case MT_EXPLOSIONRING:
+		case MT_SCATTERRING:
+		case MT_GRENADERING:
+		
+		case MT_BOUNCEPICKUP:
+		case MT_RAILPICKUP:
+		case MT_AUTOPICKUP:
+		case MT_EXPLODEPICKUP:
+		case MT_SCATTERPICKUP:
+		case MT_GRENADEPICKUP:
+		
+		case MT_REDRING:
+		case MT_THROWNBOUNCE:
+		case MT_THROWNINFINITY:
+		case MT_THROWNAUTOMATIC:
+		case MT_THROWNSCATTER:
+		case MT_THROWNEXPLOSION:
+		case MT_THROWNGRENADE:
+
 		case MT_EMBLEM:
 
 		case MT_TOKEN:
@@ -11181,12 +11203,6 @@ void P_RemoveMobj(mobj_t *mobj)
 
 	P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
 
-	// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
-#ifdef SCRAMBLE_REMOVED
-	// Invalidate mobj_t data to cause crashes if accessed!
-	memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
-#endif
-
 	R_RemoveMobjInterpolator(mobj);
 
 	// free block
@@ -11205,6 +11221,17 @@ void P_RemoveMobj(mobj_t *mobj)
 	}
 
 	P_RemoveThinker((thinker_t *)mobj);
+
+#ifdef PARANOIA
+	// Saved to avoid being scrambled like below...
+	mobj->thinker.debug_mobjtype = mobj->type;
+#endif
+
+	// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
+#ifdef SCRAMBLE_REMOVED
+	// Invalidate mobj_t data to cause crashes if accessed!
+	memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
+#endif
 }
 
 // This does not need to be added to Lua.
@@ -11836,7 +11863,7 @@ void P_MovePlayerToStarpost(INT32 playernum)
 mapthing_t *huntemeralds[MAXHUNTEMERALDS];
 INT32 numhuntemeralds;
 
-fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale)
+fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale, const boolean absolutez)
 {
 	const subsector_t *ss = R_PointInSubsector(x, y);
 
@@ -11846,9 +11873,9 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f
 
 	// Establish height.
 	if (flip)
-		return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(scale, offset + mobjinfo[mobjtype].height);
+		return (absolutez ? dz : P_GetSectorCeilingZAt(ss->sector, x, y) - dz) - FixedMul(scale, offset + mobjinfo[mobjtype].height);
 	else
-		return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(scale, offset);
+		return (absolutez ? dz : P_GetSectorFloorZAt(ss->sector, x, y) + dz) + FixedMul(scale, offset);
 }
 
 fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y)
@@ -11856,6 +11883,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 	fixed_t dz = mthing->z << FRACBITS; // Base offset from the floor.
 	fixed_t offset = 0; // Specific scaling object offset.
 	boolean flip = (!!(mobjinfo[mobjtype].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP));
+	boolean absolutez = !!(mthing->options & MTF_ABSOLUTEZ);
 
 	switch (mobjtype)
 	{
@@ -11911,7 +11939,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 			offset += mthing->args[0] ? 0 : 24*FRACUNIT;
 	}
 
-	if (!(dz + offset)) // Snap to the surfaces when there's no offset set.
+	if (!(dz + offset) && !absolutez) // Snap to the surfaces when there's no offset set.
 	{
 		if (flip)
 			return ONCEILINGZ;
@@ -11919,7 +11947,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 			return ONFLOORZ;
 	}
 
-	return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale);
+	return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale, absolutez);
 }
 
 static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
@@ -12588,7 +12616,7 @@ static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj)
 	dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0);
 
 	if (flip && mobj->height != oldheight)
-		P_MoveOrigin(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight));
+		P_SetOrigin(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight));
 
 	if (!flip)
 	{
@@ -13327,6 +13355,9 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
 	P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale));
 	mobj->destscale = FixedMul(mobj->destscale, mthing->scale);
 
+	mobj->spritexscale = mthing->spritexscale;
+	mobj->spriteyscale = mthing->spriteyscale;
+
 	if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
 		return mobj;
 
@@ -13413,7 +13444,7 @@ void P_SpawnHoop(mapthing_t *mthing)
 	vector4_t v, res;
 	fixed_t x = mthing->x << FRACBITS;
 	fixed_t y = mthing->y << FRACBITS;
-	fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale);
+	fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale, mthing->options & MTF_ABSOLUTEZ);
 
 	hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER);
 	hoopcenter->spawnpoint = mthing;
@@ -13540,7 +13571,7 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi
 			itemtypes[r] = P_GetMobjtypeSubstitute(&dummything, itemtypes[r]);
 		}
 	}
-	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale);
+	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale, mthing->options & MTF_ABSOLUTEZ);
 
 	for (r = 0; r < numitems; r++)
 	{
@@ -13599,7 +13630,7 @@ static void P_SpawnItemCircle(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 n
 			itemtypes[i] = P_GetMobjtypeSubstitute(&dummything, itemtypes[i]);
 		}
 	}
-	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, false, mthing->scale);
+	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, false, mthing->scale, mthing->options & MTF_ABSOLUTEZ);
 
 	for (i = 0; i < numitems; i++)
 	{
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 9c598f59e3ce155e66a3155c4c95bd7712f839c7..a980691beb8b296b4fd16415141e4f9e5f484af8 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -491,7 +491,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing);
 void P_MovePlayerToStarpost(INT32 playernum);
 void P_AfterPlayerSpawn(INT32 playernum);
 
-fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale);
+fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale, const boolean absolutez);
 fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y);
 
 mobj_t *P_SpawnMapThing(mapthing_t *mthing);
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 8f11e63e58a25ba43ff6f78f3d9713332330e402..faecd13770b3d81b992017af51b4b663487aa50d 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -2716,8 +2716,8 @@ static void P_NetArchiveThinkers(void)
 				continue;
 			}
 #ifdef PARANOIA
-			else if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed) // wait garbage collection
-				I_Error("unknown thinker type %p", th->function.acp1);
+			else
+				I_Assert(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed); // wait garbage collection
 #endif
 		}
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 175ab3328b627d7aceabeae3fd865ca71aba7b0b..e289b834699dd957aabc3ec554c25c720b6d5b2a 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -50,6 +50,7 @@
 #include "m_random.h"
 
 #include "dehacked.h" // for map headers
+#include "deh_tables.h" // FREE_SKINCOLORS
 #include "r_main.h"
 #include "m_cond.h" // for emblems
 
@@ -1266,12 +1267,19 @@ static void P_WriteDuplicateText(const char *text, char **target)
 
 static void P_WriteSkincolor(INT32 constant, char **target)
 {
+	const char *color_name;
+
 	if (constant <= SKINCOLOR_NONE
 	|| constant >= (INT32)numskincolors)
 		return;
 
+	if (constant >= SKINCOLOR_FIRSTFREESLOT)
+		color_name = FREE_SKINCOLORS[constant - SKINCOLOR_FIRSTFREESLOT];
+	else
+		color_name = COLOR_ENUMS[constant];
+
 	P_WriteDuplicateText(
-		va("SKINCOLOR_%s", skincolors[constant].name),
+		va("SKINCOLOR_%s", color_name),
 		target
 	);
 }
@@ -1510,6 +1518,7 @@ static void P_LoadThings(UINT8 *data)
 		mt->extrainfo = (UINT8)(mt->type >> 12);
 		Tag_FSet(&mt->tags, 0);
 		mt->scale = FRACUNIT;
+		mt->spritexscale = mt->spriteyscale = FRACUNIT;
 		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->pitch = mt->roll = 0;
@@ -2013,11 +2022,19 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char *
 		mapthings[i].roll = atol(val);
 	else if (fastcmp(param, "type"))
 		mapthings[i].type = atol(val);
-	else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley"))
+	else if (fastcmp(param, "scale"))
+		mapthings[i].spritexscale = mapthings[i].spriteyscale = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "scalex"))
+		mapthings[i].spritexscale = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "scaley"))
+		mapthings[i].spriteyscale = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "mobjscale"))
 		mapthings[i].scale = FLOAT_TO_FIXED(atof(val));
 	// Flags
 	else if (fastcmp(param, "flip") && fastcmp("true", val))
 		mapthings[i].options |= MTF_OBJECTFLIP;
+	else if (fastcmp(param, "absolutez") && fastcmp("true", val))
+		mapthings[i].options |= MTF_ABSOLUTEZ;
 
 	else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9)
 	{
@@ -2431,8 +2448,12 @@ static void P_WriteTextmap(void)
 			fprintf(f, "roll = %d;\n", wmapthings[i].roll);
 		if (wmapthings[i].type != 0)
 			fprintf(f, "type = %d;\n", wmapthings[i].type);
+		if (wmapthings[i].spritexscale != FRACUNIT)
+			fprintf(f, "scalex = %f;\n", FIXED_TO_FLOAT(wmapthings[i].spritexscale));
+		if (wmapthings[i].spriteyscale != FRACUNIT)
+			fprintf(f, "scaley = %f;\n", FIXED_TO_FLOAT(wmapthings[i].spriteyscale));
 		if (wmapthings[i].scale != FRACUNIT)
-			fprintf(f, "scale = %f;\n", FIXED_TO_FLOAT(wmapthings[i].scale));
+			fprintf(f, "mobjscale = %f;\n", FIXED_TO_FLOAT(wmapthings[i].scale));
 		if (wmapthings[i].options & MTF_OBJECTFLIP)
 			fprintf(f, "flip = true;\n");
 		for (j = 0; j < NUMMAPTHINGARGS; j++)
@@ -2978,6 +2999,7 @@ static void P_LoadTextmap(void)
 		mt->extrainfo = 0;
 		Tag_FSet(&mt->tags, 0);
 		mt->scale = FRACUNIT;
+		mt->spritexscale = mt->spriteyscale = FRACUNIT;
 		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->mobj = NULL;
@@ -5455,7 +5477,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			break;
 		case 442: //Change object type state
 			lines[i].args[0] = tag;
-			lines[i].args[3] = (lines[i].sidenum[1] == 0xffff) ? 1 : 0;
+			lines[i].args[1] = (lines[i].sidenum[1] == 0xffff) ? 1 : 0;
 			break;
 		case 443: //Call Lua function
 			if (lines[i].stringargs[0] == NULL)
@@ -6798,6 +6820,9 @@ static void P_ConvertBinaryThingTypes(void)
 		default:
 			break;
 		}
+		
+		// Clear binary thing height hacks, to prevent interfering with UDMF-only flags
+		mapthings[i].options &= 0xF;
 	}
 }
 
@@ -7795,18 +7820,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	R_InitMobjInterpolators();
 	P_InitCachedActions();
 
-	if (!fromnetsave && savedata.lives > 0)
-	{
-		numgameovers = savedata.numgameovers;
-		players[consoleplayer].continues = savedata.continues;
-		players[consoleplayer].lives = savedata.lives;
-		players[consoleplayer].score = savedata.score;
-		if ((botingame = ((botskin = savedata.botskin) != 0)))
-			botcolor = skins[botskin-1].prefcolor;
-		emeralds = savedata.emeralds;
-		savedata.lives = 0;
-	}
-
 	// internal game map
 	maplumpname = G_BuildMapName(gamemap);
 	lastloadedmaplumpnum = W_CheckNumForMap(maplumpname);
@@ -8238,7 +8251,7 @@ static boolean P_LoadAddon(UINT16 numlumps)
 	{
 		CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap);
 		if (server)
-			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+			D_SendExitLevel(false);
 	}
 
 	return true;
diff --git a/src/p_slopes.c b/src/p_slopes.c
index 6aca116a58f295eec7c69d3a9fcc9611914f461a..1c0ee81a7e9453d204049d2f0ab5986a07849edb 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -566,6 +566,7 @@ static void line_SpawnViaMapthingVertexes(const int linenum, const boolean spawn
 	case TMSP_BACKCEILING:
 		slopetoset = &line->backsector->c_slope;
 		side = &sides[line->sidenum[1]];
+		break;
 	default:
 		return;
 	}
diff --git a/src/p_spec.c b/src/p_spec.c
index 6709515201c5343d2970e993dacdbf309568a34f..28ecc60f4dedb5f67f9aa33e092232eb0b3782f6 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2561,11 +2561,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				// Change the music and apply position/fade operations
 				else
 				{
-					if (!line->stringargs[0])
-						break;
-
-					strncpy(mapmusname, line->stringargs[0], 7);
-					mapmusname[6] = 0;
+					if (!line->stringargs[0] || !strcmp(line->stringargs[0], "-"))
+						strcpy(mapmusname, "");
+					else
+					{
+						strncpy(mapmusname, line->stringargs[0], 7);
+						mapmusname[6] = 0;
+					}
 
 					mapmusflags = tracknum & MUSIC_TRACKMASK;
 					if (!(line->args[0] & TMM_NORELOAD))
@@ -3284,19 +3286,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						foundrover = true;
 
 						// If fading an invisible FOF whose render flags we did not yet set,
-						// initialize its alpha to 1
-						// for relative alpha calc
+						// initialize its alpha to 0 for relative alpha calculation
 						if (!(line->args[3] & TMST_DONTDOTRANSLUCENT) &&      // do translucent
 							(rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE
 							!(rover->spawnflags & FOF_RENDERSIDES) &&
 							!(rover->spawnflags & FOF_RENDERPLANES) &&
 							!(rover->fofflags & FOF_RENDERALL))
-							rover->alpha = 1;
+							rover->alpha = 0;
 
 						P_RemoveFakeFloorFader(rover);
 						P_FadeFakeFloor(rover,
 							rover->alpha,
-							max(1, min(256, (line->args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)),
+							max(0, min(255, (line->args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)),
 							0,                                         // set alpha immediately
 							false, NULL,                               // tic-based logic
 							false,                                     // do not handle FOF_EXISTS
@@ -3370,19 +3371,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						else
 						{
 							// If fading an invisible FOF whose render flags we did not yet set,
-							// initialize its alpha to 1
-							// for relative alpha calc
+							// initialize its alpha to 1 for relative alpha calculation
 							if (!(line->args[4] & TMFT_DONTDOTRANSLUCENT) &&      // do translucent
 								(rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE
 								!(rover->spawnflags & FOF_RENDERSIDES) &&
 								!(rover->spawnflags & FOF_RENDERPLANES) &&
 								!(rover->fofflags & FOF_RENDERALL))
-								rover->alpha = 1;
+								rover->alpha = 0;
 
 							P_RemoveFakeFloorFader(rover);
 							P_FadeFakeFloor(rover,
 								rover->alpha,
-								max(1, min(256, (line->args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)),
+								max(0, min(255, (line->args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)),
 								0,                                         // set alpha immediately
 								false, NULL,                               // tic-based logic
 								!(line->args[4] & TMFT_DONTDOEXISTS),      // do not handle FOF_EXISTS
@@ -6563,10 +6563,10 @@ void P_SpawnSpecials(boolean fromnetsave)
 				//Cutting options
 				if (ffloorflags & FOF_RENDERALL)
 				{
-					//If inside is visible, cut inner walls
-					if ((lines[i].args[1] < 255) || (lines[i].args[3] & TMFA_SPLAT) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
+					//If inside is visible from the outside, cut inner walls
+					if (lines[i].args[1] < 255 || (lines[i].args[3] & TMFA_SPLAT))
 						ffloorflags |= FOF_CUTEXTRA|FOF_EXTRA;
-					else
+					else if (!(lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
 						ffloorflags |= FOF_CUTLEVEL;
 				}
 
@@ -6622,20 +6622,19 @@ void P_SpawnSpecials(boolean fromnetsave)
 				if (lines[i].args[4] & TMFC_SPLAT)
 					ffloorflags |= FOF_SPLAT;
 
-				//If inside is visible, cut inner walls
-				if (lines[i].args[1] < 0xff || (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE) || (lines[i].args[4] & TMFC_SPLAT))
+				//If inside is visible from the outside, cut inner walls
+				if (lines[i].args[1] < 255 || (lines[i].args[4] & TMFC_SPLAT))
 					ffloorflags |= FOF_CUTEXTRA|FOF_EXTRA;
-				else
-					ffloorflags |= FOF_CUTLEVEL;
-
-				//If player can enter it, render insides
-				if (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE)
+				//If player can view it from the inside, render insides
+				else if (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE)
 				{
 					if (ffloorflags & FOF_RENDERPLANES)
 						ffloorflags |= FOF_BOTHPLANES;
 					if (ffloorflags & FOF_RENDERSIDES)
 						ffloorflags |= FOF_ALLSIDES;
 				}
+				else
+					ffloorflags |= FOF_CUTLEVEL;
 
 				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				if (lines[i].args[4] & TMFC_AIRBOB)
@@ -6686,10 +6685,10 @@ void P_SpawnSpecials(boolean fromnetsave)
 				//Cutting options
 				if (ffloorflags & FOF_RENDERALL)
 				{
-					//If inside is visible, cut inner walls
-					if ((lines[i].args[1] < 255) || (lines[i].args[3] & TMFA_SPLAT) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
+					//If inside is visible from the outside, cut inner walls
+					if (lines[i].args[1] < 255 || (lines[i].args[3] & TMFA_SPLAT))
 						ffloorflags |= FOF_CUTEXTRA|FOF_EXTRA;
-					else
+					else if (!(lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
 						ffloorflags |= FOF_CUTLEVEL;
 				}
 
@@ -7754,15 +7753,14 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval
 	if (rover->master->special == 258) // Laser block
 		return false;
 
-	// If fading an invisible FOF whose render flags we did not yet set,
-	// initialize its alpha to 1
+	// If fading an invisible FOF whose render flags we did not yet set, initialize its alpha to 1
 	if (dotranslucent &&
 		(rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE
 		!(rover->fofflags & FOF_FOG) && // do not include fog
 		!(rover->spawnflags & FOF_RENDERSIDES) &&
 		!(rover->spawnflags & FOF_RENDERPLANES) &&
 		!(rover->fofflags & FOF_RENDERALL))
-		rover->alpha = 1;
+		rover->alpha = 0;
 
 	if (fadingdata)
 		alpha = fadingdata->alpha;
@@ -7848,7 +7846,7 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval
 	{
 		if (doexists && !(rover->spawnflags & FOF_BUSTUP))
 		{
-			if (alpha <= 1)
+			if (alpha <= 0)
 				rover->fofflags &= ~FOF_EXISTS;
 			else
 				rover->fofflags |= FOF_EXISTS;
@@ -7860,7 +7858,7 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval
 
 		if (dotranslucent && !(rover->fofflags & FOF_FOG))
 		{
-			if (alpha >= 256)
+			if (alpha >= 255)
 			{
 				if (!(rover->fofflags & FOF_CUTSOLIDS) &&
 					(rover->spawnflags & FOF_CUTSOLIDS))
@@ -7960,11 +7958,11 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval
 		else // clamp fadingdata->alpha to software's alpha levels
 		{
 			if (alpha < 12)
-				rover->alpha = destvalue < 12 ? destvalue : 1; // Don't even draw it
+				rover->alpha = destvalue < 12 ? destvalue : 0; // Don't even draw it
 			else if (alpha < 38)
 				rover->alpha = destvalue >= 12 && destvalue < 38 ? destvalue : 25;
 			else if (alpha < 64)
-				rover->alpha = destvalue >=38 && destvalue < 64 ? destvalue : 51;
+				rover->alpha = destvalue >= 38 && destvalue < 64 ? destvalue : 51;
 			else if (alpha < 89)
 				rover->alpha = destvalue >= 64 && destvalue < 89 ? destvalue : 76;
 			else if (alpha < 115)
@@ -7980,7 +7978,7 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval
 			else if (alpha < 243)
 				rover->alpha = destvalue >= 217 && destvalue < 243 ? destvalue : 230;
 			else // Opaque
-				rover->alpha = destvalue >= 243 ? destvalue : 256;
+				rover->alpha = destvalue >= 243 ? destvalue : 255;
 		}
 	}
 
@@ -8010,17 +8008,16 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor
 {
 	fade_t *d;
 
-	// If fading an invisible FOF whose render flags we did not yet set,
-	// initialize its alpha to 1
+	// If fading an invisible FOF whose render flags we did not yet set, initialize its alpha to 1
 	if (dotranslucent &&
 		(rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE
 		!(rover->spawnflags & FOF_RENDERSIDES) &&
 		!(rover->spawnflags & FOF_RENDERPLANES) &&
 		!(rover->fofflags & FOF_RENDERALL))
-		rover->alpha = 1;
+		rover->alpha = 0;
 
 	// already equal, nothing to do
-	if (rover->alpha == max(1, min(256, relative ? rover->alpha + destvalue : destvalue)))
+	if (rover->alpha == max(0, min(255, relative ? rover->alpha + destvalue : destvalue)))
 		return;
 
 	d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
@@ -8031,7 +8028,7 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor
 	d->ffloornum = (UINT32)ffloornum;
 
 	d->alpha = d->sourcevalue = rover->alpha;
-	d->destvalue = max(1, min(256, relative ? rover->alpha + destvalue : destvalue)); // rover->alpha is 1-256
+	d->destvalue = max(0, min(255, relative ? rover->alpha + destvalue : destvalue)); // rover->alpha is 0-255
 
 	if (ticbased)
 	{
diff --git a/src/p_tick.c b/src/p_tick.c
index b1fd367ed94721e5aedab16b3a3e743a6df63425..ec5d8a2da0a487a6e4743e39dedc04c2fb27069d 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -30,6 +30,10 @@
 // Object place
 #include "m_cheat.h"
 
+#ifdef PARANOIA
+#include "deh_tables.h" // MOBJTYPE_LIST
+#endif
+
 tic_t leveltime;
 
 //
@@ -211,7 +215,48 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
 	thlist[n].prev = thinker;
 
 	thinker->references = 0;    // killough 11/98: init reference counter to 0
+
+#ifdef PARANOIA
+	thinker->debug_mobjtype = MT_NULL;
+#endif
+}
+
+#ifdef PARANOIA
+static const char *MobjTypeName(const mobj_t *mobj)
+{
+	actionf_p1 p1 = mobj->thinker.function.acp1;
+
+	if (p1 == (actionf_p1)P_MobjThinker)
+	{
+		return MOBJTYPE_LIST[mobj->type];
+	}
+	else if (p1 == (actionf_p1)P_RemoveThinkerDelayed)
+	{
+		if (mobj->thinker.debug_mobjtype != MT_NULL)
+		{
+			return MOBJTYPE_LIST[mobj->thinker.debug_mobjtype];
+		}
+	}
+
+	return "<Not a mobj>";
+}
+
+static const char *MobjThinkerName(const mobj_t *mobj)
+{
+	actionf_p1 p1 = mobj->thinker.function.acp1;
+
+	if (p1 == (actionf_p1)P_MobjThinker)
+	{
+		return "P_MobjThinker";
+	}
+	else if (p1 == (actionf_p1)P_RemoveThinkerDelayed)
+	{
+		return "P_RemoveThinkerDelayed";
+	}
+
+	return "<Unknown Thinker>";
 }
+#endif
 
 //
 // killough 11/98:
@@ -234,20 +279,34 @@ static thinker_t *currentthinker;
 void P_RemoveThinkerDelayed(thinker_t *thinker)
 {
 	thinker_t *next;
-#ifdef PARANOIA
-#define BEENAROUNDBIT (0x40000000) // has to be sufficiently high that it's unlikely to happen in regular gameplay. If you change this, pay attention to the bit pattern of INT32_MIN.
-	if (thinker->references & ~BEENAROUNDBIT)
+
+	if (thinker->references != 0)
 	{
-		if (thinker->references & BEENAROUNDBIT) // Usually gets cleared up in one frame; what's going on here, then?
-			CONS_Printf("Number of potentially faulty references: %d\n", (thinker->references & ~BEENAROUNDBIT));
-		thinker->references |= BEENAROUNDBIT;
+#ifdef PARANOIA
+		if (thinker->debug_time > leveltime)
+		{
+			thinker->debug_time = leveltime + 2; // do not print errors again
+		}
+		// Removed mobjs can be the target of another mobj. In
+		// that case, the other mobj will manage its reference
+		// to the removed mobj in P_MobjThinker. However, if
+		// the removed mobj is removed after the other object
+		// thinks, the reference management is delayed by one
+		// tic.
+		else if (thinker->debug_time < leveltime)
+		{
+			CONS_Printf(
+					"PARANOIA/P_RemoveThinkerDelayed: %p %s references=%d\n",
+					(void*)thinker,
+					MobjTypeName((mobj_t*)thinker),
+					thinker->references
+			);
+
+			thinker->debug_time = leveltime + 2; // do not print this error again
+		}
+#endif
 		return;
 	}
-#undef BEENAROUNDBIT
-#else
-	if (thinker->references)
-		return;
-#endif
 
 	/* Remove from main thinker list */
 	next = thinker->next;
@@ -291,12 +350,45 @@ void P_RemoveThinker(thinker_t *thinker)
  * references, and delay removal until the count is 0.
  */
 
-mobj_t *P_SetTarget(mobj_t **mop, mobj_t *targ)
+mobj_t *P_SetTarget2(mobj_t **mop, mobj_t *targ
+#ifdef PARANOIA
+		, const char *source_file, int source_line
+#endif
+)
 {
-	if (*mop)              // If there was a target already, decrease its refcount
+	if (*mop) // If there was a target already, decrease its refcount
+	{
 		(*mop)->thinker.references--;
-if ((*mop = targ) != NULL) // Set new target and if non-NULL, increase its counter
+
+#ifdef PARANOIA
+		if ((*mop)->thinker.references < 0)
+		{
+			CONS_Printf(
+					"PARANOIA/P_SetTarget: %p %s %s references=%d, references go negative! (%s:%d)\n",
+					(void*)*mop,
+					MobjTypeName(*mop),
+					MobjThinkerName(*mop),
+					(*mop)->thinker.references,
+					source_file,
+					source_line
+			);
+		}
+
+		(*mop)->thinker.debug_time = leveltime;
+#endif
+	}
+
+	if (targ != NULL) // Set new target and if non-NULL, increase its counter
+	{
 		targ->thinker.references++;
+
+#ifdef PARANOIA
+		targ->thinker.debug_time = leveltime;
+#endif
+	}
+
+	*mop = targ;
+
 	return targ;
 }
 
diff --git a/src/p_tick.h b/src/p_tick.h
index 594bbc7afb608ce88a54b8f63ae7e5494f96af4f..bbc227e081433652a9a3d79b8ab0513c531677bc 100644
--- a/src/p_tick.h
+++ b/src/p_tick.h
@@ -14,6 +14,8 @@
 #ifndef __P_TICK__
 #define __P_TICK__
 
+#include "doomdef.h"
+
 #ifdef __GNUG__
 #pragma interface
 #endif
@@ -28,6 +30,17 @@ void P_Ticker(boolean run);
 void P_PreTicker(INT32 frames);
 void P_DoTeamscrambling(void);
 void P_RemoveThinkerDelayed(thinker_t *thinker); //killed
-mobj_t *P_SetTarget(mobj_t **mo, mobj_t *target);   // killough 11/98
+
+mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target
+#ifdef PARANOIA
+		, const char *source_file, int source_line
+#endif
+);
+
+#ifdef PARANOIA
+#define P_SetTarget(...) P_SetTarget2(__VA_ARGS__, __FILE__, __LINE__)
+#else
+#define P_SetTarget P_SetTarget2
+#endif
 
 #endif
diff --git a/src/p_user.c b/src/p_user.c
index 889e887c14ea2e28898896118b951782312965c8..3b2c60e3a6d744d5da157e0a5952efd5ad0cf3d4 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1855,6 +1855,7 @@ void P_SpawnShieldOrb(player_t *player)
 	{
 		ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY);
 		P_SetTarget(&ov->target, shieldobj);
+		P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
 		P_SetMobjState(ov, shieldobj->info->seestate);
 		P_SetTarget(&shieldobj->tracer, ov);
 	}
@@ -1862,12 +1863,14 @@ void P_SpawnShieldOrb(player_t *player)
 	{
 		ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY);
 		P_SetTarget(&ov->target, shieldobj);
+		P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
 		P_SetMobjState(ov, shieldobj->info->meleestate);
 	}
 	if (shieldobj->info->missilestate)
 	{
 		ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY);
 		P_SetTarget(&ov->target, shieldobj);
+		P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
 		P_SetMobjState(ov, shieldobj->info->missilestate);
 	}
 	if (player->powers[pw_shield] & SH_FORCE)
@@ -2005,14 +2008,14 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	ghost->standingslope = mobj->standingslope;
 
 	if (mobj->flags2 & MF2_OBJECTFLIP)
-		ghost->flags |= MF2_OBJECTFLIP;
+		ghost->flags2 |= MF2_OBJECTFLIP;
 
 	if (mobj->player && mobj->player->followmobj)
 	{
 		mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj);
 		P_SetTarget(&ghost2->tracer, ghost);
 		P_SetTarget(&ghost->tracer, ghost2);
-		P_SetTarget(&ghost2->dontdrawforviewmobj, mobj); // Hide the follow-ghost for the non-follow object
+		P_SetTarget(&ghost2->dontdrawforviewmobj, mobj); // Hide the follow-ghost for the non-follow target
 		ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW);
 	}
 
@@ -8717,7 +8720,10 @@ void P_MovePlayer(player_t *player)
 			player->mo->height = P_GetPlayerHeight(player);
 
 		if (player->mo->eflags & MFE_VERTICALFLIP && player->mo->height != oldheight) // adjust z height for reverse gravity, similar to how it's done for scaling
-			player->mo->z -= player->mo->height - oldheight;
+		{
+			player->mo->z     -= player->mo->height - oldheight;
+			player->mo->old_z -= player->mo->height - oldheight; // Snap the Z adjustment, while keeping the Z interpolation
+		}
 
 		// Crush test...
 		if ((player->mo->ceilingz - player->mo->floorz < player->mo->height)
@@ -9287,7 +9293,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 
 		{
 			fixed_t zdist = (player->mo->z + player->mo->height/2) - (mo->z + mo->height/2);
-			dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
+			dist = R_PointToDist2(0, 0, player->mo->x-mo->x, player->mo->y-mo->y);
 			if (bullet)
 			{
 				if ((R_PointToAngle2(0, 0, dist, zdist) + span) > span*2)
@@ -9304,7 +9310,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 					continue;
 			}
 
-			dist = P_AproxDistance(dist, zdist);
+			dist = R_PointToDist2(0, 0, dist, zdist);
 			if (dist > maxdist)
 				continue; // out of range
 		}
@@ -11507,7 +11513,7 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 	}
 
 	fume->movecount = dashmode; // keeps track of previous dashmode value so we know whether Metal is entering or leaving it
-	fume->eflags = (fume->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); // Make sure to flip in reverse gravity!
+	fume->flags2 = (fume->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); // Make sure to flip in reverse gravity!
 	fume->eflags = (fume->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP); // Make sure to flip in reverse gravity!
 
 	// Finally, set its position
@@ -11623,9 +11629,12 @@ void P_PlayerThink(player_t *player)
 
 	cmd = &player->cmd;
 
-	// Add some extra randomization.
-	if (cmd->forwardmove)
-		P_RandomFixed();
+	if (demoplayback && demo_forwardmove_rng)
+	{
+		// Smelly demo backwards compatibility
+		if (cmd->forwardmove)
+			P_RandomFixed();
+	}
 
 #ifdef PARANOIA
 	if (player->playerstate == PST_REBORN)
@@ -11755,7 +11764,7 @@ void P_PlayerThink(player_t *player)
 			if (!total || ((4*exiting)/total) >= numneeded)
 			{
 				if (server)
-					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+					D_SendExitLevel(false);
 			}
 			else
 				player->exiting = 3;
@@ -11763,7 +11772,7 @@ void P_PlayerThink(player_t *player)
 		else
 		{
 			if (server)
-				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+				D_SendExitLevel(false);
 		}
 	}
 
@@ -11861,7 +11870,7 @@ void P_PlayerThink(player_t *player)
 			mo2 = (mobj_t *)th;
 
 			if (!(mo2->type == MT_RING || mo2->type == MT_COIN
-				|| mo2->type == MT_BLUESPHERE || mo2->type == MT_BOMBSPHERE
+				|| mo2->type == MT_BLUESPHERE // || mo2->type == MT_BOMBSPHERE
 				|| mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR))
 				continue;
 
diff --git a/src/r_bbox.c b/src/r_bbox.c
index 59d0893c4bfe32bd0307573b9406bbff2df28924..cf417ec37639477b43a5a5e5035b059dc81490b4 100644
--- a/src/r_bbox.c
+++ b/src/r_bbox.c
@@ -34,7 +34,7 @@ static CV_PossibleValue_t renderhitbox_cons_t[] = {
 	{RENDERHITBOX_RINGS, "Rings"},
 	{0}};
 
-consvar_t cv_renderhitbox = CVAR_INIT ("renderhitbox", "Off", CV_CHEAT, renderhitbox_cons_t, NULL);
+consvar_t cv_renderhitbox = CVAR_INIT ("renderhitbox", "Off", CV_CHEAT|CV_NOTINNET, renderhitbox_cons_t, NULL);
 consvar_t cv_renderhitboxinterpolation = CVAR_INIT ("renderhitbox_interpolation", "On", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_renderhitboxgldepth = CVAR_INIT ("renderhitbox_gldepth", "Off", CV_SAVE, CV_OnOff, NULL);
 
@@ -129,9 +129,6 @@ draw_bbox_row
 	x1 = a->x;
 	x2 = b->x;
 
-	if (x2 >= viewwidth)
-		x2 = viewwidth - 1;
-
 	if (x1 == x2 || x1 >= viewwidth || x2 < 0)
 		return;
 
@@ -159,6 +156,9 @@ draw_bbox_row
 		x1 = 0;
 	}
 
+	if (x2 >= viewwidth)
+		x2 = viewwidth - 1;
+
 	while (x1 < x2)
 	{
 		raster_bbox_seg(x1, y1, s1, bb->color);
@@ -268,6 +268,9 @@ boolean R_ThingBoundingBoxVisible(mobj_t *thing)
 {
 	INT32 cvmode = cv_renderhitbox.value;
 
+	if (multiplayer) // No hitboxes in multiplayer to avoid cheating
+		return false;
+
 	// Do not render bbox for these
 	switch (thing->type)
 	{
diff --git a/src/r_defs.h b/src/r_defs.h
index a9b9a4a0835a5969b93e4def8a2f231b5d565991..dfd2d6d708f8a5a9dae0da401719ad1d3eb24235 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -757,12 +757,12 @@ typedef struct drawseg_s
 	// Pointers to lists for sprite clipping, all three adjusted so [x1] is first value.
 	INT16 *sprtopclip;
 	INT16 *sprbottomclip;
-	INT16 *maskedtexturecol;
+	fixed_t *maskedtexturecol;
 
 	struct visplane_s *ffloorplanes[MAXFFLOORS];
 	INT32 numffloorplanes;
 	struct ffloor_s *thicksides[MAXFFLOORS];
-	INT16 *thicksidecol;
+	fixed_t *thicksidecol;
 	INT32 numthicksides;
 	fixed_t frontscale[MAXVIDWIDTH];
 
diff --git a/src/r_draw.c b/src/r_draw.c
index b0467e4f728d4cf757b53484a3d5ca4fda9d91cc..df9e1a4608b568706452df29bbc347adef075b01 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -179,8 +179,6 @@ CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
 void R_InitTranslucencyTables(void)
 {
 	// Load here the transparency lookup tables 'TRANSx0'
-	// NOTE: the TRANSx0 resources MUST BE aligned on 64k for the asm
-	// optimised code (in other words, transtables pointer low word is 0)
 	transtables = Z_MallocAlign(NUMTRANSTABLES*0x10000, PU_STATIC,
 		NULL, 16);
 
diff --git a/src/r_draw.h b/src/r_draw.h
index ea03a8e3d53e059570822a0119ee6431f45d105a..0103ed82782b22c7a51beb10c20473a3e8ba3787 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -225,18 +225,6 @@ void R_DrawTiltedTransSolidColorSpan_8(void);
 void R_DrawWaterSolidColorSpan_8(void);
 void R_DrawTiltedWaterSolidColorSpan_8(void);
 
-#ifdef USEASM
-void ASMCALL R_DrawColumn_8_ASM(void);
-void ASMCALL R_DrawShadeColumn_8_ASM(void);
-void ASMCALL R_DrawTranslucentColumn_8_ASM(void);
-void ASMCALL R_Draw2sMultiPatchColumn_8_ASM(void);
-
-void ASMCALL R_DrawColumn_8_MMX(void);
-
-void ASMCALL R_Draw2sMultiPatchColumn_8_MMX(void);
-void ASMCALL R_DrawSpan_8_MMX(void);
-#endif
-
 // ------------------
 // 16bpp DRAWING CODE
 // ------------------
diff --git a/src/r_fps.c b/src/r_fps.c
index c6eb5948212972d87d1e0079c668483fb927de94..de450aaa7f465b8d47891baec585c757de361c60 100644
--- a/src/r_fps.c
+++ b/src/r_fps.c
@@ -292,6 +292,8 @@ void R_InterpolateMobjState(mobj_t *mobj, fixed_t frac, interpmobjstate_t *out)
 		out->y = mobj->y;
 		out->z = mobj->z;
 		out->scale = mobj->scale;
+		out->radius = mobj->radius;
+		out->height = mobj->height;
 		out->subsector = mobj->subsector;
 		out->angle = mobj->player ? mobj->player->drawangle : mobj->angle;
 		out->pitch = mobj->pitch;
@@ -307,10 +309,22 @@ void R_InterpolateMobjState(mobj_t *mobj, fixed_t frac, interpmobjstate_t *out)
 	out->x = R_LerpFixed(mobj->old_x, mobj->x, frac);
 	out->y = R_LerpFixed(mobj->old_y, mobj->y, frac);
 	out->z = R_LerpFixed(mobj->old_z, mobj->z, frac);
-	out->scale = mobj->resetinterp ? mobj->scale : R_LerpFixed(mobj->old_scale, mobj->scale, frac);
 	out->spritexscale = mobj->resetinterp ? mobj->spritexscale : R_LerpFixed(mobj->old_spritexscale, mobj->spritexscale, frac);
 	out->spriteyscale = mobj->resetinterp ? mobj->spriteyscale : R_LerpFixed(mobj->old_spriteyscale, mobj->spriteyscale, frac);
 
+	if (mobj->scale == mobj->old_scale) // Tiny optimisation - scale is usually unchanging, so let's skip a lerp, two FixedMuls, and two FixedDivs
+	{
+		out->scale = mobj->scale;
+		out->radius = mobj->radius;
+		out->height = mobj->height;
+	}
+	else
+	{
+		out->scale = R_LerpFixed(mobj->old_scale, mobj->scale, frac);
+		out->radius = FixedMul(mobj->radius, FixedDiv(out->scale, mobj->scale));
+		out->height = FixedMul(mobj->height, FixedDiv(out->scale, mobj->scale));
+	}
+
 	// Sprite offsets are not interpolated until we have a way to interpolate them explicitly in Lua.
 	// It seems existing mods visually break more often than not if it is interpolated.
 	out->spritexoffset = mobj->spritexoffset;
@@ -340,6 +354,8 @@ void R_InterpolatePrecipMobjState(precipmobj_t *mobj, fixed_t frac, interpmobjst
 		out->y = mobj->y;
 		out->z = mobj->z;
 		out->scale = FRACUNIT;
+		out->radius = mobj->radius;
+		out->height = mobj->height;
 		out->subsector = mobj->subsector;
 		out->angle = mobj->angle;
 		out->pitch = mobj->angle;
@@ -356,6 +372,8 @@ void R_InterpolatePrecipMobjState(precipmobj_t *mobj, fixed_t frac, interpmobjst
 	out->y = R_LerpFixed(mobj->old_y, mobj->y, frac);
 	out->z = R_LerpFixed(mobj->old_z, mobj->z, frac);
 	out->scale = FRACUNIT;
+	out->radius = mobj->radius;
+	out->height = mobj->height;
 	out->spritexscale = R_LerpFixed(mobj->old_spritexscale, mobj->spritexscale, frac);
 	out->spriteyscale = R_LerpFixed(mobj->old_spriteyscale, mobj->spriteyscale, frac);
 	out->spritexoffset = R_LerpFixed(mobj->old_spritexoffset, mobj->spritexoffset, frac);
diff --git a/src/r_fps.h b/src/r_fps.h
index 9a8bfa38aeccfcab3dcd0654cd1380ac49f39d4f..f43d29f300a8a6707a3e4c6f5fa24e1e3f0ea37f 100644
--- a/src/r_fps.h
+++ b/src/r_fps.h
@@ -63,6 +63,8 @@ typedef struct {
 	angle_t roll;
 	angle_t spriteroll;
 	fixed_t scale;
+	fixed_t radius;
+	fixed_t height;
 	fixed_t spritexscale;
 	fixed_t spriteyscale;
 	fixed_t spritexoffset;
diff --git a/src/r_main.c b/src/r_main.c
index 55bb9c4ffefdfee11f38ec50a28cff7de2748ffb..54f7d7639e775f73b1d8de57d9923745eb9ff493 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -41,16 +41,6 @@
 #include "hardware/hw_main.h"
 #endif
 
-//profile stuff ---------------------------------------------------------
-//#define TIMING
-#ifdef TIMING
-#include "p5prof.h"
-INT64 mycount;
-INT64 mytotal = 0;
-//unsigned long  nombre = 100000;
-#endif
-//profile stuff ---------------------------------------------------------
-
 // Fineangles in the SCREENWIDTH wide window.
 #define FIELDOFVIEW 2048
 
@@ -157,7 +147,8 @@ consvar_t cv_flipcam2 = CVAR_INIT ("flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT,
 
 consvar_t cv_shadow = CVAR_INIT ("shadow", "On", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_skybox = CVAR_INIT ("skybox", "On", CV_SAVE, CV_OnOff, NULL);
-consvar_t cv_ffloorclip = CVAR_INIT ("ffloorclip", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_ffloorclip = CVAR_INIT ("r_ffloorclip", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_spriteclip = CVAR_INIT ("r_spriteclip", "On", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_allowmlook = CVAR_INIT ("allowmlook", "Yes", CV_NETVAR|CV_ALLOWLUA, CV_YesNo, NULL);
 consvar_t cv_showhud = CVAR_INIT ("showhud", "Yes", CV_CALL|CV_ALLOWLUA,  CV_YesNo, R_SetViewSize);
 consvar_t cv_translucenthud = CVAR_INIT ("translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL);
@@ -1479,6 +1470,7 @@ void R_RenderPlayerView(player_t *player)
 		R_ClearClipSegs();
 	}
 	R_ClearDrawSegs();
+	R_ClearSegTables();
 	R_ClearSprites();
 	Portal_InitList();
 
@@ -1489,29 +1481,17 @@ void R_RenderPlayerView(player_t *player)
 
 	Mask_Pre(&masks[nummasks - 1]);
 	curdrawsegs = ds_p;
-//profile stuff ---------------------------------------------------------
-#ifdef TIMING
-	mytotal = 0;
-	ProfZeroTimer();
-#endif
 	ps_numbspcalls.value.i = ps_numpolyobjects.value.i = ps_numdrawnodes.value.i = 0;
 	PS_START_TIMING(ps_bsptime);
 	R_RenderBSPNode((INT32)numnodes - 1);
 	PS_STOP_TIMING(ps_bsptime);
-	ps_numsprites.value.i = visspritecount;
-#ifdef TIMING
-	RDMSR(0x10, &mycount);
-	mytotal += mycount; // 64bit add
-
-	CONS_Debug(DBG_RENDER, "RenderBSPNode: 0x%d %d\n", *((INT32 *)&mytotal + 1), (INT32)mytotal);
-#endif
-//profile stuff ---------------------------------------------------------
 	Mask_Post(&masks[nummasks - 1]);
 
 	PS_START_TIMING(ps_sw_spritecliptime);
 	R_ClipSprites(drawsegs, NULL);
 	PS_STOP_TIMING(ps_sw_spritecliptime);
 
+	ps_numsprites.value.i = numvisiblesprites;
 
 	// Add skybox portals caused by sky visplanes.
 	if (cv_skybox.value && skyboxmo[0])
@@ -1602,6 +1582,7 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_shadow);
 	CV_RegisterVar(&cv_skybox);
 	CV_RegisterVar(&cv_ffloorclip);
+	CV_RegisterVar(&cv_spriteclip);
 
 	CV_RegisterVar(&cv_cam_dist);
 	CV_RegisterVar(&cv_cam_still);
diff --git a/src/r_main.h b/src/r_main.h
index f08070d0f387b544c9b0b5089e65b6251999db99..a6fb42ba2410ba42c8adc7ba84ef8fde21e0802f 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -114,7 +114,7 @@ extern consvar_t cv_chasecam, cv_chasecam2;
 extern consvar_t cv_flipcam, cv_flipcam2;
 
 extern consvar_t cv_shadow;
-extern consvar_t cv_ffloorclip;
+extern consvar_t cv_ffloorclip, cv_spriteclip;
 extern consvar_t cv_translucency;
 extern consvar_t cv_drawdist, cv_drawdist_nights, cv_drawdist_precip;
 extern consvar_t cv_fov;
diff --git a/src/r_plane.c b/src/r_plane.c
index c568484b6ed7e71335a665a40710753d2608133f..29ce26b292e5ea529d937aded0028e41a8d294ba 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -53,10 +53,6 @@ INT32 numffloors;
 #define visplane_hash(picnum,lightlevel,height) \
   ((unsigned)((picnum)*3+(lightlevel)+(height)*7) & VISPLANEHASHMASK)
 
-//SoM: 3/23/2000: Use boom opening limit removal
-size_t maxopenings;
-INT16 *openings, *lastopening; /// \todo free leak
-
 //
 // Clip values are the solid pixel bounding the range.
 //  floorclip starts out SCREENHEIGHT
@@ -366,8 +362,6 @@ void R_ClearPlanes(void)
 		freehead = &(*freehead)->next;
 	}
 
-	lastopening = openings;
-
 	// texture calculation
 	memset(cachedheight, 0, sizeof (cachedheight));
 }
diff --git a/src/r_plane.h b/src/r_plane.h
index 9870a43e26286e5e888a13f20fa72ecf8b38be08..917e8b041b75775016dbc2a1e10b8cc6ad7a3778 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -60,9 +60,6 @@ extern visplane_t *floorplane;
 extern visplane_t *ceilingplane;
 
 // Visplane related.
-extern INT16 *lastopening, *openings;
-extern size_t maxopenings;
-
 extern INT16 floorclip[MAXVIDWIDTH], ceilingclip[MAXVIDWIDTH];
 extern fixed_t frontscale[MAXVIDWIDTH], yslopetab[MAXVIDHEIGHT*16];
 extern fixed_t cachedheight[MAXVIDHEIGHT];
diff --git a/src/r_segs.c b/src/r_segs.c
index facab62ab76479faeaabccc5a4f14a64446db40b..9af83f0c7ff8af10057f253a5419dca41c86638c 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -71,9 +71,22 @@ static fixed_t topfrac, topstep;
 static fixed_t bottomfrac, bottomstep;
 
 static lighttable_t **walllights;
-static INT16 *maskedtexturecol;
+static fixed_t *maskedtexturecol;
 static fixed_t *maskedtextureheight = NULL;
 
+//SoM: 3/23/2000: Use boom opening limit removal
+static size_t numopenings;
+static INT16 *openings, *lastopening;
+
+static size_t texturecolumntablesize;
+static fixed_t *texturecolumntable, *curtexturecolumntable;
+
+void R_ClearSegTables(void)
+{
+	lastopening = openings;
+	curtexturecolumntable = texturecolumntable;
+}
+
 // ==========================================================================
 // R_RenderMaskedSegRange
 // ==========================================================================
@@ -350,170 +363,115 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 				dc_texturemid += (textureheight[texnum])*times + textureheight[texnum];
 			else
 				dc_texturemid -= (textureheight[texnum])*times;
-			// calculate lighting
-			if (maskedtexturecol[dc_x] != INT16_MAX)
+
+			// Check for overflows first
+			overflow_test = (INT64)centeryfrac - (((INT64)dc_texturemid*spryscale)>>FRACBITS);
+			if (overflow_test < 0) overflow_test = -overflow_test;
+			if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL)
 			{
-				// Check for overflows first
-				overflow_test = (INT64)centeryfrac - (((INT64)dc_texturemid*spryscale)>>FRACBITS);
-				if (overflow_test < 0) overflow_test = -overflow_test;
-				if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL)
+				// Eh, no, go away, don't waste our time
+				if (dc_numlights)
 				{
-					// Eh, no, go away, don't waste our time
-					if (dc_numlights)
+					for (i = 0; i < dc_numlights; i++)
 					{
-						for (i = 0; i < dc_numlights; i++)
-						{
-							rlight = &dc_lightlist[i];
-							rlight->height += rlight->heightstep;
-						}
+						rlight = &dc_lightlist[i];
+						rlight->height += rlight->heightstep;
 					}
-					spryscale += rw_scalestep;
-					continue;
 				}
+				spryscale += rw_scalestep;
+				continue;
+			}
 
-				if (dc_numlights)
-				{
-					lighttable_t **xwalllights;
-
-					sprbotscreen = INT32_MAX;
-					sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale));
-
-					realbot = windowbottom = FixedMul(textureheight[texnum], spryscale) + sprtopscreen;
-					dc_iscale = 0xffffffffu / (unsigned)spryscale;
+			// calculate lighting
+			if (dc_numlights)
+			{
+				lighttable_t **xwalllights;
 
-					// draw the texture
-					col = (column_t *)((UINT8 *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3);
+				sprbotscreen = INT32_MAX;
+				sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 
-					for (i = 0; i < dc_numlights; i++)
-					{
-						rlight = &dc_lightlist[i];
+				realbot = windowbottom = FixedMul(textureheight[texnum], spryscale) + sprtopscreen;
+				dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-						if ((rlight->flags & FOF_NOSHADE))
-							continue;
+				// draw the texture
+				col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
 
-						if (rlight->lightnum < 0)
-							xwalllights = scalelight[0];
-						else if (rlight->lightnum >= LIGHTLEVELS)
-							xwalllights = scalelight[LIGHTLEVELS-1];
-						else
-							xwalllights = scalelight[rlight->lightnum];
+				for (i = 0; i < dc_numlights; i++)
+				{
+					rlight = &dc_lightlist[i];
 
-						pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
+					if ((rlight->flags & FOF_NOSHADE))
+						continue;
 
-						if (pindex >= MAXLIGHTSCALE)
-							pindex = MAXLIGHTSCALE - 1;
+					if (rlight->lightnum < 0)
+						xwalllights = scalelight[0];
+					else if (rlight->lightnum >= LIGHTLEVELS)
+						xwalllights = scalelight[LIGHTLEVELS-1];
+					else
+						xwalllights = scalelight[rlight->lightnum];
 
-						if (rlight->extra_colormap)
-							rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
-						else
-							rlight->rcolormap = xwalllights[pindex];
+					pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-						height = rlight->height;
-						rlight->height += rlight->heightstep;
+					if (pindex >= MAXLIGHTSCALE)
+						pindex = MAXLIGHTSCALE - 1;
 
-						if (height <= windowtop)
-						{
-							dc_colormap = rlight->rcolormap;
-							continue;
-						}
+					if (rlight->extra_colormap)
+						rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
+					else
+						rlight->rcolormap = xwalllights[pindex];
 
-						windowbottom = height;
-						if (windowbottom >= realbot)
-						{
-							windowbottom = realbot;
-							colfunc_2s(col);
-							for (i++; i < dc_numlights; i++)
-							{
-								rlight = &dc_lightlist[i];
-								rlight->height += rlight->heightstep;
-							}
+					height = rlight->height;
+					rlight->height += rlight->heightstep;
 
-							continue;
-						}
-						colfunc_2s(col);
-						windowtop = windowbottom + 1;
+					if (height <= windowtop)
+					{
 						dc_colormap = rlight->rcolormap;
+						continue;
 					}
-					windowbottom = realbot;
-					if (windowtop < windowbottom)
+
+					windowbottom = height;
+					if (windowbottom >= realbot)
+					{
+						windowbottom = realbot;
 						colfunc_2s(col);
+						for (i++; i < dc_numlights; i++)
+						{
+							rlight = &dc_lightlist[i];
+							rlight->height += rlight->heightstep;
+						}
 
-					spryscale += rw_scalestep;
-					continue;
+						continue;
+					}
+					colfunc_2s(col);
+					windowtop = windowbottom + 1;
+					dc_colormap = rlight->rcolormap;
 				}
+				windowbottom = realbot;
+				if (windowtop < windowbottom)
+					colfunc_2s(col);
 
-				// calculate lighting
-				pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
-
-				if (pindex >= MAXLIGHTSCALE)
-					pindex = MAXLIGHTSCALE - 1;
-
-				dc_colormap = walllights[pindex];
-
-				if (frontsector->extra_colormap)
-					dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
-
-				sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
-				dc_iscale = 0xffffffffu / (unsigned)spryscale;
-
-				// draw the texture
-				col = (column_t *)((UINT8 *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3);
-
-#if 0 // Disabling this allows inside edges to render below the planes, for until the clipping is fixed to work right when POs are near the camera. -Red
-				if (curline->dontrenderme && curline->polyseg && (curline->polyseg->flags & POF_RENDERPLANES))
-				{
-					fixed_t my_topscreen;
-					fixed_t my_bottomscreen;
-					fixed_t my_yl, my_yh;
+				spryscale += rw_scalestep;
+				continue;
+			}
 
-					my_topscreen = sprtopscreen + spryscale*col->topdelta;
-					my_bottomscreen = sprbotscreen == INT32_MAX ? my_topscreen + spryscale*col->length
-					                                         : sprbotscreen + spryscale*col->length;
+			// calculate lighting
+			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-					my_yl = (my_topscreen+FRACUNIT-1)>>FRACBITS;
-					my_yh = (my_bottomscreen-1)>>FRACBITS;
-	//				CONS_Debug(DBG_RENDER, "my_topscreen: %d\nmy_bottomscreen: %d\nmy_yl: %d\nmy_yh: %d\n", my_topscreen, my_bottomscreen, my_yl, my_yh);
+			if (pindex >= MAXLIGHTSCALE)
+				pindex = MAXLIGHTSCALE - 1;
 
-					if (numffloors)
-					{
-						INT32 top = my_yl;
-						INT32 bottom = my_yh;
+			dc_colormap = walllights[pindex];
 
-						for (i = 0; i < numffloors; i++)
-						{
-							if (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg)
-								continue;
+			if (frontsector->extra_colormap)
+				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
 
-							if (ffloor[i].height < viewz)
-							{
-								INT32 top_w = ffloor[i].plane->top[dc_x];
+			sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
+			dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-	//							CONS_Debug(DBG_RENDER, "Leveltime : %d\n", leveltime);
-	//							CONS_Debug(DBG_RENDER, "Top is %d, top_w is %d\n", top, top_w);
-								if (top_w < top)
-								{
-									ffloor[i].plane->top[dc_x] = (INT16)top;
-									ffloor[i].plane->picnum = 0;
-								}
-	//							CONS_Debug(DBG_RENDER, "top_w is now %d\n", ffloor[i].plane->top[dc_x]);
-							}
-							else if (ffloor[i].height > viewz)
-							{
-								INT32 bottom_w = ffloor[i].plane->bottom[dc_x];
+			// draw the texture
+			col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
+			colfunc_2s(col);
 
-								if (bottom_w > bottom)
-								{
-									ffloor[i].plane->bottom[dc_x] = (INT16)bottom;
-									ffloor[i].plane->picnum = 0;
-								}
-							}
-						}
-					}
-				}
-				else
-#endif
-					colfunc_2s(col);
-			}
 			spryscale += rw_scalestep;
 		}
 	}
@@ -857,183 +815,182 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	// draw the columns
 	for (dc_x = x1; dc_x <= x2; dc_x++)
 	{
-		if (maskedtexturecol[dc_x] != INT16_MAX)
+		// skew FOF walls
+		if (ffloortextureslide)
 		{
-			if (ffloortextureslide) { // skew FOF walls
-				if (oldx != -1)
-					dc_texturemid += FixedMul(ffloortextureslide, (maskedtexturecol[oldx]-maskedtexturecol[dc_x])<<FRACBITS);
-				oldx = dc_x;
-			}
-			// Calculate bounds
-			// clamp the values if necessary to avoid overflows and rendering glitches caused by them
+			if (oldx != -1)
+				dc_texturemid += FixedMul(ffloortextureslide, maskedtexturecol[oldx]-maskedtexturecol[dc_x]);
+			oldx = dc_x;
+		}
 
-			if      (top_frac > (INT64)CLAMPMAX) sprtopscreen = windowtop = CLAMPMAX;
-			else if (top_frac > (INT64)CLAMPMIN) sprtopscreen = windowtop = (fixed_t)top_frac;
-			else                                 sprtopscreen = windowtop = CLAMPMIN;
-			if      (bottom_frac > (INT64)CLAMPMAX) sprbotscreen = windowbottom = CLAMPMAX;
-			else if (bottom_frac > (INT64)CLAMPMIN) sprbotscreen = windowbottom = (fixed_t)bottom_frac;
-			else                                    sprbotscreen = windowbottom = CLAMPMIN;
+		// Calculate bounds
+		// clamp the values if necessary to avoid overflows and rendering glitches caused by them
+		if      (top_frac > (INT64)CLAMPMAX) sprtopscreen = windowtop = CLAMPMAX;
+		else if (top_frac > (INT64)CLAMPMIN) sprtopscreen = windowtop = (fixed_t)top_frac;
+		else                                 sprtopscreen = windowtop = CLAMPMIN;
+		if      (bottom_frac > (INT64)CLAMPMAX) sprbotscreen = windowbottom = CLAMPMAX;
+		else if (bottom_frac > (INT64)CLAMPMIN) sprbotscreen = windowbottom = (fixed_t)bottom_frac;
+		else                                    sprbotscreen = windowbottom = CLAMPMIN;
 
-			top_frac += top_step;
-			bottom_frac += bottom_step;
+		top_frac += top_step;
+		bottom_frac += bottom_step;
 
-			// SoM: If column is out of range, why bother with it??
-			if (windowbottom < topbounds || windowtop > bottombounds)
+		// SoM: If column is out of range, why bother with it??
+		if (windowbottom < topbounds || windowtop > bottombounds)
+		{
+			if (dc_numlights)
 			{
-				if (dc_numlights)
+				for (i = 0; i < dc_numlights; i++)
 				{
-					for (i = 0; i < dc_numlights; i++)
-					{
-						rlight = &dc_lightlist[i];
-						rlight->height += rlight->heightstep;
-						if (rlight->flags & FOF_CUTLEVEL)
-							rlight->botheight += rlight->botheightstep;
-					}
+					rlight = &dc_lightlist[i];
+					rlight->height += rlight->heightstep;
+					if (rlight->flags & FOF_CUTLEVEL)
+						rlight->botheight += rlight->botheightstep;
 				}
-				spryscale += rw_scalestep;
-				continue;
 			}
+			spryscale += rw_scalestep;
+			continue;
+		}
 
-			dc_iscale = 0xffffffffu / (unsigned)spryscale;
+		dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-			// Get data for the column
-			col = (column_t *)((UINT8 *)R_GetColumn(texnum,maskedtexturecol[dc_x]) - 3);
+		// Get data for the column
+		col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
 
-			// SoM: New code does not rely on R_DrawColumnShadowed_8 which
-			// will (hopefully) put less strain on the stack.
-			if (dc_numlights)
-			{
-				lighttable_t **xwalllights;
-				fixed_t height;
-				fixed_t bheight = 0;
-				INT32 solid = 0;
-				INT32 lighteffect = 0;
+		// SoM: New code does not rely on R_DrawColumnShadowed_8 which
+		// will (hopefully) put less strain on the stack.
+		if (dc_numlights)
+		{
+			lighttable_t **xwalllights;
+			fixed_t height;
+			fixed_t bheight = 0;
+			INT32 solid = 0;
+			INT32 lighteffect = 0;
 
-				for (i = 0; i < dc_numlights; i++)
+			for (i = 0; i < dc_numlights; i++)
+			{
+				// Check if the current light effects the colormap/lightlevel
+				rlight = &dc_lightlist[i];
+				lighteffect = !(dc_lightlist[i].flags & FOF_NOSHADE);
+				if (lighteffect)
 				{
-					// Check if the current light effects the colormap/lightlevel
-					rlight = &dc_lightlist[i];
-					lighteffect = !(dc_lightlist[i].flags & FOF_NOSHADE);
-					if (lighteffect)
-					{
-						lightnum = rlight->lightnum;
-
-						if (lightnum < 0)
-							xwalllights = scalelight[0];
-						else if (lightnum >= LIGHTLEVELS)
-							xwalllights = scalelight[LIGHTLEVELS-1];
-						else
-							xwalllights = scalelight[lightnum];
-
-						pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
+					lightnum = rlight->lightnum;
 
-						if (pindex >= MAXLIGHTSCALE)
-							pindex = MAXLIGHTSCALE-1;
+					if (lightnum < 0)
+						xwalllights = scalelight[0];
+					else if (lightnum >= LIGHTLEVELS)
+						xwalllights = scalelight[LIGHTLEVELS-1];
+					else
+						xwalllights = scalelight[lightnum];
 
-						if (pfloor->fofflags & FOF_FOG)
-						{
-							if (pfloor->master->frontsector->extra_colormap)
-								rlight->rcolormap = pfloor->master->frontsector->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
-							else
-								rlight->rcolormap = xwalllights[pindex];
-						}
-						else
-						{
-							if (rlight->extra_colormap)
-								rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
-							else
-								rlight->rcolormap = xwalllights[pindex];
-						}
-					}
+					pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-					solid = 0; // don't carry over solid-cutting flag from the previous light
+					if (pindex >= MAXLIGHTSCALE)
+						pindex = MAXLIGHTSCALE-1;
 
-					// Check if the current light can cut the current 3D floor.
-					if (rlight->flags & FOF_CUTSOLIDS && !(pfloor->fofflags & FOF_EXTRA))
-						solid = 1;
-					else if (rlight->flags & FOF_CUTEXTRA && pfloor->fofflags & FOF_EXTRA)
+					if (pfloor->fofflags & FOF_FOG)
 					{
-						if (rlight->flags & FOF_EXTRA)
-						{
-							// The light is from an extra 3D floor... Check the flags so
-							// there are no undesired cuts.
-							if ((rlight->flags & (FOF_FOG|FOF_SWIMMABLE)) == (pfloor->fofflags & (FOF_FOG|FOF_SWIMMABLE)))
-								solid = 1;
-						}
+						if (pfloor->master->frontsector->extra_colormap)
+							rlight->rcolormap = pfloor->master->frontsector->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
 						else
-							solid = 1;
+							rlight->rcolormap = xwalllights[pindex];
 					}
 					else
-						solid = 0;
-
-					height = rlight->height;
-					rlight->height += rlight->heightstep;
-
-					if (solid)
 					{
-						bheight = rlight->botheight - (FRACUNIT >> 1);
-						rlight->botheight += rlight->botheightstep;
+						if (rlight->extra_colormap)
+							rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
+						else
+							rlight->rcolormap = xwalllights[pindex];
 					}
+				}
 
-					if (height <= windowtop)
-					{
-						if (lighteffect)
-							dc_colormap = rlight->rcolormap;
-						if (solid && windowtop < bheight)
-							windowtop = bheight;
-						continue;
-					}
+				solid = 0; // don't carry over solid-cutting flag from the previous light
 
-					windowbottom = height;
-					if (windowbottom >= sprbotscreen)
+				// Check if the current light can cut the current 3D floor.
+				if (rlight->flags & FOF_CUTSOLIDS && !(pfloor->fofflags & FOF_EXTRA))
+					solid = 1;
+				else if (rlight->flags & FOF_CUTEXTRA && pfloor->fofflags & FOF_EXTRA)
+				{
+					if (rlight->flags & FOF_EXTRA)
 					{
-						windowbottom = sprbotscreen;
-						// draw the texture
-						colfunc_2s (col);
-						for (i++; i < dc_numlights; i++)
-						{
-							rlight = &dc_lightlist[i];
-							rlight->height += rlight->heightstep;
-							if (rlight->flags & FOF_CUTLEVEL)
-								rlight->botheight += rlight->botheightstep;
-						}
-						continue;
+						// The light is from an extra 3D floor... Check the flags so
+						// there are no undesired cuts.
+						if ((rlight->flags & (FOF_FOG|FOF_SWIMMABLE)) == (pfloor->fofflags & (FOF_FOG|FOF_SWIMMABLE)))
+							solid = 1;
 					}
-					// draw the texture
-					colfunc_2s (col);
-					if (solid)
-						windowtop = bheight;
 					else
-						windowtop = windowbottom + 1;
+						solid = 1;
+				}
+				else
+					solid = 0;
+
+				height = rlight->height;
+				rlight->height += rlight->heightstep;
+
+				if (solid)
+				{
+					bheight = rlight->botheight - (FRACUNIT >> 1);
+					rlight->botheight += rlight->botheightstep;
+				}
+
+				if (height <= windowtop)
+				{
 					if (lighteffect)
 						dc_colormap = rlight->rcolormap;
+					if (solid && windowtop < bheight)
+						windowtop = bheight;
+					continue;
 				}
-				windowbottom = sprbotscreen;
-				// draw the texture, if there is any space left
-				if (windowtop < windowbottom)
-					colfunc_2s (col);
 
-				spryscale += rw_scalestep;
-				continue;
+				windowbottom = height;
+				if (windowbottom >= sprbotscreen)
+				{
+					windowbottom = sprbotscreen;
+					// draw the texture
+					colfunc_2s (col);
+					for (i++; i < dc_numlights; i++)
+					{
+						rlight = &dc_lightlist[i];
+						rlight->height += rlight->heightstep;
+						if (rlight->flags & FOF_CUTLEVEL)
+							rlight->botheight += rlight->botheightstep;
+					}
+					continue;
+				}
+				// draw the texture
+				colfunc_2s (col);
+				if (solid)
+					windowtop = bheight;
+				else
+					windowtop = windowbottom + 1;
+				if (lighteffect)
+					dc_colormap = rlight->rcolormap;
 			}
+			windowbottom = sprbotscreen;
+			// draw the texture, if there is any space left
+			if (windowtop < windowbottom)
+				colfunc_2s (col);
 
-			// calculate lighting
-			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
+			spryscale += rw_scalestep;
+			continue;
+		}
 
-			if (pindex >= MAXLIGHTSCALE)
-				pindex = MAXLIGHTSCALE - 1;
+		// calculate lighting
+		pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-			dc_colormap = walllights[pindex];
+		if (pindex >= MAXLIGHTSCALE)
+			pindex = MAXLIGHTSCALE - 1;
 
-			if (pfloor->fofflags & FOF_FOG && pfloor->master->frontsector->extra_colormap)
-				dc_colormap = pfloor->master->frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
-			else if (frontsector->extra_colormap)
-				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
+		dc_colormap = walllights[pindex];
 
-			// draw the texture
-			colfunc_2s (col);
-			spryscale += rw_scalestep;
-		}
+		if (pfloor->fofflags & FOF_FOG && pfloor->master->frontsector->extra_colormap)
+			dc_colormap = pfloor->master->frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
+		else if (frontsector->extra_colormap)
+			dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
+
+		// draw the texture
+		colfunc_2s (col);
+		spryscale += rw_scalestep;
 	}
 	colfunc = colfuncs[BASEDRAWFUNC];
 
@@ -1270,7 +1227,7 @@ static void R_RenderSegLoop (void)
 		}
 		oldtexturecolumn = texturecolumn;
 
-		texturecolumn >>= FRACBITS;
+		INT32 itexturecolumn = texturecolumn >> FRACBITS;
 
 		// texturecolumn and lighting are independent of wall tiers
 		if (segtextured)
@@ -1336,7 +1293,7 @@ static void R_RenderSegLoop (void)
 				dc_yl = yl;
 				dc_yh = yh;
 				dc_texturemid = rw_midtexturemid;
-				dc_source = R_GetColumn(midtexture,texturecolumn + (rw_offset_mid>>FRACBITS));
+				dc_source = R_GetColumn(midtexture, itexturecolumn + (rw_offset_mid>>FRACBITS));
 				dc_texheight = textureheight[midtexture]>>FRACBITS;
 
 				//profile stuff ---------------------------------------------------------
@@ -1397,7 +1354,7 @@ static void R_RenderSegLoop (void)
 						dc_yl = yl;
 						dc_yh = mid;
 						dc_texturemid = rw_toptexturemid;
-						dc_source = R_GetColumn(toptexture,texturecolumn + (rw_offset_top>>FRACBITS));
+						dc_source = R_GetColumn(toptexture, itexturecolumn + (rw_offset_top>>FRACBITS));
 						dc_texheight = textureheight[toptexture]>>FRACBITS;
 						colfunc();
 						ceilingclip[rw_x] = (INT16)mid;
@@ -1433,8 +1390,7 @@ static void R_RenderSegLoop (void)
 						dc_yl = mid;
 						dc_yh = yh;
 						dc_texturemid = rw_bottomtexturemid;
-						dc_source = R_GetColumn(bottomtexture,
-							texturecolumn + (rw_offset_bot>>FRACBITS));
+						dc_source = R_GetColumn(bottomtexture, itexturecolumn + (rw_offset_bot>>FRACBITS));
 						dc_texheight = textureheight[bottomtexture]>>FRACBITS;
 						colfunc();
 						floorclip[rw_x] = (INT16)mid;
@@ -1453,7 +1409,7 @@ static void R_RenderSegLoop (void)
 		{
 			// save texturecol
 			//  for backdrawing of masked mid texture
-			maskedtexturecol[rw_x] = (INT16)(texturecolumn + (rw_offset_mid>>FRACBITS));
+			maskedtexturecol[rw_x] = texturecolumn + rw_offset_mid;
 
 			if (maskedtextureheight != NULL) {
 				maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ?
@@ -1512,6 +1468,73 @@ static INT64 R_CalcSegDist(seg_t* seg, INT64 x2, INT64 y2)
 	}
 }
 
+//SoM: Code to remove limits on openings.
+static void R_AllocClippingTables(size_t range)
+{
+	size_t pos = lastopening - openings;
+	size_t need = range * 2; // for both sprtopclip and sprbottomclip
+
+	if (pos + need < numopenings)
+		return;
+
+	INT16 *oldopenings = openings;
+	INT16 *oldlast = lastopening;
+
+	if (numopenings == 0)
+		numopenings = 16384;
+
+	numopenings += need;
+	openings = Z_Realloc(openings, numopenings * sizeof (*openings), PU_STATIC, NULL);
+	lastopening = openings + pos;
+
+	if (oldopenings == NULL)
+		return;
+
+	// borrowed fix from *cough* zdoom *cough*
+	// [RH] We also need to adjust the openings pointers that
+	//    were already stored in drawsegs.
+	for (drawseg_t *ds = drawsegs; ds < ds_p; ds++)
+	{
+		// Check if it's in range of the openings
+		if (ds->sprtopclip + ds->x1 >= oldopenings && ds->sprtopclip + ds->x1 <= oldlast)
+			ds->sprtopclip = (ds->sprtopclip - oldopenings) + openings;
+		if (ds->sprbottomclip + ds->x1 >= oldopenings && ds->sprbottomclip + ds->x1 <= oldlast)
+			ds->sprbottomclip = (ds->sprbottomclip - oldopenings) + openings;
+	}
+}
+
+static void R_AllocTextureColumnTables(size_t range)
+{
+	size_t pos = curtexturecolumntable - texturecolumntable;
+
+	// For both tables, we reserve exactly an amount of memory that's equivalent to
+	// how many columns the seg will take on the entire screen (think about it)
+	if (pos + range < texturecolumntablesize)
+		return;
+
+	fixed_t *oldtable = texturecolumntable;
+	fixed_t *oldlast = curtexturecolumntable;
+
+	if (texturecolumntablesize == 0)
+		texturecolumntablesize = 16384;
+
+	texturecolumntablesize += range;
+	texturecolumntable = Z_Realloc(texturecolumntable, texturecolumntablesize * sizeof (*texturecolumntable), PU_STATIC, NULL);
+	curtexturecolumntable = texturecolumntable + pos;
+
+	if (oldtable == NULL)
+		return;
+
+	for (drawseg_t *ds = drawsegs; ds < ds_p; ds++)
+	{
+		// Check if it's in range of the tables
+		if (ds->maskedtexturecol + ds->x1 >= oldtable && ds->maskedtexturecol + ds->x1 <= oldlast)
+			ds->maskedtexturecol = (ds->maskedtexturecol - oldtable) + texturecolumntable;
+		if (ds->thicksidecol + ds->x1 >= oldtable && ds->thicksidecol + ds->x1 <= oldlast)
+			ds->thicksidecol = (ds->thicksidecol - oldtable) + texturecolumntable;
+	}
+}
+
 //
 // R_StoreWallRange
 // A wall segment will be drawn
@@ -1580,37 +1603,6 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	ds_p->curline = curline;
 	rw_stopx = stop+1;
 
-	//SoM: Code to remove limits on openings.
-	{
-		size_t pos = lastopening - openings;
-		size_t need = (rw_stopx - start)*4 + pos;
-		if (need > maxopenings)
-		{
-			drawseg_t *ds;  //needed for fix from *cough* zdoom *cough*
-			INT16 *oldopenings = openings;
-			INT16 *oldlast = lastopening;
-
-			do
-				maxopenings = maxopenings ? maxopenings*2 : 16384;
-			while (need > maxopenings);
-			openings = Z_Realloc(openings, maxopenings * sizeof (*openings), PU_STATIC, NULL);
-			lastopening = openings + pos;
-
-			// borrowed fix from *cough* zdoom *cough*
-			// [RH] We also need to adjust the openings pointers that
-			//    were already stored in drawsegs.
-			for (ds = drawsegs; ds < ds_p; ds++)
-			{
-#define ADJUST(p) if (ds->p + ds->x1 >= oldopenings && ds->p + ds->x1 <= oldlast) ds->p = ds->p - oldopenings + openings;
-				ADJUST(maskedtexturecol);
-				ADJUST(sprtopclip);
-				ADJUST(sprbottomclip);
-				ADJUST(thicksidecol);
-#undef ADJUST
-			}
-		}
-	}  // end of code to remove limits on openings
-
 	// calculate scale at both ends and step
 	ds_p->scale1 = rw_scale = R_ScaleFromGlobalAngle(viewangle + xtoviewangle[start]);
 
@@ -2026,6 +2018,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		rw_toptexturemid += sidedef->rowoffset + sidedef->offsety_top;
 		rw_bottomtexturemid += sidedef->rowoffset + sidedef->offsety_bot;
 
+		R_AllocTextureColumnTables(rw_stopx - start);
+
 		// allocate space for masked texture tables
 		if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors))
 		{
@@ -2040,8 +2034,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			//markceiling = markfloor = true;
 			maskedtexture = true;
 
-			ds_p->thicksidecol = maskedtexturecol = lastopening - rw_x;
-			lastopening += rw_stopx - rw_x;
+			ds_p->thicksidecol = maskedtexturecol = curtexturecolumntable - rw_x;
+			curtexturecolumntable += rw_stopx - rw_x;
 
 			lowcut = max(worldbottom, worldlow) + viewz;
 			highcut = min(worldtop, worldhigh) + viewz;
@@ -2224,8 +2218,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			// masked midtexture
 			if (!ds_p->thicksidecol)
 			{
-				ds_p->maskedtexturecol = maskedtexturecol = lastopening - rw_x;
-				lastopening += rw_stopx - rw_x;
+				ds_p->maskedtexturecol = maskedtexturecol = curtexturecolumntable - rw_x;
+				curtexturecolumntable += rw_stopx - rw_x;
 			}
 			else
 				ds_p->maskedtexturecol = ds_p->thicksidecol;
@@ -2737,29 +2731,34 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		ds_p->portalpass = 0;
 
 	// save sprite clipping info
-	if (((ds_p->silhouette & SIL_TOP) || maskedtexture) && !ds_p->sprtopclip)
+	if (maskedtexture || (ds_p->silhouette & (SIL_TOP | SIL_BOTTOM)))
 	{
-		M_Memcpy(lastopening, ceilingclip+start, 2*(rw_stopx - start));
-		ds_p->sprtopclip = lastopening - start;
-		lastopening += rw_stopx - start;
-	}
+		R_AllocClippingTables(rw_stopx - start);
 
-	if (((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) && !ds_p->sprbottomclip)
-	{
-		M_Memcpy(lastopening, floorclip + start, 2*(rw_stopx-start));
-		ds_p->sprbottomclip = lastopening - start;
-		lastopening += rw_stopx - start;
+		if (((ds_p->silhouette & SIL_TOP) || maskedtexture) && !ds_p->sprtopclip)
+		{
+			M_Memcpy(lastopening, ceilingclip + start, 2*(rw_stopx - start));
+			ds_p->sprtopclip = lastopening - start;
+			lastopening += rw_stopx - start;
+		}
+
+		if (((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) && !ds_p->sprbottomclip)
+		{
+			M_Memcpy(lastopening, floorclip + start, 2*(rw_stopx - start));
+			ds_p->sprbottomclip = lastopening - start;
+			lastopening += rw_stopx - start;
+		}
 	}
 
 	if (maskedtexture && !(ds_p->silhouette & SIL_TOP))
 	{
 		ds_p->silhouette |= SIL_TOP;
-		ds_p->tsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MIN: INT32_MAX;
+		ds_p->tsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MIN : INT32_MAX;
 	}
 	if (maskedtexture && !(ds_p->silhouette & SIL_BOTTOM))
 	{
 		ds_p->silhouette |= SIL_BOTTOM;
-		ds_p->bsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MAX: INT32_MIN;
+		ds_p->bsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MAX : INT32_MIN;
 	}
 	ds_p++;
 }
diff --git a/src/r_segs.h b/src/r_segs.h
index 09c68b27e95eb34deb1302762d9d8a50e72e44dc..cad0146748bb4ab77325285a6290fc195d4399bc 100644
--- a/src/r_segs.h
+++ b/src/r_segs.h
@@ -22,5 +22,6 @@ transnum_t R_GetLinedefTransTable(fixed_t alpha);
 void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2);
 void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pffloor);
 void R_StoreWallRange(INT32 start, INT32 stop);
+void R_ClearSegTables(void);
 
 #endif
diff --git a/src/r_skins.c b/src/r_skins.c
index 9443ad49b03d492e6856f4125881def0b1f463e4..72598f38185acf3d5801365e315e54a920e38511 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -113,6 +113,7 @@ static void Sk_SetDefaultValue(skin_t *skin)
 
 	strcpy(skin->realname, "Someone");
 	strcpy(skin->hudname, "???");
+	strcpy(skin->supername, "Someone super");
 
 	skin->starttranscolor = 96;
 	skin->prefcolor = SKINCOLOR_GREEN;
@@ -682,7 +683,7 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile)
 	char *value;
 	size_t size;
 	skin_t *skin;
-	boolean hudname, realname;
+	boolean hudname, realname, supername;
 
 	//
 	// search for all skin markers in pwad
@@ -712,7 +713,7 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile)
 		skin = &skins[numskins];
 		Sk_SetDefaultValue(skin);
 		skin->wadnum = wadnum;
-		hudname = realname = false;
+		hudname = realname = supername = false;
 		// parse
 		stoken = strtok (buf2, "\r\n= ");
 		while (stoken)
@@ -755,7 +756,7 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile)
 					Z_Free(value2);
 				}
 
-				// copy to hudname and fullname as a default.
+				// copy to hudname, realname, and supername as a default.
 				if (!realname)
 				{
 					STRBUFCPY(skin->realname, skin->name);
@@ -771,6 +772,19 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile)
 					strupr(skin->hudname);
 					SYMBOLCONVERT(skin->hudname)
 				}
+				if (!supername)
+				{
+					char superstring[SKINNAMESIZE+7];
+					strcpy(superstring, "Super ");
+					strlcat(superstring, skin->name, sizeof(superstring));
+					STRBUFCPY(skin->supername, superstring);
+				}
+			}
+			else if (!stricmp(stoken, "supername"))
+			{ // Super name (eg. "Super Knuckles")
+				supername = true;
+				STRBUFCPY(skin->supername, value);
+				SYMBOLCONVERT(skin->supername)
 			}
 			else if (!stricmp(stoken, "realname"))
 			{ // Display name (eg. "Knuckles")
@@ -779,6 +793,13 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile)
 				SYMBOLCONVERT(skin->realname)
 				if (!hudname)
 					HUDNAMEWRITE(skin->realname);
+				if (!supername) //copy over default to capitalise the name
+				{
+					char superstring[SKINNAMESIZE+7];
+					strcpy(superstring, "Super ");
+					strlcat(superstring, skin->realname, sizeof(superstring));
+					STRBUFCPY(skin->supername, superstring);
+				}
 			}
 			else if (!stricmp(stoken, "hudname"))
 			{ // Life icon name (eg. "K.T.E")
@@ -831,7 +852,7 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile)
 	char *value;
 	size_t size;
 	skin_t *skin;
-	boolean noskincomplain, realname, hudname;
+	boolean noskincomplain, realname, hudname, supername;
 
 	//
 	// search for all skin patch markers in pwad
@@ -855,7 +876,7 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile)
 		buf2[size] = '\0';
 
 		skin = NULL;
-		noskincomplain = realname = hudname = false;
+		noskincomplain = realname = hudname = supername = false;
 
 		/*
 		Parse. Has more phases than the parser in R_AddSkins because it needs to have the patching name first (no default skin name is acceptible for patching, unlike skin creation)
@@ -894,13 +915,26 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile)
 			else // Get the properties!
 			{
 				// Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines.
-				if (!stricmp(stoken, "realname"))
+				if (!stricmp(stoken, "supername"))
+				{ // Super name (eg. "Super Knuckles")
+					supername = true;
+					STRBUFCPY(skin->supername, value);
+					SYMBOLCONVERT(skin->supername)
+				}
+				else if (!stricmp(stoken, "realname"))
 				{ // Display name (eg. "Knuckles")
 					realname = true;
 					STRBUFCPY(skin->realname, value);
 					SYMBOLCONVERT(skin->realname)
 					if (!hudname)
 						HUDNAMEWRITE(skin->realname);
+					if (!supername) //copy over default to capitalise the name
+					{
+						char superstring[SKINNAMESIZE+7];
+						strcpy(superstring, "Super ");
+						strlcat(superstring, skin->realname, sizeof(superstring));
+						STRBUFCPY(skin->supername, superstring);
+					}
 				}
 				else if (!stricmp(stoken, "hudname"))
 				{ // Life icon name (eg. "K.T.E")
diff --git a/src/r_skins.h b/src/r_skins.h
index 082b690dda8294ed2f250a5aaba118a765073a21..fab6fc12c0f659e8b35f5d111a497850edf127b0 100644
--- a/src/r_skins.h
+++ b/src/r_skins.h
@@ -35,8 +35,9 @@ typedef struct
 	UINT16 wadnum;
 	skinflags_t flags;
 
-	char realname[SKINNAMESIZE+1]; // Display name for level completion.
+	char realname[SKINNAMESIZE+1]; // Display name for level completion
 	char hudname[SKINNAMESIZE+1]; // HUD name to display (officially exactly 5 characters long)
+	char supername[SKINNAMESIZE+7]; // Super name to display when collecting all emeralds
 
 	UINT8 ability; // ability definition
 	UINT8 ability2; // secondary ability definition
diff --git a/src/r_splats.c b/src/r_splats.c
index d182d628ba8fc09ab3d8e898a339f29b3b2d92cb..0b482d7988cc0e3a32f719a0f84a272fdf63d2f3 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -31,20 +31,8 @@ static void prepare_rastertab(void);
 
 static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis);
 
-#ifdef USEASM
-void ASMCALL rasterize_segment_tex_asm(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir);
-#endif
-
 static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir)
 {
-#ifdef USEASM
-	if (R_ASM)
-	{
-		rasterize_segment_tex_asm(x1, y1, x2, y2, tv1, tv2, tc, dir);
-		return;
-	}
-	else
-#endif
 	{
 		fixed_t xs, xe, count;
 		fixed_t dx0, dx1;
@@ -326,9 +314,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 	fixed_t planeheight = 0;
 	fixed_t step;
 
-	int spanfunctype = SPANDRAWFUNC_SPRITE;
-
-	prepare_rastertab();
+	int spanfunctype;
 
 #define RASTERPARAMS(vnum1, vnum2, tv1, tv2, tc, dir) \
     x1 = verts[vnum1].x; \
@@ -379,21 +365,15 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
     if (ry1 > maxy) \
         maxy = ry1;
 
-	// do segment a -> top of texture
-	RASTERPARAMS(3,2,0,pSplat->width-1,0,0);
-	// do segment b -> right side of texture
-	RASTERPARAMS(2,1,0,pSplat->width-1,pSplat->height-1,0);
-	// do segment c -> bottom of texture
-	RASTERPARAMS(1,0,pSplat->width-1,0,pSplat->height-1,0);
-	// do segment d -> left side of texture
-	RASTERPARAMS(0,3,pSplat->width-1,0,0,1);
-
 	ds_source = (UINT8 *)pSplat->pic;
 	ds_flatwidth = pSplat->width;
 	ds_flatheight = pSplat->height;
-	ds_powersoftwo = false;
 
-	if (R_CheckPowersOfTwo())
+	ds_powersoftwo = ds_solidcolor = false;
+
+	if (R_CheckSolidColorFlat())
+		ds_solidcolor = true;
+	else if (R_CheckPowersOfTwo())
 	{
 		R_SetFlatVars(ds_flatwidth * ds_flatheight);
 		ds_powersoftwo = true;
@@ -404,9 +384,8 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 		R_SetTiltedSpan(0);
 		R_SetScaledSlopePlane(pSplat->slope, vis->viewpoint.x, vis->viewpoint.y, vis->viewpoint.z, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, vis->viewpoint.angle, pSplat->angle);
 		R_CalculateSlopeVectors();
-		spanfunctype = SPANDRAWFUNC_TILTEDSPRITE;
 	}
-	else
+	else if (!ds_solidcolor)
 	{
 		planeheight = abs(pSplat->z - vis->viewpoint.z);
 
@@ -441,23 +420,70 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 			ds_colormap = &vis->extra_colormap->colormap[ds_colormap - colormaps];
 	}
 
-	if (vis->transmap)
+	ds_transmap = vis->transmap;
+
+	// Determine which R_DrawWhatever to use
+
+	// Solid color
+	if (ds_solidcolor)
 	{
-		ds_transmap = vis->transmap;
+		UINT16 px = *(UINT16 *)ds_source;
+
+		// Uh, it's not visible.
+		if (!(px & 0xFF00))
+			return;
+
+		// Pixel color is contained in the lower 8 bits (upper 8 are the opacity), so advance the pointer
+		ds_source++;
 
+		if (pSplat->slope)
+		{
+			if (ds_transmap)
+				spanfunctype = SPANDRAWFUNC_TILTEDTRANSSOLID;
+			else
+				spanfunctype = SPANDRAWFUNC_TILTEDSOLID;
+		}
+		else
+		{
+			if (ds_transmap)
+				spanfunctype = SPANDRAWFUNC_TRANSSOLID;
+			else
+				spanfunctype = SPANDRAWFUNC_SOLID;
+		}
+	}
+	// Transparent
+	else if (ds_transmap)
+	{
 		if (pSplat->slope)
 			spanfunctype = SPANDRAWFUNC_TILTEDTRANSSPRITE;
 		else
 			spanfunctype = SPANDRAWFUNC_TRANSSPRITE;
 	}
+	// Opaque
 	else
-		ds_transmap = NULL;
+	{
+		if (pSplat->slope)
+			spanfunctype = SPANDRAWFUNC_TILTEDSPRITE;
+		else
+			spanfunctype = SPANDRAWFUNC_SPRITE;
+	}
 
-	if (ds_powersoftwo)
+	if (ds_powersoftwo || ds_solidcolor)
 		spanfunc = spanfuncs[spanfunctype];
 	else
 		spanfunc = spanfuncs_npo2[spanfunctype];
 
+	prepare_rastertab();
+
+	// do segment a -> top of texture
+	RASTERPARAMS(3,2,0,pSplat->width-1,0,0);
+	// do segment b -> right side of texture
+	RASTERPARAMS(2,1,0,pSplat->width-1,pSplat->height-1,0);
+	// do segment c -> bottom of texture
+	RASTERPARAMS(1,0,pSplat->width-1,0,pSplat->height-1,0);
+	// do segment d -> left side of texture
+	RASTERPARAMS(0,3,pSplat->width-1,0,0,1);
+
 	if (maxy >= vid.height)
 		maxy = vid.height-1;
 
@@ -512,7 +538,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 		if (x2 < x1)
 			continue;
 
-		if (!pSplat->slope)
+		if (!ds_solidcolor && !pSplat->slope)
 		{
 			fixed_t xstep, ystep;
 			fixed_t distance, span;
@@ -561,7 +587,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 		rastertab[y].maxx = INT32_MIN;
 	}
 
-	if (pSplat->angle && !pSplat->slope)
+	if (!ds_solidcolor && pSplat->angle && !pSplat->slope)
 		memset(cachedheight, 0, sizeof(cachedheight));
 }
 
diff --git a/src/r_things.c b/src/r_things.c
index eee284d46f8e2ddb0383b97be71584a801c27386..be9c5cdffe26be0a5482489d9540a50a2a599998 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -80,6 +80,33 @@ static spriteframe_t sprtemp[64];
 static size_t maxframe;
 static const char *spritename;
 
+//
+// Clipping against drawsegs optimization, from prboom-plus
+//
+// TODO: This should be done with proper subsector pass through
+// sprites which would ideally remove the need to do it at all.
+// Unfortunately, SRB2's drawing loop has lots of annoying
+// changes from Doom for portals, which make it hard to implement.
+
+typedef struct drawseg_xrange_item_s
+{
+	INT16 x1, x2;
+	drawseg_t *user;
+} drawseg_xrange_item_t;
+
+typedef struct drawsegs_xrange_s
+{
+	drawseg_xrange_item_t *items;
+	INT32 count;
+} drawsegs_xrange_t;
+
+#define DS_RANGES_COUNT 3
+static drawsegs_xrange_t drawsegs_xranges[DS_RANGES_COUNT];
+
+static drawseg_xrange_item_t *drawsegs_xrange;
+static size_t drawsegs_xrange_size = 0;
+static INT32 drawsegs_xrange_count = 0;
+
 // ==========================================================================
 //
 // Sprite loading routines: support sprites in pwad, dehacked sprite renaming,
@@ -497,7 +524,8 @@ void R_AddSpriteDefs(UINT16 wadnum)
 //
 // GAME FUNCTIONS
 //
-UINT32 visspritecount;
+UINT32 visspritecount, numvisiblesprites;
+
 static UINT32 clippedvissprites;
 static vissprite_t *visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL};
 
@@ -571,7 +599,7 @@ void R_InitSprites(void)
 //
 void R_ClearSprites(void)
 {
-	visspritecount = clippedvissprites = 0;
+	visspritecount = numvisiblesprites = clippedvissprites = 0;
 }
 
 //
@@ -817,6 +845,15 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) return; // ditto
 	}
 
+	// TODO This check should not be necessary. But Papersprites near to the camera will sometimes create invalid values
+	// for the vissprite's startfrac. This happens because they are not depth culled like other sprites.
+	// Someone who is more familiar with papersprites pls check and try to fix <3
+	if (vis->startfrac < 0 || vis->startfrac > (patch->width << FRACBITS))
+	{
+		// never draw vissprites with startfrac out of patch range
+		return;
+	}
+
 	colfunc = colfuncs[BASEDRAWFUNC]; // hack: this isn't resetting properly somewhere.
 	dc_colormap = vis->colormap;
 	dc_translation = R_GetSpriteTranslation(vis);
@@ -860,7 +897,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	frac = vis->startfrac;
 	windowtop = windowbottom = sprbotscreen = INT32_MAX;
 
-	if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES)
+	if (vis->cut & SC_SHADOW && vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES)
 		this_scale = FixedMul(this_scale, ((skin_t *)vis->mobj->skin)->highresscale);
 	if (this_scale <= 0)
 		this_scale = 1;
@@ -870,10 +907,10 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		{
 			vis->scale = FixedMul(vis->scale, this_scale);
 			vis->scalestep = FixedMul(vis->scalestep, this_scale);
-			vis->xiscale = FixedDiv(vis->xiscale,this_scale);
+			vis->xiscale = FixedDiv(vis->xiscale, this_scale);
 			vis->cut |= SC_ISSCALED;
 		}
-		dc_texturemid = FixedDiv(dc_texturemid,this_scale);
+		dc_texturemid = FixedDiv(dc_texturemid, this_scale);
 	}
 
 	spryscale = vis->scale;
@@ -1157,7 +1194,7 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope)
 		R_InterpolateMobjState(thing, FRACUNIT, &interp);
 	}
 
-	halfHeight = interp.z + (thing->height >> 1);
+	halfHeight = interp.z + (interp.height >> 1);
 	floorz = P_GetFloorZ(thing, interp.subsector->sector, interp.x, interp.y, NULL);
 	ceilingz = P_GetCeilingZ(thing, interp.subsector->sector, interp.x, interp.y, NULL);
 
@@ -1221,8 +1258,8 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope)
 		}
 	}
 
-	if (isflipped ? (ceilingz < groundz - (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), thing->radius*3/2)))
-		: (floorz > groundz + (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), thing->radius*3/2))))
+	if (isflipped ? (ceilingz < groundz - (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), interp.radius*3/2)))
+		: (floorz > groundz + (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), interp.radius*3/2))))
 	{
 		groundz = isflipped ? ceilingz : floorz;
 		groundslope = NULL;
@@ -1265,9 +1302,9 @@ static void R_SkewShadowSprite(
 	//CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope);
 
 	if (viewz < groundz)
-		*shadowyscale += FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope);
+		*shadowyscale += FixedMul(FixedMul(interp.radius*2 / spriteheight, scalemul), zslope);
 	else
-		*shadowyscale -= FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope);
+		*shadowyscale -= FixedMul(FixedMul(interp.radius*2 / spriteheight, scalemul), zslope);
 
 	*shadowyscale = abs((*shadowyscale));
 	*shadowskew = xslope;
@@ -1318,20 +1355,18 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 			return;
 	}
 
-	floordiff = abs((isflipped ? thing->height : 0) + interp.z - groundz);
+	floordiff = abs((isflipped ? interp.height : 0) + interp.z - groundz);
 
 	trans = floordiff / (100*FRACUNIT) + 3;
 	if (trans >= 9) return;
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
-	if ((thing->scale != thing->old_scale) && (thing->scale >= FRACUNIT/1024)) // Interpolate shadows when scaling mobjs
-		scalemul = FixedMul(scalemul, FixedDiv(interp.scale, thing->scale));
 
 	patch = W_CachePatchName("DSHADOW", PU_SPRITE);
 	xscale = FixedDiv(projection, tz);
 	yscale = FixedDiv(projectiony, tz);
-	shadowxscale = FixedMul(thing->radius*2, scalemul);
-	shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz));
+	shadowxscale = FixedMul(interp.radius*2, scalemul);
+	shadowyscale = FixedMul(FixedMul(interp.radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz));
 	shadowyscale = min(shadowyscale, shadowxscale) / patch->height;
 	shadowxscale /= patch->width;
 	shadowskew = 0;
@@ -1457,8 +1492,8 @@ static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis)
 	// 0--2
 
 	// start in the (0) corner
-	gx = interp.x - thing->radius - viewx;
-	gy = interp.y - thing->radius - viewy;
+	gx = interp.x - interp.radius - viewx;
+	gy = interp.y - interp.radius - viewy;
 
 	tz = FixedMul(gx, viewcos) + FixedMul(gy, viewsin);
 
@@ -1480,14 +1515,14 @@ static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis)
 	box = R_NewVisSprite();
 	box->mobj = thing;
 	box->mobjflags = thing->flags;
-	box->thingheight = thing->height;
+	box->thingheight = interp.height;
 	box->cut = SC_BBOX;
 
 	box->gx = tx;
 	box->gy = tz;
 
-	box->scale = 2 * FixedMul(thing->radius, viewsin);
-	box->xscale = 2 * FixedMul(thing->radius, viewcos);
+	box->scale  = 2 * FixedMul(interp.radius, viewsin);
+	box->xscale = 2 * FixedMul(interp.radius, viewcos);
 
 	box->pz = interp.z;
 	box->pzt = box->pz + box->thingheight;
@@ -1536,6 +1571,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	fixed_t tr_x, tr_y;
 	fixed_t tx, tz;
 	fixed_t xscale, yscale; //added : 02-02-98 : aaargll..if I were a math-guy!!!
+	fixed_t radius, height; // For drop shadows
 	fixed_t sortscale, sortsplat = 0;
 	fixed_t linkscale = 0;
 	fixed_t sort_x = 0, sort_y = 0, sort_z;
@@ -1611,6 +1647,8 @@ static void R_ProjectSprite(mobj_t *thing)
 	}
 
 	this_scale = interp.scale;
+	radius = interp.radius; // For drop shadows
+	height = interp.height; // Ditto
 
 	// transform the origin point
 	tr_x = interp.x - viewx;
@@ -1731,9 +1769,6 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	I_Assert(lump < max_spritelumps);
 
-	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
-		this_scale = FixedMul(this_scale, ((skin_t *)thing->skin)->highresscale);
-
 	spr_width = spritecachedinfo[lump].width;
 	spr_height = spritecachedinfo[lump].height;
 	spr_offset = spritecachedinfo[lump].offset;
@@ -1783,6 +1818,14 @@ static void R_ProjectSprite(mobj_t *thing)
 	// calculate edges of the shape
 	spritexscale = interp.spritexscale;
 	spriteyscale = interp.spriteyscale;
+
+	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
+	{
+		fixed_t highresscale = ((skin_t *)thing->skin)->highresscale;
+		spritexscale = FixedMul(spritexscale, highresscale);
+		spriteyscale = FixedMul(spriteyscale, highresscale);
+	}
+
 	if (spritexscale < 1 || spriteyscale < 1)
 		return;
 
@@ -1950,6 +1993,8 @@ static void R_ProjectSprite(mobj_t *thing)
 		{
 			R_InterpolateMobjState(thing, FRACUNIT, &tracer_interp);
 		}
+		radius = tracer_interp.radius; // For drop shadows
+		height = tracer_interp.height; // Ditto
 
 		tr_x = (tracer_interp.x + sort_x) - viewx;
 		tr_y = (tracer_interp.y + sort_y) - viewy;
@@ -2041,7 +2086,7 @@ static void R_ProjectSprite(mobj_t *thing)
 				if (abs(groundz-viewz)/tz > 4)
 					return; // Prevent stretchy shadows and possible crashes
 
-				floordiff = abs((isflipped ? caster->height : 0) + casterinterp.z - groundz);
+				floordiff = abs((isflipped ? casterinterp.height : 0) + casterinterp.z - groundz);
 				trans += ((floordiff / (100*FRACUNIT)) + 3);
 				shadowscale = FixedMul(FRACUNIT - floordiff/640, casterinterp.scale);
 			}
@@ -2056,8 +2101,8 @@ static void R_ProjectSprite(mobj_t *thing)
 
 		if (shadowdraw)
 		{
-			spritexscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spritexscale));
-			spriteyscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spriteyscale));
+			spritexscale = FixedMul(radius * 2, FixedMul(shadowscale, spritexscale));
+			spriteyscale = FixedMul(radius * 2, FixedMul(shadowscale, spriteyscale));
 			spriteyscale = FixedMul(spriteyscale, FixedDiv(abs(groundz - viewz), tz));
 			spriteyscale = min(spriteyscale, spritexscale) / patch->height;
 			spritexscale /= patch->width;
@@ -2072,7 +2117,7 @@ static void R_ProjectSprite(mobj_t *thing)
 		{
 			R_SkewShadowSprite(thing, thing->standingslope, groundz, patch->height, shadowscale, &spriteyscale, &sheartan);
 
-			gzt = (isflipped ? (interp.z + thing->height) : interp.z) + patch->height * spriteyscale / 2;
+			gzt = (isflipped ? (interp.z + height) : interp.z) + patch->height * spriteyscale / 2;
 			gz = gzt - patch->height * spriteyscale;
 
 			cut |= SC_SHEAR;
@@ -2087,7 +2132,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			// When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned.
 			// sprite height - sprite topoffset is the proper inverse of the vertical offset, of course.
 			// remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes!
-			gz = interp.z + oldthing->height - FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale));
+			gz = interp.z + interp.height - FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale));
 			gzt = gz + FixedMul(spr_height, FixedMul(spriteyscale, this_scale));
 		}
 		else
@@ -2166,7 +2211,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	vis->gy = interp.y;
 	vis->gz = gz;
 	vis->gzt = gzt;
-	vis->thingheight = thing->height;
+	vis->thingheight = height;
 	vis->pz = interp.z;
 	vis->pzt = vis->pz + vis->thingheight;
 	vis->texturemid = FixedDiv(gzt - viewz, spriteyscale);
@@ -2606,6 +2651,14 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e
 	// bundle linkdraw
 	for (ds = unsorted.prev; ds != &unsorted; ds = ds->prev)
 	{
+		// Remove this sprite if it was determined to not be visible
+		if (ds->cut & SC_NOTVISIBLE)
+		{
+			ds->next->prev = ds->prev;
+			ds->prev->next = ds->next;
+			continue;
+		}
+
 		if (!(ds->cut & SC_LINKDRAW))
 			continue;
 
@@ -2632,21 +2685,27 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e
 				continue;
 
 			// don't connect if the tracer's top is cut off, but lower than the link's top
-			if ((dsfirst->cut & SC_TOP)
-			&& dsfirst->szt > ds->szt)
+			if ((dsfirst->cut & SC_TOP) && dsfirst->szt > ds->szt)
 				continue;
 
 			// don't connect if the tracer's bottom is cut off, but higher than the link's bottom
-			if ((dsfirst->cut & SC_BOTTOM)
-			&& dsfirst->sz < ds->sz)
+			if ((dsfirst->cut & SC_BOTTOM) && dsfirst->sz < ds->sz)
 				continue;
 
+			// If the object isn't visible, then the bounding box isn't either
+			if (ds->cut & SC_BBOX && dsfirst->cut & SC_NOTVISIBLE)
+				ds->cut |= SC_NOTVISIBLE;
+
 			break;
 		}
 
 		// remove from chain
 		ds->next->prev = ds->prev;
 		ds->prev->next = ds->next;
+
+		if (ds->cut & SC_NOTVISIBLE)
+			continue;
+
 		linkedvissprites++;
 
 		if (dsfirst != &unsorted)
@@ -2698,12 +2757,15 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e
 				best = ds;
 			}
 		}
-		best->next->prev = best->prev;
-		best->prev->next = best->next;
-		best->next = vsprsortedhead;
-		best->prev = vsprsortedhead->prev;
-		vsprsortedhead->prev->next = best;
-		vsprsortedhead->prev = best;
+		if (best)
+		{
+			best->next->prev = best->prev;
+			best->prev->next = best->next;
+			best->next = vsprsortedhead;
+			best->prev = vsprsortedhead->prev;
+			vsprsortedhead->prev->next = best;
+			vsprsortedhead->prev = best;
+		}
 	}
 }
 
@@ -3134,9 +3196,47 @@ static void R_HeightSecClip(vissprite_t *spr, INT32 x1, INT32 x2)
 	}
 }
 
+static boolean R_CheckSpriteVisible(vissprite_t *spr, INT32 x1, INT32 x2)
+{
+	INT16 sz = spr->sz;
+	INT16 szt = spr->szt;
+
+	fixed_t texturemid, yscale, scalestep = spr->scalestep;
+	INT32 height;
+
+	if (scalestep)
+	{
+		height = spr->patch->height;
+		yscale = spr->scale;
+		scalestep = FixedMul(scalestep, spr->spriteyscale);
+
+		if (spr->thingscale != FRACUNIT)
+			texturemid = FixedDiv(spr->texturemid, max(spr->thingscale, 1));
+		else
+			texturemid = spr->texturemid;
+	}
+
+	for (INT32 x = x1; x <= x2; x++)
+	{
+		if (scalestep)
+		{
+			fixed_t top = centeryfrac - FixedMul(texturemid, yscale);
+			fixed_t bottom = top + (height * yscale);
+			szt = (INT16)(top >> FRACBITS);
+			sz = (INT16)(bottom >> FRACBITS);
+			yscale += scalestep;
+		}
+
+		if (spr->cliptop[x] < spr->clipbot[x] && sz > spr->cliptop[x] && szt < spr->clipbot[x])
+			return true;
+	}
+
+	return false;
+}
+
 // R_ClipVisSprite
 // Clips vissprites without drawing, so that portals can work. -Red
-void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal)
+static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* portal)
 {
 	drawseg_t *ds;
 	INT32		x;
@@ -3156,21 +3256,23 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 	// Pointer check was originally nonportable
 	// and buggy, by going past LEFT end of array:
 
-	//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
-	for (ds = ds_p; ds-- > dsstart;)
+	// e6y: optimization
+	if (drawsegs_xrange_size)
 	{
-		// determine if the drawseg obscures the sprite
-		if (ds->x1 > x2 ||
-			ds->x2 < x1 ||
-			(!ds->silhouette
-			 && !ds->maskedtexturecol))
-		{
-			// does not cover sprite
-			continue;
-		}
+		const drawseg_xrange_item_t *last = &drawsegs_xrange[drawsegs_xrange_count - 1];
+		drawseg_xrange_item_t *curr = &drawsegs_xrange[-1];
 
-		if (ds->portalpass != 66)
+		while (++curr <= last)
 		{
+			// determine if the drawseg obscures the sprite
+			if (curr->x1 > x2 || curr->x2 < x1)
+			{
+				// does not cover sprite
+				continue;
+			}
+
+			ds = curr->user;
+
 			if (ds->portalpass > 0 && ds->portalpass <= portalrender)
 				continue; // is a portal
 
@@ -3195,43 +3297,43 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 				// seg is behind sprite
 				continue;
 			}
-		}
 
-		r1 = ds->x1 < x1 ? x1 : ds->x1;
-		r2 = ds->x2 > x2 ? x2 : ds->x2;
+			r1 = ds->x1 < x1 ? x1 : ds->x1;
+			r2 = ds->x2 > x2 ? x2 : ds->x2;
 
-		// clip this piece of the sprite
-		silhouette = ds->silhouette;
+			// clip this piece of the sprite
+			silhouette = ds->silhouette;
 
-		if (spr->gz >= ds->bsilheight)
-			silhouette &= ~SIL_BOTTOM;
+			if (spr->gz >= ds->bsilheight)
+				silhouette &= ~SIL_BOTTOM;
 
-		if (spr->gzt <= ds->tsilheight)
-			silhouette &= ~SIL_TOP;
+			if (spr->gzt <= ds->tsilheight)
+				silhouette &= ~SIL_TOP;
 
-		if (silhouette == SIL_BOTTOM)
-		{
-			// bottom sil
-			for (x = r1; x <= r2; x++)
-				if (spr->clipbot[x] == -2)
-					spr->clipbot[x] = ds->sprbottomclip[x];
-		}
-		else if (silhouette == SIL_TOP)
-		{
-			// top sil
-			for (x = r1; x <= r2; x++)
-				if (spr->cliptop[x] == -2)
-					spr->cliptop[x] = ds->sprtopclip[x];
-		}
-		else if (silhouette == (SIL_TOP|SIL_BOTTOM))
-		{
-			// both
-			for (x = r1; x <= r2; x++)
+			if (silhouette == SIL_BOTTOM)
 			{
-				if (spr->clipbot[x] == -2)
-					spr->clipbot[x] = ds->sprbottomclip[x];
-				if (spr->cliptop[x] == -2)
-					spr->cliptop[x] = ds->sprtopclip[x];
+				// bottom sil
+				for (x = r1; x <= r2; x++)
+					if (spr->clipbot[x] == -2)
+						spr->clipbot[x] = ds->sprbottomclip[x];
+			}
+			else if (silhouette == SIL_TOP)
+			{
+				// top sil
+				for (x = r1; x <= r2; x++)
+					if (spr->cliptop[x] == -2)
+						spr->cliptop[x] = ds->sprtopclip[x];
+			}
+			else if (silhouette == (SIL_TOP|SIL_BOTTOM))
+			{
+				// both
+				for (x = r1; x <= r2; x++)
+				{
+					if (spr->clipbot[x] == -2)
+						spr->clipbot[x] = ds->sprbottomclip[x];
+					if (spr->cliptop[x] == -2)
+						spr->cliptop[x] = ds->sprtopclip[x];
+				}
 			}
 		}
 	}
@@ -3275,8 +3377,7 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 			spr->clipbot[x] = (INT16)viewheight;
 
 		if (spr->cliptop[x] == -2)
-			//Fab : 26-04-98: was -1, now clips against console bottom
-			spr->cliptop[x] = (INT16)con_clipviewtop;
+			spr->cliptop[x] = -1;
 	}
 
 	if (portal)
@@ -3301,20 +3402,119 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 			spr->cliptop[x] = -1;
 		}
 	}
+
+	// Check if it'll be visible
+	// Not done for floorsprites.
+	if (cv_spriteclip.value && (spr->cut & SC_SPLAT) == 0)
+	{
+		if (!R_CheckSpriteVisible(spr, x1, x2))
+			spr->cut |= SC_NOTVISIBLE;
+	}
 }
 
 void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
 {
+	const size_t maxdrawsegs = ds_p - drawsegs;
+	const INT32 cx = viewwidth / 2;
+	drawseg_t* ds;
+	INT32 i;
+
+	// e6y
+	// Reducing of cache misses in the following R_DrawSprite()
+	// Makes sense for scenes with huge amount of drawsegs.
+	// ~12% of speed improvement on epic.wad map05
+	for (i = 0; i < DS_RANGES_COUNT; i++)
+	{
+		drawsegs_xranges[i].count = 0;
+	}
+
+	if (visspritecount - clippedvissprites <= 0)
+	{
+		return;
+	}
+
+	if (drawsegs_xrange_size < maxdrawsegs)
+	{
+		drawsegs_xrange_size = 2 * maxdrawsegs;
+
+		for (i = 0; i < DS_RANGES_COUNT; i++)
+		{
+			drawsegs_xranges[i].items = Z_Realloc(
+				drawsegs_xranges[i].items,
+				drawsegs_xrange_size * sizeof(drawsegs_xranges[i].items[0]),
+				PU_STATIC, NULL
+			);
+		}
+	}
+
+	for (ds = ds_p; ds-- > dsstart;)
+	{
+		if (ds->silhouette || ds->maskedtexturecol)
+		{
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].x1 = ds->x1;
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].x2 = ds->x2;
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].user = ds;
+
+			// e6y: ~13% of speed improvement on sunder.wad map10
+			if (ds->x1 < cx)
+			{
+				drawsegs_xranges[1].items[drawsegs_xranges[1].count] =
+					drawsegs_xranges[0].items[drawsegs_xranges[0].count];
+				drawsegs_xranges[1].count++;
+			}
+
+			if (ds->x2 >= cx)
+			{
+				drawsegs_xranges[2].items[drawsegs_xranges[2].count] =
+					drawsegs_xranges[0].items[drawsegs_xranges[0].count];
+				drawsegs_xranges[2].count++;
+			}
+
+			drawsegs_xranges[0].count++;
+		}
+	}
+
 	for (; clippedvissprites < visspritecount; clippedvissprites++)
 	{
 		vissprite_t *spr = R_GetVisSprite(clippedvissprites);
 
-		if (!(spr->cut & SC_BBOX)) // Do not clip bounding boxes
+		if (cv_spriteclip.value
+		&& (spr->szt > vid.height || spr->sz < 0)
+		&& !((spr->cut & SC_SPLAT) || spr->scalestep))
 		{
-			INT32 x1 = (spr->cut & SC_SPLAT) ? 0 : spr->x1;
-			INT32 x2 = (spr->cut & SC_SPLAT) ? viewwidth : spr->x2;
-			R_ClipVisSprite(spr, x1, x2, dsstart, portal);
+			spr->cut |= SC_NOTVISIBLE;
+			continue;
 		}
+
+		if (spr->cut & SC_BBOX)
+		{
+			numvisiblesprites++;
+			continue;
+		}
+
+		INT32 x1 = (spr->cut & SC_SPLAT) ? 0 : spr->x1;
+		INT32 x2 = (spr->cut & SC_SPLAT) ? viewwidth : spr->x2;
+
+		if (x2 < cx)
+		{
+			drawsegs_xrange = drawsegs_xranges[1].items;
+			drawsegs_xrange_count = drawsegs_xranges[1].count;
+		}
+		else if (x1 >= cx)
+		{
+			drawsegs_xrange = drawsegs_xranges[2].items;
+			drawsegs_xrange_count = drawsegs_xranges[2].count;
+		}
+		else
+		{
+			drawsegs_xrange = drawsegs_xranges[0].items;
+			drawsegs_xrange_count = drawsegs_xranges[0].count;
+		}
+
+		R_ClipVisSprite(spr, x1, x2, portal);
+
+		if ((spr->cut & SC_NOTVISIBLE) == 0)
+			numvisiblesprites++;
 	}
 }
 
diff --git a/src/r_things.h b/src/r_things.h
index bb8a1e97b024e5b2cb749d71b46ef2eff785460b..318234886bbbfa2b603f1883d50bbfea4c67d370 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -123,21 +123,22 @@ typedef enum
 	SC_NONE       = 0,
 	SC_TOP        = 1,
 	SC_BOTTOM     = 1<<1,
+	SC_NOTVISIBLE = 1<<2,
 	// other flags
-	SC_PRECIP     = 1<<2,
-	SC_LINKDRAW   = 1<<3,
-	SC_FULLBRIGHT = 1<<4,
-	SC_SEMIBRIGHT = 1<<5,
-	SC_FULLDARK   = 1<<6,
-	SC_VFLIP      = 1<<7,
-	SC_ISSCALED   = 1<<8,
-	SC_ISROTATED  = 1<<9,
-	SC_SHADOW     = 1<<10,
-	SC_SHEAR      = 1<<11,
-	SC_SPLAT      = 1<<12,
-	SC_BBOX       = 1<<13,
+	SC_PRECIP     = 1<<3,
+	SC_LINKDRAW   = 1<<4,
+	SC_FULLBRIGHT = 1<<5,
+	SC_SEMIBRIGHT = 1<<6,
+	SC_FULLDARK   = 1<<7,
+	SC_VFLIP      = 1<<8,
+	SC_ISSCALED   = 1<<9,
+	SC_ISROTATED  = 1<<10,
+	SC_SHADOW     = 1<<11,
+	SC_SHEAR      = 1<<12,
+	SC_SPLAT      = 1<<13,
+	SC_BBOX       = 1<<14,
 	// masks
-	SC_CUTMASK    = SC_TOP|SC_BOTTOM,
+	SC_CUTMASK    = SC_TOP|SC_BOTTOM|SC_NOTVISIBLE,
 	SC_FLAGMASK   = ~SC_CUTMASK
 } spritecut_e;
 
@@ -219,10 +220,9 @@ typedef struct vissprite_s
 	INT32 dispoffset; // copy of mobj->dispoffset, affects ordering but not drawing
 } vissprite_t;
 
-extern UINT32 visspritecount;
+extern UINT32 visspritecount, numvisiblesprites;
 
 void R_ClipSprites(drawseg_t* dsstart, portal_t* portal);
-void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal);
 
 boolean R_SpriteIsFlashing(vissprite_t *vis);
 
diff --git a/src/screen.c b/src/screen.c
index fe5b399958e7082bd872478a53a4ef2b3da37df1..417e793bde540c62a9edf2b6a0b8073250ee7f73 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -44,10 +44,6 @@
 // SRB2Kart
 #include "r_fps.h" // R_GetFramerateCap
 
-#if defined (USEASM) && !defined (NORUSEASM)//&& (!defined (_MSC_VER) || (_MSC_VER <= 1200))
-#define RUSEASM //MSC.NET can't patch itself
-#endif
-
 // --------------------------------------------
 // assembly or c drawer routines for 8bpp/16bpp
 // --------------------------------------------
@@ -102,7 +98,6 @@ UINT8 *scr_borderpatch; // flat used to fill the reduced view borders set at ST_
 //  Short and Tall sky drawer, for the current color mode
 void (*walldrawerfunc)(void);
 
-boolean R_ASM = true;
 boolean R_486 = false;
 boolean R_586 = false;
 boolean R_MMX = false;
@@ -169,26 +164,6 @@ void SCR_SetDrawFuncs(void)
 		spanfuncs_npo2[SPANDRAWFUNC_WATER] = R_DrawWaterSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedWaterSpan_NPO2_8;
 
-#ifdef RUSEASM
-		if (R_ASM)
-		{
-			if (R_MMX)
-			{
-				colfuncs[BASEDRAWFUNC] = R_DrawColumn_8_MMX;
-				//colfuncs[COLDRAWFUNC_SHADE] = R_DrawShadeColumn_8_ASM;
-				//colfuncs[COLDRAWFUNC_FUZZY] = R_DrawTranslucentColumn_8_ASM;
-				colfuncs[COLDRAWFUNC_TWOSMULTIPATCH] = R_Draw2sMultiPatchColumn_8_MMX;
-				spanfuncs[BASEDRAWFUNC] = R_DrawSpan_8_MMX;
-			}
-			else
-			{
-				colfuncs[BASEDRAWFUNC] = R_DrawColumn_8_ASM;
-				//colfuncs[COLDRAWFUNC_SHADE] = R_DrawShadeColumn_8_ASM;
-				//colfuncs[COLDRAWFUNC_FUZZY] = R_DrawTranslucentColumn_8_ASM;
-				colfuncs[COLDRAWFUNC_TWOSMULTIPATCH] = R_Draw2sMultiPatchColumn_8_ASM;
-			}
-		}
-#endif
 	}
 /*	else if (vid.bpp > 1)
 	{
@@ -271,8 +246,6 @@ void SCR_Startup(void)
 		CONS_Printf("CPU Info: 486: %i, 586: %i, MMX: %i, 3DNow: %i, MMXExt: %i, SSE2: %i\n", R_486, R_586, R_MMX, R_3DNow, R_MMXExt, R_SSE2);
 	}
 
-	if (M_CheckParm("-noASM"))
-		R_ASM = false;
 	if (M_CheckParm("-486"))
 		R_486 = true;
 	if (M_CheckParm("-586"))
diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt
index be540b778b733976a9ceb08c636bcd30d36d29d2..4c4cdafb640e7806ee1efb79380ee10eaf26e119 100644
--- a/src/sdl/CMakeLists.txt
+++ b/src/sdl/CMakeLists.txt
@@ -1,17 +1,17 @@
 # Declare SDL2 interface sources
 
-target_sources(SRB2SDL2 PRIVATE mixer_sound.c)
-
-target_sourcefile(c)
-
-target_sources(SRB2SDL2 PRIVATE ogl_sdl.c)
-
-target_sources(SRB2SDL2 PRIVATE i_threads.c)
-
-if(${SRB2_USEASM})
-	set_source_files_properties(${SRB2_ASM_SOURCES} PROPERTIES LANGUAGE C)
-	set_source_files_properties(${SRB2_ASM_SOURCES} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp")
-endif()
+target_sources(SRB2SDL2 PRIVATE
+	mixer_sound.c
+	ogl_sdl.c
+	i_threads.c
+	i_net.c
+	i_system.c
+	i_main.c
+	i_video.c
+	dosstr.c
+	endtxt.c
+	hwsym_sdl.c
+)
 
 if("${CMAKE_SYSTEM_NAME}" MATCHES Windows)
 	target_sources(SRB2SDL2 PRIVATE
@@ -68,18 +68,6 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES Linux)
 	target_link_libraries(SRB2SDL2 PRIVATE m rt)
 endif()
 
-if(${SRB2_USEASM})
-	if(${SRB2_CONFIG_YASM})
-		set(ASM_ASSEMBLER_TEMP ${CMAKE_ASM_YASM_COMPILER})
-		set(ASM_ASSEMBLER_OBJFORMAT ${CMAKE_ASM_YASM_OBJECT_FORMAT})
-		set_source_files_properties(${SRB2_NASM_SOURCES} LANGUAGE ASM_YASM)
-	else()
-		set(ASM_ASSEMBLER_TEMP ${CMAKE_ASM_NASM_COMPILER})
-		set(ASM_ASSEMBLER_OBJFORMAT ${CMAKE_ASM_NASM_OBJECT_FORMAT})
-		set_source_files_properties(${SRB2_NASM_SOURCES} LANGUAGE ASM_NASM)
-	endif()
-endif()
-
 if("${CMAKE_SYSTEM_NAME}" MATCHES Windows)
 	target_link_libraries(SRB2SDL2 PRIVATE
 		ws2_32
diff --git a/src/sdl/MakeCYG.cfg b/src/sdl/MakeCYG.cfg
index 5907579c1bc9d16abb0e338cb709566fd75b6c61..b78316b00142dfcb96188f6fb45baa047a628ed0 100644
--- a/src/sdl/MakeCYG.cfg
+++ b/src/sdl/MakeCYG.cfg
@@ -7,7 +7,6 @@
 
 	NOHW=1
 	NOHS=1
-	NOASM=1
 
 	OPTS+=-DLINUX
 
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index 1dee379c0d8d95db186f1e9e3bb301554ed0549f..3eeacd83569188bd3bfdb5c8f2b9e720b5dce9f4 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -70,39 +70,6 @@ char logfilename[1024];
 typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID);
 #endif
 
-#if defined (_WIN32)
-static inline VOID MakeCodeWritable(VOID)
-{
-#ifdef USEASM // Disable write-protection of code segment
-	DWORD OldRights;
-	const DWORD NewRights = PAGE_EXECUTE_READWRITE;
-	PBYTE pBaseOfImage = (PBYTE)GetModuleHandle(NULL);
-	PIMAGE_DOS_HEADER dosH =(PIMAGE_DOS_HEADER)pBaseOfImage;
-	PIMAGE_NT_HEADERS ntH = (PIMAGE_NT_HEADERS)(pBaseOfImage + dosH->e_lfanew);
-	PIMAGE_OPTIONAL_HEADER oH = (PIMAGE_OPTIONAL_HEADER)
-		((PBYTE)ntH + sizeof (IMAGE_NT_SIGNATURE) + sizeof (IMAGE_FILE_HEADER));
-	LPVOID pA = pBaseOfImage+oH->BaseOfCode;
-	SIZE_T pS = oH->SizeOfCode;
-#if 1 // try to find the text section
-	PIMAGE_SECTION_HEADER ntS = IMAGE_FIRST_SECTION (ntH);
-	WORD s;
-	for (s = 0; s < ntH->FileHeader.NumberOfSections; s++)
-	{
-		if (memcmp (ntS[s].Name, ".text\0\0", 8) == 0)
-		{
-			pA = pBaseOfImage+ntS[s].VirtualAddress;
-			pS = ntS[s].Misc.VirtualSize;
-			break;
-		}
-	}
-#endif
-
-	if (!VirtualProtect(pA,pS,NewRights,&OldRights))
-		I_Error("Could not make code writable\n");
-#endif
-}
-#endif
-
 #ifdef LOGMESSAGES
 static void InitLogging(void)
 {
@@ -243,7 +210,6 @@ int main(int argc, char **argv)
 #ifndef __MINGW32__
 	prevExceptionFilter = SetUnhandledExceptionFilter(RecordExceptionInfo);
 #endif
-	MakeCodeWritable();
 #endif
 
 	// startup SRB2
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 847ab2646f2502151a45de214640f1da5945f578..902194f4f332592f69f8b0f872bdf04f0159ba90 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -23,12 +23,6 @@
 /// \file
 /// \brief SRB2 system stuff for SDL
 
-#ifdef CMAKECONFIG
-#include "config.h"
-#else
-#include "../config.h.in"
-#endif
-
 #include <signal.h>
 
 #ifdef _WIN32
@@ -41,6 +35,12 @@ typedef DWORD (WINAPI *p_timeGetTime) (void);
 typedef UINT (WINAPI *p_timeEndPeriod) (UINT);
 typedef HANDLE (WINAPI *p_OpenFileMappingA) (DWORD, BOOL, LPCSTR);
 typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
+
+// This is for RtlGenRandom.
+#define SystemFunction036 NTAPI SystemFunction036
+#include <ntsecapi.h>
+#undef SystemFunction036
+
 #endif
 #include <stdio.h>
 #include <stdlib.h>
@@ -90,7 +90,7 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #include <kvm.h>
 #endif
 #include <nlist.h>
-#include <sys/vmmeter.h>
+#include <sys/sysctl.h>
 #endif
 #endif
 
@@ -325,8 +325,10 @@ static void write_backtrace(INT32 signal)
 static void I_ReportSignal(int num, int coredumped)
 {
 	//static char msg[] = "oh no! back to reality!\r\n";
-	const char *sigmsg, *sigttl;
+	const char *sigmsg, *signame;
 	char ttl[128];
+	char sigttl[512] = "Process killed by signal: ";
+	const char *reportmsg = "\n\nTo help us figure out the cause, you can visit our official Discord server\nwhere you will find more instructions on how to submit a crash report.\n\nSorry for the inconvenience!";
 
 	switch (num)
 	{
@@ -335,16 +337,16 @@ static void I_ReportSignal(int num, int coredumped)
 //		sigmsg = "SRB2 was interrupted prematurely by the user.";
 //		break;
 	case SIGILL:
-		sigmsg = "SRB2 has attempted to execute an illegal instruction and needs to close. %s";
-		sigttl = "SIGILL"; // illegal instruction - invalid function image
+		sigmsg = "SRB2 has attempted to execute an illegal instruction and needs to close.";
+		signame = "SIGILL"; // illegal instruction - invalid function image
 		break;
 	case SIGFPE:
-		sigmsg = "SRB2 has encountered a mathematical exception and needs to close. %s";
-		sigttl = "SIGFPE"; // mathematical exception
+		sigmsg = "SRB2 has encountered a mathematical exception and needs to close.";
+		signame = "SIGFPE"; // mathematical exception
 		break;
 	case SIGSEGV:
-		sigmsg = "SRB2 has attempted to access a memory location that it shouldn't and needs to close. %s";
-		sigttl = "SIGSEGV"; // segment violation
+		sigmsg = "SRB2 has attempted to access a memory location that it shouldn't and needs to close.";
+		signame = "SIGSEGV"; // segment violation
 		break;
 //	case SIGTERM:
 //		sigmsg = "SRB2 was terminated by a kill signal.";
@@ -355,34 +357,31 @@ static void I_ReportSignal(int num, int coredumped)
 //		sigttl = "SIGBREAK" // Ctrl-Break sequence
 //		break;
 	case SIGABRT:
-		sigmsg = "SRB2 was terminated by an abort signal. %s";
-		sigttl = "SIGABRT"; // abnormal termination triggered by abort call
+		sigmsg = "SRB2 was terminated by an abort signal.";
+		signame = "SIGABRT"; // abnormal termination triggered by abort call
 		break;
 	default:
-		sigmsg = "SRB2 was terminated by an unknown signal. %s";
+		sigmsg = "SRB2 was terminated by an unknown signal.";
 
 		sprintf(ttl, "number %d", num);
 		if (coredumped)
-			sigttl = 0;
+			signame = 0;
 		else
-			sigttl = ttl;
+			signame = ttl;
 	}
 
 	if (coredumped)
 	{
-		if (sigttl)
-			sprintf(ttl, "%s (core dumped)", sigttl);
+		if (signame)
+			sprintf(ttl, "%s (core dumped)", signame);
 		else
 			strcat(ttl, " (core dumped)");
 
-		sigttl = ttl;
+		signame = ttl;
 	}
 
-	sprintf(ttl, "Process killed by signal: %s", sigttl);
-
-	sigttl = ttl;
-
-	I_OutputMsg("\n%s\n\n", sigttl);
+	strcat(sigttl, signame);
+	I_OutputMsg("%s\n", sigttl);
 
 	if (M_CheckParm("-dedicated"))
 		return;
@@ -396,8 +395,7 @@ static void I_ReportSignal(int num, int coredumped)
 		SDL_MESSAGEBOX_ERROR, /* .flags */
 		NULL, /* .window */
 		sigttl, /* .title */
-		va(sigmsg,
-			"\n\nTo help us figure out the cause, you can visit our official Discord server\nwhere you will find more instructions on how to submit a crash report.\n\nSorry for the inconvenience!"), /* .message */
+		va("%s %s", sigmsg, reportmsg), /* .message */
 		SDL_arraysize(buttons), /* .numbuttons */
 		buttons, /* .buttons */
 		NULL /* .colorScheme */
@@ -2356,7 +2354,10 @@ INT32 I_StartupSystem(void)
 #endif
 	I_StartupConsole();
 #ifdef NEWSIGNALHANDLER
-	I_Fork();
+	// This is useful when debugging. It lets GDB attach to
+	// the correct process easily.
+	if (!M_CheckParm("-nofork"))
+		I_Fork();
 #endif
 	I_RegisterSignals();
 	I_OutputMsg("Compiled for SDL version: %d.%d.%d\n",
@@ -2642,9 +2643,10 @@ void I_ShutdownSystem(void)
 {
 	INT32 c;
 
-#ifndef NEWSIGNALHANDLER
-	I_ShutdownConsole();
+#ifdef NEWSIGNALHANDLER
+	if (M_CheckParm("-nofork"))
 #endif
+		I_ShutdownConsole();
 
 	for (c = MAX_QUIT_FUNCS-1; c >= 0; c--)
 		if (quit_funcs[c])
@@ -2776,6 +2778,38 @@ INT32 I_PutEnv(char *variable)
 #endif
 }
 
+size_t I_GetRandomBytes(char *destination, size_t count)
+{
+#if defined (__unix__) || defined (UNIXCOMMON) || defined(__APPLE__)
+	FILE *rndsource;
+	size_t actual_bytes;
+
+	if (!(rndsource = fopen("/dev/urandom", "r")))
+		if (!(rndsource = fopen("/dev/random", "r")))
+			actual_bytes = 0;
+
+	if (rndsource)
+	{
+		actual_bytes = fread(destination, 1, count, rndsource);
+		fclose(rndsource);
+	}
+
+	if (actual_bytes == 0)
+		I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes");
+
+	return actual_bytes;
+#elif defined (_WIN32)
+	if (RtlGenRandom(destination, count))
+		return count;
+
+	I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes");
+	return 0;
+#else
+	#warning SDL I_GetRandomBytes is not implemented on this platform.
+	return 0;
+#endif
+}
+
 INT32 I_ClipboardCopy(const char *data, size_t size)
 {
 	char storage[256];
@@ -2999,40 +3033,17 @@ static long get_entry(const char* name, const char* buf)
 size_t I_GetFreeMem(size_t *total)
 {
 #ifdef FREEBSD
-	struct vmmeter sum;
-	kvm_t *kd;
-	struct nlist namelist[] =
-	{
-#define X_SUM   0
-		{"_cnt"},
-		{NULL}
-	};
-	if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open")) == NULL)
-	{
-		if (total)
-			*total = 0L;
-		return 0;
-	}
-	if (kvm_nlist(kd, namelist) != 0)
-	{
-		kvm_close (kd);
-		if (total)
-			*total = 0L;
-		return 0;
-	}
-	if (kvm_read(kd, namelist[X_SUM].n_value, &sum,
-		sizeof (sum)) != sizeof (sum))
-	{
-		kvm_close(kd);
-		if (total)
-			*total = 0L;
-		return 0;
-	}
-	kvm_close(kd);
+	u_int v_free_count, v_page_size, v_page_count;
+	size_t size = sizeof(v_free_count);
+	sysctlbyname("vm.stat.vm.v_free_count", &v_free_count, &size, NULL, 0);
+	size_t size = sizeof(v_page_size);
+	sysctlbyname("vm.stat.vm.v_page_size", &v_page_size, &size, NULL, 0);
+	size_t size = sizeof(v_page_count);
+	sysctlbyname("vm.stat.vm.v_page_count", &v_page_count, &size, NULL, 0);
 
 	if (total)
-		*total = sum.v_page_count * sum.v_page_size;
-	return sum.v_free_count * sum.v_page_size;
+		*total = v_page_count * v_page_size;
+	return v_free_count * v_page_size;
 #elif defined (SOLARIS)
 	/* Just guess */
 	if (total)
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index b2215f634a189d1bce50083ef8394ad63b434e85..590d7d142a7a536678bfb96d3891c78264a19fba 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1593,7 +1593,6 @@ boolean VID_CheckRenderer(void)
 			else if (vid.glstate == VID_GL_LIBRARY_ERROR)
 				rendererchanged = false;
 		}
-		else
 #endif
 
 		if (!contextcreated)
diff --git a/src/st_stuff.c b/src/st_stuff.c
index c6e6befc62ee046e2a0ab7b333d33db1d5a7a494..b9f0c6bb93e1ab4d1b9e35a958670571883086e4 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -174,7 +174,7 @@ static huddrawlist_h luahuddrawlist_titlecard;
 skincolornum_t linkColor[3][NUMLINKCOLORS] = {
 {SKINCOLOR_SHAMROCK, SKINCOLOR_AQUA, SKINCOLOR_SKY, SKINCOLOR_BLUE, SKINCOLOR_PURPLE, SKINCOLOR_MAGENTA,
  SKINCOLOR_ROSY, SKINCOLOR_RED, SKINCOLOR_ORANGE, SKINCOLOR_GOLD, SKINCOLOR_YELLOW, SKINCOLOR_PERIDOT},
-{SKINCOLOR_EMERALD, SKINCOLOR_AQUAMARINE, SKINCOLOR_WAVE, SKINCOLOR_SAPPHIRE, SKINCOLOR_GALAXY, SKINCOLOR_CRYSTAL,
+{SKINCOLOR_EMERALD, SKINCOLOR_OCEAN, SKINCOLOR_AQUAMARINE, SKINCOLOR_SAPPHIRE, SKINCOLOR_GALAXY, SKINCOLOR_SIBERITE,
  SKINCOLOR_TAFFY, SKINCOLOR_RUBY, SKINCOLOR_GARNET, SKINCOLOR_TOPAZ, SKINCOLOR_LEMON, SKINCOLOR_LIME},
 {SKINCOLOR_ISLAND, SKINCOLOR_TURQUOISE, SKINCOLOR_DREAM, SKINCOLOR_DAYBREAK, SKINCOLOR_VAPOR, SKINCOLOR_FUCHSIA,
  SKINCOLOR_VIOLET, SKINCOLOR_EVENTIDE, SKINCOLOR_KETCHUP, SKINCOLOR_FOUNDATION, SKINCOLOR_HEADLIGHT, SKINCOLOR_CHARTREUSE}};
@@ -2512,6 +2512,8 @@ num:
 static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offset)
 {
 	INT32 interval, i;
+	if (stplyr->mo == NULL)
+		return 0;  // player just joined after spectating, can happen on custom gamemodes.
 	UINT32 dist = ((UINT32)P_AproxDistance(P_AproxDistance(stplyr->mo->x - hunt->x, stplyr->mo->y - hunt->y), stplyr->mo->z - hunt->z))>>FRACBITS;
 
 	if (dist < 128)
diff --git a/src/strcasestr.c b/src/strcasestr.c
index 2796f11d52658f2376b547a3d41d6b2d2974f15e..37899a8425b3d2082957c41cbdae07056798fe74 100644
--- a/src/strcasestr.c
+++ b/src/strcasestr.c
@@ -61,7 +61,7 @@ swapp (char ***ppap, char ***ppbp, char **cpap, char **cpbp)
 }
 
 char *
-strcasestr (const char *s, const char *q)
+nongnu_strcasestr (const char *s, const char *q)
 {
 	size_t  qn;
 
diff --git a/src/string.c b/src/string.c
index dd3080a979ca3ccffa811f27ee408bef7540bb26..2f16fa4c68a35fa287156f546fc2973e9d4ad426 100644
--- a/src/string.c
+++ b/src/string.c
@@ -15,7 +15,7 @@
 #include <string.h>
 #include "doomdef.h"
 
-#if !defined (__APPLE__)
+#ifndef SRB2_HAVE_STRLCPY
 
 // Like the OpenBSD version, but it doesn't check for src not being a valid
 // C string.
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..28c4ce492f2d8b4e7590e3ac10f9ce6a95d98a0f
--- /dev/null
+++ b/src/tests/CMakeLists.txt
@@ -0,0 +1,3 @@
+target_sources(srb2tests PRIVATE
+	boolcompat.cpp
+)
diff --git a/src/tests/boolcompat.cpp b/src/tests/boolcompat.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fee40cd36f2bce34217a875024d6b41fe71adbd1
--- /dev/null
+++ b/src/tests/boolcompat.cpp
@@ -0,0 +1,8 @@
+#include <catch2/catch_test_macros.hpp>
+
+#include "../doomtype.h"
+
+TEST_CASE("C++ bool is convertible to doomtype.h boolean") {
+	REQUIRE(static_cast<boolean>(true) == 1);
+	REQUIRE(static_cast<boolean>(false) == 0);
+}
diff --git a/src/tmap.nas b/src/tmap.nas
deleted file mode 100644
index 85091cbd5d8dd9b1ab33cdd4938325eefeb1922b..0000000000000000000000000000000000000000
--- a/src/tmap.nas
+++ /dev/null
@@ -1,957 +0,0 @@
-;; SONIC ROBO BLAST 2
-;;-----------------------------------------------------------------------------
-;; Copyright (C) 1998-2000 by DooM Legacy Team.
-;; Copyright (C) 1999-2023 by Sonic Team Junior.
-;;
-;; This program is free software distributed under the
-;; terms of the GNU General Public License, version 2.
-;; See the 'LICENSE' file for more details.
-;;-----------------------------------------------------------------------------
-;; FILE:
-;;      tmap.nas
-;; DESCRIPTION:
-;;      Assembler optimised rendering code for software mode.
-;;      Draw wall columns.
-
-
-[BITS 32]
-
-%define FRACBITS 16
-%define TRANSPARENTPIXEL 255
-
-%ifdef LINUX
-%macro cextern 1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-[global %1]
-%endmacro
-
-%else
-%macro cextern 1
-%define %1 _%1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-%define %1 _%1
-[global %1]
-%endmacro
-
-%endif
-
-
-; The viddef_s structure. We only need the width field.
-struc viddef_s
-        resb 12
-.width: resb 4
-        resb 44
-endstruc
-
-;; externs
-;; columns
-cextern dc_x
-cextern dc_yl
-cextern dc_yh
-cextern ylookup
-cextern columnofs
-cextern dc_source
-cextern dc_texturemid
-cextern dc_texheight
-cextern dc_iscale
-cextern dc_hires
-cextern centery
-cextern centeryfrac
-cextern dc_colormap
-cextern dc_transmap
-cextern colormaps
-cextern vid
-cextern topleft
-
-; DELME
-cextern R_DrawColumn_8
-
-; polygon edge rasterizer
-cextern prastertab
-
-[SECTION .data]
-
-;;.align        4
-loopcount       dd      0
-pixelcount      dd      0
-tystep          dd      0
-
-[SECTION .text]
-
-;;----------------------------------------------------------------------
-;;
-;; R_DrawColumn : 8bpp column drawer
-;;
-;; New  optimised version 10-01-1998 by D.Fabrice and P.Boris
-;; Revised by G. Dick July 2010 to support the intervening twelve years'
-;; worth of changes to the renderer. Since I only vaguely know what I'm
-;; doing, this is probably rather suboptimal. Help appreciated!
-;;
-;;----------------------------------------------------------------------
-;; fracstep, vid.width in memory
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = heightmask
-;; esi = source
-;; edi = dest
-;; ebp = frac
-;;----------------------------------------------------------------------
-
-cglobal R_DrawColumn_8_ASM
-;       align   16
-R_DrawColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     edi,[ylookup+ebp*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]  ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     ecx,[dc_yh]
-        add     ecx,1
-        sub     ecx,ebp                 ;; pixel count
-        jle     near .done              ;; nothing to scale
-;;
-;; fracstep = dc_iscale;	// But we just use [dc_iscale]
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-        mov     eax,ebp                 ;; dc_yl
-        shl     eax,FRACBITS
-        sub     eax,[centeryfrac]
-        imul    dword [dc_iscale]
-        shrd    eax,edx,FRACBITS
-        add     eax,[dc_texturemid]
-        mov     ebp,eax                 ;; ebp = frac
-
-        mov     ebx,[dc_colormap]
-
-        mov     esi,[dc_source]
-;;
-;; if (dc_hires) frac = 0;
-;;
-        test    byte [dc_hires],0x01
-        jz      .texheightcheck
-        xor     ebp,ebp
-
-;;
-;; Check for power of two
-;;
-.texheightcheck:
-        mov     edx,[dc_texheight]
-        sub     edx,1                   ;; edx = heightmask
-        test    edx,[dc_texheight]
-        jnz     .notpowertwo
-
-        test    ecx,0x01                ;; Test for odd no. pixels
-        jnz     .odd
-
-;;
-;; Texture height is a power of two, so we get modular arithmetic by
-;; masking
-;;
-.powertwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-.odd:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-
-        sub     ecx,2                   ;; count -= 2
-        jg      .powertwo
-
-        jmp     .done
-
-.notpowertwo:
-        add     edx,1
-        shl     edx,FRACBITS
-        test    ebp,ebp
-        jns     .notpowtwoloop
-
-.makefracpos:
-        add     ebp,edx                 ;; frac is negative; make it positive
-        js      .makefracpos
-
-.notpowtwoloop:
-        cmp     ebp,edx                 ;; Reduce mod height
-        jl      .writenonpowtwo
-        sub     ebp,edx
-        jmp     .notpowtwoloop
-
-.writenonpowtwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part.
-        mov     bl,[esi + eax]          ;; ebx = colormap + texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        movzx   eax,byte [ebx]          ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-        sub     ecx,1
-        jnz     .notpowtwoloop
-
-;;
-
-.done:
-        pop     ebx                     ;; restore register variables
-        pop     edi
-        pop     esi
-        pop     ebp                     ;; restore caller's stack frame pointer
-        ret
-
-
-;;----------------------------------------------------------------------
-;;
-;; R_Draw2sMultiPatchColumn : Like R_DrawColumn, but omits transparent
-;;                            pixels.
-;;
-;; New  optimised version 10-01-1998 by D.Fabrice and P.Boris
-;; Revised by G. Dick July 2010 to support the intervening twelve years'
-;; worth of changes to the renderer. Since I only vaguely know what I'm
-;; doing, this is probably rather suboptimal. Help appreciated!
-;;
-;;----------------------------------------------------------------------
-;; fracstep, vid.width in memory
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = heightmask
-;; esi = source
-;; edi = dest
-;; ebp = frac
-;;----------------------------------------------------------------------
-
-cglobal R_Draw2sMultiPatchColumn_8_ASM
-;       align   16
-R_Draw2sMultiPatchColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     edi,[ylookup+ebp*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]  ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     ecx,[dc_yh]
-        add     ecx,1
-        sub     ecx,ebp                 ;; pixel count
-        jle     near .done              ;; nothing to scale
-;;
-;; fracstep = dc_iscale;	// But we just use [dc_iscale]
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-        mov     eax,ebp                 ;; dc_yl
-        shl     eax,FRACBITS
-        sub     eax,[centeryfrac]
-        imul    dword [dc_iscale]
-        shrd    eax,edx,FRACBITS
-        add     eax,[dc_texturemid]
-        mov     ebp,eax                 ;; ebp = frac
-
-        mov     ebx,[dc_colormap]
-
-        mov     esi,[dc_source]
-;;
-;; if (dc_hires) frac = 0;
-;;
-        test    byte [dc_hires],0x01
-        jz      .texheightcheck
-        xor     ebp,ebp
-
-;;
-;; Check for power of two
-;;
-.texheightcheck:
-        mov     edx,[dc_texheight]
-        sub     edx,1                   ;; edx = heightmask
-        test    edx,[dc_texheight]
-        jnz     .notpowertwo
-
-        test    ecx,0x01                ;; Test for odd no. pixels
-        jnz     .odd
-
-;;
-;; Texture height is a power of two, so we get modular arithmetic by
-;; masking
-;;
-.powertwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        cmp     al,TRANSPARENTPIXEL     ;; Is pixel transparent?
-        je      .nextpowtwoeven         ;; If so, advance.
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov	    [edi],al                ;; Write pixel
-.nextpowtwoeven:
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-.odd:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        cmp     al,TRANSPARENTPIXEL     ;; Is pixel transparent?
-        je      .nextpowtwoodd          ;; If so, advance.
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-.nextpowtwoodd:
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-
-        sub     ecx,2                   ;; count -= 2
-        jg      .powertwo
-
-        jmp     .done
-
-.notpowertwo:
-        add     edx,1
-        shl     edx,FRACBITS
-        test    ebp,ebp
-        jns     .notpowtwoloop
-
-.makefracpos:
-        add     ebp,edx                 ;; frac is negative; make it positive
-        js      .makefracpos
-
-.notpowtwoloop:
-        cmp     ebp,edx                 ;; Reduce mod height
-        jl      .writenonpowtwo
-        sub     ebp,edx
-        jmp     .notpowtwoloop
-
-.writenonpowtwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part.
-        mov     bl,[esi + eax]          ;; ebx = colormap + texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        cmp     bl,TRANSPARENTPIXEL     ;; Is pixel transparent?
-        je      .nextnonpowtwo          ;; If so, advance.
-        movzx   eax,byte [ebx]          ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-.nextnonpowtwo:
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-        sub     ecx,1
-        jnz     .notpowtwoloop
-
-;;
-
-.done:
-        pop     ebx                     ;; restore register variables
-        pop     edi
-        pop     esi
-        pop     ebp                     ;; restore caller's stack frame pointer
-        ret
-
-;;----------------------------------------------------------------------
-;; R_DrawTranslucentColumnA_8
-;;
-;; Vertical column texture drawer, with transparency. Replaces Doom2's
-;; 'fuzz' effect, which was not so beautiful.
-;; Transparency is always impressive in some way, don't know why...
-;;----------------------------------------------------------------------
-
-cglobal R_DrawTranslucentColumn_8_ASM
-R_DrawTranslucentColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     ebx,ebp
-        mov     edi,[ylookup+ebx*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]   ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     eax,[dc_yh]
-        inc     eax
-        sub     eax,ebp                 ;; pixel count
-        mov     [pixelcount],eax        ;; save for final pixel
-        jle     near    vtdone         ;; nothing to scale
-;;
-;; frac = dc_texturemid - (centery-dc_yl)*fracstep;
-;;
-        mov     ecx,[dc_iscale]        ;; fracstep
-        mov     eax,[centery]
-        sub     eax,ebp
-        imul    eax,ecx
-        mov     edx,[dc_texturemid]
-        sub     edx,eax
-        mov     ebx,edx
-
-        shr     ebx,16                  ;; frac int.
-        and     ebx,0x7f
-        shl     edx,16                  ;; y frac up
-
-        mov     ebp,ecx
-        shl     ebp,16                  ;; fracstep f. up
-        shr     ecx,16                  ;; fracstep i. ->cl
-        and     cl,0x7f
-        push    cx
-        mov     ecx,edx
-        pop     cx
-        mov     edx,[dc_colormap]
-        mov     esi,[dc_source]
-;;
-;; lets rock :) !
-;;
-        mov     eax,[pixelcount]
-        shr     eax,0x2
-        test    byte [pixelcount],0x3
-        mov     ch,al                   ;; quad count
-        mov     eax,[dc_transmap]
-        je      vt4quadloop
-;;
-;;  do un-even pixel
-;;
-        test    byte [pixelcount],0x1
-        je      trf2
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     ecx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     dl,[edx]
-        mov     [edi],dl
-pf:     add     edi,0x12345678
-;;
-;;  do two non-quad-aligned pixels
-;;
-trf2:    test    byte [pixelcount],0x2
-        je      trf3
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     ecx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     dl,[edx]
-        mov     [edi],dl
-pg:     add     edi,0x12345678
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     ecx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     dl,[edx]
-        mov     [edi],dl
-ph:     add     edi,0x12345678
-;;
-;;  test if there was at least 4 pixels
-;;
-trf3:   test    ch,0xff                 ;; test quad count
-        je near vtdone
-
-;;
-;; ebp : ystep frac. upper 24 bits
-;; edx : y     frac. upper 24 bits
-;; ebx : y     i.    lower 7 bits,  masked for index
-;; ecx : ch = counter, cl = y step i.
-;; eax : colormap aligned 256
-;; esi : source texture column
-;; edi : dest screen
-;;
-vt4quadloop:
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [tystep],ebp
-pi:     add     edi,0x12345678
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-pj:     sub     edi,0x12345678
-        mov     ebp,edi
-pk:     sub     edi,0x12345678
-        jmp short inloop
-align 4
-vtquadloop:
-        add     ecx,[tystep]
-        adc     bl,cl
-q1:     add     ebp,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest   : index into colormap
-inloop:
-        add     ecx,[tystep]
-        adc     bl,cl
-q2:     add     edi,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [ebp+0x0],dl
-        mov     al,[edi]                ;; fetch dest   : index into colormap
-
-        add     ecx,[tystep]
-        adc     bl,cl
-q3:     add     ebp,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest   : index into colormap
-
-        add     ecx,[tystep]
-        adc     bl,cl
-q4:     add     edi,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [ebp],dl
-        mov     al,[edi]                ;; fetch dest   : index into colormap
-
-        dec     ch
-        jne     vtquadloop
-vtdone:
-        pop     ebx
-        pop     edi
-        pop     esi
-        pop     ebp
-        ret
-
-;;----------------------------------------------------------------------
-;; R_DrawShadeColumn
-;;
-;;   for smoke..etc.. test.
-;;----------------------------------------------------------------------
-cglobal R_DrawShadeColumn_8_ASM
-R_DrawShadeColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     ebx,ebp
-        mov     edi,[ylookup+ebx*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]  ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     eax,[dc_yh]
-        inc     eax
-        sub     eax,ebp                 ;; pixel count
-        mov     [pixelcount],eax       ;; save for final pixel
-        jle near shdone                ;; nothing to scale
-;;
-;; frac = dc_texturemid - (centery-dc_yl)*fracstep;
-;;
-        mov     ecx,[dc_iscale]        ;; fracstep
-        mov     eax,[centery]
-        sub     eax,ebp
-        imul    eax,ecx
-        mov     edx,[dc_texturemid]
-        sub     edx,eax
-        mov     ebx,edx
-        shr     ebx,16                  ;; frac int.
-        and     ebx,byte +0x7f
-        shl     edx,16                  ;; y frac up
-
-        mov     ebp,ecx
-        shl     ebp,16                  ;; fracstep f. up
-        shr     ecx,16                  ;; fracstep i. ->cl
-        and     cl,0x7f
-
-        mov     esi,[dc_source]
-;;
-;; lets rock :) !
-;;
-        mov     eax,[pixelcount]
-        mov     dh,al
-        shr     eax,2
-        mov     ch,al                   ;; quad count
-        mov     eax,[colormaps]
-        test    dh,3
-        je      sh4quadloop
-;;
-;;  do un-even pixel
-;;
-        test    dh,0x1
-        je      shf2
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     edx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     [edi],dl
-pl:     add     edi,0x12345678
-;;
-;;  do two non-quad-aligned pixels
-;;
-shf2:
-        test    dh,0x2
-        je      shf3
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     edx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     [edi],dl
-pm:     add     edi,0x12345678
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     edx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     [edi],dl
-pn:     add     edi,0x12345678
-;;
-;;  test if there was at least 4 pixels
-;;
-shf3:
-        test    ch,0xff                 ;; test quad count
-        je near shdone
-
-;;
-;; ebp : ystep frac. upper 24 bits
-;; edx : y     frac. upper 24 bits
-;; ebx : y     i.    lower 7 bits,  masked for index
-;; ecx : ch = counter, cl = y step i.
-;; eax : colormap aligned 256
-;; esi : source texture column
-;; edi : dest screen
-;;
-sh4quadloop:
-        mov     dh,0x7f                 ;; prep mask
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [tystep],ebp
-po:     add     edi,0x12345678
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-pp:     sub     edi,0x12345678
-        mov     ebp,edi
-pq:     sub     edi,0x12345678
-        jmp short shinloop
-
-align  4
-shquadloop:
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q5:     add     ebp,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest : index into colormap
-shinloop:
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q6:     add     edi,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [ebp],dl
-        mov     al,[edi]                ;; fetch dest : index into colormap
-
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q7:     add     ebp,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest : index into colormap
-
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q8:     add     edi,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [ebp],dl
-        mov     al,[edi]                ;; fetch dest : index into colormap
-
-        dec     ch
-        jne     shquadloop
-
-shdone:
-        pop     ebx                     ;; restore register variables
-        pop     edi
-        pop     esi
-        pop     ebp                     ;; restore caller's stack frame pointer
-        ret
-
-
-;; ========================================================================
-;;  Rasterization of the segments of a LINEAR polygne textur of manire.
-;;  It is thus a question of interpolating coordinate them at the edges of texture in
-;;  the time that the X-coordinates minx/maxx for each line.
-;;  the argument ' dir' indicates which edges of texture are Interpol?:
-;;    0:  segments associs at edge TOP? and BOTTOM? (constant TY)
-;;    1:  segments associs at the LEFT and RIGHT edge (constant TX)
-;; ========================================================================
-;;
-;;  void   rasterize_segment_tex( LONG x1, LONG y1, LONG x2, LONG y2, LONG tv1, LONG tv2, LONG tc, LONG dir );
-;;                                   ARG1     ARG2     ARG3     ARG4      ARG5      ARG6     ARG7       ARG8
-;;
-;;  Pour dir = 0, (tv1,tv2) = (tX1,tX2), tc = tY, en effet TY est constant.
-;;
-;;  Pour dir = 1, (tv1,tv2) = (tY1,tY2), tc = tX, en effet TX est constant.
-;;
-;;
-;;  Uses:  extern struct rastery *_rastertab;
-;;
-
-MINX            EQU    0
-MAXX            EQU    4
-TX1             EQU    8
-TY1             EQU    12
-TX2             EQU    16
-TY2             EQU    20
-RASTERY_SIZEOF  EQU    24
-
-cglobal rasterize_segment_tex_asm
-rasterize_segment_tex_asm:
-        push    ebp
-        mov     ebp,esp
-
-        sub     esp,byte +0x8           ;; allocate the local variables
-
-        push    ebx
-        push    esi
-        push    edi
-        o16 mov ax,es
-        push    eax
-
-;;        #define DX       [ebp-4]
-;;        #define TD       [ebp-8]
-
-        mov     eax,[ebp+0xc]           ;; y1
-        mov     ebx,[ebp+0x14]          ;; y2
-        cmp     ebx,eax
-        je near .L_finished             ;; special (y1==y2) segment horizontal, exit!
-
-        jg near .L_rasterize_right
-
-;;rasterize_left:       ;; one rasterize a segment LEFT of the polygne
-
-        mov     ecx,eax
-        sub     ecx,ebx
-        inc     ecx                     ;; y1-y2+1
-
-        mov     eax,RASTERY_SIZEOF
-        mul     ebx                     ;; * y2
-        mov     esi,[prastertab]
-        add     esi,eax                 ;; point into rastertab[y2]
-
-        mov     eax,[ebp+0x8]           ;; ARG1
-        sub     eax,[ebp+0x10]          ;; ARG3
-        shl     eax,0x10                ;;     ((x1-x2)<<PRE) ...
-        cdq
-        idiv    ecx                     ;; dx =     ...        / (y1-y2+1)
-        mov     [ebp-0x4],eax           ;; DX
-
-        mov     eax,[ebp+0x18]          ;; ARG5
-        sub     eax,[ebp+0x1c]          ;; ARG6
-        shl     eax,0x10
-        cdq
-        idiv    ecx                     ;;      tdx =((tx1-tx2)<<PRE) / (y1-y2+1)
-        mov     [ebp-0x8],eax           ;; idem tdy =((ty1-ty2)<<PRE) / (y1-y2+1)
-
-        mov     eax,[ebp+0x10]          ;; ARG3
-        shl     eax,0x10                ;; x = x2<<PRE
-
-        mov     ebx,[ebp+0x1c]          ;; ARG6
-        shl     ebx,0x10                ;; tx = tx2<<PRE    d0
-                                        ;; ty = ty2<<PRE    d1
-        mov     edx,[ebp+0x20]          ;; ARG7
-        shl     edx,0x10                ;; ty = ty<<PRE     d0
-                                        ;; tx = tx<<PRE     d1
-        push    ebp
-        mov     edi,[ebp-0x4]           ;; DX
-        cmp     dword [ebp+0x24],byte +0x0      ;; ARG8   direction ?
-
-        mov     ebp,[ebp-0x8]           ;; TD
-        je      .L_rleft_h_loop
-;;
-;; TY varies, TX is constant
-;;
-.L_rleft_v_loop:
-        mov     [esi+MINX],eax           ;; rastertab[y].minx = x
-          add     ebx,ebp
-        mov     [esi+TX1],edx           ;;             .tx1  = tx
-          add     eax,edi
-        mov     [esi+TY1],ebx           ;;             .ty1  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // ty    += tdy
-
-        add     esi,RASTERY_SIZEOF      ;; next raster line into rastertab[]
-        dec     ecx
-        jne     .L_rleft_v_loop
-        pop     ebp
-        jmp     .L_finished
-;;
-;; TX varies, TY is constant
-;;
-.L_rleft_h_loop:
-        mov     [esi+MINX],eax           ;; rastertab[y].minx = x
-          add     eax,edi
-        mov     [esi+TX1],ebx           ;;             .tx1  = tx
-          add     ebx,ebp
-        mov     [esi+TY1],edx           ;;             .ty1  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // tx    += tdx
-
-        add     esi,RASTERY_SIZEOF      ;; next raster line into rastertab[]
-        dec     ecx
-        jne     .L_rleft_h_loop
-        pop     ebp
-        jmp     .L_finished
-;;
-;; one rasterize a segment LINE of the polygne
-;;
-.L_rasterize_right:
-        mov     ecx,ebx
-        sub     ecx,eax
-        inc     ecx                     ;; y2-y1+1
-
-        mov     ebx,RASTERY_SIZEOF
-        mul     ebx                     ;;   * y1
-        mov     esi,[prastertab]
-        add     esi,eax                 ;;  point into rastertab[y1]
-
-        mov     eax,[ebp+0x10]          ;; ARG3
-        sub     eax,[ebp+0x8]           ;; ARG1
-        shl     eax,0x10                ;; ((x2-x1)<<PRE) ...
-        cdq
-        idiv    ecx                     ;;  dx =     ...        / (y2-y1+1)
-        mov     [ebp-0x4],eax           ;; DX
-
-        mov     eax,[ebp+0x1c]          ;; ARG6
-        sub     eax,[ebp+0x18]          ;; ARG5
-        shl     eax,0x10
-        cdq
-        idiv    ecx                     ;;       tdx =((tx2-tx1)<<PRE) / (y2-y1+1)
-        mov     [ebp-0x8],eax           ;;  idem tdy =((ty2-ty1)<<PRE) / (y2-y1+1)
-
-        mov     eax,[ebp+0x8]           ;; ARG1
-        shl     eax,0x10                ;; x  = x1<<PRE
-
-        mov     ebx,[ebp+0x18]          ;; ARG5
-        shl     ebx,0x10                ;; tx = tx1<<PRE    d0
-                                        ;; ty = ty1<<PRE    d1
-        mov     edx,[ebp+0x20]          ;; ARG7
-        shl     edx,0x10                ;; ty = ty<<PRE     d0
-                                        ;; tx = tx<<PRE     d1
-        push    ebp
-        mov     edi,[ebp-0x4]           ;; DX
-
-        cmp     dword [ebp+0x24], 0     ;; direction ?
-
-         mov     ebp,[ebp-0x8]          ;; TD
-        je      .L_rright_h_loop
-;;
-;; TY varies, TX is constant
-;;
-.L_rright_v_loop:
-
-        mov     [esi+MAXX],eax           ;; rastertab[y].maxx = x
-          add     ebx,ebp
-        mov     [esi+TX2],edx          ;;             .tx2  = tx
-          add     eax,edi
-        mov     [esi+TY2],ebx          ;;             .ty2  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // ty    += tdy
-
-        add     esi,RASTERY_SIZEOF
-        dec     ecx
-        jne     .L_rright_v_loop
-
-        pop     ebp
-
-        jmp     short .L_finished
-;;
-;; TX varies, TY is constant
-;;
-.L_rright_h_loop:
-        mov     [esi+MAXX],eax           ;; rastertab[y].maxx = x
-          add     eax,edi
-        mov     [esi+TX2],ebx          ;;             .tx2  = tx
-          add     ebx,ebp
-        mov     [esi+TY2],edx          ;;             .ty2  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // tx    += tdx
-
-        add     esi,RASTERY_SIZEOF
-        dec     ecx
-        jne     .L_rright_h_loop
-
-        pop     ebp
-
-.L_finished:
-        pop     eax
-        o16 mov es,ax
-        pop     edi
-        pop     esi
-        pop     ebx
-
-        mov     esp,ebp
-        pop     ebp
-        ret
diff --git a/src/tmap.s b/src/tmap.s
deleted file mode 100644
index d98d82e25cedbea383b71beb122e7f250e12d765..0000000000000000000000000000000000000000
--- a/src/tmap.s
+++ /dev/null
@@ -1,1587 +0,0 @@
-// SONIC ROBO BLAST 2
-//-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2023 by Sonic Team Junior.
-//
-// This program is free software distributed under the
-// terms of the GNU General Public License, version 2.
-// See the 'LICENSE' file for more details.
-//-----------------------------------------------------------------------------
-/// \file  tmap.s
-/// \brief optimised drawing routines for span/column rendering
-
-// structures, must match the C structures!
-#include "asm_defs.inc"
-
-// Rappel: seuls EAX, ECX, EDX peuvent �tre �cras�s librement.
-//         il faut sauver esi,edi, cd...gs
-
-/* Attention aux comparaisons!                                              */
-/*                                                                          */
-/*      Intel_compare:                                                      */
-/*                                                                          */
-/*              cmp     A,B                     // A-B , set flags          */
-/*              jg      A_greater_than_B                                    */
-/*                                                                          */
-/*      AT&T_compare:                                                       */
-/*                                                                          */
-/*              cmp     A,B                     // B-A , set flags          */
-/*              jg      B_greater_than_A                                    */
-/*                                                                          */
-/*        (soustrait l'op�rande source DE l'op�rande destination,           */
-/*         comme sur Motorola! )                                            */
-
-// RAPPEL: Intel
-//         SECTION:[BASE+INDEX*SCALE+DISP]
-// devient SECTION:DISP(BASE,INDEX,SCALE)
-
-//----------------------------------------------------------------------
-//
-// R_DrawColumn
-//
-//   New optimised version 10-01-1998 by D.Fabrice and P.Boris
-//   TO DO: optimise it much farther... should take at most 3 cycles/pix
-//          once it's fixed, add code to patch the offsets so that it
-//          works in every screen width.
-//
-//----------------------------------------------------------------------
-
-    .data
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-C(loopcount):   .long   0
-C(pixelcount):  .long   0
-C(tystep):      .long   0
-
-C(vidwidth):    .long   0       //use this one out of the inner loops
-                                //so you don't need to patch everywhere...
-
-#ifdef USEASM
-#if !defined( LINUX)
-    .text
-#endif
-.globl C(ASM_PatchRowBytes)
-C(ASM_PatchRowBytes):
-    pushl   %ebp
-    movl    %esp, %ebp      // assure l'"adressabilit� du stack"
-
-    movl    ARG1, %edx         // read first arg
-    movl    %edx, C(vidwidth)
-
-    // 1 * vidwidth
-    movl    %edx,p1+2
-    movl    %edx,w1+2   //water
-    movl    %edx,p1b+2  //sky
-
-    movl    %edx,p5+2
-      movl    %edx,sh5+2        //smokie test
-
-    // 2 * vidwidth
-    addl    ARG1,%edx
-
-    movl    %edx,p2+2
-    movl    %edx,w2+2   //water
-    movl    %edx,p2b+2  //sky
-
-    movl    %edx,p6+2
-    movl    %edx,p7+2
-    movl    %edx,p8+2
-    movl    %edx,p9+2
-      movl    %edx,sh6+2         //smokie test
-      movl    %edx,sh7+2
-      movl    %edx,sh8+2
-      movl    %edx,sh9+2
-
-    // 3 * vidwidth
-    addl    ARG1,%edx
-
-    movl    %edx,p3+2
-    movl    %edx,w3+2   //water
-    movl    %edx,p3b+2  //sky
-
-    // 4 * vidwidth
-    addl    ARG1,%edx
-
-    movl    %edx,p4+2
-    movl    %edx,w4+2   //water
-    movl    %edx,p4b+2  //sky
-
-    popl    %ebp
-    ret
-
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawColumn_8)
-C(R_DrawColumn_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(dc_colormap),%eax
-    testb   $3,%dh
-    jz      v4quadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%al     // prep un-even loops
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-    addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-    addl    C(vidwidth),%edi
-     movb    %dl,(%edi)           // output pixel
-
-    addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      vdone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-v4quadloop:
-    movb    $0x7f,%dh           // prep mask
-//    .align  4
-vquadloop:
-    movb    (%esi,%ebx),%al     // prep loop
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    movb    %dl,(%edi)           // output pixel
-     andb    $0x7f,%bl            // mask 0-127 texture index
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p1:    movb    %dl,0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p2:    movb    %dl,2*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p3:    movb    %dl,3*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-p4:    addl    $4*0x12345678,%edi
-
-    decb   %ch
-     jnz    vquadloop
-
-vdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-#ifdef HORIZONTALDRAW
-// --------------------------------------------------------------------------
-// Horizontal Column Drawer Optimisation
-// --------------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawHColumn_8)
-C(R_DrawHColumn_8):
-    pushl   %ebp
-    pushl   %esi
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = yhlookup[dc_x] + hcolumnofs[dc_yl];
-//
-    movl    C(dc_x),%ebx
-    movl    C(yhlookup)(,%ebx,4),%edi
-    movl    C(dc_yl),%ebp
-    movl    %ebp,%ebx
-    addl    C(hcolumnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vhdone                      // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch     // quad count
-
-    testb   %ch, %ch
-    jz      vhnearlydone
-
-    movl    C(dc_colormap),%eax
-    decl    %edi                  //-----
-
-vhloop:
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     andb    $0x7f,%bl
-    incl    %edi                 //-----
-     movb    (%eax),%dh
-    movb    %dh,(%edi)           //-----
-
-     movb    (%esi,%ebx),%al      // fetch source texel
-    addl    %ebp,%edx
-     incl    %edi                //-----
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-    andb    $0x7f,%bl
-     movb    %dl,(%edi)          //-----
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-//    shll    $16,%edx
-     andb    $0x7f,%bl
-    incl    %edi                //-----
-     movb    (%eax),%dh
-    movb    %dh,(%edi)          //-----
-
-     movb    (%esi,%ebx),%al      // fetch source texel
-    addl    %ebp,%edx
-     incl    %edi               //-----
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-    andb    $0x7f,%bl
-     movb    %dl,(%edi)
-//     movl    %edx,(%edi)
-//    addl    $4,%edi
-
-    decb   %ch
-     jnz    vhloop
-
-vhnearlydone:
-//    movl    C(pixelcount)
-
-vhdone:
-    popl    %ebx
-    popl    %edi
-    popl    %esi
-    popl    %ebp
-    ret
-
-
-// --------------------------------------------------------------------------
-// Rotate a buffer 90 degree in clockwise order after horiz.col. draws
-// --------------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_RotateBuffer)
-C(R_RotateBuffer):
-    pushl   %ebp
-    pushl   %esi
-    pushl   %edi
-    pushl   %ebx
-
-
-    movl    C(dc_source),%esi
-    movl    C(dc_colormap),%edi
-
-
-    movb    (%esi),%ah
-     addl    $200,%esi
-    movb    (%ebx),%al
-     addl    $200,%ebx
-    bswap    %eax
-    movb    (%esi),%ah
-     addl    $200,%esi
-    movb    (%ebx),%al
-     addl    $200,%ebx
-    movl    %eax,(%edi)
-     addl    $4,%edi
-
-
-    popl    %ebx
-    popl    %edi
-    popl    %esi
-    popl    %ebp
-    ret
-#endif
-
-//----------------------------------------------------------------------
-//13-02-98:
-//   R_DrawSkyColumn : same as R_DrawColumn but:
-//
-//            - wrap around 256 instead of 127.
-//   this is needed because we have a higher texture for mouselook,
-//   we need at least 200 lines for the sky.
-//
-//   NOTE: the sky should never wrap, so it could use a faster method.
-//         for the moment, we'll still use a wrapping method...
-//
-//  IT S JUST A QUICK CUT N PASTE, WAS NOT OPTIMISED AS IT SHOULD BE !!!
-//
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawSkyColumn_8)
-C(R_DrawSkyColumn_8):
-    pushl   %ebp
-    pushl   %esi
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vskydone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x000000ff,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(dc_colormap),%eax
-    testb   $3,%dh
-    jz      v4skyquadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%al     // prep un-even loops
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-     movb    %dl,(%edi)           // output pixel
-    addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-     movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    addl    C(vidwidth),%edi
-     movb    %dl,(%edi)           // output pixel
-
-    addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      vskydone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-v4skyquadloop:
-//    .align  4
-vskyquadloop:
-    movb    (%esi,%ebx),%al     // prep loop
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p1b:    movb    %dl,0x12345678(%edi)
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p2b:    movb    %dl,2*0x12345678(%edi)
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p3b:    movb    %dl,3*0x12345678(%edi)
-
-p4b:    addl    $4*0x12345678,%edi
-
-    decb   %ch
-     jnz    vskyquadloop
-
-vskydone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-
-//----------------------------------------------------------------------
-//
-// R_DrawSpan
-//
-// Horizontal texture mapping
-//
-//----------------------------------------------------------------------
-
-    .data
-
-ystep:          .long   0
-xstep:          .long   0
-C(texwidth):    .long   64      // texture width
-#if !defined( LINUX)
-    .text
-#endif
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-.globl C(R_DrawSpan_8)
-C(R_DrawSpan_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-
-//
-// find loop count
-//
-    movl    C(ds_x2),%eax
-    incl    %eax
-    subl    C(ds_x1),%eax               // pixel count
-    movl    %eax,C(pixelcount)          // save for final pixel
-    js      hdone                       // nothing to scale
-    shrl    $1,%eax                     // double pixel count
-    movl    %eax,C(loopcount)
-
-//
-// build composite position
-//
-    movl    C(ds_xfrac),%ebp
-    shll    $10,%ebp
-    andl    $0x0ffff0000,%ebp
-    movl    C(ds_yfrac),%eax
-    shrl    $6,%eax
-    andl    $0x0ffff,%eax
-    movl    C(ds_y),%edi
-    orl     %eax,%ebp
-
-    movl    C(ds_source),%esi
-
-//
-// calculate screen dest
-//
-
-    movl    C(ylookup)(,%edi,4),%edi
-    movl    C(ds_x1),%eax
-    addl    C(columnofs)(,%eax,4),%edi
-
-//
-// build composite step
-//
-    movl    C(ds_xstep),%ebx
-    shll    $10,%ebx
-    andl    $0x0ffff0000,%ebx
-    movl    C(ds_ystep),%eax
-    shrl    $6,%eax
-    andl    $0x0ffff,%eax
-    orl     %eax,%ebx
-
-    //movl        %eax,OFFSET hpatch1+2        // convice tasm to modify code...
-    movl    %ebx,hpatch1+2
-    //movl        %eax,OFFSET hpatch2+2        // convice tasm to modify code...
-    movl    %ebx,hpatch2+2
-    movl    %esi,hpatch3+2
-    movl    %esi,hpatch4+2
-// %eax      aligned colormap
-// %ebx      aligned colormap
-// %ecx,%edx  scratch
-// %esi      virtual source
-// %edi      moving destination pointer
-// %ebp      frac
-    movl    C(ds_colormap),%eax
-//    shld    $22,%ebp,%ecx           // begin calculating third pixel (y units)
-//    shld    $6,%ebp,%ecx            // begin calculating third pixel (x units)
-     movl    %ebp,%ecx
-    addl    %ebx,%ebp               // advance frac pointer
-     shrw    $10,%cx
-     roll    $6,%ecx
-    andl    $4095,%ecx              // finish calculation for third pixel
-//    shld    $22,%ebp,%edx           // begin calculating fourth pixel (y units)
-//    shld    $6,%ebp,%edx            // begin calculating fourth pixel (x units)
-     movl    %ebp,%edx
-     shrw    $10,%dx
-     roll    $6,%edx
-    addl    %ebx,%ebp               // advance frac pointer
-    andl    $4095,%edx              // finish calculation for fourth pixel
-    movl    %eax,%ebx
-    movb    (%esi,%ecx),%al         // get first pixel
-    movb    (%esi,%edx),%bl         // get second pixel
-    testl   $0x0fffffffe,C(pixelcount)
-    movb    (%eax),%dl             // color translate first pixel
-
-//    jnz hdoubleloop             // at least two pixels to map
-//    jmp hchecklast
-
-//    movw $0xf0f0,%dx //see visplanes start
-
-    jz      hchecklast
-    movb    (%ebx),%dh              // color translate second pixel
-    movl    C(loopcount),%esi
-//    .align  4
-hdoubleloop:
-//    shld    $22,%ebp,%ecx        // begin calculating third pixel (y units)
-//    shld    $6,%ebp,%ecx         // begin calculating third pixel (x units)
-    movl    %ebp,%ecx
-    shrw    $10,%cx
-    roll    $6,%ecx
-hpatch1:
-    addl    $0x012345678,%ebp    // advance frac pointer
-    movw    %dx,(%edi)           // write first pixel
-    andl    $4095,%ecx           // finish calculation for third pixel
-//    shld    $22,%ebp,%edx        // begin calculating fourth pixel (y units)
-//    shld    $6,%ebp,%edx         // begin calculating fourth pixel (x units)
-    movl    %ebp,%edx
-    shrw    $10,%dx
-    roll    $6,%edx
-hpatch3:
-    movb    0x012345678(%ecx),%al      // get third pixel
-//    movb    %bl,1(%edi)          // write second pixel
-    andl    $4095,%edx           // finish calculation for fourth pixel
-hpatch2:
-    addl    $0x012345678,%ebp    // advance frac pointer
-hpatch4:
-    movb    0x012345678(%edx),%bl      // get fourth pixel
-    movb    (%eax),%dl           // color translate third pixel
-    addl    $2,%edi              // advance to third pixel destination
-    decl    %esi                 // done with loop?
-    movb    (%ebx),%dh           // color translate fourth pixel
-    jnz hdoubleloop
-
-// check for final pixel
-hchecklast:
-    testl   $1,C(pixelcount)
-    jz      hdone
-    movb    %dl,(%edi)           // write final pixel
-
-hdone:
-    popl    %ebx                 // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                 // restore caller's stack frame pointer
-    ret
-
-
-//.endif
-
-
-//----------------------------------------------------------------------
-// R_DrawTransColumn
-//
-// Vertical column texture drawer, with transparency. Replaces Doom2's
-// 'fuzz' effect, which was not so beautiful.
-// Transparency is always impressive in some way, don't know why...
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-
-.globl C(R_DrawTranslucentColumn_8)
-C(R_DrawTranslucentColumn_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vtdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-    movl     %edx,%ebx
-
-    shrl     $16,%ebx          // frac int.
-    andl     $0x0000007f,%ebx
-    shll     $16,%edx          // y frac up
-
-    movl     %ecx,%ebp
-    shll     $16,%ebp          // fracstep f. up
-    shrl     $16,%ecx          // fracstep i. ->cl
-    andb     $0x7f,%cl
-    pushw    %cx
-    movl     %edx,%ecx
-    popw     %cx
-    movl     C(dc_colormap),%edx
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    shrl    $2,%eax
-    testb   $0x03,C(pixelcount)
-    movb    %al,%ch             // quad count
-    movl    C(dc_transmap),%eax
-    jz      vt4quadloop
-//
-//  do un-even pixel
-//
-    testb   $1,C(pixelcount)
-    jz      2f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%ecx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,C(pixelcount)
-    jz      3f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%ecx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%ecx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      vtdone
-
-//
-// tystep : ystep frac. upper 24 bits
-// edx : upper 24 bit : colomap
-//  dl : tmp pixel to write
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : y     frac. upper 16 bits
-// ecx : ch = counter, cl = y step i.
-// eax : transmap aligned 65535 (upper 16 bit)
-//  ah : background pixel (from the screen buffer)
-//  al : foreground pixel (from the texture)
-// esi : source texture column
-// ebp,edi : dest screen
-//
-vt4quadloop:
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-p5: movb    0x12345678(%edi),%al           // fetch dest  : index into colormap
-
-    movl    %ebp,C(tystep)
-    movl    %edi,%ebp
-    subl    C(vidwidth),%edi
-    jmp inloop
-//    .align  4
-vtquadloop:
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p6: addl    $2*0x12345678,%ebp
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-inloop:
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p7: addl    $2*0x12345678,%edi
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p8: addl    $2*0x12345678,%ebp
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p9: addl    $2*0x12345678,%edi
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    decb   %ch
-     jnz    vtquadloop
-
-vtdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-#endif // ifdef USEASM
-
-
-
-//----------------------------------------------------------------------
-// R_DrawShadeColumn
-//
-//   for smoke..etc.. test.
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawShadeColumn_8)
-C(R_DrawShadeColumn_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      shdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(colormaps),%eax
-    testb   $0x03,%dh
-    jz      sh4quadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      shdone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-sh4quadloop:
-    movb    $0x7f,%dh           // prep mask
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-sh5:    movb    0x12345678(%edi),%al           // fetch dest  : index into colormap
-
-    movl    %ebp,C(tystep)
-    movl    %edi,%ebp
-    subl    C(vidwidth),%edi
-    jmp shinloop
-//    .align  4
-shquadloop:
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh6:    addl    $2*0x12345678,%ebp
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-shinloop:
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh7:    addl    $2*0x12345678,%edi
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh8:    addl    $2*0x12345678,%ebp
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh9:    addl    $2*0x12345678,%edi
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    decb   %ch
-     jnz    shquadloop
-
-shdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-
-//----------------------------------------------------------------------
-//
-//  R_DrawWaterColumn : basically it's just a copy of R_DrawColumn,
-//                      but it uses dc_colormap from dc_yl to dc_yw-1
-//                      then it uses dc_wcolormap from dc_yw to dc_yh
-//
-//  Thus, the 'underwater' part of the walls is remapped to 'water-like'
-//  colors.
-//
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawWaterColumn)
-C(R_DrawWaterColumn):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      wdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(dc_wcolormap),%eax
-    testb   $3,%dh
-    jz      w4quadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%al     // prep un-even loops
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-    addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-    addl    C(vidwidth),%edi
-     movb    %dl,(%edi)           // output pixel
-
-    addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      wdone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-w4quadloop:
-    movb    $0x7f,%dh           // prep mask
-//    .align  4
-wquadloop:
-    movb    (%esi,%ebx),%al     // prep loop
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    movb    %dl,(%edi)           // output pixel
-     andb    $0x7f,%bl            // mask 0-127 texture index
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-w1:    movb    %dl,0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-w2:    movb    %dl,2*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-w3:    movb    %dl,3*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-w4:    addl    $4*0x12345678,%edi
-
-    decb   %ch
-     jnz    wquadloop
-
-wdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-
-
-
-
-
-//----------------------------------------------------------------------
-//
-//  R_DrawSpanNoWrap
-//
-//      Horizontal texture mapping, does not remap colors,
-//      neither needs to wrap around the source texture.
-//
-//      Thus, a special optimisation can be used...
-//
-//----------------------------------------------------------------------
-
-    .data
-
-advancetable:   .long   0, 0
-#if !defined( LINUX)
-    .text
-#endif
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-.globl C(R_DrawSpanNoWrap)
-C(R_DrawSpanNoWrap):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// find loop count
-//
-
-    movl    C(ds_x2),%eax
-    incl    %eax
-    subl    C(ds_x1),%eax               // pixel count
-    movl    %eax,C(pixelcount)          // save for final pixel
-    jle     htvdone                       // nothing to scale
-//    shrl    $1,%eax                     // double pixel count
-//    movl    %eax,C(loopcount)
-
-//
-// calculate screen dest
-//
-
-    movl    C(ds_y),%edi        //full destination start address
-
-//
-// set up advancetable
-//
-
-    movl    C(ds_xstep),%ebp
-    movl    C(ds_ystep),%ecx
-    movl    %ecx,%eax
-    movl    %ebp,%edx
-    sarl    $16,%edx            // xstep >>= 16;
-    movl    C(vidwidth),%ebx
-    sarl    $16,%eax            // ystep >>= 16;
-    jz      0f
-    imull   %ebx,%eax           // (ystep >> 16) * texwidth;
-0:
-    addl    %edx,%eax           // add in xstep
-                                // (ystep >> 16) * texwidth + (xstep >> 16);
-
-    movl    %eax,advancetable+4 // advance base in y
-    addl    %ebx,%eax           // ((ystep >> 16) + 1) * texwidth +
-                                //  (xstep >> 16);
-    movl    %eax,advancetable   // advance extra in y
-
-    shll    $16,%ebp            // left-justify xstep fractional part
-    movl    %ebp,xstep
-    shll    $16,%ecx            // left-justify ystep fractional part
-    movl    %ecx,ystep
-
-//
-// calculate the texture starting address
-//
-    movl    C(ds_source),%esi       // texture source
-
-     movl    C(ds_yfrac),%eax
-     movl    %eax,%edx
-     sarl    $16,%eax
-    movl    C(ds_xfrac),%ecx
-     imull   %ebx,%eax               // (yfrac >> 16) * texwidth
-    movl    %ecx,%ebx
-    sarl    $16,%ecx
-    movl    %ecx,%ebp
-     addl    %eax,%ebp               // source = (xfrac >> 16) +
-                                    //           ((yfrac >> 16) * texwidth);
-
-//
-//  esi : texture source
-//  edi : screen dest
-//  eax : colormap aligned on 256 boundary, hehehe...
-//  ebx : xfrac << 16
-//  ecx : used in loop, contains either 0 or -1, *4, offset into advancetable
-//  edx : yfrac << 16
-//  ebp : offset into texture
-//
-
-    shll    $16,%edx             // yfrac upper word, lower byte will be used
-    movl    C(ds_colormap),%eax
-    shll    $16,%ebx             // xfrac upper word, lower unused
-
-    movl    C(pixelcount),%ecx
-    shrl    $2,%ecx
-    movb    %cl,%dh             // quad pixels count
-
-    movl    C(pixelcount),%ecx
-    andl    $3,%ecx
-    jz      htvquadloop         // pixelcount is multiple of 4
-    decl    %ecx
-    jz      1f
-    decl    %ecx
-    jz      2f
-
-//
-//  do one to three pixels first
-//
-    addl    ystep,%edx          // yfrac += ystep
-   sbbl    %ecx,%ecx           // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al          // get texture pixel
-   addl    xstep,%ebx           // xfrac += xstep
-//    movb    (%eax),%dl           // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)           // write pixel dest
-
-   incl    %edi
-
-2:
-    addl    ystep,%edx          // yfrac += ystep
-   sbbl    %ecx,%ecx           // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al          // get texture pixel
-   addl    xstep,%ebx           // xfrac += xstep
-//    movb    (%eax),%dl           // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)           // write pixel dest
-
-   incl    %edi
-
-1:
-    addl    ystep,%edx          // yfrac += ystep
-   sbbl    %ecx,%ecx           // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al          // get texture pixel
-   addl    xstep,%ebx           // xfrac += xstep
-//    movb    (%eax),%dl           // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)           // write pixel dest
-
-   incl    %edi
-
-//
-//  test if there was at least 4 pixels
-//
-    testb   $0xFF,%dh
-    jz      htvdone
-
-//
-//  two pixels per loop
-// U
-//  V
-htvquadloop:
-    addl    ystep,%edx             // yfrac += ystep
-   sbbl    %ecx,%ecx               // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al        // get texture pixel
-   addl    xstep,%ebx              // xfrac += xstep
-//    movb    (%eax),%dl             // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)             // write pixel dest
-
-    addl    ystep,%edx
-   sbbl    %ecx,%ecx
-    movb    (%esi,%ebp),%al
-   addl    xstep,%ebx
-//    movb    (%eax),%dl
-   adcl    advancetable+4(,%ecx,4),%ebp
-    movb    %al,1(%edi)
-
-    addl    ystep,%edx
-   sbbl    %ecx,%ecx
-    movb    (%esi,%ebp),%al
-   addl    xstep,%ebx
-//    movb    (%eax),%dl
-   adcl    advancetable+4(,%ecx,4),%ebp
-    movb    %al,2(%edi)
-
-    addl    ystep,%edx
-   sbbl    %ecx,%ecx
-    movb    (%esi,%ebp),%al
-   addl    xstep,%ebx
-//    movb    (%eax),%dl
-   adcl    advancetable+4(,%ecx,4),%ebp
-    movb    %al,3(%edi)
-
-   addl    $4, %edi
-    incl    %ecx    //dummy
-
-   decb   %dh
-    jnz    htvquadloop          // paire dans V-pipe
-
-htvdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-//.endif
-
-#ifdef HORIZONTALDRAW
-// void R_RotateBuffere (void)
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-.globl C(R_RotateBufferasm)
-C(R_RotateBufferasm):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-    movl    C(dc_source),%esi
-    movl    C(dc_colormap),%edi
-
-    movl    $200,%edx
-ra2:
-    movl    $40,%ecx
-ra:
-    movb    -2*200(%esi),%al
-    movb    -6*200(%esi),%bl
-    movb    -3*200(%esi),%ah
-    movb    -7*200(%esi),%bh
-    shll    $16,%eax
-    shll    $16,%ebx
-    movb    (%esi),%al
-    movb    -4*200(%esi),%bl
-    movb    -1*200(%esi),%ah
-    movb    -5*200(%esi),%bh
-    movl    %eax,(%edi)
-    subl    $8*200,%esi
-    movl    %ebx,4(%edi)
-    addl    $8,%edi
-    decl    %ecx
-    jnz     ra
-
-    addl    $320*200+1,%esi      //32*480 passe a la ligne suivante
-//    addl    320-32,%edi
-
-    decl    %edx
-    jnz     ra2
-
-    pop   %ebp                // preserve caller's stack frame pointer
-    pop   %esi                // preserve register variables
-    pop   %edi
-    pop   %ebx
-    ret
-#endif
diff --git a/src/tmap_asm.s b/src/tmap_asm.s
deleted file mode 100644
index d8967178cdf28e3b9bedbda863232ef0bf0978d4..0000000000000000000000000000000000000000
--- a/src/tmap_asm.s
+++ /dev/null
@@ -1,322 +0,0 @@
-// SONIC ROBO BLAST 2
-//-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2023 by Sonic Team Junior.
-//
-// This program is free software distributed under the
-// terms of the GNU General Public License, version 2.
-// See the 'LICENSE' file for more details.
-//-----------------------------------------------------------------------------
-/// \file  tmap_asm.s
-/// \brief ???
-
-//.comm _dc_colormap,4
-//.comm _dc_x,4
-//.comm _dc_yl,4
-//.comm _dc_yh,4
-//.comm _dc_iscale,4
-//.comm _dc_texturemid,4
-//.comm _dc_source,4
-//.comm _ylookup,4
-//.comm _columnofs,4
-//.comm _loopcount,4
-//.comm _pixelcount,4
-.data
-_pixelcount:
-.long 0x00000000
-_loopcount:
-.long 0x00000000
-.align 8
-_mmxcomm:
-.long 0x00000000
-.text
-
-        .align 4
-.globl _R_DrawColumn8_NOMMX
-_R_DrawColumn8_NOMMX:
-   pushl %ebp
-   pushl %esi
-   pushl %edi
-   pushl %ebx
-	movl _dc_yl,%edx
-	movl _dc_yh,%eax
-	subl %edx,%eax
-	leal 1(%eax),%ebx
-	testl %ebx,%ebx
-	jle rdc8ndone
-	movl _dc_x,%eax
-        movl _ylookup, %edi
-	movl (%edi,%edx,4),%esi
-	movl _columnofs, %edi
-	addl (%edi,%eax,4),%esi
-	movl _dc_iscale,%edi
-	movl %edx,%eax
-	imull %edi,%eax
-	movl _dc_texturemid,%ecx
-	addl %eax,%ecx
-
-	movl _dc_source,%ebp
-   xorl %edx, %edx
-   subl $0x12345678, %esi
-.globl rdc8nwidth1
-rdc8nwidth1:
-	.align 4,0x90
-rdc8nloop:
-	movl %ecx,%eax
-	shrl $16,%eax
-	addl %edi,%ecx
-	andl $127,%eax
-	addl $0x12345678,%esi
-.globl rdc8nwidth2
-rdc8nwidth2:
-	movb (%eax,%ebp),%dl
-	movl _dc_colormap,%eax
-	movb (%eax,%edx),%al
-	movb %al,(%esi)
-	decl %ebx
-	jne rdc8nloop
-rdc8ndone:
-   popl %ebx
-   popl %edi
-   popl %esi
-   popl %ebp
-   ret
-
-//
-// Optimised specifically for P54C/P55C (aka Pentium with/without MMX)
-// By ES 1998/08/01
-//
-
-.globl _R_DrawColumn_8_Pentium
-_R_DrawColumn_8_Pentium:
-	pushl %ebp
-        pushl %ebx
-	pushl %esi
-        pushl %edi
-	movl _dc_yl,%eax        // Top pixel
-	movl _dc_yh,%ebx        // Bottom pixel
-        movl _ylookup, %edi
-	movl (%edi,%ebx,4),%ecx
-	subl %eax,%ebx          // ebx=number of pixels-1
-	jl rdc8pdone            // no pixel to draw, done
-	jnz rdc8pmany
-	movl _dc_x,%edx         // Special case: only one pixel
-        movl _columnofs, %edi
-	addl (%edi,%edx,4),%ecx // dest pixel at (%ecx)
-	movl _dc_iscale,%esi
-	imull %esi,%eax
-	movl _dc_texturemid,%edi
-	addl %eax,%edi          // texture index in edi
-	movl _dc_colormap,%edx
-   	shrl $16, %edi
-   	movl _dc_source,%ebp
-	andl $127,%edi
-	movb (%edi,%ebp),%dl    // read texture pixel
-	movb (%edx),%al	        // lookup for light
-	movb %al,0(%ecx) 	// write it
-	jmp rdc8pdone		// done!
-.align 4, 0x90
-rdc8pmany:			// draw >1 pixel
-	movl _dc_x,%edx
-        movl _columnofs, %edi
-	movl (%edi,%edx,4),%edx
-	leal 0x12345678(%edx, %ecx), %edi  // edi = two pixels above bottom
-.globl rdc8pwidth5
-rdc8pwidth5:  // DeadBeef = -2*SCREENWIDTH
-        movl _dc_iscale,%edx	// edx = fracstep
-	imull %edx,%eax
-   	shll $9, %edx           // fixme: Should get 7.25 fix as input
-	movl _dc_texturemid,%ecx
-	addl %eax,%ecx          // ecx = frac
-	movl _dc_colormap,%eax  // eax = lighting/special effects LUT
-   	shll $9, %ecx
-   	movl _dc_source,%esi    // esi = source ptr
-
-	imull $0x12345678, %ebx // ebx = negative offset to pixel
-.globl rdc8pwidth6
-rdc8pwidth6:  // DeadBeef = -SCREENWIDTH
-
-// Begin the calculation of the two first pixels
-        leal (%ecx, %edx), %ebp
-	shrl $25, %ecx
-	movb (%esi, %ecx), %al
-	leal (%edx, %ebp), %ecx
-	shrl $25, %ebp
-        movb (%eax), %dl
-
-// The main loop
-rdc8ploop:
-	movb (%esi,%ebp), %al		// load 1
-        leal (%ecx, %edx), %ebp         // calc frac 3
-
-	shrl $25, %ecx                  // shift frac 2
-        movb %dl, 0x12345678(%edi, %ebx)// store 0
-.globl rdc8pwidth1
-rdc8pwidth1:  // DeadBeef = 2*SCREENWIDTH
-
-        movb (%eax), %al                // lookup 1
-
-        movb %al, 0x12345678(%edi, %ebx)// store 1
-.globl rdc8pwidth2
-rdc8pwidth2:  // DeadBeef = 3*SCREENWIDTH
-        movb (%esi, %ecx), %al          // load 2
-
-        leal (%ebp, %edx), %ecx         // calc frac 4
-
-        shrl $25, %ebp                  // shift frac 3
-        movb (%eax), %dl                // lookup 2
-
-        addl $0x12345678, %ebx          // counter
-.globl rdc8pwidth3
-rdc8pwidth3:  // DeadBeef = 2*SCREENWIDTH
-        jl rdc8ploop                    // loop
-
-// End of loop. Write extra pixel or just exit.
-        jnz rdc8pdone
-        movb %dl, 0x12345678(%edi, %ebx)// Write odd pixel
-.globl rdc8pwidth4
-rdc8pwidth4:  // DeadBeef = 2*SCREENWIDTH
-
-rdc8pdone:
-
-        popl %edi
-	popl %esi
-        popl %ebx
-	popl %ebp
-        ret
-
-//
-// MMX asm version, optimised for K6
-// By ES 1998/07/05
-//
-
-.globl _R_DrawColumn_8_K6_MMX
-_R_DrawColumn_8_K6_MMX:
-	pushl %ebp
-        pushl %ebx
-	pushl %esi
-        pushl %edi
-
-        movl %esp, %eax // Push 8 or 12, so that (%esp) gets aligned by 8
-        andl $7,%eax
-        addl $8,%eax
-        movl %eax, _mmxcomm // Temp storage in mmxcomm: (%esp) is used instead
-        subl %eax,%esp
-
-	movl _dc_yl,%edx        // Top pixel
-	movl _dc_yh,%ebx        // Bottom pixel
-        movl _ylookup, %edi
-	movl (%edi,%ebx,4),%ecx
-	subl %edx,%ebx         // ebx=number of pixels-1
-	jl 0x12345678            // no pixel to draw, done
-.globl rdc8moffs1
-rdc8moffs1:
-	jnz rdc8mmany
-	movl _dc_x,%eax         // Special case: only one pixel
-        movl _columnofs, %edi
-	addl (%edi,%eax,4),%ecx  // dest pixel at (%ecx)
-	movl _dc_iscale,%esi
-	imull %esi,%edx
-	movl _dc_texturemid,%edi
-	addl %edx,%edi         // texture index in edi
-	movl _dc_colormap,%edx
-   	shrl $16, %edi
-   	movl _dc_source,%ebp
-	andl $127,%edi
-	movb (%edi,%ebp),%dl  // read texture pixel
-	movb (%edx),%al	 // lookup for light
-	movb %al,0(%ecx) 	 // write it
-	jmp rdc8mdone		 // done!
-.globl rdc8moffs2
-rdc8moffs2:
-.align 4, 0x90
-rdc8mmany:			 // draw >1 pixel
-	movl _dc_x,%eax
-        movl _columnofs, %edi
-	movl (%edi,%eax,4),%eax
-	leal 0x12345678(%eax, %ecx), %esi  // esi = two pixels above bottom
-.globl rdc8mwidth3
-rdc8mwidth3:  // DeadBeef = -2*SCREENWIDTH
-        movl _dc_iscale,%ecx	 // ecx = fracstep
-	imull %ecx,%edx
-   	shll $9, %ecx           // fixme: Should get 7.25 fix as input
-	movl _dc_texturemid,%eax
-	addl %edx,%eax         // eax = frac
-	movl _dc_colormap,%edx  // edx = lighting/special effects LUT
-   	shll $9, %eax
-	leal (%ecx, %ecx), %edi
-   	movl _dc_source,%ebp    // ebp = source ptr
-	movl %edi, 0(%esp)     // Start moving frac and fracstep to MMX regs
-
-	imull $0x12345678, %ebx  // ebx = negative offset to pixel
-.globl rdc8mwidth5
-rdc8mwidth5:  // DeadBeef = -SCREENWIDTH
-
-	movl %edi, 4(%esp)
-	leal (%eax, %ecx), %edi
-	movq 0(%esp), %mm1     // fracstep:fracstep in mm1
-	movl %eax, 0(%esp)
-	shrl $25, %eax
-	movl %edi, 4(%esp)
-	movzbl (%ebp, %eax), %eax
-	movq 0(%esp), %mm0     // frac:frac in mm0
-
-	paddd %mm1, %mm0
-	shrl $25, %edi
-	movq %mm0, %mm2
-	psrld $25, %mm2         // texture index in mm2
-	paddd %mm1, %mm0
-	movq %mm2, 0(%esp)
-
-.globl rdc8mloop
-rdc8mloop:                      		// The main loop
-	movq %mm0, %mm2                    // move 4-5 to temp reg
-	movzbl (%ebp, %edi), %edi 		// read 1
-
-	psrld $25, %mm2 			// shift 4-5
-	movb (%edx,%eax), %cl 		// lookup 0
-
-	movl 0(%esp), %eax 			// load 2
-	addl $0x12345678, %ebx 		// counter
-.globl rdc8mwidth2
-rdc8mwidth2:  // DeadBeef = 2*SCREENWIDTH
-
-	movb %cl, (%esi, %ebx)		// write 0
-	movb (%edx,%edi), %ch 		// lookup 1
-
-	movb %ch, 0x12345678(%esi, %ebx) 	// write 1
-.globl rdc8mwidth1
-rdc8mwidth1:  // DeadBeef = SCREENWIDTH
-	movl 4(%esp), %edi			// load 3
-
-	paddd %mm1, %mm0 			// frac 6-7
-	movzbl (%ebp, %eax), %eax 		// lookup 2
-
-	movq %mm2, 0(%esp) 		     // store texture index 4-5
-	jl rdc8mloop
-
-	jnz rdc8mno_odd
-	movb (%edx,%eax), %cl  // write the last odd pixel
-	movb %cl, 0x12345678(%esi)
-.globl rdc8mwidth4
-rdc8mwidth4:  // DeadBeef = 2*SCREENWIDTH
-rdc8mno_odd:
-
-.globl rdc8mdone
-rdc8mdone:
-        emms
-
-        addl _mmxcomm, %esp
-        popl %edi
-	popl %esi
-        popl %ebx
-	popl %ebp
-        ret
-
-// Need some extra space to align run-time
-.globl R_DrawColumn_8_K6_MMX_end
-R_DrawColumn_8_K6_MMX_end:
-nop;nop;nop;nop;nop;nop;nop;nop;
-nop;nop;nop;nop;nop;nop;nop;nop;
-nop;nop;nop;nop;nop;nop;nop;nop;
-nop;nop;nop;nop;nop;nop;nop;
diff --git a/src/tmap_mmx.nas b/src/tmap_mmx.nas
deleted file mode 100644
index a45667e23d539997193e0df23862dba71458c6f6..0000000000000000000000000000000000000000
--- a/src/tmap_mmx.nas
+++ /dev/null
@@ -1,674 +0,0 @@
-;; SONIC ROBO BLAST 2
-;;-----------------------------------------------------------------------------
-;; Copyright (C) 1998-2000 by DOSDOOM.
-;; Copyright (C) 2010-2023 by Sonic Team Junior.
-;;
-;; This program is free software distributed under the
-;; terms of the GNU General Public License, version 2.
-;; See the 'LICENSE' file for more details.
-;;-----------------------------------------------------------------------------
-;; FILE:
-;;      tmap_mmx.nas
-;; DESCRIPTION:
-;;      Assembler optimised rendering code for software mode, using SIMD
-;;      instructions.
-;;      Draw wall columns.
-
-
-[BITS 32]
-
-%define FRACBITS 16
-%define TRANSPARENTPIXEL 255
-
-%ifdef LINUX
-%macro cextern 1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-[global %1]
-%endmacro
-
-%else
-%macro cextern 1
-%define %1 _%1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-%define %1 _%1
-[global %1]
-%endmacro
-
-%endif
-
-
-; The viddef_s structure. We only need the width field.
-struc viddef_s
-		resb 12
-.width: resb 4
-		resb 44
-endstruc
-
-
-;; externs
-;; columns
-cextern dc_colormap
-cextern dc_x
-cextern dc_yl
-cextern dc_yh
-cextern dc_iscale
-cextern dc_texturemid
-cextern dc_texheight
-cextern dc_source
-cextern dc_hires
-cextern centery
-cextern centeryfrac
-cextern dc_transmap
-
-cextern R_DrawColumn_8_ASM
-cextern R_Draw2sMultiPatchColumn_8_ASM
-
-;; spans
-cextern nflatshiftup
-cextern nflatxshift
-cextern nflatyshift
-cextern nflatmask
-cextern ds_xfrac
-cextern ds_yfrac
-cextern ds_xstep
-cextern ds_ystep
-cextern ds_x1
-cextern ds_x2
-cextern ds_y
-cextern ds_source
-cextern ds_colormap
-
-cextern ylookup
-cextern columnofs
-cextern vid
-
-[SECTION .data]
-
-nflatmask64		dq		0
-
-
-[SECTION .text]
-
-;;----------------------------------------------------------------------
-;;
-;; R_DrawColumn : 8bpp column drawer
-;;
-;; MMX column drawer.
-;;
-;;----------------------------------------------------------------------
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = accumulator
-;; esi = source
-;; edi = dest
-;; ebp = vid.width
-;; mm0 = accumulator
-;; mm1 = heightmask, twice
-;; mm2 = 2 * fracstep, twice
-;; mm3 = pair of consecutive fracs
-;;----------------------------------------------------------------------
-
-
-cglobal R_DrawColumn_8_MMX
-R_DrawColumn_8_MMX:
-		push		ebp						;; preserve caller's stack frame pointer
-		push		esi						;; preserve register variables
-		push		edi
-		push		ebx
-
-;;
-;; Our algorithm requires that the texture height be a power of two.
-;; If not, fall back to the non-MMX drawer.
-;;
-.texheightcheck:
-		mov			edx, [dc_texheight]
-		sub			edx, 1					;; edx = heightmask
-		test		edx, [dc_texheight]
-		jnz			near .usenonMMX
-
-		mov			ebp, edx				;; Keep a copy of heightmask in a
-											;; GPR for the time being.
-
-;;
-;; Fill mm1 with heightmask
-;;
-		movd		mm1, edx				;; low dword = heightmask
-		punpckldq	mm1, mm1				;; copy low dword to high dword
-
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-		mov			eax, [dc_yl]
-		mov			edi, [ylookup+eax*4]
-		mov			ebx, [dc_x]
-		add			edi, [columnofs+ebx*4]	;; edi = dest
-
-
-;;
-;; pixelcount = yh - yl + 1
-;;
-		mov			ecx, [dc_yh]
-		add			ecx, 1
-		sub			ecx, eax				;; pixel count
-		jle			near .done				;; nothing to scale
-
-;;
-;; fracstep = dc_iscale;
-;;
-		movd		mm2, [dc_iscale]		;; fracstep in low dword
-		punpckldq	mm2, mm2				;; copy to high dword
-
-		mov			ebx, [dc_colormap]
-		mov			esi, [dc_source]
-
-;;
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-											;; eax == dc_yl already
-		shl			eax, FRACBITS
-		sub			eax, [centeryfrac]
-		imul		dword [dc_iscale]
-		shrd		eax, edx, FRACBITS
-		add			eax, [dc_texturemid]
-
-;;
-;; if (dc_hires) frac = 0;
-;;
-		test		byte [dc_hires], 0x01
-		jz			.mod2
-		xor			eax, eax
-
-
-;;
-;; Do mod-2 pixel.
-;;
-.mod2:
-		test		ecx, 1
-		jz			.pairprepare
-		mov			edx, eax				;; edx = frac
-		add			eax, [dc_iscale]		;; eax += fracstep
-		sar			edx, FRACBITS
-		and			edx, ebp				;; edx &= heightmask
-		movzx		edx, byte [esi + edx]
-		movzx		edx, byte [ebx + edx]
-		mov			[edi], dl
-
-		add			edi, [vid + viddef_s.width]
-		sub			ecx, 1
-		jz			.done
-
-.pairprepare:
-;;
-;; Prepare for the main loop.
-;;
-		movd		mm3, eax				;; Low dword = frac
-		movq		mm4, mm3				;; Copy to intermediate register
-		paddd		mm4, mm2				;; dwords of mm4 += fracstep
-		punpckldq	mm3, mm4				;; Low dword = first frac, high = second
-		pslld		mm2, 1					;; fracstep *= 2
-
-;;
-;; ebp = vid.width
-;;
-		mov			ebp, [vid + viddef_s.width]
-
-		align		16
-.pairloop:
-		movq		mm0, mm3				;; 3B 1u.
-		psrad		mm0, FRACBITS			;; 4B 1u.
-		pand		mm0, mm1				;; 3B 1u. frac &= heightmask
-		paddd		mm3, mm2				;; 3B 1u. frac += fracstep
-
-		movd		eax, mm0				;; 3B 1u. Get first frac
-;; IFETCH boundary
-		movzx		eax, byte [esi + eax]	;; 4B 1u. Texture map
-		movzx		eax, byte [ebx + eax]	;; 4B 1u. Colormap
-
-		punpckhdq	mm0, mm0				;; 3B 1(2)u. low dword = high dword
-		movd		edx, mm0				;; 3B 1u. Get second frac
-		mov			[edi], al				;; 2B 1(2)u. First pixel
-;; IFETCH boundary
-
-		movzx		edx, byte [esi + edx]	;; 4B 1u. Texture map
-		movzx		edx, byte [ebx + edx]	;; 4B 1u. Colormap
-		mov			[edi + 1*ebp], dl		;; 3B 1(2)u. Second pixel
-
-		lea			edi, [edi + 2*ebp]		;; 3B 1u. edi += 2 * vid.width
-;; IFETCH boundary
-		sub			ecx, 2					;; 3B 1u. count -= 2
-		jnz			.pairloop				;; 2B 1u. if(count != 0) goto .pairloop
-
-
-.done:
-;;
-;; Clear MMX state, or else FPU operations will go badly awry.
-;;
-		emms
-
-		pop			ebx
-		pop			edi
-		pop			esi
-		pop			ebp
-		ret
-
-.usenonMMX:
-		call		R_DrawColumn_8_ASM
-		jmp			.done
-
-
-;;----------------------------------------------------------------------
-;;
-;; R_Draw2sMultiPatchColumn : Like R_DrawColumn, but omits transparent
-;;                            pixels.
-;;
-;; MMX column drawer.
-;;
-;;----------------------------------------------------------------------
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = accumulator
-;; esi = source
-;; edi = dest
-;; ebp = vid.width
-;; mm0 = accumulator
-;; mm1 = heightmask, twice
-;; mm2 = 2 * fracstep, twice
-;; mm3 = pair of consecutive fracs
-;;----------------------------------------------------------------------
-
-
-cglobal R_Draw2sMultiPatchColumn_8_MMX
-R_Draw2sMultiPatchColumn_8_MMX:
-		push		ebp						;; preserve caller's stack frame pointer
-		push		esi						;; preserve register variables
-		push		edi
-		push		ebx
-
-;;
-;; Our algorithm requires that the texture height be a power of two.
-;; If not, fall back to the non-MMX drawer.
-;;
-.texheightcheck:
-		mov			edx, [dc_texheight]
-		sub			edx, 1					;; edx = heightmask
-		test		edx, [dc_texheight]
-		jnz			near .usenonMMX
-
-		mov			ebp, edx				;; Keep a copy of heightmask in a
-											;; GPR for the time being.
-
-;;
-;; Fill mm1 with heightmask
-;;
-		movd		mm1, edx				;; low dword = heightmask
-		punpckldq	mm1, mm1				;; copy low dword to high dword
-
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-		mov			eax, [dc_yl]
-		mov			edi, [ylookup+eax*4]
-		mov			ebx, [dc_x]
-		add			edi, [columnofs+ebx*4]	;; edi = dest
-
-
-;;
-;; pixelcount = yh - yl + 1
-;;
-		mov			ecx, [dc_yh]
-		add			ecx, 1
-		sub			ecx, eax				;; pixel count
-		jle			near .done				;; nothing to scale
-;;
-;; fracstep = dc_iscale;
-;;
-		movd		mm2, [dc_iscale]		;; fracstep in low dword
-		punpckldq	mm2, mm2				;; copy to high dword
-
-		mov			ebx, [dc_colormap]
-		mov			esi, [dc_source]
-
-;;
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-											;; eax == dc_yl already
-		shl			eax, FRACBITS
-		sub			eax, [centeryfrac]
-		imul		dword [dc_iscale]
-		shrd		eax, edx, FRACBITS
-		add			eax, [dc_texturemid]
-
-;;
-;; if (dc_hires) frac = 0;
-;;
-		test		byte [dc_hires], 0x01
-		jz			.mod2
-		xor			eax, eax
-
-
-;;
-;; Do mod-2 pixel.
-;;
-.mod2:
-		test		ecx, 1
-		jz			.pairprepare
-		mov			edx, eax				;; edx = frac
-		add			eax, [dc_iscale]		;; eax += fracstep
-		sar			edx, FRACBITS
-		and			edx, ebp				;; edx &= heightmask
-		movzx		edx, byte [esi + edx]
-		cmp			dl, TRANSPARENTPIXEL
-		je			.nextmod2
-		movzx		edx, byte [ebx + edx]
-		mov			[edi], dl
-
-.nextmod2:
-		add			edi, [vid + viddef_s.width]
-		sub			ecx, 1
-		jz			.done
-
-.pairprepare:
-;;
-;; Prepare for the main loop.
-;;
-		movd		mm3, eax				;; Low dword = frac
-		movq		mm4, mm3				;; Copy to intermediate register
-		paddd		mm4, mm2				;; dwords of mm4 += fracstep
-		punpckldq	mm3, mm4				;; Low dword = first frac, high = second
-		pslld		mm2, 1					;; fracstep *= 2
-
-;;
-;; ebp = vid.width
-;;
-		mov			ebp, [vid + viddef_s.width]
-
-		align		16
-.pairloop:
-		movq		mm0, mm3				;; 3B 1u.
-		psrad		mm0, FRACBITS			;; 4B 1u.
-		pand		mm0, mm1				;; 3B 1u. frac &= heightmask
-		paddd		mm3, mm2				;; 3B 1u. frac += fracstep
-
-		movd		eax, mm0				;; 3B 1u. Get first frac
-;; IFETCH boundary
-		movzx		eax, byte [esi + eax]	;; 4B 1u. Texture map
-		punpckhdq	mm0, mm0				;; 3B 1(2)u. low dword = high dword
-		movd		edx, mm0				;; 3B 1u. Get second frac
-		cmp			al, TRANSPARENTPIXEL	;; 2B 1u.
-		je			.secondinpair			;; 2B 1u.
-;; IFETCH boundary
-		movzx		eax, byte [ebx + eax]	;; 4B 1u. Colormap
-		mov			[edi], al				;; 2B 1(2)u. First pixel
-
-.secondinpair:
-		movzx		edx, byte [esi + edx]	;; 4B 1u. Texture map
-		cmp			dl, TRANSPARENTPIXEL	;; 2B 1u.
-		je			.nextpair				;; 2B 1u.
-;; IFETCH boundary
-		movzx		edx, byte [ebx + edx]	;; 4B 1u. Colormap
-		mov			[edi + 1*ebp], dl		;; 3B 1(2)u. Second pixel
-
-.nextpair:
-		lea			edi, [edi + 2*ebp]		;; 3B 1u. edi += 2 * vid.width
-		sub			ecx, 2					;; 3B 1u. count -= 2
-		jnz			.pairloop				;; 2B 1u. if(count != 0) goto .pairloop
-
-
-.done:
-;;
-;; Clear MMX state, or else FPU operations will go badly awry.
-;;
-		emms
-
-		pop			ebx
-		pop			edi
-		pop			esi
-		pop			ebp
-		ret
-
-.usenonMMX:
-		call		R_Draw2sMultiPatchColumn_8_ASM
-		jmp			.done
-
-
-;;----------------------------------------------------------------------
-;;
-;; R_DrawSpan : 8bpp span drawer
-;;
-;; MMX span drawer.
-;;
-;;----------------------------------------------------------------------
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = accumulator
-;; esi = source
-;; edi = dest
-;; ebp = two pixels
-;; mm0 = accumulator
-;; mm1 = xposition
-;; mm2 = yposition
-;; mm3 = 2 * xstep
-;; mm4 = 2 * ystep
-;; mm5 = nflatxshift
-;; mm6 = nflatyshift
-;; mm7 = accumulator
-;;----------------------------------------------------------------------
-
-cglobal R_DrawSpan_8_MMX
-R_DrawSpan_8_MMX:
-		push		ebp						;; preserve caller's stack frame pointer
-		push		esi						;; preserve register variables
-		push		edi
-		push		ebx
-
-;;
-;; esi = ds_source
-;; ebx = ds_colormap
-;;
-		mov			esi, [ds_source]
-		mov			ebx, [ds_colormap]
-
-;;
-;; edi = ylookup[ds_y] + columnofs[ds_x1]
-;;
-		mov			eax, [ds_y]
-		mov			edi, [ylookup + eax*4]
-		mov			edx, [ds_x1]
-		add			edi, [columnofs + edx*4]
-
-;;
-;; ecx = ds_x2 - ds_x1 + 1
-;;
-		mov			ecx, [ds_x2]
-		sub			ecx, edx
-		add			ecx, 1
-
-;;
-;; Needed for fracs and steps
-;;
-		movd		mm7, [nflatshiftup]
-
-;;
-;; mm3 = xstep
-;;
-		movd		mm3, [ds_xstep]
-		pslld		mm3, mm7
-		punpckldq	mm3, mm3
-
-;;
-;; mm4 = ystep
-;;
-		movd		mm4, [ds_ystep]
-		pslld		mm4, mm7
-		punpckldq	mm4, mm4
-
-;;
-;; mm1 = pair of consecutive xpositions
-;;
-		movd		mm1, [ds_xfrac]
-		pslld		mm1, mm7
-		movq		mm6, mm1
-		paddd		mm6, mm3
-		punpckldq	mm1, mm6
-
-;;
-;; mm2 = pair of consecutive ypositions
-;;
-		movd		mm2, [ds_yfrac]
-		pslld		mm2, mm7
-		movq		mm6, mm2
-		paddd		mm6, mm4
-		punpckldq	mm2, mm6
-
-;;
-;; mm5 = nflatxshift
-;; mm6 = nflatyshift
-;;
-		movd		mm5, [nflatxshift]
-		movd		mm6, [nflatyshift]
-
-;;
-;; Mask is in memory due to lack of registers.
-;;
-		mov			eax, [nflatmask]
-		mov			[nflatmask64], eax
-		mov			[nflatmask64 + 4], eax
-
-
-;;
-;; Go until we reach a dword boundary.
-;;
-.unaligned:
-		test		edi, 3
-		jz			.alignedprep
-.stragglers:
-		cmp			ecx, 0
-		je			.done					;; If ecx == 0, we're finished.
-
-;;
-;; eax = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)
-;;
-		movq		mm0, mm1				;; mm0 = xposition
-		movq		mm7, mm2				;; mm7 = yposition
-		paddd		mm1, mm3				;; xposition += xstep (once!)
-		paddd		mm2, mm4				;; yposition += ystep (once!)
-		psrld		mm0, mm5				;; shift
-		psrld		mm7, mm6				;; shift
-		pand		mm7, [nflatmask64]		;; mask
-		por			mm0, mm7				;; or x and y together
-
-		movd		eax, mm0				;; eax = index of first pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-
-		mov			[edi], al
-		add			edi, 1
-
-		sub			ecx, 1
-		jmp			.unaligned
-
-
-.alignedprep:
-;;
-;; We can double the steps now.
-;;
-		pslld		mm3, 1
-		pslld		mm4, 1
-
-
-;;
-;; Generate chunks of four pixels.
-;;
-.alignedloop:
-
-;;
-;; Make sure we have at least four pixels.
-;;
-		cmp			ecx, 4
-		jl			.prestragglers
-
-;;
-;; First two pixels.
-;;
-		movq		mm0, mm1				;; mm0 = xposition
-		movq		mm7, mm2				;; mm7 = yposition
-		paddd		mm1, mm3				;; xposition += xstep
-		paddd		mm2, mm4				;; yposition += ystep
-		psrld		mm0, mm5				;; shift
-		psrld		mm7, mm6				;; shift
-		pand		mm7, [nflatmask64]		;; mask
-		por			mm0, mm7				;; or x and y together
-
-		movd		eax, mm0				;; eax = index of first pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		ebp, byte [ebx + eax]	;; ebp = colormap[al]
-
-		punpckhdq	mm0, mm0				;; both dwords = high dword
-		movd		eax, mm0				;; eax = index of second pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-		shl			eax, 8					;; get pixel in right byte
-		or			ebp, eax				;; put pixel in ebp
-
-;;
-;; Next two pixels.
-;;
-		movq		mm0, mm1				;; mm0 = xposition
-		movq		mm7, mm2				;; mm7 = yposition
-		paddd		mm1, mm3				;; xposition += xstep
-		paddd		mm2, mm4				;; yposition += ystep
-		psrld		mm0, mm5				;; shift
-		psrld		mm7, mm6				;; shift
-		pand		mm7, [nflatmask64]		;; mask
-		por			mm0, mm7				;; or x and y together
-
-		movd		eax, mm0				;; eax = index of third pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-		shl			eax, 16					;; get pixel in right byte
-		or			ebp, eax				;; put pixel in ebp
-
-		punpckhdq	mm0, mm0				;; both dwords = high dword
-		movd		eax, mm0				;; eax = index of second pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-		shl			eax, 24					;; get pixel in right byte
-		or			ebp, eax				;; put pixel in ebp
-
-;;
-;; Write pixels.
-;;
-		mov			[edi], ebp
-		add			edi, 4
-
-		sub			ecx, 4
-		jmp			.alignedloop
-
-.prestragglers:
-;;
-;; Back to one step at a time.
-;;
-		psrad		mm3, 1
-		psrad		mm4, 1
-		jmp			.stragglers
-
-.done:
-;;
-;; Clear MMX state, or else FPU operations will go badly awry.
-;;
-		emms
-
-		pop			ebx
-		pop			edi
-		pop			esi
-		pop			ebp
-		ret
diff --git a/src/tmap_vc.nas b/src/tmap_vc.nas
deleted file mode 100644
index c85cf70035f8588387420d479725242bb708cc42..0000000000000000000000000000000000000000
--- a/src/tmap_vc.nas
+++ /dev/null
@@ -1,48 +0,0 @@
-;; SONIC ROBO BLAST 2
-;;-----------------------------------------------------------------------------
-;; Copyright (C) 1998-2000 by DooM Legacy Team.
-;; Copyright (C) 1999-2023 by Sonic Team Junior.
-;;
-;; This program is free software distributed under the
-;; terms of the GNU General Public License, version 2.
-;; See the 'LICENSE' file for more details.
-;;-----------------------------------------------------------------------------
-;; FILE:
-;;      tmap_vc.nas
-;; DESCRIPTION:
-;;      Assembler optimised math code for Visual C++.
-
-
-[BITS 32]
-
-%macro cglobal 1
-%define %1 _%1
-[global %1]
-%endmacro
-
-[SECTION .text write]
-
-;----------------------------------------------------------------------------
-;fixed_t FixedMul (fixed_t a, fixed_t b)
-;----------------------------------------------------------------------------
-cglobal FixedMul
-;       align   16
-FixedMul:
-        mov     eax,[esp+4]
-        imul    dword [esp+8]
-        shrd    eax,edx,16
-        ret
-
-;----------------------------------------------------------------------------
-;fixed_t FixedDiv2 (fixed_t a, fixed_t b);
-;----------------------------------------------------------------------------
-cglobal FixedDiv2
-;       align   16
-FixedDiv2:
-        mov     eax,[esp+4]
-        mov     edx,eax                 ;; these two instructions allow the next
-        sar     edx,31                  ;; two to pair, on the Pentium processor.
-        shld    edx,eax,16
-        sal     eax,16
-        idiv    dword [esp+8]
-        ret
diff --git a/src/v_video.c b/src/v_video.c
index 461a5e3bc7671684f5fdcc62a8c1a728ea913a55..3f958b286cdfdcc275a23c5822605812d218c242 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -447,12 +447,6 @@ static void CV_palette_OnChange(void)
 	V_SetPalette(0);
 }
 
-#if defined (__GNUC__) && defined (__i386__) && !defined (NOASM) && !defined (__APPLE__) && !defined (NORUSEASM)
-void VID_BlitLinearScreen_ASM(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes,
-	size_t destrowbytes);
-#define HAVE_VIDCOPY
-#endif
-
 static void CV_constextsize_OnChange(void)
 {
 	if (!con_refresh)
@@ -466,9 +460,6 @@ static void CV_constextsize_OnChange(void)
 void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes,
 	size_t destrowbytes)
 {
-#ifdef HAVE_VIDCOPY
-    VID_BlitLinearScreen_ASM(srcptr,destptr,width,height,srcrowbytes,destrowbytes);
-#else
 	if (srcrowbytes == destrowbytes)
 		M_Memcpy(destptr, srcptr, srcrowbytes * height);
 	else
@@ -481,7 +472,6 @@ void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT3
 			srcptr += srcrowbytes;
 		}
 	}
-#endif
 }
 
 static UINT8 hudplusalpha[11]  = { 10,  8,  6,  4,  2,  0,  0,  0,  0,  0,  0};
diff --git a/src/version.h b/src/version.h
index 083c531343cf0dd0628133110d338374fde71231..3242cad672df6757e74741ca482a403f7544e31b 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,4 +1,4 @@
-#define SRB2VERSION "2.2.11"/* this must be the first line, for cmake !! */
+#define SRB2VERSION "2.2.13"/* this must be the first line, for cmake !! */
 
 // The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/members/?key=ms_admin ).
 // DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server.
@@ -9,7 +9,7 @@
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.2.0 is not version "1".
-#define MODVERSION 52
+#define MODVERSION 54
 
 // Define this as a prerelease version suffix (pre#, RC#)
 //#define BETAVERSION "pre1"
diff --git a/src/vid_copy.s b/src/vid_copy.s
deleted file mode 100644
index 1473a3856f192145e3739738de85bd4f6cb96222..0000000000000000000000000000000000000000
--- a/src/vid_copy.s
+++ /dev/null
@@ -1,61 +0,0 @@
-// SONIC ROBO BLAST 2
-//-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2023 by Sonic Team Junior.
-//
-// This program is free software distributed under the
-// terms of the GNU General Public License, version 2.
-// See the 'LICENSE' file for more details.
-//-----------------------------------------------------------------------------
-/// \file  vid_copy.s
-/// \brief code for updating the linear frame buffer screen.
-
-#include "asm_defs.inc"           // structures, must match the C structures!
-
-// DJGPPv2 is as fast as this one, but then someone may compile with a less
-// good version of DJGPP than mine, so this little asm will do the trick!
-
-#define srcptr          4+16
-#define destptr         8+16
-#define width           12+16
-#define height          16+16
-#define srcrowbytes     20+16
-#define destrowbytes    24+16
-
-// VID_BlitLinearScreen( src, dest, width, height, srcwidth, destwidth );
-//         width is given as BYTES
-
-#ifdef __i386__
-
-.globl C(VID_BlitLinearScreen_ASM)
-C(VID_BlitLinearScreen_ASM):
-    pushl   %ebp                // preserve caller's stack frame
-    pushl   %edi
-    pushl   %esi                // preserve register variables
-    pushl   %ebx
-
-    cld
-    movl    srcptr(%esp),%esi
-    movl    destptr(%esp),%edi
-    movl    width(%esp),%ebx
-    movl    srcrowbytes(%esp),%eax
-    subl    %ebx,%eax
-    movl    destrowbytes(%esp),%edx
-    subl    %ebx,%edx
-    shrl    $2,%ebx
-    movl    height(%esp),%ebp
-LLRowLoop:
-    movl    %ebx,%ecx
-    rep/movsl   (%esi),(%edi)
-    addl    %eax,%esi
-    addl    %edx,%edi
-    decl    %ebp
-    jnz     LLRowLoop
-
-    popl    %ebx                // restore register variables
-    popl    %esi
-    popl    %edi
-    popl    %ebp                // restore the caller's stack frame
-
-    ret
-#endif
diff --git a/src/w_wad.c b/src/w_wad.c
index 171eab4f31bc246281424157a3f35a047d9d8c4f..c8880f6934f0f04bbe892da599e90f487339b242 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1150,6 +1150,7 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
 	Z_Calloc(numlumps * sizeof (*wadfile->patchcache), PU_STATIC, &wadfile->patchcache);
 
 	CONS_Printf(M_GetText("Added folder %s (%u files, %u folders)\n"), fn, numlumps, foldercount);
+	wadfiles = Z_Realloc(wadfiles, sizeof(wadfile_t *) * (numwadfiles + 1), PU_STATIC, NULL);
 	wadfiles[numwadfiles] = wadfile;
 	numwadfiles++;
 
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index 869c0e7d36e24a86f299d6013ec933c69a92ec4b..b699007463ad3f37527367b40e4a1c29411012ab 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -77,8 +77,8 @@ END
 #include "../doomdef.h" // Needed for version string
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 2,2,11,0
- PRODUCTVERSION 2,2,11,0
+ FILEVERSION 2,2,13,0
+ PRODUCTVERSION 2,2,13,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
diff --git a/src/y_inter.c b/src/y_inter.c
index 6e7d362a779d9f8bcfb43d7999aacaf433cb2ee1..8bec2b30f4e9314eb529defe4578daac5d0b697b 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -998,8 +998,7 @@ void Y_Ticker(void)
 	if (paused || P_AutoPause())
 		return;
 
-	LUA_HookBool(intertype == int_spec && stagefailed,
-			HOOK(IntermissionThinker));
+	LUA_HookBool(stagefailed, HOOK(IntermissionThinker));
 
 	intertic++;
 
@@ -1477,10 +1476,10 @@ void Y_StartIntermission(void)
 				if (players[consoleplayer].charflags & SF_SUPER)
 				{
 					strcpy(data.spec.passed3, "can now become");
-					snprintf(data.spec.passed4,
-						sizeof data.spec.passed4, "Super %s",
-						skins[players[consoleplayer].skin].realname);
-					data.spec.passed4[sizeof data.spec.passed4 - 1] = '\0';
+					if (strlen(skins[players[consoleplayer].skin].supername) > 20) //too long, use generic
+						strcpy(data.spec.passed4, "their super form");
+					else
+						strcpy(data.spec.passed4, skins[players[consoleplayer].skin].supername);
 				}
 			}
 			else
@@ -2044,7 +2043,7 @@ static void Y_AwardCoopBonuses(void)
 	y_bonus_t localbonuses[4];
 
 	// set score/total first
-	data.coop.total = 0;
+	data.coop.total = players[consoleplayer].recordscore;
 	data.coop.score = players[consoleplayer].score;
 	data.coop.gotperfbonus = -1;
 	memset(data.coop.bonuses, 0, sizeof(data.coop.bonuses));
@@ -2065,6 +2064,9 @@ static void Y_AwardCoopBonuses(void)
 			players[i].score += localbonuses[j].points;
 			if (players[i].score > MAXSCORE)
 				players[i].score = MAXSCORE;
+			players[i].recordscore += localbonuses[j].points;
+			if (players[i].recordscore > MAXSCORE)
+				players[i].recordscore = MAXSCORE;
 		}
 
 		ptlives = min(
@@ -2121,6 +2123,10 @@ static void Y_AwardSpecialStageBonus(void)
 		players[i].score += localbonuses[1].points;
 		if (players[i].score > MAXSCORE)
 			players[i].score = MAXSCORE;
+		players[i].recordscore += localbonuses[0].points;
+		players[i].recordscore += localbonuses[1].points;
+		if (players[i].recordscore > MAXSCORE)
+			players[i].recordscore = MAXSCORE;
 
 		// grant extra lives right away since tally is faked
 		ptlives = min(
diff --git a/src/z_zone.c b/src/z_zone.c
index d23c43b7b586775d2aa9bb8e5f4686bdc1313053..5750f8ae016becd9c9548b690b0e7fd5b32ec7cb 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -51,27 +51,11 @@ static boolean Z_calloc = false;
 //#define ZDEBUG2
 #endif
 
-struct memblock_s;
-
-typedef struct
-{
-	struct memblock_s *block; // Describing this memory
-	UINT32 id; // Should be ZONEID
-} ATTRPACK memhdr_t;
-
-// Some code might want aligned memory. Assume it wants memory n bytes
-// aligned -- then we allocate n-1 extra bytes and return a pointer to
-// the first byte aligned as requested.
-// Thus, "real" is the pointer we get from malloc() and will free()
-// later, but "hdr" is where the memhdr_t starts.
-// For non-aligned allocations they will be the same.
 typedef struct memblock_s
 {
-	void *real;
-	memhdr_t *hdr;
-
 	void **user;
 	INT32 tag; // purgelevel
+	UINT32 id; // Should be ZONEID
 
 	size_t size; // including the header and blocks
 	size_t realsize; // size of real data only
@@ -82,7 +66,10 @@ typedef struct memblock_s
 #endif
 
 	struct memblock_s *next, *prev;
-} ATTRPACK memblock_t;
+} memblock_t;
+
+#define MEMORY(x) (void *)((uintptr_t)(x) + sizeof(memblock_t))
+#define MEMBLOCK(x) (memblock_t *)((uintptr_t)(x) - sizeof(memblock_t))
 
 // both the head and tail of the zone memory block list
 static memblock_t head;
@@ -128,64 +115,6 @@ void Z_Init(void)
 // Zone memory allocation
 // ----------------------
 
-/** Returns the corresponding memblock_t for a given memory block.
-  *
-  * \param ptr A pointer to allocated memory,
-  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
-  * \param func A string containing the name of the function that called this,
-  *              to be printed if the function I_Errors
-  * \return A pointer to the memblock_t for the given memory.
-  * \sa Z_Free, Z_ReallocAlign
-  */
-#ifdef ZDEBUG
-#define Ptr2Memblock(s, f) Ptr2Memblock2(s, f, __FILE__, __LINE__)
-static memblock_t *Ptr2Memblock2(void *ptr, const char* func, const char *file, INT32 line)
-#else
-static memblock_t *Ptr2Memblock(void *ptr, const char* func)
-#endif
-{
-	memhdr_t *hdr;
-	memblock_t *block;
-
-	if (ptr == NULL)
-		return NULL;
-
-#ifdef ZDEBUG2
-	CONS_Printf("%s %s:%d\n", func, file, line);
-#endif
-
-	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
-
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
-
-#ifdef VALGRIND_MEMPOOL_EXISTS
-	if (!VALGRIND_MEMPOOL_EXISTS(hdr->block))
-	{
-#ifdef ZDEBUG
-		I_Error("%s: bad memblock from %s:%d", func, file, line);
-#else
-		I_Error("%s: bad memblock", func);
-#endif
-	}
-#endif
-	if (hdr->id != ZONEID)
-	{
-#ifdef ZDEBUG
-		I_Error("%s: wrong id from %s:%d", func, file, line);
-#else
-		I_Error("%s: wrong id", func);
-#endif
-	}
-	block = hdr->block;
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
-#endif
-	return block;
-
-}
-
 /** Frees allocated memory.
   *
   * \param ptr A pointer to allocated memory,
@@ -207,10 +136,14 @@ void Z_Free(void *ptr)
 	CONS_Debug(DBG_MEMORY, "Z_Free %s:%d\n", file, line);
 #endif
 
+	block = MEMBLOCK(ptr);
+#ifdef PARANOIA
+	if (block->id != ZONEID)
 #ifdef ZDEBUG
-	block = Ptr2Memblock2(ptr, "Z_Free", file, line);
+		I_Error("Z_Free at %s:%d: wrong id", file, line);
 #else
-	block = Ptr2Memblock(ptr, "Z_Free");
+		I_Error("Z_Free: wrong id");
+#endif
 #endif
 
 #ifdef ZDEBUG
@@ -229,8 +162,6 @@ void Z_Free(void *ptr)
 	if (block->user != NULL)
 		*block->user = NULL;
 
-	// Free the memory and get rid of the block.
-	free(block->real);
 #ifdef VALGRIND_DESTROY_MEMPOOL
 	VALGRIND_DESTROY_MEMPOOL(block);
 #endif
@@ -287,35 +218,17 @@ void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits,
 void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 #endif
 {
-	I_Assert(alignbits >= 0 && alignbits < (INT32)(sizeof(size_t) * 8));
-	size_t extrabytes = ((size_t)1<<alignbits) - 1;
-	size_t padsize = 0;
 	memblock_t *block;
 	void *ptr;
-	memhdr_t *hdr;
-	void *given;
-	size_t blocksize = extrabytes + sizeof *hdr + size;
+	(void)(alignbits); // no longer used, so silence warnings.
 
 #ifdef ZDEBUG2
 	CONS_Debug(DBG_MEMORY, "Z_Malloc %s:%d\n", file, line);
 #endif
 
-	if (blocksize < size)/* overflow check */
-		I_Error("You are allocating memory too large!");
-
-	block = xm(sizeof *block);
-#ifdef HAVE_VALGRIND
-	padsize += (1<<sizeof(size_t))*2;
-#endif
-	ptr = xm(blocksize + padsize*2);
-
-	// This horrible calculation makes sure that "given" is aligned
-	// properly.
-	given = (void *)((size_t)((UINT8 *)ptr + extrabytes + sizeof *hdr + padsize/2)
-		& ~extrabytes);
-
-	// The mem header lives 'sizeof (memhdr_t)' bytes before given.
-	hdr = (memhdr_t *)((UINT8 *)given - sizeof *hdr);
+	block = xm(sizeof (memblock_t) + size);
+	ptr = MEMORY(block);
+	I_Assert((intptr_t)ptr % sizeof (void *) == 0);
 
 #ifdef HAVE_VALGRIND
 	Z_calloc = false;
@@ -326,41 +239,31 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	head.next = block;
 	block->next->prev = block;
 
-	block->real = ptr;
-	block->hdr = hdr;
 	block->tag = tag;
 	block->user = NULL;
 #ifdef ZDEBUG
 	block->ownerline = line;
 	block->ownerfile = file;
 #endif
-	block->size = blocksize;
+	block->size = sizeof (memblock_t) + size;
 	block->realsize = size;
 
 #ifdef VALGRIND_CREATE_MEMPOOL
-	VALGRIND_CREATE_MEMPOOL(block, padsize, Z_calloc);
+	VALGRIND_CREATE_MEMPOOL(block, size, Z_calloc);
 #endif
-//#ifdef VALGRIND_MEMPOOL_ALLOC
-//	VALGRIND_MEMPOOL_ALLOC(block, hdr, size + sizeof *hdr);
-//#endif
 
-	hdr->id = ZONEID;
-	hdr->block = block;
-
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
-#endif
+	block->id = ZONEID;
 
 	if (user != NULL)
 	{
 		block->user = user;
-		*(void **)user = given;
+		*(void **)user = ptr;
 	}
 	else if (tag >= PU_PURGELEVEL)
 		I_Error("Z_Malloc: attempted to allocate purgable block "
 			"(size %s) with no user", sizeu1(size));
 
-	return given;
+	return ptr;
 }
 
 /** The Z_CallocAlign function.
@@ -437,10 +340,14 @@ void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignb
 #endif
 	}
 
+	block = MEMBLOCK(ptr);
+#ifdef PARANOIA
+	if (block->id != ZONEID)
 #ifdef ZDEBUG
-	block = Ptr2Memblock2(ptr, "Z_Realloc", file, line);
+		I_Error("Z_ReallocAlign at %s:%d: wrong id", file, line);
 #else
-	block = Ptr2Memblock(ptr, "Z_Realloc");
+		I_Error("Z_ReallocAlign: wrong id");
+#endif
 #endif
 
 	if (block == NULL)
@@ -491,9 +398,8 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag)
 	for (block = head.next; block != &head; block = next)
 	{
 		next = block->next; // get link before freeing
-
 		if (block->tag >= lowtag && block->tag <= hightag)
-			Z_Free((UINT8 *)block->hdr + sizeof *block->hdr);
+			Z_Free(MEMORY(block));
 	}
 }
 
@@ -516,7 +422,7 @@ void Z_IterateTags(INT32 lowtag, INT32 hightag, boolean (*iterfunc)(void *))
 
 		if (block->tag >= lowtag && block->tag <= hightag)
 		{
-			void *mem = (UINT8 *)block->hdr + sizeof *block->hdr;
+			void *mem = MEMORY(block);
 			boolean free = iterfunc(mem);
 			if (free)
 				Z_Free(mem);
@@ -561,15 +467,13 @@ void Z_CheckMemCleanup(void)
 void Z_CheckHeap(INT32 i)
 {
 	memblock_t *block;
-	memhdr_t *hdr;
 	UINT32 blocknumon = 0;
 	void *given;
 
 	for (block = head.next; block != &head; block = block->next)
 	{
 		blocknumon++;
-		hdr = block->hdr;
-		given = (UINT8 *)hdr + sizeof *hdr;
+		given = MEMORY(block);
 #ifdef ZDEBUG2
 		CONS_Debug(DBG_MEMORY, "block %u owned by %s:%d\n",
 			blocknumon, block->ownerfile, block->ownerline);
@@ -585,7 +489,7 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			        );
+				);
 		}
 #endif
 		if (block->user != NULL && *(block->user) != given)
@@ -598,7 +502,7 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			       );
+				);
 		}
 		if (block->next->prev != block)
 		{
@@ -610,7 +514,7 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			       );
+				);
 		}
 		if (block->prev->next != block)
 		{
@@ -622,25 +526,9 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			       );
+				);
 		}
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-		VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
-		if (hdr->block != block)
-		{
-			I_Error("Z_CheckHeap %d: block %u"
-#ifdef ZDEBUG
-				"(owned by %s:%d)"
-#endif
-				" doesn't have linkback from allocated memory",
-				i, blocknumon
-#ifdef ZDEBUG
-				, block->ownerfile, block->ownerline
-#endif
-					);
-		}
-		if (hdr->id != ZONEID)
+		if (block->id != ZONEID)
 		{
 			I_Error("Z_CheckHeap %d: block %u"
 #ifdef ZDEBUG
@@ -650,11 +538,8 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-					);
+				);
 		}
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
-#endif
 	}
 }
 
@@ -676,35 +561,14 @@ void Z_ChangeTag(void *ptr, INT32 tag)
 #endif
 {
 	memblock_t *block;
-	memhdr_t *hdr;
 
 	if (ptr == NULL)
 		return;
 
-	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
-
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
+	block = MEMBLOCK(ptr);
 
-#ifdef VALGRIND_MEMPOOL_EXISTS
-	if (!VALGRIND_MEMPOOL_EXISTS(hdr->block))
-	{
-#ifdef PARANOIA
-		I_Error("Z_CT at %s:%d: bad memblock", file, line);
-#else
-		I_Error("Z_CT: bad memblock");
-#endif
-	}
-#endif
 #ifdef PARANOIA
-	if (hdr->id != ZONEID) I_Error("Z_CT at %s:%d: wrong id", file, line);
-#endif
-
-	block = hdr->block;
-
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
+	if (block->id != ZONEID) I_Error("Z_ChangeTag at %s:%d: wrong id", file, line);
 #endif
 
 	if (tag >= PU_PURGELEVEL && block->user == NULL)
@@ -728,25 +592,14 @@ void Z_SetUser(void *ptr, void **newuser)
 #endif
 {
 	memblock_t *block;
-	memhdr_t *hdr;
 
 	if (ptr == NULL)
 		return;
 
-	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
-
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
+	block = MEMBLOCK(ptr);
 
 #ifdef PARANOIA
-	if (hdr->id != ZONEID) I_Error("Z_CT at %s:%d: wrong id", file, line);
-#endif
-
-	block = hdr->block;
-
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
+	if (block->id != ZONEID) I_Error("Z_SetUser at %s:%d: wrong id", file, line);
 #endif
 
 	if (block->tag >= PU_PURGELEVEL && newuser == NULL)
diff --git a/src/z_zone.h b/src/z_zone.h
index f00f57749407c053dabc119a81c5b9c4a8ed07e7..ce7af4a159555e3a6c2be83e8d0eadcf8a7a69eb 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -15,6 +15,7 @@
 #define __Z_ZONE__
 
 #include <stdio.h>
+#include "doomdef.h"
 #include "doomtype.h"
 
 #ifdef __GNUC__ // __attribute__ ((X))
diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt
index 7aff16601efd49d71693a137a4aab7b98721e7fa..f33b3bf3f836b86b98fda8e78f73ec5be19758d8 100644
--- a/thirdparty/CMakeLists.txt
+++ b/thirdparty/CMakeLists.txt
@@ -9,521 +9,13 @@ else()
 	set(NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES ON)
 endif()
 
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME SDL2
-		VERSION 2.24.2
-		URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS
-			"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL_TEST OFF"
-			"SDL2_DISABLE_SDL2MAIN ON"
-			"SDL2_DISABLE_INSTALL ON"
-	)
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME SDL2_mixer
-		VERSION 2.6.2
-		URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS
-			"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL2MIXER_INSTALL OFF"
-			"SDL2MIXER_DEPS_SHARED OFF"
-			"SDL2MIXER_SAMPLES OFF"
-			"SDL2MIXER_VENDORED ON"
-			"SDL2MIXER_FLAC ON"
-			"SDL2MIXER_FLAC_LIBFLAC OFF"
-			"SDL2MIXER_FLAC_DRFLAC ON"
-			"SDL2MIXER_MOD OFF"
-			"SDL2MIXER_MP3 ON"
-			"SDL2MIXER_MP3_DRMP3 ON"
-			"SDL2MIXER_MIDI ON"
-			"SDL2MIXER_OPUS OFF"
-			"SDL2MIXER_VORBIS STB"
-			"SDL2MIXER_WAVE ON"
-	)
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME ZLIB
-		VERSION 1.2.13
-		URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip"
-		EXCLUDE_FROM_ALL
-		DOWNLOAD_ONLY YES
-	)
-	if(ZLIB_ADDED)
-		set(ZLIB_SRCS
-			crc32.h
-			deflate.h
-			gzguts.h
-			inffast.h
-			inffixed.h
-			inflate.h
-			inftrees.h
-			trees.h
-			zutil.h
-
-			adler32.c
-			compress.c
-			crc32.c
-			deflate.c
-			gzclose.c
-			gzlib.c
-			gzread.c
-			gzwrite.c
-			inflate.c
-			infback.c
-			inftrees.c
-			inffast.c
-			trees.c
-			uncompr.c
-			zutil.c
-		)
-		list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/")
-
-		configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY)
-		configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY)
-		configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY)
-
-		add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS})
-		set_target_properties(ZLIB PROPERTIES
-			VERSION 1.2.13
-			OUTPUT_NAME "z"
-		)
-		target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}")
-		target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include")
-		if(MSVC)
-			target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
-		endif()
-		add_library(ZLIB::ZLIB ALIAS ZLIB)
-	endif()
-endif()
-
 if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME png
-		VERSION 1.6.38
-		URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip"
-		# png cmake build is broken on msys/mingw32
-		DOWNLOAD_ONLY YES
-	)
-
-	if(png_ADDED)
-		# Since png's cmake build is broken, we're going to create a target manually
-		set(
-			PNG_SOURCES
-
-			png.h
-			pngconf.h
-
-			pngpriv.h
-			pngdebug.h
-			pnginfo.h
-			pngstruct.h
-
-			png.c
-			pngerror.c
-			pngget.c
-			pngmem.c
-			pngpread.c
-			pngread.c
-			pngrio.c
-			pngrtran.c
-			pngrutil.c
-			pngset.c
-			pngtrans.c
-			pngwio.c
-			pngwrite.c
-			pngwtran.c
-			pngwutil.c
-		)
-		list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/")
-
-		add_custom_command(
-			OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h"
-			COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include"
-			DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h"
-			VERBATIM
-		)
-		add_custom_command(
-			OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h"
-			COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h"
-			DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt"
-			VERBATIM
-		)
-		list(
-			APPEND PNG_SOURCES
-			"${png_BINARY_DIR}/include/png.h"
-			"${png_BINARY_DIR}/include/pngconf.h"
-			"${png_BINARY_DIR}/include/pnglibconf.h"
-		)
-		add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES})
-
-		# Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason
-		target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0)
-
-		# The png includes need to be available to consumers
-		target_include_directories(png PUBLIC "${png_BINARY_DIR}/include")
-
-		# ... and these also need to be present only for png build
-		target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}")
-		target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}")
-		target_include_directories(png PRIVATE "${png_BINARY_DIR}")
-
-		target_link_libraries(png PRIVATE ZLIB::ZLIB)
-		add_library(PNG::PNG ALIAS png)
-	endif()
+	include("cpm-sdl2.cmake")
+	include("cpm-sdl2-mixer.cmake")
+	include("cpm-zlib.cmake")
+	include("cpm-png.cmake")
+	include("cpm-curl.cmake")
+	include("cpm-openmpt.cmake")
 endif()
 
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	set(
-		internal_curl_options
-
-		"BUILD_CURL_EXE OFF"
-		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-		"CURL_DISABLE_TESTS ON"
-		"HTTP_ONLY ON"
-		"CURL_DISABLE_CRYPTO_AUTH ON"
-		"CURL_DISABLE_NTLM ON"
-		"ENABLE_MANUAL OFF"
-		"ENABLE_THREADED_RESOLVER OFF"
-		"CURL_USE_LIBPSL OFF"
-		"CURL_USE_LIBSSH2 OFF"
-		"USE_LIBIDN2 OFF"
-		"CURL_ENABLE_EXPORT_TARGET OFF"
-	)
-	if(${CMAKE_SYSTEM} MATCHES Windows)
-		list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
-		list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON")
-	endif()
-	if(${CMAKE_SYSTEM} MATCHES Darwin)
-		list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
-		list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON")
-	endif()
-	if(${CMAKE_SYSTEM} MATCHES Linux)
-		list(APPEND internal_curl_options "CURL_USE_OPENSSL ON")
-	endif()
-
-	CPMAddPackage(
-		NAME curl
-		VERSION 7.86.0
-		URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS ${internal_curl_options}
-	)
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME openmpt
-		VERSION 0.4.30
-		URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip"
-		DOWNLOAD_ONLY ON
-	)
-
-	if(openmpt_ADDED)
-		set(
-			openmpt_SOURCES
-
-			# minimp3
-			# -DMPT_WITH_MINIMP3
-			include/minimp3/minimp3.c
-
-			common/mptStringParse.cpp
-			common/mptLibrary.cpp
-			common/Logging.cpp
-			common/Profiler.cpp
-			common/version.cpp
-			common/mptCPU.cpp
-			common/ComponentManager.cpp
-			common/mptOS.cpp
-			common/serialization_utils.cpp
-			common/mptStringFormat.cpp
-			common/FileReader.cpp
-			common/mptWine.cpp
-			common/mptPathString.cpp
-			common/mptAlloc.cpp
-			common/mptUUID.cpp
-			common/mptTime.cpp
-			common/mptString.cpp
-			common/mptFileIO.cpp
-			common/mptStringBuffer.cpp
-			common/mptRandom.cpp
-			common/mptIO.cpp
-			common/misc_util.cpp
-
-			common/mptCRC.h
-			common/mptLibrary.h
-			common/mptIO.h
-			common/version.h
-			common/stdafx.h
-			common/ComponentManager.h
-			common/Endianness.h
-			common/mptStringFormat.h
-			common/mptMutex.h
-			common/mptUUID.h
-			common/mptExceptionText.h
-			common/BuildSettings.h
-			common/mptAlloc.h
-			common/mptTime.h
-			common/FileReaderFwd.h
-			common/Logging.h
-			common/mptException.h
-			common/mptWine.h
-			common/mptStringBuffer.h
-			common/misc_util.h
-			common/mptBaseMacros.h
-			common/mptMemory.h
-			common/mptFileIO.h
-			common/serialization_utils.h
-			common/mptSpan.h
-			common/mptThread.h
-			common/FlagSet.h
-			common/mptString.h
-			common/mptStringParse.h
-			common/mptBaseUtils.h
-			common/mptRandom.h
-			common/CompilerDetect.h
-			common/FileReader.h
-			common/mptAssert.h
-			common/mptPathString.h
-			common/Profiler.h
-			common/mptOS.h
-			common/mptBaseTypes.h
-			common/mptCPU.h
-			common/mptBufferIO.h
-			common/versionNumber.h
-
-			soundlib/WAVTools.cpp
-			soundlib/ITTools.cpp
-			soundlib/AudioCriticalSection.cpp
-			soundlib/Load_stm.cpp
-			soundlib/MixerLoops.cpp
-			soundlib/Load_dbm.cpp
-			soundlib/ModChannel.cpp
-			soundlib/Load_gdm.cpp
-			soundlib/Snd_fx.cpp
-			soundlib/Load_mid.cpp
-			soundlib/mod_specifications.cpp
-			soundlib/Snd_flt.cpp
-			soundlib/Load_psm.cpp
-			soundlib/Load_far.cpp
-			soundlib/patternContainer.cpp
-			soundlib/Load_med.cpp
-			soundlib/Load_dmf.cpp
-			soundlib/Paula.cpp
-			soundlib/modcommand.cpp
-			soundlib/Message.cpp
-			soundlib/SoundFilePlayConfig.cpp
-			soundlib/Load_uax.cpp
-			soundlib/plugins/PlugInterface.cpp
-			soundlib/plugins/LFOPlugin.cpp
-			soundlib/plugins/PluginManager.cpp
-			soundlib/plugins/DigiBoosterEcho.cpp
-			soundlib/plugins/dmo/DMOPlugin.cpp
-			soundlib/plugins/dmo/Flanger.cpp
-			soundlib/plugins/dmo/Distortion.cpp
-			soundlib/plugins/dmo/ParamEq.cpp
-			soundlib/plugins/dmo/Gargle.cpp
-			soundlib/plugins/dmo/I3DL2Reverb.cpp
-			soundlib/plugins/dmo/Compressor.cpp
-			soundlib/plugins/dmo/WavesReverb.cpp
-			soundlib/plugins/dmo/Echo.cpp
-			soundlib/plugins/dmo/Chorus.cpp
-			soundlib/Load_ams.cpp
-			soundlib/tuningbase.cpp
-			soundlib/ContainerUMX.cpp
-			soundlib/Load_ptm.cpp
-			soundlib/ContainerXPK.cpp
-			soundlib/SampleFormatMP3.cpp
-			soundlib/tuning.cpp
-			soundlib/Sndfile.cpp
-			soundlib/ContainerMMCMP.cpp
-			soundlib/Load_amf.cpp
-			soundlib/Load_669.cpp
-			soundlib/modsmp_ctrl.cpp
-			soundlib/Load_mtm.cpp
-			soundlib/OggStream.cpp
-			soundlib/Load_plm.cpp
-			soundlib/Tables.cpp
-			soundlib/Load_c67.cpp
-			soundlib/Load_mod.cpp
-			soundlib/Load_sfx.cpp
-			soundlib/Sndmix.cpp
-			soundlib/load_j2b.cpp
-			soundlib/ModSequence.cpp
-			soundlib/SampleFormatFLAC.cpp
-			soundlib/ModInstrument.cpp
-			soundlib/Load_mo3.cpp
-			soundlib/ModSample.cpp
-			soundlib/Dlsbank.cpp
-			soundlib/Load_itp.cpp
-			soundlib/UpgradeModule.cpp
-			soundlib/MIDIMacros.cpp
-			soundlib/ContainerPP20.cpp
-			soundlib/RowVisitor.cpp
-			soundlib/Load_imf.cpp
-			soundlib/SampleFormatVorbis.cpp
-			soundlib/Load_dsm.cpp
-			soundlib/Load_mt2.cpp
-			soundlib/MixerSettings.cpp
-			soundlib/S3MTools.cpp
-			soundlib/Load_xm.cpp
-			soundlib/MIDIEvents.cpp
-			soundlib/pattern.cpp
-			soundlib/Load_digi.cpp
-			soundlib/Load_s3m.cpp
-			soundlib/tuningCollection.cpp
-			soundlib/SampleIO.cpp
-			soundlib/Dither.cpp
-			soundlib/Load_mdl.cpp
-			soundlib/OPL.cpp
-			soundlib/WindowedFIR.cpp
-			soundlib/SampleFormats.cpp
-			soundlib/Load_wav.cpp
-			soundlib/Load_it.cpp
-			soundlib/UMXTools.cpp
-			soundlib/Load_stp.cpp
-			soundlib/Load_okt.cpp
-			soundlib/Load_ult.cpp
-			soundlib/MixFuncTable.cpp
-			soundlib/SampleFormatOpus.cpp
-			soundlib/Fastmix.cpp
-			soundlib/Tagging.cpp
-			soundlib/ITCompression.cpp
-			soundlib/Load_dtm.cpp
-			soundlib/MPEGFrame.cpp
-			soundlib/XMTools.cpp
-			soundlib/SampleFormatMediaFoundation.cpp
-			soundlib/InstrumentExtensions.cpp
-
-			soundlib/MixerInterface.h
-			soundlib/SoundFilePlayConfig.h
-			soundlib/ModSample.h
-			soundlib/MIDIEvents.h
-			soundlib/ModSampleCopy.h
-			soundlib/patternContainer.h
-			soundlib/ChunkReader.h
-			soundlib/ITCompression.h
-			soundlib/Dither.h
-			soundlib/S3MTools.h
-			soundlib/MPEGFrame.h
-			soundlib/WAVTools.h
-			soundlib/mod_specifications.h
-			soundlib/ITTools.h
-			soundlib/RowVisitor.h
-			soundlib/plugins/PluginMixBuffer.h
-			soundlib/plugins/PluginStructs.h
-			soundlib/plugins/LFOPlugin.h
-			soundlib/plugins/PlugInterface.h
-			soundlib/plugins/DigiBoosterEcho.h
-			soundlib/plugins/OpCodes.h
-			soundlib/plugins/dmo/Echo.h
-			soundlib/plugins/dmo/I3DL2Reverb.h
-			soundlib/plugins/dmo/WavesReverb.h
-			soundlib/plugins/dmo/ParamEq.h
-			soundlib/plugins/dmo/Gargle.h
-			soundlib/plugins/dmo/DMOPlugin.h
-			soundlib/plugins/dmo/Chorus.h
-			soundlib/plugins/dmo/Compressor.h
-			soundlib/plugins/dmo/Distortion.h
-			soundlib/plugins/dmo/Flanger.h
-			soundlib/plugins/PluginManager.h
-			soundlib/SampleIO.h
-			soundlib/Container.h
-			soundlib/ModSequence.h
-			soundlib/UMXTools.h
-			soundlib/Message.h
-			soundlib/modcommand.h
-			soundlib/XMTools.h
-			soundlib/Snd_defs.h
-			soundlib/MixFuncTable.h
-			soundlib/pattern.h
-			soundlib/modsmp_ctrl.h
-			soundlib/Tagging.h
-			soundlib/tuningcollection.h
-			soundlib/Mixer.h
-			soundlib/FloatMixer.h
-			soundlib/AudioCriticalSection.h
-			soundlib/Tables.h
-			soundlib/tuningbase.h
-			soundlib/WindowedFIR.h
-			soundlib/Sndfile.h
-			soundlib/Paula.h
-			soundlib/ModInstrument.h
-			soundlib/Dlsbank.h
-			soundlib/IntMixer.h
-			soundlib/OPL.h
-			soundlib/Resampler.h
-			soundlib/ModChannel.h
-			soundlib/MixerSettings.h
-			soundlib/AudioReadTarget.h
-			soundlib/MixerLoops.h
-			soundlib/tuning.h
-			soundlib/MIDIMacros.h
-			soundlib/OggStream.h
-			soundlib/Loaders.h
-			soundlib/BitReader.h
-			soundlib/opal.h
-
-			sounddsp/AGC.cpp
-			sounddsp/EQ.cpp
-			sounddsp/DSP.cpp
-			sounddsp/Reverb.cpp
-			sounddsp/Reverb.h
-			sounddsp/EQ.h
-			sounddsp/DSP.h
-			sounddsp/AGC.h
-
-			libopenmpt/libopenmpt_c.cpp
-			libopenmpt/libopenmpt_cxx.cpp
-			libopenmpt/libopenmpt_impl.cpp
-			libopenmpt/libopenmpt_ext_impl.cpp
-		)
-		list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/")
-
-		# -DLIBOPENMPT_BUILD
-		configure_file("openmpt_svn_version.h" "svn_version.h")
-		add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h)
-		if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang)
-			target_compile_options(openmpt PRIVATE "-g0")
-		endif()
-		if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC)
-			target_link_libraries(openmpt PRIVATE Rpcrt4)
-		endif()
-		target_compile_features(openmpt PRIVATE cxx_std_11)
-		target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD)
-
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common")
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src")
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include")
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}")
-		target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
-
-		# I wish this wasn't necessary, but it is
-		target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}")
-	endif()
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME libgme
-		VERSION 0.6.3
-		URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS
-			"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"ENABLE_UBSAN OFF"
-			"GME_YM2612_EMU MAME"
-	)
-	target_compile_features(gme PRIVATE cxx_std_11)
-	target_link_libraries(gme PRIVATE ZLIB::ZLIB)
-endif()
+include("cpm-libgme.cmake")
diff --git a/thirdparty/cpm-curl.cmake b/thirdparty/cpm-curl.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..3d8c6e61d46dee6f06f4e6d69beb09d1605f6584
--- /dev/null
+++ b/thirdparty/cpm-curl.cmake
@@ -0,0 +1,35 @@
+set(
+	internal_curl_options
+
+	"BUILD_CURL_EXE OFF"
+	"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+	"CURL_DISABLE_TESTS ON"
+	"HTTP_ONLY ON"
+	"CURL_DISABLE_CRYPTO_AUTH ON"
+	"CURL_DISABLE_NTLM ON"
+	"ENABLE_MANUAL OFF"
+	"ENABLE_THREADED_RESOLVER OFF"
+	"CURL_USE_LIBPSL OFF"
+	"CURL_USE_LIBSSH2 OFF"
+	"USE_LIBIDN2 OFF"
+	"CURL_ENABLE_EXPORT_TARGET OFF"
+)
+if(${CMAKE_SYSTEM} MATCHES Windows)
+	list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
+	list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON")
+endif()
+if(${CMAKE_SYSTEM} MATCHES Darwin)
+	list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
+	list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON")
+endif()
+if(${CMAKE_SYSTEM} MATCHES Linux)
+	list(APPEND internal_curl_options "CURL_USE_OPENSSL ON")
+endif()
+
+CPMAddPackage(
+	NAME curl
+	VERSION 7.86.0
+	URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS ${internal_curl_options}
+)
diff --git a/thirdparty/cpm-libgme.cmake b/thirdparty/cpm-libgme.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f15bc3b31cb23ed7663ed950c14c1d6a0dc36567
--- /dev/null
+++ b/thirdparty/cpm-libgme.cmake
@@ -0,0 +1,16 @@
+CPMAddPackage(
+	NAME libgme
+	VERSION 0.6.3
+	URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS
+		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"ENABLE_UBSAN OFF"
+		"GME_YM2612_EMU MAME"
+)
+
+if(libgme_ADDED)
+	target_compile_features(gme PRIVATE cxx_std_11)
+	# libgme's CMakeLists.txt already links this
+	#target_link_libraries(gme PRIVATE ZLIB::ZLIB)
+endif()
diff --git a/thirdparty/cpm-openmpt.cmake b/thirdparty/cpm-openmpt.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..01f7ff75f64d915799e432f2923a2c7e8c344466
--- /dev/null
+++ b/thirdparty/cpm-openmpt.cmake
@@ -0,0 +1,289 @@
+CPMAddPackage(
+	NAME openmpt
+	VERSION 0.4.30
+	URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip"
+	DOWNLOAD_ONLY ON
+)
+
+if(openmpt_ADDED)
+	set(
+		openmpt_SOURCES
+
+		# minimp3
+		# -DMPT_WITH_MINIMP3
+		include/minimp3/minimp3.c
+
+		common/mptStringParse.cpp
+		common/mptLibrary.cpp
+		common/Logging.cpp
+		common/Profiler.cpp
+		common/version.cpp
+		common/mptCPU.cpp
+		common/ComponentManager.cpp
+		common/mptOS.cpp
+		common/serialization_utils.cpp
+		common/mptStringFormat.cpp
+		common/FileReader.cpp
+		common/mptWine.cpp
+		common/mptPathString.cpp
+		common/mptAlloc.cpp
+		common/mptUUID.cpp
+		common/mptTime.cpp
+		common/mptString.cpp
+		common/mptFileIO.cpp
+		common/mptStringBuffer.cpp
+		common/mptRandom.cpp
+		common/mptIO.cpp
+		common/misc_util.cpp
+
+		common/mptCRC.h
+		common/mptLibrary.h
+		common/mptIO.h
+		common/version.h
+		common/stdafx.h
+		common/ComponentManager.h
+		common/Endianness.h
+		common/mptStringFormat.h
+		common/mptMutex.h
+		common/mptUUID.h
+		common/mptExceptionText.h
+		common/BuildSettings.h
+		common/mptAlloc.h
+		common/mptTime.h
+		common/FileReaderFwd.h
+		common/Logging.h
+		common/mptException.h
+		common/mptWine.h
+		common/mptStringBuffer.h
+		common/misc_util.h
+		common/mptBaseMacros.h
+		common/mptMemory.h
+		common/mptFileIO.h
+		common/serialization_utils.h
+		common/mptSpan.h
+		common/mptThread.h
+		common/FlagSet.h
+		common/mptString.h
+		common/mptStringParse.h
+		common/mptBaseUtils.h
+		common/mptRandom.h
+		common/CompilerDetect.h
+		common/FileReader.h
+		common/mptAssert.h
+		common/mptPathString.h
+		common/Profiler.h
+		common/mptOS.h
+		common/mptBaseTypes.h
+		common/mptCPU.h
+		common/mptBufferIO.h
+		common/versionNumber.h
+
+		soundlib/WAVTools.cpp
+		soundlib/ITTools.cpp
+		soundlib/AudioCriticalSection.cpp
+		soundlib/Load_stm.cpp
+		soundlib/MixerLoops.cpp
+		soundlib/Load_dbm.cpp
+		soundlib/ModChannel.cpp
+		soundlib/Load_gdm.cpp
+		soundlib/Snd_fx.cpp
+		soundlib/Load_mid.cpp
+		soundlib/mod_specifications.cpp
+		soundlib/Snd_flt.cpp
+		soundlib/Load_psm.cpp
+		soundlib/Load_far.cpp
+		soundlib/patternContainer.cpp
+		soundlib/Load_med.cpp
+		soundlib/Load_dmf.cpp
+		soundlib/Paula.cpp
+		soundlib/modcommand.cpp
+		soundlib/Message.cpp
+		soundlib/SoundFilePlayConfig.cpp
+		soundlib/Load_uax.cpp
+		soundlib/plugins/PlugInterface.cpp
+		soundlib/plugins/LFOPlugin.cpp
+		soundlib/plugins/PluginManager.cpp
+		soundlib/plugins/DigiBoosterEcho.cpp
+		soundlib/plugins/dmo/DMOPlugin.cpp
+		soundlib/plugins/dmo/Flanger.cpp
+		soundlib/plugins/dmo/Distortion.cpp
+		soundlib/plugins/dmo/ParamEq.cpp
+		soundlib/plugins/dmo/Gargle.cpp
+		soundlib/plugins/dmo/I3DL2Reverb.cpp
+		soundlib/plugins/dmo/Compressor.cpp
+		soundlib/plugins/dmo/WavesReverb.cpp
+		soundlib/plugins/dmo/Echo.cpp
+		soundlib/plugins/dmo/Chorus.cpp
+		soundlib/Load_ams.cpp
+		soundlib/tuningbase.cpp
+		soundlib/ContainerUMX.cpp
+		soundlib/Load_ptm.cpp
+		soundlib/ContainerXPK.cpp
+		soundlib/SampleFormatMP3.cpp
+		soundlib/tuning.cpp
+		soundlib/Sndfile.cpp
+		soundlib/ContainerMMCMP.cpp
+		soundlib/Load_amf.cpp
+		soundlib/Load_669.cpp
+		soundlib/modsmp_ctrl.cpp
+		soundlib/Load_mtm.cpp
+		soundlib/OggStream.cpp
+		soundlib/Load_plm.cpp
+		soundlib/Tables.cpp
+		soundlib/Load_c67.cpp
+		soundlib/Load_mod.cpp
+		soundlib/Load_sfx.cpp
+		soundlib/Sndmix.cpp
+		soundlib/load_j2b.cpp
+		soundlib/ModSequence.cpp
+		soundlib/SampleFormatFLAC.cpp
+		soundlib/ModInstrument.cpp
+		soundlib/Load_mo3.cpp
+		soundlib/ModSample.cpp
+		soundlib/Dlsbank.cpp
+		soundlib/Load_itp.cpp
+		soundlib/UpgradeModule.cpp
+		soundlib/MIDIMacros.cpp
+		soundlib/ContainerPP20.cpp
+		soundlib/RowVisitor.cpp
+		soundlib/Load_imf.cpp
+		soundlib/SampleFormatVorbis.cpp
+		soundlib/Load_dsm.cpp
+		soundlib/Load_mt2.cpp
+		soundlib/MixerSettings.cpp
+		soundlib/S3MTools.cpp
+		soundlib/Load_xm.cpp
+		soundlib/MIDIEvents.cpp
+		soundlib/pattern.cpp
+		soundlib/Load_digi.cpp
+		soundlib/Load_s3m.cpp
+		soundlib/tuningCollection.cpp
+		soundlib/SampleIO.cpp
+		soundlib/Dither.cpp
+		soundlib/Load_mdl.cpp
+		soundlib/OPL.cpp
+		soundlib/WindowedFIR.cpp
+		soundlib/SampleFormats.cpp
+		soundlib/Load_wav.cpp
+		soundlib/Load_it.cpp
+		soundlib/UMXTools.cpp
+		soundlib/Load_stp.cpp
+		soundlib/Load_okt.cpp
+		soundlib/Load_ult.cpp
+		soundlib/MixFuncTable.cpp
+		soundlib/SampleFormatOpus.cpp
+		soundlib/Fastmix.cpp
+		soundlib/Tagging.cpp
+		soundlib/ITCompression.cpp
+		soundlib/Load_dtm.cpp
+		soundlib/MPEGFrame.cpp
+		soundlib/XMTools.cpp
+		soundlib/SampleFormatMediaFoundation.cpp
+		soundlib/InstrumentExtensions.cpp
+
+		soundlib/MixerInterface.h
+		soundlib/SoundFilePlayConfig.h
+		soundlib/ModSample.h
+		soundlib/MIDIEvents.h
+		soundlib/ModSampleCopy.h
+		soundlib/patternContainer.h
+		soundlib/ChunkReader.h
+		soundlib/ITCompression.h
+		soundlib/Dither.h
+		soundlib/S3MTools.h
+		soundlib/MPEGFrame.h
+		soundlib/WAVTools.h
+		soundlib/mod_specifications.h
+		soundlib/ITTools.h
+		soundlib/RowVisitor.h
+		soundlib/plugins/PluginMixBuffer.h
+		soundlib/plugins/PluginStructs.h
+		soundlib/plugins/LFOPlugin.h
+		soundlib/plugins/PlugInterface.h
+		soundlib/plugins/DigiBoosterEcho.h
+		soundlib/plugins/OpCodes.h
+		soundlib/plugins/dmo/Echo.h
+		soundlib/plugins/dmo/I3DL2Reverb.h
+		soundlib/plugins/dmo/WavesReverb.h
+		soundlib/plugins/dmo/ParamEq.h
+		soundlib/plugins/dmo/Gargle.h
+		soundlib/plugins/dmo/DMOPlugin.h
+		soundlib/plugins/dmo/Chorus.h
+		soundlib/plugins/dmo/Compressor.h
+		soundlib/plugins/dmo/Distortion.h
+		soundlib/plugins/dmo/Flanger.h
+		soundlib/plugins/PluginManager.h
+		soundlib/SampleIO.h
+		soundlib/Container.h
+		soundlib/ModSequence.h
+		soundlib/UMXTools.h
+		soundlib/Message.h
+		soundlib/modcommand.h
+		soundlib/XMTools.h
+		soundlib/Snd_defs.h
+		soundlib/MixFuncTable.h
+		soundlib/pattern.h
+		soundlib/modsmp_ctrl.h
+		soundlib/Tagging.h
+		soundlib/tuningcollection.h
+		soundlib/Mixer.h
+		soundlib/FloatMixer.h
+		soundlib/AudioCriticalSection.h
+		soundlib/Tables.h
+		soundlib/tuningbase.h
+		soundlib/WindowedFIR.h
+		soundlib/Sndfile.h
+		soundlib/Paula.h
+		soundlib/ModInstrument.h
+		soundlib/Dlsbank.h
+		soundlib/IntMixer.h
+		soundlib/OPL.h
+		soundlib/Resampler.h
+		soundlib/ModChannel.h
+		soundlib/MixerSettings.h
+		soundlib/AudioReadTarget.h
+		soundlib/MixerLoops.h
+		soundlib/tuning.h
+		soundlib/MIDIMacros.h
+		soundlib/OggStream.h
+		soundlib/Loaders.h
+		soundlib/BitReader.h
+		soundlib/opal.h
+
+		sounddsp/AGC.cpp
+		sounddsp/EQ.cpp
+		sounddsp/DSP.cpp
+		sounddsp/Reverb.cpp
+		sounddsp/Reverb.h
+		sounddsp/EQ.h
+		sounddsp/DSP.h
+		sounddsp/AGC.h
+
+		libopenmpt/libopenmpt_c.cpp
+		libopenmpt/libopenmpt_cxx.cpp
+		libopenmpt/libopenmpt_impl.cpp
+		libopenmpt/libopenmpt_ext_impl.cpp
+	)
+	list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/")
+
+	# -DLIBOPENMPT_BUILD
+	configure_file("openmpt_svn_version.h" "svn_version.h")
+	add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h)
+	if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang)
+		target_compile_options(openmpt PRIVATE "-g0")
+	endif()
+	if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC)
+		target_link_libraries(openmpt PRIVATE Rpcrt4)
+	endif()
+	target_compile_features(openmpt PRIVATE cxx_std_11)
+	target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD)
+
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common")
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src")
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include")
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}")
+	target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
+
+	# I wish this wasn't necessary, but it is
+	target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}")
+endif()
diff --git a/thirdparty/cpm-png.cmake b/thirdparty/cpm-png.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f16ac037b0cc9c077f3ef315b67b430e68cf9b40
--- /dev/null
+++ b/thirdparty/cpm-png.cmake
@@ -0,0 +1,69 @@
+CPMAddPackage(
+	NAME png
+	VERSION 1.6.38
+	URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip"
+	# png cmake build is broken on msys/mingw32
+	DOWNLOAD_ONLY YES
+)
+
+if(png_ADDED)
+	# Since png's cmake build is broken, we're going to create a target manually
+	set(
+		PNG_SOURCES
+		png.h
+		pngconf.h
+		pngpriv.h
+		pngdebug.h
+		pnginfo.h
+		pngstruct.h
+		png.c
+		pngerror.c
+		pngget.c
+		pngmem.c
+		pngpread.c
+		pngread.c
+		pngrio.c
+		pngrtran.c
+		pngrutil.c
+		pngset.c
+		pngtrans.c
+		pngwio.c
+		pngwrite.c
+		pngwtran.c
+		pngwutil.c
+	)
+	list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/")
+
+	add_custom_command(
+		OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h"
+		COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include"
+		DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h"
+		VERBATIM
+	)
+	add_custom_command(
+		OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h"
+		COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h"
+		DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt"
+		VERBATIM
+	)
+	list(
+		APPEND PNG_SOURCES
+		"${png_BINARY_DIR}/include/png.h"
+		"${png_BINARY_DIR}/include/pngconf.h"
+		"${png_BINARY_DIR}/include/pnglibconf.h"
+	)
+	add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES})
+
+	# Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason
+	target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0)
+
+	# The png includes need to be available to consumers
+	target_include_directories(png PUBLIC "${png_BINARY_DIR}/include")
+
+	# ... and these also need to be present only for png build
+	target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}")
+	target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}")
+	target_include_directories(png PRIVATE "${png_BINARY_DIR}")
+	target_link_libraries(png PRIVATE ZLIB::ZLIB)
+	add_library(PNG::PNG ALIAS png)
+endif()
diff --git a/thirdparty/cpm-sdl2-mixer.cmake b/thirdparty/cpm-sdl2-mixer.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..b7dfeae0d3eab254651f0c5e6e69e7af0626012e
--- /dev/null
+++ b/thirdparty/cpm-sdl2-mixer.cmake
@@ -0,0 +1,22 @@
+CPMAddPackage(
+	NAME SDL2_mixer
+	VERSION 2.6.2
+	URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS
+		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL2MIXER_INSTALL OFF"
+		"SDL2MIXER_DEPS_SHARED OFF"
+		"SDL2MIXER_SAMPLES OFF"
+		"SDL2MIXER_VENDORED ON"
+		"SDL2MIXER_FLAC ON"
+		"SDL2MIXER_FLAC_LIBFLAC OFF"
+		"SDL2MIXER_FLAC_DRFLAC ON"
+		"SDL2MIXER_MOD OFF"
+		"SDL2MIXER_MP3 ON"
+		"SDL2MIXER_MP3_DRMP3 ON"
+		"SDL2MIXER_MIDI ON"
+		"SDL2MIXER_OPUS OFF"
+		"SDL2MIXER_VORBIS STB"
+		"SDL2MIXER_WAVE ON"
+)
diff --git a/thirdparty/cpm-sdl2.cmake b/thirdparty/cpm-sdl2.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..58cf9afc2a8c9f443379625049ebd28446382b84
--- /dev/null
+++ b/thirdparty/cpm-sdl2.cmake
@@ -0,0 +1,13 @@
+CPMAddPackage(
+	NAME SDL2
+	VERSION 2.24.2
+	URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS
+		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL_TEST OFF"
+		"SDL2_DISABLE_SDL2MAIN ON"
+		"SDL2_DISABLE_INSTALL ON"
+)
diff --git a/thirdparty/cpm-zlib.cmake b/thirdparty/cpm-zlib.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..5368366fd14a4246da476e48bb98ce708812646e
--- /dev/null
+++ b/thirdparty/cpm-zlib.cmake
@@ -0,0 +1,53 @@
+CPMAddPackage(
+	NAME ZLIB
+	VERSION 1.2.13
+	URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip"
+	EXCLUDE_FROM_ALL
+	DOWNLOAD_ONLY YES
+)
+
+if(ZLIB_ADDED)
+	set(ZLIB_SRCS
+		crc32.h
+		deflate.h
+		gzguts.h
+		inffast.h
+		inffixed.h
+		inflate.h
+		inftrees.h
+		trees.h
+		zutil.h
+		adler32.c
+		compress.c
+		crc32.c
+		deflate.c
+		gzclose.c
+		gzlib.c
+		gzread.c
+		gzwrite.c
+		inflate.c
+		infback.c
+		inftrees.c
+		inffast.c
+		trees.c
+		uncompr.c
+		zutil.c
+	)
+	list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/")
+
+	configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY)
+	configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY)
+	configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY)
+
+	add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS})
+	set_target_properties(ZLIB PROPERTIES
+		VERSION 1.2.13
+		OUTPUT_NAME "z"
+	)
+	target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}")
+	target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include")
+	if(MSVC)
+		target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
+	endif()
+	add_library(ZLIB::ZLIB ALIAS ZLIB)
+endif()
diff --git a/tools/anglechk.c b/tools/anglechk.c
index 4a67069bf744772082afeac5d8875991f2075903..7f56abff7e56336090af76e81335d76951dcec39 100644
--- a/tools/anglechk.c
+++ b/tools/anglechk.c
@@ -22,7 +22,6 @@
 #ifdef _MSC_VER
 #include <assert.h>
 #endif
-#define NOASM
 #include "../src/tables.h"
 #define NO_M
 #include "../src/m_fixed.c"