Browse Source

Merge branch 'main' into 12.0-development

Sasha Szpakowski 2 years ago
parent
commit
6e80d85110
45 changed files with 1197 additions and 323 deletions
  1. 15 0
      .github/workflows/main.yml
  2. 2 0
      CMakeLists.txt
  3. 1 1
      platform/unix/configure.ac
  4. 13 3
      platform/xcode/liblove.xcodeproj/project.pbxproj
  5. 90 99
      src/common/android.cpp
  6. 6 0
      src/common/android.h
  7. 13 4
      src/common/runtime.cpp
  8. 15 4
      src/libraries/enet/enet.cpp
  9. 92 33
      src/libraries/physfs/physfs.c
  10. 37 7
      src/libraries/physfs/physfs.h
  11. 7 4
      src/libraries/physfs/physfs_archiver_7z.c
  12. 1 1
      src/libraries/physfs/physfs_archiver_grp.c
  13. 67 11
      src/libraries/physfs/physfs_archiver_hog.c
  14. 3 2
      src/libraries/physfs/physfs_archiver_iso9660.c
  15. 1 1
      src/libraries/physfs/physfs_archiver_mvl.c
  16. 2 1
      src/libraries/physfs/physfs_archiver_qpak.c
  17. 3 2
      src/libraries/physfs/physfs_archiver_slb.c
  18. 2 2
      src/libraries/physfs/physfs_archiver_unpacked.c
  19. 2 1
      src/libraries/physfs/physfs_archiver_vdf.c
  20. 1 1
      src/libraries/physfs/physfs_archiver_wad.c
  21. 12 4
      src/libraries/physfs/physfs_archiver_zip.c
  22. 57 10
      src/libraries/physfs/physfs_internal.h
  23. 3 0
      src/libraries/physfs/physfs_lzmasdk.h
  24. 13 5
      src/libraries/physfs/physfs_miniz.h
  25. 117 0
      src/libraries/physfs/physfs_platform_android.c
  26. 19 6
      src/libraries/physfs/physfs_platform_apple.m
  27. 4 4
      src/libraries/physfs/physfs_platform_os2.c
  28. 40 8
      src/libraries/physfs/physfs_platform_posix.c
  29. 10 6
      src/libraries/physfs/physfs_platform_unix.c
  30. 5 5
      src/libraries/physfs/physfs_platform_windows.c
  31. 5 5
      src/libraries/physfs/physfs_platforms.h
  32. 12 7
      src/libraries/physfs/physfs_unicode.c
  33. 4 0
      src/modules/audio/openal/RecordingDevice.cpp
  34. 51 60
      src/modules/data/HashFunction.cpp
  35. 18 0
      src/modules/event/sdl/Event.cpp
  36. 27 2
      src/modules/filesystem/physfs/Filesystem.cpp
  37. 203 0
      src/modules/filesystem/physfs/PhysfsIo.cpp
  38. 167 0
      src/modules/filesystem/physfs/PhysfsIo.h
  39. 3 1
      src/modules/joystick/sdl/Joystick.cpp
  40. 15 6
      src/modules/joystick/sdl/JoystickModule.cpp
  41. 21 14
      src/modules/mouse/sdl/Mouse.cpp
  42. 7 3
      src/modules/video/theora/TheoraVideoStream.cpp
  43. 2 0
      src/modules/window/Window.h
  44. 7 0
      src/modules/window/sdl/Window.cpp
  45. 2 0
      src/modules/window/sdl/Window.h

+ 15 - 0
.github/workflows/main.yml

@@ -25,6 +25,16 @@ jobs:
       uses: actions/checkout@v3
       with:
         path: love2d-${{ github.sha }}
+    - name: Get Dependencies for AppImage
+      shell: python
+      env:
+        LOVE_BRANCH: ${{ github.sha }}
+      run: |
+        import os
+        for i in range(250):
+            if os.system(f"make getdeps LOVE_BRANCH={os.environ['LOVE_BRANCH']}") == 0:
+                raise SystemExit(0)
+        raise Exception("make getdeps failed")
     - name: Build AppImage
       run: make LOVE_BRANCH=${{ github.sha }}
     - name: Print LuaJIT branch
@@ -34,6 +44,11 @@ jobs:
       with:
         name: love-linux-x86_64.AppImage
         path: love-${{ github.sha }}.AppImage
+    - name: Artifact Debug Symbols
+      uses: actions/upload-artifact@v3
+      with:
+        name: love-x86_64-AppImage-debug
+        path: love-${{ github.sha }}.AppImage-debug.tar.gz
   windows-os:
     runs-on: windows-latest
     strategy:

+ 2 - 0
CMakeLists.txt

@@ -462,6 +462,8 @@ set(LOVE_SRC_MODULE_FILESYSTEM_PHYSFS
 	src/modules/filesystem/physfs/File.h
 	src/modules/filesystem/physfs/Filesystem.cpp
 	src/modules/filesystem/physfs/Filesystem.h
+	src/modules/filesystem/physfs/PhysfsIo.h
+	src/modules/filesystem/physfs/PhysfsIo.cpp
 )
 
 set(LOVE_SRC_MODULE_FILESYSTEM

+ 1 - 1
platform/unix/configure.ac

@@ -31,7 +31,7 @@ ACLOVE_CPP14_TEST
 
 # Add -fvisibility=hidden and -fvisibility-inlines-hidden
 CFLAGS="-fvisibility=hidden $CFLAGS"
-CPPFLAGS="-fvisibility=hidden -fvisibility-inlines-hidden $CPPFLAGS"
+CXXFLAGS="-fvisibility=hidden -fvisibility-inlines-hidden $CXXFLAGS"
 
 # Allow people on OSX to use autotools, they need their platform files
 AC_ARG_ENABLE([osx],

+ 13 - 3
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -61,6 +61,9 @@
 		D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; };
 		D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; };
 		D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9312963CD7500C64820 /* harfbuzz.framework */; };
+		D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
+		D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
+		D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */ = {isa = PBXBuildFile; fileRef = D943E58D2A24D56000D80361 /* PhysfsIo.h */; };
 		FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; };
 		FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
 		FA0A3A6123366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
@@ -1410,6 +1413,8 @@
 		D9DAB9272961F0FF00C64820 /* TextShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextShaper.h; sourceTree = "<group>"; };
 		D9DAB9282961F10000C64820 /* TextShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextShaper.cpp; sourceTree = "<group>"; };
 		D9DAB9312963CD7500C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = "<group>"; };
+		D943E58C2A24D56000D80361 /* PhysfsIo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PhysfsIo.cpp; sourceTree = "<group>"; };
+		D943E58D2A24D56000D80361 /* PhysfsIo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhysfsIo.h; sourceTree = "<group>"; };
 		FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = "<group>"; };
 		FA0A3A5D23366CE9001C269E /* floattypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = floattypes.h; sourceTree = "<group>"; };
 		FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = "<group>"; };
@@ -2819,6 +2824,8 @@
 				FA0B7B651A95902C000E1D17 /* File.h */,
 				FA0B7B661A95902C000E1D17 /* Filesystem.cpp */,
 				FA0B7B671A95902C000E1D17 /* Filesystem.h */,
+				D943E58C2A24D56000D80361 /* PhysfsIo.cpp */,
+				D943E58D2A24D56000D80361 /* PhysfsIo.h */,
 			);
 			path = physfs;
 			sourceTree = "<group>";
@@ -4276,6 +4283,7 @@
 				FA0B7EEA1A95902D000E1D17 /* wrap_Window.h in Headers */,
 				FA1557C01CE90A2C00AFF582 /* tinyexr.h in Headers */,
 				FA0B7E381A95902C000E1D17 /* WheelJoint.h in Headers */,
+				D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */,
 				FA0B7D851A95902C000E1D17 /* Image.h in Headers */,
 				FABDA9EA2552448300B5C523 /* b2_world_callbacks.h in Headers */,
 				FA0B7E7D1A95902C000E1D17 /* wrap_World.h in Headers */,
@@ -4770,6 +4778,7 @@
 				FA0B7E161A95902C000E1D17 /* Joint.cpp in Sources */,
 				FA0B7EE91A95902D000E1D17 /* wrap_Window.cpp in Sources */,
 				FA1583E21E196180005E603B /* wrap_Shader.cpp in Sources */,
+				D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */,
 				FA0B7AB91A958EA3000E1D17 /* enet.cpp in Sources */,
 				FA0B7E281A95902C000E1D17 /* PulleyJoint.cpp in Sources */,
 				FA56AA391FAFF02000A43D5F /* memory.cpp in Sources */,
@@ -5207,6 +5216,7 @@
 				FAF140A01E20934C00F898D2 /* RemoveTree.cpp in Sources */,
 				FA0B7E151A95902C000E1D17 /* Joint.cpp in Sources */,
 				FA0B7EE81A95902D000E1D17 /* wrap_Window.cpp in Sources */,
+				D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */,
 				FA0B7E271A95902C000E1D17 /* PulleyJoint.cpp in Sources */,
 				FA1BA0B71E17043400AA2803 /* wrap_Shader.cpp in Sources */,
 				FA0B7B301A958EA3000E1D17 /* wuff.c in Sources */,
@@ -5759,7 +5769,7 @@
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
-				MARKETING_VERSION = 11.4;
+				MARKETING_VERSION = 11.5;
 				OTHER_LDFLAGS = (
 					"-undefined",
 					dynamic_lookup,
@@ -5795,7 +5805,7 @@
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
-				MARKETING_VERSION = 11.4;
+				MARKETING_VERSION = 11.5;
 				OTHER_LDFLAGS = (
 					"-undefined",
 					dynamic_lookup,
@@ -5832,7 +5842,7 @@
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
-				MARKETING_VERSION = 11.4;
+				MARKETING_VERSION = 11.5;
 				OTHER_LDFLAGS = (
 					"-undefined",
 					dynamic_lookup,

+ 90 - 99
src/common/android.cpp

@@ -37,6 +37,7 @@
 #include <unistd.h>
 
 #include "libraries/physfs/physfs.h"
+#include "filesystem/physfs/PhysfsIo.h"
 
 namespace love
 {
@@ -324,127 +325,109 @@ static AAssetManager *getAssetManager()
 
 namespace aasset
 {
-namespace io
-{
 
-struct AssetInfo
+struct AssetInfo: public love::filesystem::physfs::PhysfsIo<AssetInfo>
 {
+	static const uint32_t version = 0;
+
 	AAssetManager *assetManager;
 	AAsset *asset;
 	char *filename;
 	size_t size;
-};
-
-static std::unordered_map<std::string, PHYSFS_FileType> fileTree;
 
-PHYSFS_sint64 read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
-{
-	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
-	int readed = AAsset_read(asset, buf, (size_t) len);
-
-	PHYSFS_setErrorCode(readed < 0 ? PHYSFS_ERR_OS_ERROR : PHYSFS_ERR_OK);
-	return (PHYSFS_sint64) readed;
-}
+	static AssetInfo *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset)
+	{
+		return new AssetInfo(assetManager, filename, asset);
+	}
 
-PHYSFS_sint64 write(PHYSFS_Io *io, const void *buf, PHYSFS_uint64 len)
-{
-	LOVE_UNUSED(io);
-	LOVE_UNUSED(buf);
-	LOVE_UNUSED(len);
+	int64_t read(void* buf, uint64_t len) const
+	{
+		int readed = AAsset_read(asset, buf, (size_t) len);
 
-	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
-	return -1;
-}
+		PHYSFS_setErrorCode(readed < 0 ? PHYSFS_ERR_OS_ERROR : PHYSFS_ERR_OK);
+		return (PHYSFS_sint64) readed;
+	}
 
-int seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
-{
-	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
-	int success = AAsset_seek64(asset, (off64_t) offset, SEEK_SET) != -1;
+	int64_t write(const void* buf, uint64_t len) const
+	{
+		LOVE_UNUSED(buf);
+		LOVE_UNUSED(len);
 
-	PHYSFS_setErrorCode(success ? PHYSFS_ERR_OK : PHYSFS_ERR_OS_ERROR);
-	return success;
-}
+		PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+		return -1;
+	}
 
-PHYSFS_sint64 tell(PHYSFS_Io *io)
-{
-	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
-	off64_t len = AAsset_getLength64(asset);
-	off64_t remain = AAsset_getRemainingLength64(asset);
+	int64_t seek(uint64_t offset) const
+	{
+		int64_t success = AAsset_seek64(asset, (off64_t) offset, SEEK_SET) != -1;
 
-	return len - remain;
-}
+		PHYSFS_setErrorCode(success ? PHYSFS_ERR_OK : PHYSFS_ERR_OS_ERROR);
+		return success;
+	}
 
-PHYSFS_sint64 length(PHYSFS_Io *io)
-{
-	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
-	return AAsset_getLength64(asset);
-}
+	int64_t tell() const
+	{
+		off64_t len = AAsset_getLength64(asset);
+		off64_t remain = AAsset_getRemainingLength64(asset);
 
-// Forward declaration
-PHYSFS_Io *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset);
+		return len - remain;
+	}
 
-PHYSFS_Io *duplicate(PHYSFS_Io *io)
-{
-	AssetInfo *assetInfo = (AssetInfo *) io->opaque;
-	AAsset *asset = AAssetManager_open(assetInfo->assetManager, assetInfo->filename, AASSET_MODE_RANDOM);
+	int64_t length() const
+	{
+		return AAsset_getLength64(asset);
+	}
 
-	if (asset == nullptr)
+	int64_t flush() const
 	{
-		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
-		return nullptr;
+		// Do nothing
+		PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+		return 1;
 	}
 
-	AAsset_seek64(asset, tell(io), SEEK_SET);
-	return fromAAsset(assetInfo->assetManager, assetInfo->filename, asset);
-}
+	AssetInfo *duplicate() const
+	{
+		AAsset *newAsset = AAssetManager_open(assetManager, filename, AASSET_MODE_RANDOM);
 
-void destroy(PHYSFS_Io *io)
-{
-	AssetInfo *assetInfo = (AssetInfo *) io->opaque;
-	AAsset_close(assetInfo->asset);
-	delete[] assetInfo->filename;
-	delete assetInfo;
-	delete io;
-}
+		if (newAsset == nullptr)
+		{
+			PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+			return nullptr;
+		}
 
-PHYSFS_Io *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset)
-{
-	// Create AssetInfo
-	AssetInfo *assetInfo = new (std::nothrow) AssetInfo();
-	assetInfo->assetManager = assetManager;
-	assetInfo->asset = asset;
-	assetInfo->size = strlen(filename) + 1;
-	assetInfo->filename = new (std::nothrow) char[assetInfo->size];
-	memcpy(assetInfo->filename, filename, assetInfo->size);
+		AAsset_seek64(asset, tell(), SEEK_SET);
+		return fromAAsset(assetManager, filename, asset);
+	}
 
-	// Create PHYSFS_Io
-	PHYSFS_Io *io = new (std::nothrow) PHYSFS_Io();
-	io->version = 0;
-	io->opaque = assetInfo;
-	io->read = read;
-	io->write = write;
-	io->seek = seek;
-	io->tell = tell;
-	io->length = length;
-	io->duplicate = duplicate;
-	io->flush = nullptr;
-	io->destroy = destroy;
+	~AssetInfo() override
+	{
+		AAsset_close(asset);
+		delete[] filename;
+	}
 
-	return io;
-}
+private:
+	AssetInfo(AAssetManager *assetManager, const char *filename, AAsset *asset)
+	: assetManager(assetManager)
+	, asset(asset)
+	, size(strlen(filename) + 1)
+	{
+		this->filename = new (std::nothrow) char[size];
+		memcpy(this->filename, filename, size);
+	}
+};
 
-}
+static std::unordered_map<std::string, PHYSFS_FileType> fileTree;
 
 void *openArchive(PHYSFS_Io *io, const char *name, int forWrite, int *claimed)
 {
-	if (io->opaque == nullptr || memcmp(io->opaque, "ASET", 4) != 0)
+	if (forWrite || io->opaque == nullptr || memcmp(io->opaque, "ASET", 4) != 0)
 		return nullptr;
 
 	// It's our archive
 	*claimed = 1;
 	AAssetManager *assetManager = getAssetManager();
 
-	if (io::fileTree.empty())
+	if (fileTree.empty())
 	{
 		// AAssetDir_getNextFileName intentionally excludes directories, so
 		// we have to use JNI that calls AssetManager.list() recursively.
@@ -460,7 +443,7 @@ void *openArchive(PHYSFS_Io *io, const char *name, int forWrite, int *claimed)
 			jstring jstr = (jstring) env->GetObjectArrayElement(list, i);
 			const char *str = env->GetStringUTFChars(jstr, nullptr);
 
-			io::fileTree[str + 1] = str[0] == 'd' ? PHYSFS_FILETYPE_DIRECTORY : PHYSFS_FILETYPE_REGULAR;
+			fileTree[str + 1] = str[0] == 'd' ? PHYSFS_FILETYPE_DIRECTORY : PHYSFS_FILETYPE_REGULAR;
 
 			env->ReleaseStringUTFChars(jstr, str);
 			env->DeleteLocalRef(jstr);
@@ -491,9 +474,9 @@ PHYSFS_EnumerateCallbackResult enumerate(
 
 	if (path[0] != 0)
 	{
-		FileTreeIterator result = io::fileTree.find(path);
+		FileTreeIterator result = fileTree.find(path);
 
-		if (result == io::fileTree.end() || result->second != PHYSFS_FILETYPE_DIRECTORY)
+		if (result == fileTree.end() || result->second != PHYSFS_FILETYPE_DIRECTORY)
 		{
 			PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
 			return PHYSFS_ENUM_ERROR;
@@ -551,7 +534,7 @@ PHYSFS_Io *openRead(void *opaque, const char *name)
 	}
 
 	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
-	return io::fromAAsset(assetManager, name, file);
+	return AssetInfo::fromAAsset(assetManager, name, file);
 }
 
 PHYSFS_Io *openWriteAppend(void *opaque, const char *name)
@@ -576,11 +559,12 @@ int removeMkdir(void *opaque, const char *name)
 
 int stat(void *opaque, const char *name, PHYSFS_Stat *out)
 {
+	using FileTreeIterator = std::unordered_map<std::string, PHYSFS_FileType>::iterator;
 	LOVE_UNUSED(opaque);
 
-	auto result = io::fileTree.find(name);
+	FileTreeIterator result = fileTree.find(name);
 
-	if (result != io::fileTree.end())
+	if (result != fileTree.end())
 	{
 		out->filetype = result->second;
 		out->modtime = -1;
@@ -591,11 +575,9 @@ int stat(void *opaque, const char *name, PHYSFS_Stat *out)
 		PHYSFS_setErrorCode(PHYSFS_ERR_OK);
 		return 1;
 	}
-	else
-	{
-		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
-		return 0;
-	}
+
+	PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+	return 0;
 }
 
 void closeArchive(void *opaque)
@@ -705,7 +687,7 @@ bool checkFusedGame(void **physfsIO_Out)
 	asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
 	if (asset)
 	{
-		io = aasset::io::fromAAsset(assetManager, "game.love", asset);
+		io = aasset::AssetInfo::fromAAsset(assetManager, "game.love", asset);
 		return true;
 	}
 
@@ -883,6 +865,15 @@ void *getIOFromFD(int fd)
 	return io;
 }
 
+const char *getArg0()
+{
+	static PHYSFS_AndroidInit androidInit = {
+		SDL_AndroidGetJNIEnv(),
+		SDL_AndroidGetActivity()
+	};
+	return (const char *) &androidInit;
+}
+
 } // android
 } // love
 

+ 6 - 0
src/common/android.h

@@ -117,6 +117,12 @@ int getFDFromLoveProtocol(const char *path);
  */
 void *getIOFromFD(int fd);
 
+/**
+ * Retrieve PHYSFS_AndroidInit structure.
+ * @return Pointer to PHYSFS_AndroidInit structure, casted to pointer of char.
+ */
+const char *getArg0();
+
 } // android
 } // love
 

+ 13 - 4
src/common/runtime.cpp

@@ -30,6 +30,7 @@
 // C++
 #include <algorithm>
 #include <iostream>
+#include <cstdint>
 #include <cstdio>
 #include <cstddef>
 #include <cmath>
@@ -98,6 +99,10 @@ static bool luax_isfulllightuserdatasupported(lua_State *L)
 	static bool checked = false;
 	static bool supported = false;
 
+	if (sizeof(void*) == 4)
+		// 32-bit platforms always supports full-lightuserdata.
+		return true;
+
 	if (!checked)
 	{
 		lua_pushcclosure(L, [](lua_State *L) -> int
@@ -128,17 +133,21 @@ static ObjectKey luax_computeloveobjectkey(lua_State *L, love::Object *object)
 	// can store all possible integers up to 2^53. We can store pointers that
 	// use more than 53 bits if their alignment is guaranteed to be more than 1.
 	// For example an alignment requirement of 8 means we can shift the
-	// pointer's bits by 3.
-	const size_t minalign = alignof(std::max_align_t);
+	// pointer's bits by 3. However, this is not always reliable on 32-bit platforms
+	// as can be seen in this bug report: https://github.com/love2d/love/issues/1916.
+	// It appears to be ABI violation. However it seems there's no reliable way to
+	// get the correct alignment pre-C++17. Consider that 32-bit pointer still fits
+	// in 2^53 range, it's perfectly fine to assume alignment of 1 there.
+	const size_t minalign = sizeof(void*) == 8 ? alignof(std::max_align_t) : 1;
 	uintptr_t key = (uintptr_t) object;
 
 	if ((key & (minalign - 1)) != 0)
 	{
 		luaL_error(L, "Cannot push love object to Lua: unexpected alignment "
-				   "(pointer is %p but alignment should be %d)", object, minalign);
+				   "(pointer is %p but alignment should be %d)", object, (int) minalign);
 	}
 
-	static const size_t shift = (size_t) log2(alignof(std::max_align_t));
+	static const size_t shift = (size_t) log2(minalign);
 
 	key >>= shift;
 

+ 15 - 4
src/libraries/enet/enet.cpp

@@ -116,6 +116,10 @@ static bool supports_full_lightuserdata(lua_State *L)
 	static bool checked = false;
 	static bool supported = false;
 
+	if (sizeof(void*) == 4)
+		// 32-bit platforms always supports full-lightuserdata.
+		return true;
+
 	if (!checked)
 	{
 		lua_pushcclosure(L, [](lua_State* L) -> int
@@ -141,7 +145,11 @@ static uintptr_t compute_peer_key(lua_State *L, ENetPeer *peer)
 	// pointers that use more than 53 bits if their alignment is guaranteed to
 	// be more than 1. For example an alignment requirement of 8 means we can
 	// shift the pointer's bits by 3.
-	const size_t minalign = std::min(alignof(ENetPeer), alignof(std::max_align_t));
+
+	// Please see these for the reason of this ternary operator:
+	// * https://github.com/love2d/love/issues/1916
+	// * https://github.com/love2d/love/commit/4ab9a1ce8c
+	const size_t minalign = sizeof(void*) == 8 ? std::min(alignof(ENetPeer), alignof(std::max_align_t)) : 1;
 	uintptr_t key = (uintptr_t) peer;
 
 	if ((key & (minalign - 1)) != 0)
@@ -157,13 +165,16 @@ static uintptr_t compute_peer_key(lua_State *L, ENetPeer *peer)
 
 static void push_peer_key(lua_State *L, uintptr_t key)
 {
-	// If full 64-bit lightuserdata is supported, always use that. Otherwise,
-	// if the key is smaller than 2^53 (which is integer precision for double
-	// datatype), then push number. Otherwise, throw error.
+	// If full 64-bit lightuserdata is supported (or it's 32-bit platform),
+	// always use that. Otherwise, if the key is smaller than 2^53 (which is
+	// integer precision for double datatype) on 64-bit platform, then push
+	// number. Otherwise, throw error.
 	if (supports_full_lightuserdata(L))
 		lua_pushlightuserdata(L, (void*) key);
+#if UINTPTR_MAX == 0xffffffffffffffff
 	else if (key > 0x20000000000000ULL) // 2^53
 		luaL_error(L, "Cannot push enet peer to Lua: pointer value %p is too large", key);
+#endif
 	else
 		lua_pushnumber(L, (lua_Number) key);
 }

+ 92 - 33
src/libraries/physfs/physfs.c

@@ -12,8 +12,6 @@
 #include "physfs_internal.h"
 
 #if defined(_MSC_VER)
-#include <stdarg.h>
-
 /* this code came from https://stackoverflow.com/a/8712996 */
 int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
 {
@@ -105,8 +103,8 @@ static inline int __PHYSFS_atomicAdd(int *ptrval, const int val)
 {
     int retval;
     __PHYSFS_platformGrabMutex(stateLock);
+    *ptrval += val;
     retval = *ptrval;
-    *ptrval = retval + val;
     __PHYSFS_platformReleaseMutex(stateLock);
     return retval;
 } /* __PHYSFS_atomicAdd */
@@ -925,12 +923,12 @@ static DirHandle *openDirectory(PHYSFS_Io *io, const char *d, int forWriting)
             retval = tryOpenDir(io, *i, d, forWriting, &claimed);
     } /* else */
 
-    errcode = currentErrorCode();
+    errcode = claimed ? currentErrorCode() : PHYSFS_ERR_UNSUPPORTED;
 
     if ((!retval) && (created_io))
         io->destroy(io);
 
-    BAIL_IF(!retval, claimed ? errcode : PHYSFS_ERR_UNSUPPORTED, NULL);
+    BAIL_IF(!retval, errcode, NULL);
     return retval;
 } /* openDirectory */
 
@@ -1097,6 +1095,8 @@ static int freeDirHandle(DirHandle *dh, FileHandle *openList)
         BAIL_IF(i->dirHandle == dh, PHYSFS_ERR_FILES_STILL_OPEN, 0);
 
     dh->funcs->closeArchive(dh->opaque);
+
+    if (dh->root) allocator.Free(dh->root);
     allocator.Free(dh->dirName);
     allocator.Free(dh->mountPoint);
     allocator.Free(dh);
@@ -1252,7 +1252,9 @@ int PHYSFS_init(const char *argv0)
     if (!userDir) goto initFailed;
 
     /* Platform layer is required to append a dirsep. */
+    #ifndef __ANDROID__  /* it's an APK file, not a directory, on Android. */
     assert(baseDir[strlen(baseDir) - 1] == __PHYSFS_platformDirSeparator);
+    #endif
     assert(userDir[strlen(userDir) - 1] == __PHYSFS_platformDirSeparator);
 
     if (!initStaticArchivers()) goto initFailed;
@@ -1452,15 +1454,60 @@ char *__PHYSFS_strdup(const char *str)
 } /* __PHYSFS_strdup */
 
 
-PHYSFS_uint32 __PHYSFS_hashString(const char *str, size_t len)
+PHYSFS_uint32 __PHYSFS_hashString(const char *str)
 {
     PHYSFS_uint32 hash = 5381;
-    while (len--)
-        hash = ((hash << 5) + hash) ^ *(str++);
+    while (1)
+    {
+        const char ch = *(str++);
+        if (ch == 0)
+            break;
+        hash = ((hash << 5) + hash) ^ ch;
+    } /* while */
     return hash;
 } /* __PHYSFS_hashString */
 
 
+PHYSFS_uint32 __PHYSFS_hashStringCaseFold(const char *str)
+{
+    PHYSFS_uint32 hash = 5381;
+    while (1)
+    {
+        const PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&str);
+        if (cp == 0)
+            break;
+        else
+        {
+            PHYSFS_uint32 folded[3];
+            const int numbytes = (int) (PHYSFS_caseFold(cp, folded) * sizeof (PHYSFS_uint32));
+            const char *bytes = (const char *) folded;
+            int i;
+            for (i = 0; i < numbytes; i++)
+                hash = ((hash << 5) + hash) ^ *(bytes++);
+        } /* else */
+    } /* while */
+
+    return hash;
+} /* __PHYSFS_hashStringCaseFold */
+
+
+PHYSFS_uint32 __PHYSFS_hashStringCaseFoldUSAscii(const char *str)
+{
+    PHYSFS_uint32 hash = 5381;
+    while (1)
+    {
+        char ch = *(str++);
+        if (ch == 0)
+            break;
+        else if ((ch >= 'A') && (ch <= 'Z'))
+            ch -= ('A' - 'a');
+
+        hash = ((hash << 5) + hash) ^ ch;
+    } /* while */
+    return hash;
+} /* __PHYSFS_hashStringCaseFoldUSAscii */
+
+
 /* MAKE SURE you hold stateLock before calling this! */
 static int doRegisterArchiver(const PHYSFS_Archiver *_archiver)
 {
@@ -1752,10 +1799,10 @@ int PHYSFS_setRoot(const char *archive, const char *subdir)
                 if (i->root)
                     allocator.Free(i->root);
                 i->root = ptr;
-                i->rootlen = len;
+                i->rootlen = strlen(i->root);  /* in case sanitizePlatformIndependentPath changed subdir */
 
-                if (longest_root < len)
-                    longest_root = len;
+                if (longest_root < i->rootlen)
+                    longest_root = i->rootlen;
             } /* else */
 
             break;
@@ -2157,10 +2204,10 @@ static int verifyPath(DirHandle *h, char **_fname, int allowMissing)
     if (h->root)
     {
         const int isempty = (*fname == '\0');
-        fname -= h->rootlen - 1;
+        fname -= h->rootlen + (isempty ? 0 : 1);
         strcpy(fname, h->root);
         if (!isempty)
-            fname[h->rootlen - 2] = '/';
+            fname[h->rootlen] = '/';
         *_fname = fname;
     } /* if */
 
@@ -2303,7 +2350,12 @@ static int doMkdir(const char *_dname, char *dname, DirHandle *h)
             const int rc = h->funcs->stat(h->opaque, dname, &statbuf);
             if ((!rc) && (currentErrorCode() == PHYSFS_ERR_NOT_FOUND))
                 exists = 0;
-            retval = ((rc) && (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY));
+            /* verifyPath made sure that (dname) doesn't have symlinks if they aren't
+               allowed, but it's possible the mounted writeDir itself has symlinks in it,
+               (for example "/var" on iOS is a symlink, and the prefpath will be somewhere
+               under that)...if we mounted that writeDir, we must allow those symlinks here
+               unconditionally. */
+            retval = ( (rc) && ((statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY) || (statbuf.filetype == PHYSFS_FILETYPE_SYMLINK)) );
         } /* if */
 
         if (!exists)
@@ -2384,10 +2436,10 @@ static DirHandle *getRealDirHandle(const char *_fname)
     BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, NULL);
 
     __PHYSFS_platformGrabMutex(stateLock);
-    len = strlen(_fname) + longest_root + 1;
+    len = strlen(_fname) + longest_root + 2;
     allocated_fname = __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, NULL);
-    fname = allocated_fname + longest_root;
+    fname = allocated_fname + longest_root + 1;
     if (sanitizePlatformIndependentPath(_fname, fname))
     {
         DirHandle *i;
@@ -2615,10 +2667,10 @@ int PHYSFS_enumerate(const char *_fn, PHYSFS_EnumerateCallback cb, void *data)
 
     __PHYSFS_platformGrabMutex(stateLock);
 
-    len = strlen(_fn) + longest_root + 1;
+    len = strlen(_fn) + longest_root + 2;
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
-    fname = allocated_fname + longest_root;
+    fname = allocated_fname + longest_root + 1;
     if (!sanitizePlatformIndependentPath(_fn, fname))
         retval = PHYSFS_ENUM_STOP;
     else
@@ -2821,10 +2873,10 @@ PHYSFS_File *PHYSFS_openRead(const char *_fname)
 
     BAIL_IF_MUTEX(!searchPath, PHYSFS_ERR_NOT_FOUND, stateLock, 0);
 
-    len = strlen(_fname) + longest_root + 1;
+    len = strlen(_fname) + longest_root + 2;
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
-    fname = allocated_fname + longest_root;
+    fname = allocated_fname + longest_root + 1;
 
     if (sanitizePlatformIndependentPath(_fname, fname))
     {
@@ -2850,13 +2902,15 @@ PHYSFS_File *PHYSFS_openRead(const char *_fname)
                 io->destroy(io);
                 PHYSFS_setErrorCode(PHYSFS_ERR_OUT_OF_MEMORY);
             } /* if */
-
-            memset(fh, '\0', sizeof (FileHandle));
-            fh->io = io;
-            fh->forReading = 1;
-            fh->dirHandle = i;
-            fh->next = openReadList;
-            openReadList = fh;
+            else
+            {
+                memset(fh, '\0', sizeof (FileHandle));
+                fh->io = io;
+                fh->forReading = 1;
+                fh->dirHandle = i;
+                fh->next = openReadList;
+                openReadList = fh;
+            } /* else */
         } /* if */
     } /* if */
 
@@ -3210,10 +3264,10 @@ int PHYSFS_stat(const char *_fname, PHYSFS_Stat *stat)
     stat->readonly = 1;
 
     __PHYSFS_platformGrabMutex(stateLock);
-    len = strlen(_fname) + longest_root + 1;
+    len = strlen(_fname) + longest_root + 2;
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
-    fname = allocated_fname + longest_root;
+    fname = allocated_fname + longest_root + 1;
 
     if (sanitizePlatformIndependentPath(_fname, fname))
     {
@@ -3346,7 +3400,7 @@ static void setDefaultAllocator(void)
 } /* setDefaultAllocator */
 
 
-int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen)
+int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen, const int case_sensitive, const int only_usascii)
 {
     static char rootpath[2] = { '/', '\0' };
     size_t alloclen;
@@ -3354,6 +3408,8 @@ int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen)
     assert(entrylen >= sizeof (__PHYSFS_DirTreeEntry));
 
     memset(dt, '\0', sizeof (*dt));
+    dt->case_sensitive = case_sensitive;
+    dt->only_usascii = only_usascii;
 
     dt->root = (__PHYSFS_DirTreeEntry *) allocator.Malloc(entrylen);
     BAIL_IF(!dt->root, PHYSFS_ERR_OUT_OF_MEMORY, 0);
@@ -3374,9 +3430,10 @@ int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen)
 } /* __PHYSFS_DirTreeInit */
 
 
-static inline PHYSFS_uint32 hashPathName(__PHYSFS_DirTree *dt, const char *name)
+static PHYSFS_uint32 hashPathName(__PHYSFS_DirTree *dt, const char *name)
 {
-    return __PHYSFS_hashString(name, strlen(name)) % dt->hashBuckets;
+    const PHYSFS_uint32 hashval = dt->case_sensitive ? __PHYSFS_hashString(name) : dt->only_usascii ? __PHYSFS_hashStringCaseFoldUSAscii(name) : __PHYSFS_hashStringCaseFold(name);
+    return hashval % dt->hashBuckets;
 } /* hashPathName */
 
 
@@ -3437,6 +3494,7 @@ void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir)
 /* Find the __PHYSFS_DirTreeEntry for a path in platform-independent notation. */
 void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path)
 {
+    const int cs = dt->case_sensitive;
     PHYSFS_uint32 hashval;
     __PHYSFS_DirTreeEntry *prev = NULL;
     __PHYSFS_DirTreeEntry *retval;
@@ -3447,7 +3505,8 @@ void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path)
     hashval = hashPathName(dt, path);
     for (retval = dt->hash[hashval]; retval; retval = retval->hashnext)
     {
-        if (strcmp(retval->name, path) == 0)
+        const int cmp = cs ? strcmp(retval->name, path) : PHYSFS_utf8stricmp(retval->name, path);
+        if (cmp == 0)
         {
             if (prev != NULL)  /* move this to the front of the list */
             {

+ 37 - 7
src/libraries/physfs/physfs.h

@@ -145,7 +145,7 @@
  *   - .ISO (ISO9660 files, CD-ROM images)
  *   - .GRP (Build Engine groupfile archives)
  *   - .PAK (Quake I/II archive format)
- *   - .HOG (Descent I/II HOG file archives)
+ *   - .HOG (Descent I/II/III HOG file archives)
  *   - .MVL (Descent II movielib archives)
  *   - .WAD (DOOM engine archives)
  *   - .VDF (Gothic I/II engine archives)
@@ -225,7 +225,9 @@ extern "C" {
 
 #if defined(PHYSFS_DECL)
 /* do nothing. */
-#elif defined(_MSC_VER)
+#elif defined(PHYSFS_STATIC)
+#define PHYSFS_DECL   /**/
+#elif defined(_WIN32) || defined(__OS2__)
 #define PHYSFS_DECL __declspec(dllexport)
 #elif defined(__SUNPRO_C)
 #define PHYSFS_DECL __global
@@ -433,7 +435,7 @@ typedef struct PHYSFS_Version
 
 #ifndef DOXYGEN_SHOULD_IGNORE_THIS
 #define PHYSFS_VER_MAJOR 3
-#define PHYSFS_VER_MINOR 1
+#define PHYSFS_VER_MINOR 2
 #define PHYSFS_VER_PATCH 0
 #endif  /* DOXYGEN_SHOULD_IGNORE_THIS */
 
@@ -493,6 +495,14 @@ typedef struct PHYSFS_Version
 PHYSFS_DECL void PHYSFS_getLinkedVersion(PHYSFS_Version *ver);
 
 
+#ifdef __ANDROID__
+typedef struct PHYSFS_AndroidInit
+{
+    void *jnienv;
+    void *context;
+} PHYSFS_AndroidInit;
+#endif
+
 /**
  * \fn int PHYSFS_init(const char *argv0)
  * \brief Initialize the PhysicsFS library.
@@ -502,11 +512,22 @@ PHYSFS_DECL void PHYSFS_getLinkedVersion(PHYSFS_Version *ver);
  * This should be called prior to any attempts to change your process's
  *  current working directory.
  *
+ * \warning On Android, argv0 should be a non-NULL pointer to a
+ *          PHYSFS_AndroidInit struct. This struct must hold a valid JNIEnv *
+ *          and a JNI jobject of a Context (either the application context or
+ *          the current Activity is fine). Both are cast to a void * so we
+ *          don't need jni.h included wherever physfs.h is. PhysicsFS
+ *          uses these objects to query some system details. PhysicsFS does
+ *          not hold a reference to the JNIEnv or Context past the call to
+ *          PHYSFS_init(). If you pass a NULL here, PHYSFS_init can still
+ *          succeed, but PHYSFS_getBaseDir() and PHYSFS_getPrefDir() will be
+ *          incorrect.
+ *
  *   \param argv0 the argv[0] string passed to your program's mainline.
  *          This may be NULL on most platforms (such as ones without a
  *          standard main() function), but you should always try to pass
- *          something in here. Unix-like systems such as Linux _need_ to
- *          pass argv[0] from main() in here.
+ *          something in here. Many Unix-like systems _need_ to pass argv[0]
+ *          from main() in here. See warning about Android, too!
  *  \return nonzero on success, zero on error. Specifics of the error can be
  *          gleaned from PHYSFS_getLastError().
  *
@@ -762,6 +783,15 @@ PHYSFS_DECL char **PHYSFS_getCdRomDirs(void);
  *
  * You should probably use the base dir in your search path.
  *
+ * \warning On most platforms, this is a directory; on Android, this gives
+ *          you the path to the app's package (APK) file. As APK files are
+ *          just .zip files, you can mount them in PhysicsFS like regular
+ *          directories. You'll probably want to call
+ *          PHYSFS_setRoot(basedir, "/assets") after mounting to make your
+ *          app's actual data available directly without all the Android
+ *          metadata and directory offset. Note that if you passed a NULL to
+ *          PHYSFS_init(), you will not get the APK file here.
+ *
  *  \return READ ONLY string of base dir in platform-dependent notation.
  *
  * \sa PHYSFS_getPrefDir
@@ -2710,10 +2740,10 @@ typedef PHYSFS_EnumerateCallbackResult (*PHYSFS_EnumerateCallback)(void *data,
  *
  * \code
  *
- * static int printDir(void *data, const char *origdir, const char *fname)
+ * static PHYSFS_EnumerateCallbackResult printDir(void *data, const char *origdir, const char *fname)
  * {
  *     printf(" * We've got [%s] in [%s].\n", fname, origdir);
- *     return 1;  // give me more data, please.
+ *     return PHYSFS_ENUM_OK;  // give me more data, please.
  * }
  *
  * // ...

+ 7 - 4
src/libraries/physfs/physfs_archiver_7z.c

@@ -185,7 +185,7 @@ static int szipLoadEntries(SZIPinfo *info)
 {
     int retval = 0;
 
-    if (__PHYSFS_DirTreeInit(&info->tree, sizeof (SZIPentry)))
+    if (__PHYSFS_DirTreeInit(&info->tree, sizeof (SZIPentry), 1, 0))
     {
         const PHYSFS_uint32 count = info->db.NumFiles;
         PHYSFS_uint32 i;
@@ -285,13 +285,16 @@ static PHYSFS_Io *SZIP_openRead(void *opaque, const char *path)
                         &blockIndex, &outBuffer, &outBufferSize, &offset,
                         &outSizeProcessed, alloc, alloc);
     GOTO_IF(rc != SZ_OK, szipErrorCode(rc), SZIP_openRead_failed);
+    GOTO_IF(outBuffer == NULL, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed);
 
     io->destroy(io);
     io = NULL;
 
-    buf = allocator.Malloc(outSizeProcessed);
-    GOTO_IF(rc != SZ_OK, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed);
-    memcpy(buf, outBuffer + offset, outSizeProcessed);
+    buf = allocator.Malloc(outSizeProcessed ? outSizeProcessed : 1);
+    GOTO_IF(buf == NULL, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed);
+
+    if (outSizeProcessed > 0)
+        memcpy(buf, outBuffer + offset, outSizeProcessed);
 
     alloc->Free(alloc, outBuffer);
     outBuffer = NULL;

+ 1 - 1
src/libraries/physfs/physfs_archiver_grp.c

@@ -76,7 +76,7 @@ static void *GRP_openArchive(PHYSFS_Io *io, const char *name,
     BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL);
     count = PHYSFS_swapULE32(count);
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
     if (!grpLoadEntries(io, count, unpkarc))

+ 67 - 11
src/libraries/physfs/physfs_archiver_hog.c

@@ -1,9 +1,9 @@
 /*
  * HOG support routines for PhysicsFS.
  *
- * This driver handles Descent I/II HOG archives.
+ * This driver handles Descent I/II/III HOG archives.
  *
- * The format is very simple:
+ * The Descent I/II format is very simple:
  *
  *   The file always starts with the 3-byte signature "DHF" (Descent
  *   HOG file). After that the files of a HOG are just attached after
@@ -23,10 +23,23 @@
  *
  * (That info is from http://www.descent2.com/ddn/specs/hog/)
  *
+ * Descent 3 moved to HOG2 format, which starts with the chars "HOG2",
+ *  then 32-bits for the number of contained files, 32 bits for the offset
+ *  to the first file's data, then 56 bytes of 0xFF (reserved?). Then for
+ *  each file, there's 36 bytes for filename (null-terminated, rest of bytes
+ *  are garbage), 32-bits unknown/reserved (always zero?), 32-bits of length
+ *  of file data, 32-bits of time since Unix epoch. Then immediately following,
+ *  for each file is their uncompressed content, you can find its offset
+ *  by starting at the initial data offset and adding the filesize of each
+ *  prior file.
+ *
+ * This information was found at:
+ *  https://web.archive.org/web/20020213004051/http://descent-3.com/ddn/specs/hog/
+ *
+ *
  * Please see the file LICENSE.txt in the source's root directory.
  *
- *  This file written by Bradley Bell.
- *  Based on grp.c by Ryan C. Gordon.
+ * This file written by Bradley Bell and Ryan C. Gordon.
  */
 
 #define __PHYSICSFS_INTERNAL__
@@ -34,7 +47,15 @@
 
 #if PHYSFS_SUPPORTS_HOG
 
-static int hogLoadEntries(PHYSFS_Io *io, void *arc)
+static int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val)
+{
+    PHYSFS_uint32 v;
+    BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
+    *val = PHYSFS_swapULE32(v);
+    return 1;
+} /* readui32 */
+
+static int hog1LoadEntries(PHYSFS_Io *io, void *arc)
 {
     const PHYSFS_uint64 iolen = io->length(io);
     PHYSFS_uint32 pos = 3;
@@ -45,11 +66,10 @@ static int hogLoadEntries(PHYSFS_Io *io, void *arc)
         char name[13];
 
         BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 13), 0);
-        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0);
+        BAIL_IF_ERRPASS(!readui32(io, &size), 0);
         name[12] = '\0';  /* just in case. */
         pos += 13 + 4;
 
-        size = PHYSFS_swapULE32(size);
         BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
         pos += size;
 
@@ -60,24 +80,60 @@ static int hogLoadEntries(PHYSFS_Io *io, void *arc)
     return 1;
 } /* hogLoadEntries */
 
+static int hog2LoadEntries(PHYSFS_Io *io, void *arc)
+{
+    PHYSFS_uint32 numfiles;
+    PHYSFS_uint32 pos;
+    PHYSFS_uint32 i;
+
+    BAIL_IF_ERRPASS(!readui32(io, &numfiles), 0);
+    BAIL_IF_ERRPASS(!readui32(io, &pos), 0);
+    BAIL_IF_ERRPASS(!io->seek(io, 68), 0);  /* skip to end of header. */
+
+    for (i = 0; i < numfiles; i++) {
+        char name[37];
+        PHYSFS_uint32 reserved;
+        PHYSFS_uint32 size;
+        PHYSFS_uint32 mtime;
+        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 36), 0);
+        BAIL_IF_ERRPASS(!readui32(io, &reserved), 0);
+        BAIL_IF_ERRPASS(!readui32(io, &size), 0);
+        BAIL_IF_ERRPASS(!readui32(io, &mtime), 0);
+        name[36] = '\0';  /* just in case */
+        BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, mtime, mtime, pos, size), 0);
+        pos += size;
+    }
+
+    return 1;
+} /* hog2LoadEntries */
+
 
 static void *HOG_openArchive(PHYSFS_Io *io, const char *name,
                              int forWriting, int *claimed)
 {
     PHYSFS_uint8 buf[3];
     void *unpkarc = NULL;
+    int hog1 = 0;
 
     assert(io != NULL);  /* shouldn't ever happen. */
     BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
     BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 3), NULL);
-    BAIL_IF(memcmp(buf, "DHF", 3) != 0, PHYSFS_ERR_UNSUPPORTED, NULL);
+
+    if (memcmp(buf, "DHF", 3) == 0)
+        hog1 = 1;  /* original HOG (Descent 1 and 2) archive */
+    else
+    {
+        BAIL_IF(memcmp(buf, "HOG", 3) != 0, PHYSFS_ERR_UNSUPPORTED, NULL); /* Not HOG2 */
+        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 1), NULL);
+        BAIL_IF(buf[0] != '2', PHYSFS_ERR_UNSUPPORTED, NULL); /* Not HOG2 */
+    } /* else */
 
     *claimed = 1;
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
-    if (!hogLoadEntries(io, unpkarc))
+    if (!(hog1 ? hog1LoadEntries(io, unpkarc) : hog2LoadEntries(io, unpkarc)))
     {
         UNPK_abandonArchive(unpkarc);
         return NULL;
@@ -92,7 +148,7 @@ const PHYSFS_Archiver __PHYSFS_Archiver_HOG =
     CURRENT_PHYSFS_ARCHIVER_API_VERSION,
     {
         "HOG",
-        "Descent I/II HOG file format",
+        "Descent I/II/III HOG file format",
         "Bradley Bell <[email protected]>",
         "https://icculus.org/physfs/",
         0,  /* supportsSymlinks */

+ 3 - 2
src/libraries/physfs/physfs_archiver_iso9660.c

@@ -251,7 +251,7 @@ static int parseVolumeDescriptor(PHYSFS_Io *io, PHYSFS_uint64 *_rootpos,
         pos += 2048;  /* each volume descriptor is 2048 bytes */
 
         BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &type, 1), 0);
-        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &identifier, 5), 0);
+        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, identifier, 5), 0);
 
         if (memcmp(identifier, "CD001", 5) != 0)  /* maybe not an iso? */
         {
@@ -346,7 +346,8 @@ static void *ISO9660_openArchive(PHYSFS_Io *io, const char *filename,
     if (!parseVolumeDescriptor(io, &rootpos, &len, &joliet, claimed))
         return NULL;
 
-    unpkarc = UNPK_openArchive(io);
+    /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+    unpkarc = UNPK_openArchive(io, 1, 0);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
     if (!iso9660LoadEntries(io, joliet, "", rootpos, rootpos + len, unpkarc))

+ 1 - 1
src/libraries/physfs/physfs_archiver_mvl.c

@@ -70,7 +70,7 @@ static void *MVL_openArchive(PHYSFS_Io *io, const char *name,
     BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL);
     count = PHYSFS_swapULE32(count);
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
     if (!mvlLoadEntries(io, count, unpkarc))

+ 2 - 1
src/libraries/physfs/physfs_archiver_qpak.c

@@ -86,7 +86,8 @@ static void *QPAK_openArchive(PHYSFS_Io *io, const char *name,
 
     BAIL_IF_ERRPASS(!io->seek(io, pos), NULL);
 
-    unpkarc = UNPK_openArchive(io);
+    /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+    unpkarc = UNPK_openArchive(io, 1, 0);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
     if (!qpakLoadEntries(io, count, unpkarc))

+ 3 - 2
src/libraries/physfs/physfs_archiver_slb.c

@@ -36,7 +36,7 @@ static int slbLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc)
         BAIL_IF(backslash != '\\', PHYSFS_ERR_CORRUPT, 0);
 
         /* read the rest of the buffer, 63 bytes */
-        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &name, 63), 0);
+        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 63), 0);
         name[63] = '\0'; /* in case the name lacks the null terminator */
 
         /* convert backslashes */
@@ -94,7 +94,8 @@ static void *SLB_openArchive(PHYSFS_Io *io, const char *name,
     /* seek to the table of contents */
     BAIL_IF_ERRPASS(!io->seek(io, tocPos), NULL);
 
-    unpkarc = UNPK_openArchive(io);
+    /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+    unpkarc = UNPK_openArchive(io, 1, 0);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
     if (!slbLoadEntries(io, count, unpkarc))

+ 2 - 2
src/libraries/physfs/physfs_archiver_unpacked.c

@@ -285,12 +285,12 @@ void *UNPK_addEntry(void *opaque, char *name, const int isdir,
 } /* UNPK_addEntry */
 
 
-void *UNPK_openArchive(PHYSFS_Io *io)
+void *UNPK_openArchive(PHYSFS_Io *io, const int case_sensitive, const int only_usascii)
 {
     UNPKinfo *info = (UNPKinfo *) allocator.Malloc(sizeof (UNPKinfo));
     BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
 
-    if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (UNPKentry)))
+    if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (UNPKentry), case_sensitive, only_usascii))
     {
         allocator.Free(info);
         return NULL;

+ 2 - 1
src/libraries/physfs/physfs_archiver_vdf.c

@@ -129,7 +129,8 @@ static void *VDF_openArchive(PHYSFS_Io *io, const char *name,
 
     BAIL_IF_ERRPASS(!io->seek(io, rootCatOffset), NULL);
 
-    unpkarc = UNPK_openArchive(io);
+    /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+    unpkarc = UNPK_openArchive(io, 1, 0);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
     if (!vdfLoadEntries(io, count, vdfDosTimeToEpoch(timestamp), unpkarc))

+ 1 - 1
src/libraries/physfs/physfs_archiver_wad.c

@@ -95,7 +95,7 @@ static void *WAD_openArchive(PHYSFS_Io *io, const char *name,
 
     BAIL_IF_ERRPASS(!io->seek(io, directoryOffset), 0);
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
     if (!wadLoadEntries(io, count, unpkarc))

+ 12 - 4
src/libraries/physfs/physfs_archiver_zip.c

@@ -15,6 +15,11 @@
 #include <errno.h>
 #include <time.h>
 
+#if (PHYSFS_BYTEORDER == PHYSFS_LIL_ENDIAN)
+#define MINIZ_LITTLE_ENDIAN 1
+#else
+#define MINIZ_LITTLE_ENDIAN 0
+#endif
 #include "physfs_miniz.h"
 
 /*
@@ -568,7 +573,7 @@ static PHYSFS_sint64 zip_find_end_of_central_dir(PHYSFS_Io *io, PHYSFS_sint64 *l
         {
             if (!__PHYSFS_readAll(io, buf, maxread - 4))
                 return -1;
-            memcpy(&buf[maxread - 4], &extra, sizeof (extra));
+            memcpy(&buf[maxread - 4], extra, sizeof (extra));
             totalread += maxread - 4;
         } /* if */
         else
@@ -578,7 +583,7 @@ static PHYSFS_sint64 zip_find_end_of_central_dir(PHYSFS_Io *io, PHYSFS_sint64 *l
             totalread += maxread;
         } /* else */
 
-        memcpy(&extra, buf, sizeof (extra));
+        memcpy(extra, buf, sizeof (extra));
 
         for (i = maxread - 4; i > 0; i--)
         {
@@ -833,7 +838,10 @@ static int zip_parse_local(PHYSFS_Io *io, ZIPentry *entry)
     BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
     BAIL_IF(ui32 != ZIP_LOCAL_FILE_SIG, PHYSFS_ERR_CORRUPT, 0);
     BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
-    BAIL_IF(ui16 != entry->version_needed, PHYSFS_ERR_CORRUPT, 0);
+    /* Windows Explorer might rewrite the entire central directory, setting
+       this field to 2.0/MS-DOS for all files, so favor the local version,
+       which it leaves intact if it didn't alter that specific file. */
+    entry->version_needed = ui16;
     BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);  /* general bits. */
     BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
     BAIL_IF(ui16 != entry->compression_method, PHYSFS_ERR_CORRUPT, 0);
@@ -1482,7 +1490,7 @@ static void *ZIP_openArchive(PHYSFS_Io *io, const char *name,
 
     if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &count))
         goto ZIP_openarchive_failed;
-    else if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (ZIPentry)))
+    else if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (ZIPentry), 1, 0))
         goto ZIP_openarchive_failed;
 
     root = (ZIPentry *) info->tree.root;

+ 57 - 10
src/libraries/physfs/physfs_internal.h

@@ -38,7 +38,7 @@
 #include <malloc.h>
 #endif
 
-#ifdef PHYSFS_PLATFORM_SOLARIS
+#if defined(PHYSFS_PLATFORM_SOLARIS) || defined(PHYSFS_PLATFORM_LINUX)
 #include <alloca.h>
 #endif
 
@@ -69,7 +69,7 @@ extern "C" {
    All file-private symbols need to be marked "static".
    Everything shared between PhysicsFS sources needs to be in this
    file between the visibility pragma blocks. */
-#if PHYSFS_MINIMUM_GCC_VERSION(4,0) || defined(__clang__)
+#if !defined(_WIN32) && (PHYSFS_MINIMUM_GCC_VERSION(4,0) || defined(__clang__))
 #define PHYSFS_HAVE_PRAGMA_VISIBILITY 1
 #endif
 
@@ -95,6 +95,7 @@ extern const PHYSFS_Archiver __PHYSFS_Archiver_VDF;
 /* a real C99-compliant snprintf() is in Visual Studio 2015,
    but just use this everywhere for binary compatibility. */
 #if defined(_MSC_VER)
+#include <stdarg.h>
 int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap);
 int __PHYSFS_msvc_snprintf(char *outBuf, size_t size, const char *format, ...);
 #define vsnprintf __PHYSFS_msvc_vsnprintf
@@ -108,14 +109,24 @@ const void *__PHYSFS_winrtCalcPrefDir(void);
 #endif
 
 /* atomic operations. */
+/* increment/decrement operations return the final incremented/decremented value. */
 #if defined(_MSC_VER) && (_MSC_VER >= 1500)
 #include <intrin.h>
 __PHYSFS_COMPILE_TIME_ASSERT(LongEqualsInt, sizeof (int) == sizeof (long));
 #define __PHYSFS_ATOMIC_INCR(ptrval) _InterlockedIncrement((long*)(ptrval))
 #define __PHYSFS_ATOMIC_DECR(ptrval) _InterlockedDecrement((long*)(ptrval))
 #elif defined(__clang__) || (defined(__GNUC__) && (((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100)) >= 40100))
-#define __PHYSFS_ATOMIC_INCR(ptrval) __sync_fetch_and_add(ptrval, 1)
-#define __PHYSFS_ATOMIC_DECR(ptrval) __sync_fetch_and_add(ptrval, -1)
+#define __PHYSFS_ATOMIC_INCR(ptrval) __sync_add_and_fetch(ptrval, 1)
+#define __PHYSFS_ATOMIC_DECR(ptrval) __sync_add_and_fetch(ptrval, -1)
+#elif defined(__WATCOMC__) && defined(__386__)
+extern __inline int _xadd_watcom(volatile int *a, int v);
+#pragma aux _xadd_watcom = \
+  "lock xadd [ecx], eax" \
+  parm [ecx] [eax] \
+  value [eax] \
+  modify exact [eax];
+#define __PHYSFS_ATOMIC_INCR(ptrval) (_xadd_watcom(ptrval, 1)+1)
+#define __PHYSFS_ATOMIC_DECR(ptrval) (_xadd_watcom(ptrval, -1)-1)
 #else
 #define PHYSFS_NEED_ATOMIC_OP_FALLBACK 1
 int __PHYSFS_ATOMIC_INCR(int *ptrval);
@@ -213,6 +224,7 @@ extern void SZIP_global_init(void);
 /* The latest supported PHYSFS_Archiver::version value. */
 #define CURRENT_PHYSFS_ARCHIVER_API_VERSION 0
 
+
 /* This byteorder stuff was lifted from SDL. https://www.libsdl.org/ */
 #define PHYSFS_LIL_ENDIAN  1234
 #define PHYSFS_BIG_ENDIAN  4321
@@ -220,11 +232,26 @@ extern void SZIP_global_init(void);
 #ifdef __linux__
 #include <endian.h>
 #define PHYSFS_BYTEORDER  __BYTE_ORDER
-#else /* __linux__ */
+#elif defined(__OpenBSD__) || defined(__DragonFly__)
+#include <endian.h>
+#define PHYSFS_BYTEORDER  BYTE_ORDER
+#elif defined(__FreeBSD__) || defined(__NetBSD__)
+#include <sys/endian.h>
+#define PHYSFS_BYTEORDER  BYTE_ORDER
+/* predefs from newer gcc and clang versions: */
+#elif defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) && defined(__BYTE_ORDER__)
+#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#define PHYSFS_BYTEORDER   PHYSFS_LIL_ENDIAN
+#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define PHYSFS_BYTEORDER   PHYSFS_BIG_ENDIAN
+#else
+#error Unsupported endianness
+#endif /**/
+#else
 #if defined(__hppa__) || \
     defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
-    (defined(__MIPS__) && defined(__MISPEB__)) || \
-    defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
+    (defined(__MIPS__) && defined(__MIPSEB__)) || \
+    defined(__ppc__) || defined(__POWERPC__) || defined(__powerpc__) || defined(__PPC__) || \
     defined(__sparc__)
 #define PHYSFS_BYTEORDER   PHYSFS_BIG_ENDIAN
 #else
@@ -312,7 +339,18 @@ char *__PHYSFS_strdup(const char *str);
 /*
  * Give a hash value for a C string (uses djb's xor hashing algorithm).
  */
-PHYSFS_uint32 __PHYSFS_hashString(const char *str, size_t len);
+PHYSFS_uint32 __PHYSFS_hashString(const char *str);
+
+/*
+ * Give a hash value for a C string (uses djb's xor hashing algorithm), case folding as it goes.
+ */
+PHYSFS_uint32 __PHYSFS_hashStringCaseFold(const char *str);
+
+/*
+ * Give a hash value for a C string (uses djb's xor hashing algorithm), case folding as it goes,
+ *  assuming that this is only US-ASCII chars (one byte per char, only 'A' through 'Z' need folding).
+ */
+PHYSFS_uint32 __PHYSFS_hashStringCaseFoldUSAscii(const char *str);
 
 
 /*
@@ -348,9 +386,10 @@ int __PHYSFS_readAll(PHYSFS_Io *io, void *buf, const size_t len);
 
 /* These are shared between some archivers. */
 
+/* LOTS of legacy formats that only use US ASCII, not actually UTF-8, so let them optimize here. */
+void *UNPK_openArchive(PHYSFS_Io *io, const int case_sensitive, const int only_usascii);
 void UNPK_abandonArchive(void *opaque);
 void UNPK_closeArchive(void *opaque);
-void *UNPK_openArchive(PHYSFS_Io *io);
 void *UNPK_addEntry(void *opaque, char *name, const int isdir,
                     const PHYSFS_sint64 ctime, const PHYSFS_sint64 mtime,
                     const PHYSFS_uint64 pos, const PHYSFS_uint64 len);
@@ -382,10 +421,13 @@ typedef struct __PHYSFS_DirTree
     __PHYSFS_DirTreeEntry **hash;  /* all entries hashed for fast lookup. */
     size_t hashBuckets;            /* number of buckets in hash.          */
     size_t entrylen;    /* size in bytes of entries (including subclass). */
+    int case_sensitive;  /* non-zero to treat entries as case-sensitive in DirTreeFind */
+    int only_usascii;  /* non-zero to treat paths as US ASCII only (one byte per char, only 'A' through 'Z' are considered for case folding). */
 } __PHYSFS_DirTree;
 
 
-int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen);
+/* LOTS of legacy formats that only use US ASCII, not actually UTF-8, so let them optimize here. */
+int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen, const int case_sensitive, const int only_usascii);
 void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir);
 void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path);
 PHYSFS_EnumerateCallbackResult __PHYSFS_DirTreeEnumerate(void *opaque,
@@ -715,6 +757,11 @@ int __PHYSFS_platformGrabMutex(void *mutex);
  */
 void __PHYSFS_platformReleaseMutex(void *mutex);
 
+
+/* !!! FIXME: move to public API? */
+PHYSFS_uint32 __PHYSFS_utf8codepoint(const char **_str);
+
+
 #if PHYSFS_HAVE_PRAGMA_VISIBILITY
 #pragma GCC visibility pop
 #endif

+ 3 - 0
src/libraries/physfs/physfs_lzmasdk.h

@@ -506,6 +506,7 @@ MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned mem
 #endif
 
 #if defined(MY_CPU_AMD64) \
+    || defined(_M_ARM64) \
     || defined(_M_IA64) \
     || defined(__AARCH64EL__) \
     || defined(__AARCH64EB__) \
@@ -532,6 +533,8 @@ MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned mem
 
 #if defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64))
 #define MY_CPU_ARM_LE
+#elif defined(_WIN64) && defined(_M_ARM64)
+#define MY_CPU_ARM_LE
 #endif
 
 #if defined(_WIN32) && defined(_M_IA64)

+ 13 - 5
src/libraries/physfs/physfs_miniz.h

@@ -22,12 +22,14 @@ typedef unsigned long mz_ulong;
 typedef void *(*mz_alloc_func)(void *opaque, unsigned int items, unsigned int size);
 typedef void (*mz_free_func)(void *opaque, void *address);
 
+#ifndef MINIZ_LITTLE_ENDIAN /* if not defined by PHYSFS */
 #if defined(_M_IX86) || defined(_M_X64)
 /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 if integer loads and stores to unaligned addresses are acceptable on the target platform (slightly faster). */
 #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
 /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */
 #define MINIZ_LITTLE_ENDIAN 1
 #endif
+#endif /**/
 
 #if defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__)
 /* Set MINIZ_HAS_64BIT_REGISTERS to 1 if the processor has 64-bit general purpose registers (enables 64-bit bitbuffer in inflator) */
@@ -117,6 +119,8 @@ struct tinfl_decompressor_tag
 #define MZ_MAX(a,b) (((a)>(b))?(a):(b))
 #define MZ_MIN(a,b) (((a)<(b))?(a):(b))
 #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))
+#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj))
+#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj))
 
 #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
   #define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
@@ -166,13 +170,17 @@ struct tinfl_decompressor_tag
     if (temp >= 0) { \
       code_len = temp >> 9; \
       if ((code_len) && (num_bits >= code_len)) \
-      break; \
+          break; \
     } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \
        code_len = TINFL_FAST_LOOKUP_BITS; \
        do { \
           temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
-       } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \
-    } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \
+       } while ((temp < 0) && (num_bits >= (code_len + 1))); \
+       if (temp >= 0) break; \
+    } \
+    TINFL_GET_BYTE(state_index, c); \
+    bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \
+    num_bits += 8; \
   } while (num_bits < 15);
 
 /* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */
@@ -274,13 +282,13 @@ static tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_
       else
       {
         for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; }
-        MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; }
+        MZ_CLEAR_ARR(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; }
         r->m_table_sizes[2] = 19;
       }
       for ( ; (int)r->m_type >= 0; r->m_type--)
       {
         int tree_next, tree_cur; tinfl_huff_table *pTable;
-        mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree);
+        mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_ARR(total_syms); MZ_CLEAR_ARR(pTable->m_look_up); MZ_CLEAR_ARR(pTable->m_tree);
         for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++;
         used_syms = 0, total = 0; next_code[0] = next_code[1] = 0;
         for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); }

+ 117 - 0
src/libraries/physfs/physfs_platform_android.c

@@ -0,0 +1,117 @@
+/*
+ * Android support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ *  This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_ANDROID
+
+#include <jni.h>
+#include <android/log.h>
+#include "physfs_internal.h"
+
+static char *prefpath = NULL;
+
+
+int __PHYSFS_platformInit(void)
+{
+    return 1;  /* always succeed. */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+    if (prefpath)
+    {
+        allocator.Free(prefpath);
+        prefpath = NULL;
+    } /* if */
+} /* __PHYSFS_platformDeinit */
+
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+    /* no-op. */
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+    /* as a cheat, we expect argv0 to be a PHYSFS_AndroidInit* on Android. */
+    PHYSFS_AndroidInit *ainit = (PHYSFS_AndroidInit *) argv0;
+    char *retval = NULL;
+    JNIEnv *jenv = NULL;
+    jobject jcontext;
+
+    if (ainit == NULL)
+        return __PHYSFS_strdup("/");  /* oh well. */
+
+    jenv = (JNIEnv *) ainit->jnienv;
+    jcontext = (jobject) ainit->context;
+
+    if ((*jenv)->PushLocalFrame(jenv, 16) >= 0)
+    {
+        jobject jfileobj = 0;
+        jmethodID jmeth = 0;
+        jthrowable jexception = 0;
+        jstring jstr = 0;
+
+        jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jcontext), "getPackageResourcePath", "()Ljava/lang/String;");
+        jstr = (jstring)(*jenv)->CallObjectMethod(jenv, jcontext, jmeth);
+        jexception = (*jenv)->ExceptionOccurred(jenv);  /* this can't throw an exception, right? Just in case. */
+        if (jexception != NULL)
+            (*jenv)->ExceptionClear(jenv);
+        else
+        {
+            const char *path = (*jenv)->GetStringUTFChars(jenv, jstr, NULL);
+            retval = __PHYSFS_strdup(path);
+            (*jenv)->ReleaseStringUTFChars(jenv, jstr, path);
+        } /* else */
+
+        /* We only can rely on the Activity being valid during this function call,
+           so go ahead and grab the prefpath too. */
+        jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jcontext), "getFilesDir", "()Ljava/io/File;");
+        jfileobj = (*jenv)->CallObjectMethod(jenv, jcontext, jmeth);
+        if (jfileobj)
+        {
+            jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jfileobj), "getCanonicalPath", "()Ljava/lang/String;");
+            jstr = (jstring)(*jenv)->CallObjectMethod(jenv, jfileobj, jmeth);
+            jexception = (*jenv)->ExceptionOccurred(jenv);
+            if (jexception != NULL)
+                (*jenv)->ExceptionClear(jenv);
+            else
+            {
+                const char *path = (*jenv)->GetStringUTFChars(jenv, jstr, NULL);
+                const size_t len = strlen(path) + 2;
+                prefpath = allocator.Malloc(len);
+                if (prefpath)
+                    snprintf(prefpath, len, "%s/", path);
+                (*jenv)->ReleaseStringUTFChars(jenv, jstr, path);
+            } /* else */
+        } /* if */
+
+        (*jenv)->PopLocalFrame(jenv, NULL);
+    } /* if */
+
+    /* we can't return NULL because then PhysicsFS will treat argv0 as a string, but it's a non-NULL jobject! */
+    if (retval == NULL)
+        retval = __PHYSFS_strdup("/");   /* we pray this works. */
+
+    return retval;
+} /* __PHYSFS_platformCalcBaseDir */
+
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+    return __PHYSFS_strdup(prefpath ? prefpath : "/");
+} /* __PHYSFS_platformCalcPrefDir */
+
+#endif /* PHYSFS_PLATFORM_ANDROID */
+
+/* end of physfs_platform_android.c ... */
+

+ 19 - 6
src/libraries/physfs/physfs_platform_apple.m

@@ -12,6 +12,7 @@
 #ifdef PHYSFS_PLATFORM_APPLE
 
 #include <Foundation/Foundation.h>
+#include <dlfcn.h>
 
 #include "physfs_internal.h"
 
@@ -99,7 +100,7 @@ static int darwinIsWholeMedia(io_service_t service)
 } /* darwinIsWholeMedia */
 
 
-static int darwinIsMountedDisc(char *bsdName, mach_port_t masterPort)
+static int darwinIsMountedDisc(char *bsdName, mach_port_t mainPort)
 {
     int retval = 0;
     CFMutableDictionaryRef matchingDict;
@@ -107,10 +108,10 @@ static int darwinIsMountedDisc(char *bsdName, mach_port_t masterPort)
     io_iterator_t iter;
     io_service_t service;
 
-    if ((matchingDict = IOBSDNameMatching(masterPort, 0, bsdName)) == NULL)
+    if ((matchingDict = IOBSDNameMatching(mainPort, 0, bsdName)) == NULL)
         return 0;
 
-    rc = IOServiceGetMatchingServices(masterPort, matchingDict, &iter);
+    rc = IOServiceGetMatchingServices(mainPort, matchingDict, &iter);
     if ((rc != KERN_SUCCESS) || (!iter))
         return 0;
 
@@ -158,13 +159,25 @@ static int darwinIsMountedDisc(char *bsdName, mach_port_t masterPort)
 void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
 {
 #if !defined(PHYSFS_NO_CDROM_SUPPORT)
+    /* macOS 12.0 changed "master" names to "main". */
+    typedef kern_return_t (*ioMainPortFn)(mach_port_t, mach_port_t *);
+    static ioMainPortFn ioMainPort = NULL;
     const char *devPrefix = "/dev/";
     const int prefixLen = strlen(devPrefix);
-    mach_port_t masterPort = 0;
+    mach_port_t mainPort = 0;
     struct statfs *mntbufp;
     int i, mounts;
 
-    if (IOMasterPort(MACH_PORT_NULL, &masterPort) != KERN_SUCCESS)
+    if (ioMainPort == NULL)
+    {
+        ioMainPort = (ioMainPortFn) dlsym(RTLD_DEFAULT, "IOMainPort");
+        if (!ioMainPort)
+            ioMainPort = (ioMainPortFn) dlsym(RTLD_DEFAULT, "IOMasterPort");
+        if (!ioMainPort)
+            return; /* oh well, no CD-ROMs for you. */
+    } /* if */
+
+    if (ioMainPort(MACH_PORT_NULL, &mainPort) != KERN_SUCCESS)
         BAIL(PHYSFS_ERR_OS_ERROR, ) /*return void*/;
 
     mounts = getmntinfo(&mntbufp, MNT_WAIT);  /* NOT THREAD SAFE! */
@@ -176,7 +189,7 @@ void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
             continue;
 
         dev += prefixLen;
-        if (darwinIsMountedDisc(dev, masterPort))
+        if (darwinIsMountedDisc(dev, mainPort))
             cb(data, mnt);
     } /* for */
 #endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */

+ 4 - 4
src/libraries/physfs/physfs_platform_os2.c

@@ -222,7 +222,7 @@ static char *cvtPathToCorrectCase(char *buf)
         if (ptr != NULL)  /* isolate element to find (fname is the start). */
             *ptr = '\0';
 
-        rc = DosFindFirst((unsigned char *) spec, &hdir, FILE_DIRECTORY,
+        rc = DosFindFirst(spec, &hdir, FILE_DIRECTORY,
                           &fb, sizeof (fb), &count, FIL_STANDARD);
         if (rc == NO_ERROR)
         {
@@ -331,7 +331,7 @@ static int isCdRomDrive(ULONG drive)
     ULONG ul1, ul2;
     APIRET rc;
     HFILE hfile = NULLHANDLE;
-    unsigned char drivename[3] = { 0, ':', '\0' };
+    char drivename[3] = { 0, ':', '\0' };
 
     drivename[0] = 'A' + drive;
 
@@ -443,7 +443,7 @@ PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
     __PHYSFS_smallFree(utf8);
     BAIL_IF_ERRPASS(!cpspec, PHYSFS_ENUM_ERROR);
 
-    rc = DosFindFirst((unsigned char *) cpspec, &hdir,
+    rc = DosFindFirst(cpspec, &hdir,
                       FILE_DIRECTORY | FILE_ARCHIVED |
                       FILE_READONLY | FILE_HIDDEN | FILE_SYSTEM,
                       &fb, sizeof (fb), &count, FIL_STANDARD);
@@ -535,7 +535,7 @@ int __PHYSFS_platformMkDir(const char *filename)
     APIRET rc;
     char *cpstr = cvtUtf8ToCodepage(filename);
     BAIL_IF_ERRPASS(!cpstr, 0);
-    rc = DosCreateDir((unsigned char *) cpstr, NULL);
+    rc = DosCreateDir(cpstr, NULL);
     allocator.Free(cpstr);
     BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
     return 1;

+ 40 - 8
src/libraries/physfs/physfs_platform_posix.c

@@ -6,8 +6,6 @@
  *  This file written by Ryan C. Gordon.
  */
 
-/* !!! FIXME: check for EINTR? */
-
 #define __PHYSICSFS_INTERNAL__
 #include "physfs_platforms.h"
 
@@ -157,19 +155,41 @@ int __PHYSFS_platformMkDir(const char *path)
 } /* __PHYSFS_platformMkDir */
 
 
+#if !defined(O_CLOEXEC) && defined(FD_CLOEXEC)
+static inline void set_CLOEXEC(int fildes)
+{
+    int flags = fcntl(fildes, F_GETFD);
+    if (flags != -1) {
+        fcntl(fildes, F_SETFD, flags | FD_CLOEXEC);
+    }
+}
+#endif
+
 static void *doOpen(const char *filename, int mode)
 {
     const int appending = (mode & O_APPEND);
     int fd;
     int *retval;
+
     errno = 0;
 
     /* O_APPEND doesn't actually behave as we'd like. */
     mode &= ~O_APPEND;
 
-    fd = open(filename, mode, S_IRUSR | S_IWUSR);
+#ifdef O_CLOEXEC
+    /* Add O_CLOEXEC if defined */
+    mode |= O_CLOEXEC;
+#endif
+
+    do {
+        fd = open(filename, mode, S_IRUSR | S_IWUSR);
+    } while ((fd < 0) && (errno == EINTR));
     BAIL_IF(fd < 0, errcodeFromErrno(), NULL);
 
+#if !defined(O_CLOEXEC) && defined(FD_CLOEXEC)
+    set_CLOEXEC(fd);
+#endif
+
     if (appending)
     {
         if (lseek(fd, 0, SEEK_END) < 0)
@@ -219,7 +239,9 @@ PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buffer,
     if (!__PHYSFS_ui64FitsAddressSpace(len))
         BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
 
-    rc = read(fd, buffer, (size_t) len);
+    do {
+        rc = read(fd, buffer, (size_t) len);
+    } while ((rc == -1) && (errno == EINTR));
     BAIL_IF(rc == -1, errcodeFromErrno(), -1);
     assert(rc >= 0);
     assert(rc <= len);
@@ -236,7 +258,9 @@ PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
     if (!__PHYSFS_ui64FitsAddressSpace(len))
         BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
 
-    rc = write(fd, (void *) buffer, (size_t) len);
+    do {
+        rc = write(fd, (void *) buffer, (size_t) len);
+    } while ((rc == -1) && (errno == EINTR));
     BAIL_IF(rc == -1, errcodeFromErrno(), rc);
     assert(rc >= 0);
     assert(rc <= len);
@@ -275,8 +299,13 @@ PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
 int __PHYSFS_platformFlush(void *opaque)
 {
     const int fd = *((int *) opaque);
-    if ((fcntl(fd, F_GETFL) & O_ACCMODE) != O_RDONLY)
-        BAIL_IF(fsync(fd) == -1, errcodeFromErrno(), 0);
+    int rc = -1;
+    if ((fcntl(fd, F_GETFL) & O_ACCMODE) != O_RDONLY) {
+        do {
+            rc = fsync(fd);
+        } while ((rc == -1) && (errno == EINTR));
+        BAIL_IF(rc == -1, errcodeFromErrno(), 0);
+    }
     return 1;
 } /* __PHYSFS_platformFlush */
 
@@ -284,7 +313,10 @@ int __PHYSFS_platformFlush(void *opaque)
 void __PHYSFS_platformClose(void *opaque)
 {
     const int fd = *((int *) opaque);
-    (void) close(fd);  /* we don't check this. You should have used flush! */
+    int rc = -1;
+    do {
+        rc = close(fd);  /* we don't check this. You should have used flush! */
+    } while ((rc == -1) && (errno == EINTR));
     allocator.Free(opaque);
 } /* __PHYSFS_platformClose */
 

+ 10 - 6
src/libraries/physfs/physfs_platform_unix.c

@@ -261,12 +261,6 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0)
         if (sysctl(mib, 4, fullpath, &buflen, NULL, 0) != -1)
             retval = __PHYSFS_strdup(fullpath);
     }
-    #elif defined(PHYSFS_PLATFORM_SOLARIS)
-    {
-        const char *path = getexecname();
-        if ((path != NULL) && (path[0] == '/'))  /* must be absolute path... */
-            retval = __PHYSFS_strdup(path);
-    }
     #endif
 
     /* If there's a Linux-like /proc filesystem, you can get the full path to
@@ -278,6 +272,7 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0)
         retval = readSymLink("/proc/self/exe");
         if (!retval) retval = readSymLink("/proc/curproc/file");
         if (!retval) retval = readSymLink("/proc/curproc/exe");
+        if (!retval) retval = readSymLink("/proc/self/path/a.out");
         if (retval == NULL)
         {
             /* older kernels don't have /proc/self ... try PID version... */
@@ -289,6 +284,15 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0)
         } /* if */
     } /* if */
 
+    #if defined(PHYSFS_PLATFORM_SOLARIS)
+    if (!retval)  /* try getexecname() if /proc didn't pan out. This may not be an absolute path! */
+    {
+        const char *path = getexecname();
+        if ((path != NULL) && (path[0] == '/'))  /* must be absolute path... */
+            retval = __PHYSFS_strdup(path);
+    } /* if */
+    #endif
+
     if (retval != NULL)  /* chop off filename. */
     {
         char *ptr = strrchr(retval, '/');

+ 5 - 5
src/libraries/physfs/physfs_platform_windows.c

@@ -101,7 +101,7 @@ static char *unicodeToUtf8Heap(const WCHAR *w_str)
 
 static inline HANDLE winFindFirstFileW(const WCHAR *path, LPWIN32_FIND_DATAW d)
 {
-    #ifdef PHYSFS_PLATFORM_WINRT
+    #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0501) // Windows XP+
     return FindFirstFileExW(path, FindExInfoStandard, d,
                             FindExSearchNameMatch, NULL, 0);
     #else
@@ -111,7 +111,7 @@ static inline HANDLE winFindFirstFileW(const WCHAR *path, LPWIN32_FIND_DATAW d)
 
 static inline BOOL winInitializeCriticalSection(LPCRITICAL_SECTION lpcs)
 {
-    #ifdef PHYSFS_PLATFORM_WINRT
+    #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0600) // Windows Vista+
     return InitializeCriticalSectionEx(lpcs, 2000, 0);
     #else
     InitializeCriticalSection(lpcs);
@@ -123,7 +123,7 @@ static inline HANDLE winCreateFileW(const WCHAR *wfname, const DWORD mode,
                                     const DWORD creation)
 {
     const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE;
-    #ifdef PHYSFS_PLATFORM_WINRT
+    #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0602) // Windows 8+
     return CreateFile2(wfname, mode, share, creation, NULL);
     #else
     return CreateFileW(wfname, mode, share, NULL, creation,
@@ -134,7 +134,7 @@ static inline HANDLE winCreateFileW(const WCHAR *wfname, const DWORD mode,
 static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos,
                               PHYSFS_sint64 *_newpos, const DWORD whence)
 {
-    #ifdef PHYSFS_PLATFORM_WINRT
+    #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0501) // Windows XP+
     LARGE_INTEGER lipos;
     LARGE_INTEGER linewpos;
     BOOL rc;
@@ -158,7 +158,7 @@ static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos,
 
 static PHYSFS_sint64 winGetFileSize(HANDLE h)
 {
-    #ifdef PHYSFS_PLATFORM_WINRT
+    #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0600) // Windows Vista+
     FILE_STANDARD_INFO info;
     const BOOL rc = GetFileInformationByHandleEx(h, FileStandardInfo,
                                                  &info, sizeof (info));

+ 5 - 5
src/libraries/physfs/physfs_platforms.h

@@ -40,11 +40,11 @@
 #  define PHYSFS_PLATFORM_POSIX 1
 #elif defined(macintosh)
 #  error Classic Mac OS support was dropped from PhysicsFS 2.0. Move to OS X.
-#elif defined(ANDROID)
-#  define PHYSFS_PLATFORM_LINUX 1
-#  define PHYSFS_PLATFORM_UNIX 1
-#  define PHYSFS_PLATFORM_POSIX 1
-#  define PHYSFS_NO_CDROM_SUPPORT 1
+#elif defined(__ANDROID__)
+ #  define PHYSFS_PLATFORM_LINUX 1
+ #  define PHYSFS_PLATFORM_ANDROID 1
+ #  define PHYSFS_PLATFORM_POSIX 1
+ #  define PHYSFS_NO_CDROM_SUPPORT 1
 #elif defined(__linux)
 #  define PHYSFS_PLATFORM_LINUX 1
 #  define PHYSFS_PLATFORM_UNIX 1

+ 12 - 7
src/libraries/physfs/physfs_unicode.c

@@ -21,8 +21,8 @@
 /*
  * This may not be the best value, but it's one that isn't represented
  *  in Unicode (0x10FFFF is the largest codepoint value). We return this
- *  value from utf8codepoint() if there's bogus bits in the
- *  stream. utf8codepoint() will turn this value into something
+ *  value from __PHYSFS_utf8codepoint() if there's bogus bits in the
+ *  stream. __PHYSFS_utf8codepoint() will turn this value into something
  *  reasonable (like a question mark), for text that wants to try to recover,
  *  whereas utf8valid() will use the value to determine if a string has bad
  *  bits.
@@ -35,7 +35,7 @@
  */
 #define UNICODE_BOGUS_CHAR_CODEPOINT '?'
 
-static PHYSFS_uint32 utf8codepoint(const char **_str)
+PHYSFS_uint32 __PHYSFS_utf8codepoint(const char **_str)
 {
     const char *str = *_str;
     PHYSFS_uint32 retval = 0;
@@ -188,6 +188,11 @@ static PHYSFS_uint32 utf8codepoint(const char **_str)
     } /* else if */
 
     return UNICODE_BOGUS_CHAR_VALUE;
+} /* __PHYSFS_utf8codepoint */
+
+static inline PHYSFS_uint32 utf8codepoint(const char **_str)
+{
+    return __PHYSFS_utf8codepoint(_str);
 } /* utf8codepoint */
 
 static PHYSFS_uint32 utf16codepoint(const PHYSFS_uint16 **_str)
@@ -210,7 +215,7 @@ static PHYSFS_uint32 utf16codepoint(const PHYSFS_uint16 **_str)
         else
         {
             src++;  /* eat the other surrogate. */
-            cp = (((cp - 0xD800) << 10) | (pair - 0xDC00));
+            cp = 0x10000 + (((cp - 0xD800) << 10) | (pair - 0xDC00));
         } /* else */
     } /* else if */
 
@@ -238,7 +243,7 @@ void PHYSFS_utf8ToUcs4(const char *src, PHYSFS_uint32 *dst, PHYSFS_uint64 len)
     len -= sizeof (PHYSFS_uint32);   /* save room for null char. */
     while (len >= sizeof (PHYSFS_uint32))
     {
-        PHYSFS_uint32 cp = utf8codepoint(&src);
+        PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
         if (cp == 0)
             break;
         else if (cp == UNICODE_BOGUS_CHAR_VALUE)
@@ -256,7 +261,7 @@ void PHYSFS_utf8ToUcs2(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len)
     len -= sizeof (PHYSFS_uint16);   /* save room for null char. */
     while (len >= sizeof (PHYSFS_uint16))
     {
-        PHYSFS_uint32 cp = utf8codepoint(&src);
+        PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
         if (cp == 0)
             break;
         else if (cp == UNICODE_BOGUS_CHAR_VALUE)
@@ -278,7 +283,7 @@ void PHYSFS_utf8ToUtf16(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len)
     len -= sizeof (PHYSFS_uint16);   /* save room for null char. */
     while (len >= sizeof (PHYSFS_uint16))
     {
-        PHYSFS_uint32 cp = utf8codepoint(&src);
+        PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
         if (cp == 0)
             break;
         else if (cp == UNICODE_BOGUS_CHAR_VALUE)

+ 4 - 0
src/modules/audio/openal/RecordingDevice.cpp

@@ -67,7 +67,11 @@ bool RecordingDevice::start(int samples, int sampleRate, int bitDepth, int chann
 	if (isRecording())
 		stop();
 
+	// This hard-crashes on iOS with Apple's OpenAL implementation, even when
+	// the user gives permission to the app.
+#ifndef LOVE_IOS
 	device = alcCaptureOpenDevice(name.c_str(), sampleRate, format, samples);
+#endif
 	if (device == nullptr)
 		return false;
 

+ 51 - 60
src/modules/data/HashFunction.cpp

@@ -51,6 +51,13 @@ inline uint64 rightrot(uint64 x, uint8 amount)
 	return (x >> amount) | (x << (64 - amount));
 }
 
+// Extend the value of `a` to make it a multiple of `n`.
+inline uint64 extend_multiple(uint64 a, uint64 n)
+{
+	uint64 r = a % n;
+	return r == 0 ? a : a + (n-r);
+}
+
 /**
  * The following implementation is based on the pseudocode provided by multiple
  * authors on wikipedia: https://en.wikipedia.org/wiki/MD5
@@ -80,25 +87,22 @@ public:
 		uint32 c0 = 0x98badcfe;
 		uint32 d0 = 0x10325476;
 
-		//Do the required padding (MD5, SHA1 and SHA2 use the same padding)
-		uint64 paddedLength = length + 1; //Consider the appended bit
-		if (paddedLength % 64 < 56)
-			paddedLength += 56 - paddedLength % 64;
-		if (paddedLength % 64 > 56)
-			paddedLength += 120 - paddedLength % 64;
+		// Compute final padded length, accounting for the appended bit (byte) and size
+		uint64 paddedLength = extend_multiple(length + 1 + 8, 64);
 
-		uint8 *padded = new uint8[paddedLength + 8];
+		uint32 *padded = new uint32[paddedLength / 4];
 		memcpy(padded, input, length);
-		memset(padded + length, 0, paddedLength - length);
-		padded[length] = 0x80;
+		memset(((uint8*)padded) + length, 0, paddedLength - length);
+		*(((uint8*)padded) + length) = 0x80; // append bit
 
-		//Now we need the length in bits
-		*((uint64*) &padded[paddedLength]) = length * 8;
-		paddedLength += 8;
+		// Append length in bits
+		uint64 bit_length = length * 8;
+		memcpy(((uint8*)padded) + paddedLength - 8, &bit_length, 8);
 
-		for (uint64 i = 0; i < paddedLength; i += 64)
+		// Process chunks
+		for (uint64 i = 0; i < paddedLength/4; i += 16)
 		{
-			uint32 *chunk = (uint32*) &padded[i];
+			uint32 *chunk = &padded[i];
 
 			uint32 A = a0;
 			uint32 B = b0;
@@ -201,29 +205,25 @@ public:
 			0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0
 		};
 
-		//Do the required padding (MD5, SHA1 and SHA2 use the same padding)
-		uint64 paddedLength = length + 1; //Consider the appended bit
-		if (paddedLength % 64 < 56)
-			paddedLength += 56 - paddedLength % 64;
-		if (paddedLength % 64 > 56)
-			paddedLength += 120 - paddedLength % 64;
+		// Compute final padded length, accounting for the appended bit (byte) and size
+		uint64 paddedLength = extend_multiple(length + 1 + 8, 64);
 
-		uint8 *padded = new uint8[paddedLength + 8];
+		uint32 *padded = new uint32[paddedLength / 4];
 		memcpy(padded, input, length);
-		memset(padded + length, 0, paddedLength - length);
-		padded[length] = 0x80;
+		memset(((uint8*)padded) + length, 0, paddedLength - length);
+		*(((uint8*)padded) + length) = 0x80; // append bit
 
-		// Now we need the length in bits (big endian)
-		length *= 8;
-		for (int i = 0; i < 8; ++i, ++paddedLength)
-			padded[paddedLength] = (length >> (56 - i * 8)) & 0xFF;
+		// Append length in bits (big endian)
+		uint64 bit_length = length * 8;
+		for (int i = 0; i < 8; ++i)
+			*(((uint8*)padded) + (paddedLength - 8 + i)) = (bit_length >> (56 - i * 8)) & 0xFF;
 
 		// Allocate our extended words
 		uint32 words[80];
 
-		for (uint64 i = 0; i < paddedLength; i += 64)
+		for (uint64 i = 0; i < paddedLength/4; i += 16)
 		{
-			uint32 *chunk = (uint32*) &padded[i];
+			uint32 *chunk = &padded[i];
 			for (int j = 0; j < 16; j++)
 			{
 				char *c = (char*) &words[j];
@@ -304,22 +304,18 @@ public:
 		if (!isSupported(function))
 			throw love::Exception("Hash function not supported by SHA-224/SHA-256 implementation");
 
-		//Do the required padding (MD5, SHA1 and SHA2 use the same padding)
-		uint64 paddedLength = length + 1; //Consider the appended bit
-		if (paddedLength % 64 < 56)
-			paddedLength += 56 - paddedLength % 64;
-		if (paddedLength % 64 > 56)
-			paddedLength += 120 - paddedLength % 64;
+		// Compute final padded length, accounting for the appended bit (byte) and size
+		uint64 paddedLength = extend_multiple(length + 1 + 8, 64);
 
-		uint8 *padded = new uint8[paddedLength + 8];
+		uint32 *padded = new uint32[paddedLength / 4];
 		memcpy(padded, input, length);
-		memset(padded + length, 0, paddedLength - length);
-		padded[length] = 0x80;
+		memset(((uint8*)padded) + length, 0, paddedLength - length);
+		*(((uint8*)padded) + length) = 0x80; // append bit
 
-		// Now we need the length in bits (big endian)
-		length *= 8;
-		for (int i = 0; i < 8; ++i, ++paddedLength)
-			padded[paddedLength] = (length >> (56 - i * 8)) & 0xFF;
+		// Append length in bits (big endian)
+		uint64 bit_length = length * 8;
+		for (int i = 0; i < 8; ++i)
+			*(((uint8*)padded) + (paddedLength - 8 + i)) = (bit_length >> (56 - i * 8)) & 0xFF;
 
 		uint32 intermediate[8];
 		if (function == FUNCTION_SHA224)
@@ -330,9 +326,9 @@ public:
 		// Allocate our extended words
 		uint32 words[64];
 
-		for (uint64 i = 0; i < paddedLength; i += 64)
+		for (uint64 i = 0; i < paddedLength/4; i += 16)
 		{
-			uint32 *chunk = (uint32*) &padded[i];
+			uint32 *chunk = &padded[i];
 			for (int j = 0; j < 16; j++)
 			{
 				char *c = (char*) &words[j];
@@ -460,31 +456,26 @@ public:
 		else
 			memcpy(intermediates, initial512, sizeof(intermediates));
 
-		//Do the required padding
-		uint64 paddedLength = length + 1; //Consider the appended bit
-		if (paddedLength % 128 < 112)
-			paddedLength += 112 - paddedLength % 128;
-		if (paddedLength % 128 > 112)
-			paddedLength += 240 - paddedLength % 128;
+		// Compute final padded length, accounting for the appended bit (byte) and size
+		uint64 paddedLength = extend_multiple(length + 1 + 16, 128);
 
-		uint8 *padded = new uint8[paddedLength + 16];
-		paddedLength += 8;
+		uint64 *padded = new uint64[paddedLength / 8];
 		memcpy(padded, input, length);
-		memset(padded + length, 0, paddedLength - length);
-		padded[length] = 0x80;
+		memset(((uint8*)padded) + length, 0, paddedLength - length);
+		*(((uint8*)padded) + length) = 0x80; // append bit
 
-		// Now we need the length in bits (big endian), note we only write a 64-bit int, so
+		// Append length in bits (big endian), note we only write a 64-bit int, so
 		// we have filled the first 8 bytes with zeroes
-		length *= 8;
-		for (int i = 0; i < 8; ++i, ++paddedLength)
-			padded[paddedLength] = (length >> (56 - i * 8)) & 0xFF;
+		uint64 bit_length = length * 8;
+		for (int i = 0; i < 8; ++i)
+			*(((uint8*)padded) + (paddedLength - 8 + i)) = (bit_length >> (56 - i * 8)) & 0xFF;
 
 		// Allocate our extended words
 		uint64 words[80];
 
-		for (uint64 i = 0; i < paddedLength; i += 128)
+		for (uint64 i = 0; i < paddedLength/8; i += 16)
 		{
-			uint64 *chunk = (uint64*) &padded[i];
+			uint64 *chunk = &padded[i];
 			for (int j = 0; j < 16; ++j)
 			{
 				char *c = (char*) &words[j];

+ 18 - 0
src/modules/event/sdl/Event.cpp

@@ -54,6 +54,13 @@ static void windowToDPICoords(double *x, double *y)
 		window->windowToDPICoords(x, y);
 }
 
+static void clampToWindow(double *x, double *y)
+{
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	if (window)
+		window->clampPositionInWindow(x, y);
+}
+
 #ifndef LOVE_MACOS
 static void normalizedToDPICoords(double *x, double *y)
 {
@@ -253,8 +260,16 @@ Message *Event::convert(const SDL_Event &e)
 			double y = (double) e.motion.y;
 			double xrel = (double) e.motion.xrel;
 			double yrel = (double) e.motion.yrel;
+
+			// SDL reports mouse coordinates outside the window bounds when click-and-
+			// dragging. For compatibility we clamp instead since user code may not be
+			// able to handle out-of-bounds coordinates. SDL has a hint to turn off
+			// auto capture, but it doesn't report the mouse's position at the edge of
+			// the window if the mouse moves fast enough when it's off.
+			clampToWindow(&x, &y);
 			windowToDPICoords(&x, &y);
 			windowToDPICoords(&xrel, &yrel);
+
 			vargs.emplace_back(x);
 			vargs.emplace_back(y);
 			vargs.emplace_back(xrel);
@@ -280,7 +295,10 @@ Message *Event::convert(const SDL_Event &e)
 
 			double px = (double) e.button.x;
 			double py = (double) e.button.y;
+
+			clampToWindow(&px, &py);
 			windowToDPICoords(&px, &py);
+
 			vargs.emplace_back(px);
 			vargs.emplace_back(py);
 			vargs.emplace_back((double) button);

+ 27 - 2
src/modules/filesystem/physfs/Filesystem.cpp

@@ -27,6 +27,7 @@
 
 #include "Filesystem.h"
 #include "File.h"
+#include "PhysfsIo.h"
 
 // PhysFS
 #include "libraries/physfs/physfs.h"
@@ -134,6 +135,10 @@ const char *Filesystem::getName() const
 
 void Filesystem::init(const char *arg0)
 {
+#ifdef LOVE_ANDROID
+	arg0 = love::android::getArg0();
+#endif
+
 	if (!PHYSFS_init(arg0))
 		throw love::Exception("Failed to initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
 
@@ -156,7 +161,7 @@ bool Filesystem::isFused() const
 	return fused;
 }
 
-bool Filesystem::setIdentity(const char *ident, bool appendToPath) 
+bool Filesystem::setIdentity(const char *ident, bool appendToPath)
 {
 	if (!PHYSFS_isInit())
 		return false;
@@ -279,7 +284,27 @@ bool Filesystem::setSource(const char *source)
 
 	// Add the directory.
 	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
-		return false;
+	{
+		// It's possible there is additional data at the end of the fused executable,
+		// e.g. for signed windows executables (the signature).
+		// In this case let's try a little bit harder to find the zip file.
+		// This is not used by default because I assume that the physfs IOs are probably
+		// more robust and more performant, so they should be favored, if possible.
+		auto io = StripSuffixIo::create(new_search_path);
+		if (!io->determineStrippedLength())
+		{
+			delete io;
+			return false;
+		}
+		if (!PHYSFS_mountIo(io, io->filename.c_str(), nullptr, 1))
+		{
+			// If PHYSFS_mountIo fails, io->destroy(io) is not called and we have
+			// to delete ourselves.
+			delete io;
+			return false;
+		}
+		return true;
+	}
 
 	// Save the game source.
 	gameSource = new_search_path;

+ 203 - 0
src/modules/filesystem/physfs/PhysfsIo.cpp

@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2006-2023 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include <cassert>
+#include <algorithm>
+
+#include "PhysfsIo.h"
+
+namespace love
+{
+namespace filesystem
+{
+namespace physfs
+{
+
+bool StripSuffixIo::determineStrippedLength()
+{
+	if (!file) {
+		return false;
+	}
+	const int64_t fullSize = fullLength();
+	int64_t chunkSize = std::min(fullSize, (int64_t)8192);
+	std::string buffer;
+	buffer.reserve(chunkSize);
+	int64_t i = fullSize - chunkSize;
+	// I don't think we really need to go through the whole file. The main known use
+	// case for this functionality is to skip windows codesign signatures, which are
+	// from what I have seen ~12KB or so, but trying is better than just failing.
+	while (i >= 0)
+	{
+		buffer.resize(chunkSize);
+		if (seek(i) == 0)
+			return false;
+		const auto n = read(&buffer[0], chunkSize);
+		if (n <= 0)
+			return false;
+		buffer.resize(n);
+		// We are looking for the magic bytes that indicate the start
+		// of the "End of cental directory record (EOCD)".
+		// As this is most likely not a multi-disk zip, we could include 4 bytes of 0x00,
+		// but I'd rather make fewer assumptions.
+		const auto endOfCentralDirectory = buffer.rfind("\x50\x4B\x05\x06");
+		if (endOfCentralDirectory != std::string::npos)
+		{
+			i = i + endOfCentralDirectory;
+			break;
+		}
+		if (i == 0)
+			break;
+		i = std::max((int64_t)0, i - chunkSize);
+	}
+
+	if (i > 0)
+	{
+		// The EOCD record is at least 22 bytes but may include a comment
+		if (i + 22 > fullSize)
+			return false; // Incomplete central directory
+		// The comment length (u16) is located 20 bytes from the start of the EOCD record
+		if (seek(i + 20) == 0)
+			return false;
+		uint8_t buffer[2];
+		const auto n = read(buffer, 2);
+		if (n <= 0)
+			return false;
+		const auto commentSize = (buffer[1] << 8) | buffer[0];
+		if (i + 22 + commentSize > fullSize) // Comment incomplete
+			return false;
+		// We pretend the file ends just after the comment
+		// (which should be the end of the embedded zip file)
+		strippedLength_ = i + 22 + commentSize;
+	}
+	else
+	{
+		strippedLength_ = fullSize;
+	}
+
+	if (seek(0) == 0)
+		return false;
+	return true;
+}
+
+int64_t StripSuffixIo::read(void* buf, uint64_t len)
+{
+	if (!file)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return -1;
+	}
+	const auto ret = std::fread(buf, 1, len, file);
+	if (ret == 0)
+	{
+		if (std::feof(file))
+		{
+			PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+			return 0;
+		}
+		else
+		{
+			PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+			return -1;
+		}
+	}
+	else if (ret < len && std::ferror(file))
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return -1;
+	}
+	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+	return ret;
+}
+
+int64_t StripSuffixIo::write(const void* /*buf*/, uint64_t /*len*/)
+{
+	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+	return -1;
+}
+
+int64_t StripSuffixIo::seek(uint64_t offset)
+{
+	if (!file)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return 0;
+	}
+	const auto ret = std::fseek(file, offset, SEEK_SET);
+	PHYSFS_setErrorCode(ret != 0 ? PHYSFS_ERR_OS_ERROR : PHYSFS_ERR_OK);
+	return ret == 0 ? 1 : 0;
+}
+
+int64_t StripSuffixIo::tell()
+{
+	if (!file)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return -1;
+	}
+	return std::ftell(file);
+}
+
+int64_t StripSuffixIo::length()
+{
+	return strippedLength_;
+}
+
+int64_t StripSuffixIo::flush()
+{
+	if (!file)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return 0;
+	}
+	return std::fflush(file) == 0 ? 1 : 0;
+}
+
+int64_t StripSuffixIo::fullLength()
+{
+	assert(file);
+	const auto cur = std::ftell(file);
+	if (cur == -1)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return -1;
+	}
+	if (std::fseek(file, 0, SEEK_END) != 0)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return -1;
+	}
+	const auto len = std::ftell(file);
+	if (len == -1)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return -1;
+	}
+	if (std::fseek(file, cur, SEEK_SET) != 0)
+	{
+		// We do have the length now, but something is wrong, so we return an error anyways
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return -1;
+	}
+	return len;
+}
+
+} // physfs
+} // filesystem
+} // love

+ 167 - 0
src/modules/filesystem/physfs/PhysfsIo.h

@@ -0,0 +1,167 @@
+/**
+ * Copyright (c) 2006-2023 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#ifndef LOVE_FILESYSTEM_PHYSFS_PHYSFSIO_H
+#define LOVE_FILESYSTEM_PHYSFS_PHYSFSIO_H
+
+#include <cstdint>
+#include <cstdio>
+#include <string>
+
+#include "libraries/physfs/physfs.h"
+
+namespace love
+{
+namespace filesystem
+{
+namespace physfs
+{
+
+template <typename Derived>
+struct PhysfsIo : PHYSFS_Io
+{
+protected:
+
+	PhysfsIo()
+	: PHYSFS_Io()
+	{
+		// Direct initialization of PHYSFS_Io members in the initializer list
+		// doesn't work in VS2013.
+		this->version = Derived::version;
+		this->opaque = this;
+		this->read = staticRead; // May be null.
+		this->write = staticWrite; // May be null.
+		this->seek = staticSeek;
+		this->tell = staticTell;
+		this->length = staticLength;
+		this->duplicate = staticDuplicate;
+		this->flush = staticFlush; // May be null.
+		this->destroy = staticDestroy;
+	}
+
+	virtual ~PhysfsIo() {}
+
+private:
+
+	// Returns: number of bytes read, 0 on EOF, -1 on failure
+	static PHYSFS_sint64 staticRead(struct PHYSFS_Io* io, void* buf, PHYSFS_uint64 len)
+	{
+		return derived(io)->read(buf, len);
+	}
+
+	// Returns: number of bytes written, -1 on failure
+	static PHYSFS_sint64 staticWrite(struct PHYSFS_Io* io, const void* buf, PHYSFS_uint64 len)
+	{
+		return derived(io)->write(buf, len);
+	}
+
+	// Returns: non-zero on success, zero on error
+	static int staticSeek(struct PHYSFS_Io* io, PHYSFS_uint64 offset)
+	{
+		return derived(io)->seek(offset);
+	}
+
+	// Returns: current offset from start, -1 on error
+	static PHYSFS_sint64 staticTell(struct PHYSFS_Io* io)
+	{
+		return derived(io)->tell();
+	}
+
+	// Returns: total size in bytes, -1 on error
+	static PHYSFS_sint64 staticLength(struct PHYSFS_Io* io)
+	{
+		return derived(io)->length();
+	}
+
+	static struct PHYSFS_Io* staticDuplicate(struct PHYSFS_Io* io)
+	{
+		// Just use copy constructor
+		return new Derived(*derived(io));
+	}
+
+	// Returns: non-zero on success, zero on error
+	static int staticFlush(struct PHYSFS_Io* io)
+	{
+		return derived(io)->flush();
+	}
+
+	static void staticDestroy(struct PHYSFS_Io* io)
+	{
+		// Just use destructor
+		delete derived(io);
+	}
+
+	static Derived* derived(PHYSFS_Io* io)
+	{
+		return static_cast<Derived*>(reinterpret_cast<PhysfsIo*>(io->opaque));
+	}
+};
+
+struct StripSuffixIo : public PhysfsIo<StripSuffixIo>
+{
+	static const uint32_t version = 0;
+
+	std::string filename;
+	FILE* file = nullptr;
+
+	// The constructor is private in favor of this function to prevent stack allocation
+	// because Physfs will take ownership of this object and call destroy on it later.
+	static StripSuffixIo* create(std::string f) { return new StripSuffixIo(f); }
+
+	virtual ~StripSuffixIo()
+	{
+		if (file)
+		{
+			std::fclose(file);
+		}
+	}
+
+	StripSuffixIo(const StripSuffixIo& other)
+		: StripSuffixIo(other.filename)
+	{
+	}
+
+	bool determineStrippedLength();
+
+	int64_t read(void* buf, uint64_t len);
+	int64_t write(const void* buf, uint64_t len);
+	int64_t seek(uint64_t offset);
+	int64_t tell();
+	int64_t length();
+	int64_t flush();
+
+private:
+
+	StripSuffixIo(std::string f)
+		: filename(std::move(f))
+		, file(std::fopen(filename.c_str(), "rb"))
+	{
+	}
+
+	int64_t fullLength();
+
+	int64_t strippedLength_ = -1;
+};
+
+} // physfs
+} // filesystem
+} // love
+
+#endif

+ 3 - 1
src/modules/joystick/sdl/Joystick.cpp

@@ -425,7 +425,9 @@ std::string Joystick::getGamepadMappingString() const
 	// Matches SDL_GameControllerAddMappingsFromRW.
 	if (mappingstr.find_last_of(',') != mappingstr.length() - 1)
 		mappingstr += ",";
-	mappingstr += "platform:" + std::string(SDL_GetPlatform());
+
+	if (mappingstr.find("platform:") == std::string::npos)
+		mappingstr += "platform:" + std::string(SDL_GetPlatform());
 
 	return mappingstr;
 }

+ 15 - 6
src/modules/joystick/sdl/JoystickModule.cpp

@@ -259,12 +259,17 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
 		if (endpos == std::string::npos)
 			endpos = mapstr.length() - 1;
 
-		mapstr.replace(findpos + 1, endpos - findpos + 1, insertstr);
+		mapstr.replace(findpos + 1, endpos - findpos, insertstr);
 	}
 	else
 	{
-		// Just append to the end if we don't need to replace anything.
-		mapstr += insertstr;
+		// Just append to the end (or before the platform section if that exists),
+		// if we don't need to replace anything.
+		size_t platformpos = mapstr.find("platform:");
+		if (platformpos != std::string::npos)
+			mapstr.insert(platformpos, insertstr);
+		else
+			mapstr += insertstr;
 	}
 
 	// 1 == added, 0 == updated, -1 == error.
@@ -465,7 +470,9 @@ std::string JoystickModule::getGamepadMappingString(const std::string &guid) con
 	// Matches SDL_GameControllerAddMappingsFromRW.
 	if (mapping.find_last_of(',') != mapping.length() - 1)
 		mapping += ",";
-	mapping += "platform:" + std::string(SDL_GetPlatform());
+
+	if (mapping.find("platform:") == std::string::npos)
+		mapping += "platform:" + std::string(SDL_GetPlatform());
 
 	return mapping;
 }
@@ -489,8 +496,10 @@ std::string JoystickModule::saveGamepadMappings()
 			mapping += ",";
 
 		// Matches SDL_GameControllerAddMappingsFromRW.
-		mapping += "platform:" + std::string(SDL_GetPlatform()) + ",\n";
-		mappings += mapping;
+		if (mapping.find("platform:") == std::string::npos)
+			mapping += "platform:" + std::string(SDL_GetPlatform()) + ",";
+
+		mappings += mapping + "\n";
 	}
 
 	return mappings;

+ 21 - 14
src/modules/mouse/sdl/Mouse.cpp

@@ -49,6 +49,13 @@ static void DPIToWindowCoords(double *x, double *y)
 		window->DPIToWindowCoords(x, y);
 }
 
+static void clampToWindow(double *x, double *y)
+{
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	if (window)
+		window->clampPositionInWindow(x, y);
+}
+
 const char *Mouse::getName() const
 {
 	return "love.mouse.sdl";
@@ -119,24 +126,16 @@ bool Mouse::isCursorSupported() const
 
 double Mouse::getX() const
 {
-	int x;
-	SDL_GetMouseState(&x, nullptr);
-
-	double dx = (double) x;
-	windowToDPICoords(&dx, nullptr);
-
-	return dx;
+	double x, y;
+	getPosition(x, y);
+	return x;
 }
 
 double Mouse::getY() const
 {
-	int y;
-	SDL_GetMouseState(nullptr, &y);
-
-	double dy = (double) y;
-	windowToDPICoords(nullptr, &dy);
-
-	return dy;
+	double x, y;
+	getPosition(x, y);
+	return y;
 }
 
 void Mouse::getPosition(double &x, double &y) const
@@ -146,6 +145,14 @@ void Mouse::getPosition(double &x, double &y) const
 
 	x = (double) mx;
 	y = (double) my;
+
+	// SDL reports mouse coordinates outside the window bounds when click-and-
+	// dragging. For compatibility we clamp instead since user code may not be
+	// able to handle out-of-bounds coordinates. SDL has a hint to turn off
+	// auto capture, but it doesn't report the mouse's position at the edge of
+	// the window if the mouse moves fast enough when it's off.
+	clampToWindow(&x, &y);
+
 	windowToDPICoords(&x, &y);
 }
 

+ 7 - 3
src/modules/video/theora/TheoraVideoStream.cpp

@@ -232,14 +232,18 @@ void TheoraVideoStream::threadedFillBackBuffer(double dt)
 		th_decode_ycbcr_out(decoder, bufferinfo);
 		hasFrame = true;
 
-		ogg_int64_t granulePosition;
+		ogg_int64_t decoderPosition;
 		do
 		{
 			if (demuxer.readPacket(packet))
 				return;
-		} while (th_decode_packetin(decoder, &packet, &granulePosition) != 0);
+
+			if (packet.granulepos > 0)
+				th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &packet.granulepos, sizeof(packet.granulepos));
+		} while (th_decode_packetin(decoder, &packet, &decoderPosition) != 0);
+
 		lastFrame = nextFrame;
-		nextFrame = th_granule_time(decoder, granulePosition);
+		nextFrame = th_granule_time(decoder, decoderPosition);
 	}
 
 	// Only swap once, even if we read many frames to get here

+ 2 - 0
src/modules/window/Window.h

@@ -199,6 +199,8 @@ public:
 	virtual int getPixelWidth() const = 0;
 	virtual int getPixelHeight() const = 0;
 
+	virtual void clampPositionInWindow(double *wx, double *wy) const = 0;
+
 	// Note: window-space coordinates are not necessarily the same as
 	// density-independent units (which toPixels and fromPixels use.)
 	virtual void windowToPixelCoords(double *x, double *y) const = 0;

+ 7 - 0
src/modules/window/sdl/Window.cpp

@@ -1309,6 +1309,13 @@ int Window::getPixelHeight() const
 	return pixelHeight;
 }
 
+void Window::clampPositionInWindow(double *wx, double *wy) const
+{
+	if (wx != nullptr)
+		*wx = std::min(std::max(0.0, *wx), (double) getWidth() - 1);
+	if (wy != nullptr)
+		*wy = std::min(std::max(0.0, *wy), (double) getHeight() - 1);
+}
 
 void Window::windowToPixelCoords(double *x, double *y) const
 {

+ 2 - 0
src/modules/window/sdl/Window.h

@@ -107,6 +107,8 @@ public:
 	int getPixelWidth() const override;
 	int getPixelHeight() const override;
 
+	void clampPositionInWindow(double *wx, double *wy) const override;
+
 	void windowToPixelCoords(double *x, double *y) const override;
 	void pixelToWindowCoords(double *x, double *y) const override;