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
       uses: actions/checkout@v3
       with:
       with:
         path: love2d-${{ github.sha }}
         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
     - name: Build AppImage
       run: make LOVE_BRANCH=${{ github.sha }}
       run: make LOVE_BRANCH=${{ github.sha }}
     - name: Print LuaJIT branch
     - name: Print LuaJIT branch
@@ -34,6 +44,11 @@ jobs:
       with:
       with:
         name: love-linux-x86_64.AppImage
         name: love-linux-x86_64.AppImage
         path: love-${{ github.sha }}.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:
   windows-os:
     runs-on: windows-latest
     runs-on: windows-latest
     strategy:
     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/File.h
 	src/modules/filesystem/physfs/Filesystem.cpp
 	src/modules/filesystem/physfs/Filesystem.cpp
 	src/modules/filesystem/physfs/Filesystem.h
 	src/modules/filesystem/physfs/Filesystem.h
+	src/modules/filesystem/physfs/PhysfsIo.h
+	src/modules/filesystem/physfs/PhysfsIo.cpp
 )
 )
 
 
 set(LOVE_SRC_MODULE_FILESYSTEM
 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
 # Add -fvisibility=hidden and -fvisibility-inlines-hidden
 CFLAGS="-fvisibility=hidden $CFLAGS"
 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
 # Allow people on OSX to use autotools, they need their platform files
 AC_ARG_ENABLE([osx],
 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 */; };
 		D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; };
 		D9DAB92E2961F10000C64820 /* 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 */; };
 		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 */; };
 		FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; };
 		FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
 		FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
 		FA0A3A6123366CE9001C269E /* 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>"; };
 		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>"; };
 		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>"; };
 		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>"; };
 		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>"; };
 		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>"; };
 		FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = "<group>"; };
@@ -2819,6 +2824,8 @@
 				FA0B7B651A95902C000E1D17 /* File.h */,
 				FA0B7B651A95902C000E1D17 /* File.h */,
 				FA0B7B661A95902C000E1D17 /* Filesystem.cpp */,
 				FA0B7B661A95902C000E1D17 /* Filesystem.cpp */,
 				FA0B7B671A95902C000E1D17 /* Filesystem.h */,
 				FA0B7B671A95902C000E1D17 /* Filesystem.h */,
+				D943E58C2A24D56000D80361 /* PhysfsIo.cpp */,
+				D943E58D2A24D56000D80361 /* PhysfsIo.h */,
 			);
 			);
 			path = physfs;
 			path = physfs;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -4276,6 +4283,7 @@
 				FA0B7EEA1A95902D000E1D17 /* wrap_Window.h in Headers */,
 				FA0B7EEA1A95902D000E1D17 /* wrap_Window.h in Headers */,
 				FA1557C01CE90A2C00AFF582 /* tinyexr.h in Headers */,
 				FA1557C01CE90A2C00AFF582 /* tinyexr.h in Headers */,
 				FA0B7E381A95902C000E1D17 /* WheelJoint.h in Headers */,
 				FA0B7E381A95902C000E1D17 /* WheelJoint.h in Headers */,
+				D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */,
 				FA0B7D851A95902C000E1D17 /* Image.h in Headers */,
 				FA0B7D851A95902C000E1D17 /* Image.h in Headers */,
 				FABDA9EA2552448300B5C523 /* b2_world_callbacks.h in Headers */,
 				FABDA9EA2552448300B5C523 /* b2_world_callbacks.h in Headers */,
 				FA0B7E7D1A95902C000E1D17 /* wrap_World.h in Headers */,
 				FA0B7E7D1A95902C000E1D17 /* wrap_World.h in Headers */,
@@ -4770,6 +4778,7 @@
 				FA0B7E161A95902C000E1D17 /* Joint.cpp in Sources */,
 				FA0B7E161A95902C000E1D17 /* Joint.cpp in Sources */,
 				FA0B7EE91A95902D000E1D17 /* wrap_Window.cpp in Sources */,
 				FA0B7EE91A95902D000E1D17 /* wrap_Window.cpp in Sources */,
 				FA1583E21E196180005E603B /* wrap_Shader.cpp in Sources */,
 				FA1583E21E196180005E603B /* wrap_Shader.cpp in Sources */,
+				D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */,
 				FA0B7AB91A958EA3000E1D17 /* enet.cpp in Sources */,
 				FA0B7AB91A958EA3000E1D17 /* enet.cpp in Sources */,
 				FA0B7E281A95902C000E1D17 /* PulleyJoint.cpp in Sources */,
 				FA0B7E281A95902C000E1D17 /* PulleyJoint.cpp in Sources */,
 				FA56AA391FAFF02000A43D5F /* memory.cpp in Sources */,
 				FA56AA391FAFF02000A43D5F /* memory.cpp in Sources */,
@@ -5207,6 +5216,7 @@
 				FAF140A01E20934C00F898D2 /* RemoveTree.cpp in Sources */,
 				FAF140A01E20934C00F898D2 /* RemoveTree.cpp in Sources */,
 				FA0B7E151A95902C000E1D17 /* Joint.cpp in Sources */,
 				FA0B7E151A95902C000E1D17 /* Joint.cpp in Sources */,
 				FA0B7EE81A95902D000E1D17 /* wrap_Window.cpp in Sources */,
 				FA0B7EE81A95902D000E1D17 /* wrap_Window.cpp in Sources */,
+				D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */,
 				FA0B7E271A95902C000E1D17 /* PulleyJoint.cpp in Sources */,
 				FA0B7E271A95902C000E1D17 /* PulleyJoint.cpp in Sources */,
 				FA1BA0B71E17043400AA2803 /* wrap_Shader.cpp in Sources */,
 				FA1BA0B71E17043400AA2803 /* wrap_Shader.cpp in Sources */,
 				FA0B7B301A958EA3000E1D17 /* wuff.c in Sources */,
 				FA0B7B301A958EA3000E1D17 /* wuff.c in Sources */,
@@ -5759,7 +5769,7 @@
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
-				MARKETING_VERSION = 11.4;
+				MARKETING_VERSION = 11.5;
 				OTHER_LDFLAGS = (
 				OTHER_LDFLAGS = (
 					"-undefined",
 					"-undefined",
 					dynamic_lookup,
 					dynamic_lookup,
@@ -5795,7 +5805,7 @@
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
-				MARKETING_VERSION = 11.4;
+				MARKETING_VERSION = 11.5;
 				OTHER_LDFLAGS = (
 				OTHER_LDFLAGS = (
 					"-undefined",
 					"-undefined",
 					dynamic_lookup,
 					dynamic_lookup,
@@ -5832,7 +5842,7 @@
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
-				MARKETING_VERSION = 11.4;
+				MARKETING_VERSION = 11.5;
 				OTHER_LDFLAGS = (
 				OTHER_LDFLAGS = (
 					"-undefined",
 					"-undefined",
 					dynamic_lookup,
 					dynamic_lookup,

+ 90 - 99
src/common/android.cpp

@@ -37,6 +37,7 @@
 #include <unistd.h>
 #include <unistd.h>
 
 
 #include "libraries/physfs/physfs.h"
 #include "libraries/physfs/physfs.h"
+#include "filesystem/physfs/PhysfsIo.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -324,127 +325,109 @@ static AAssetManager *getAssetManager()
 
 
 namespace aasset
 namespace aasset
 {
 {
-namespace io
-{
 
 
-struct AssetInfo
+struct AssetInfo: public love::filesystem::physfs::PhysfsIo<AssetInfo>
 {
 {
+	static const uint32_t version = 0;
+
 	AAssetManager *assetManager;
 	AAssetManager *assetManager;
 	AAsset *asset;
 	AAsset *asset;
 	char *filename;
 	char *filename;
 	size_t size;
 	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)
 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;
 		return nullptr;
 
 
 	// It's our archive
 	// It's our archive
 	*claimed = 1;
 	*claimed = 1;
 	AAssetManager *assetManager = getAssetManager();
 	AAssetManager *assetManager = getAssetManager();
 
 
-	if (io::fileTree.empty())
+	if (fileTree.empty())
 	{
 	{
 		// AAssetDir_getNextFileName intentionally excludes directories, so
 		// AAssetDir_getNextFileName intentionally excludes directories, so
 		// we have to use JNI that calls AssetManager.list() recursively.
 		// 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);
 			jstring jstr = (jstring) env->GetObjectArrayElement(list, i);
 			const char *str = env->GetStringUTFChars(jstr, nullptr);
 			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->ReleaseStringUTFChars(jstr, str);
 			env->DeleteLocalRef(jstr);
 			env->DeleteLocalRef(jstr);
@@ -491,9 +474,9 @@ PHYSFS_EnumerateCallbackResult enumerate(
 
 
 	if (path[0] != 0)
 	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);
 			PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
 			return PHYSFS_ENUM_ERROR;
 			return PHYSFS_ENUM_ERROR;
@@ -551,7 +534,7 @@ PHYSFS_Io *openRead(void *opaque, const char *name)
 	}
 	}
 
 
 	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
 	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)
 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)
 int stat(void *opaque, const char *name, PHYSFS_Stat *out)
 {
 {
+	using FileTreeIterator = std::unordered_map<std::string, PHYSFS_FileType>::iterator;
 	LOVE_UNUSED(opaque);
 	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->filetype = result->second;
 		out->modtime = -1;
 		out->modtime = -1;
@@ -591,11 +575,9 @@ int stat(void *opaque, const char *name, PHYSFS_Stat *out)
 		PHYSFS_setErrorCode(PHYSFS_ERR_OK);
 		PHYSFS_setErrorCode(PHYSFS_ERR_OK);
 		return 1;
 		return 1;
 	}
 	}
-	else
-	{
-		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
-		return 0;
-	}
+
+	PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+	return 0;
 }
 }
 
 
 void closeArchive(void *opaque)
 void closeArchive(void *opaque)
@@ -705,7 +687,7 @@ bool checkFusedGame(void **physfsIO_Out)
 	asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
 	asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
 	if (asset)
 	if (asset)
 	{
 	{
-		io = aasset::io::fromAAsset(assetManager, "game.love", asset);
+		io = aasset::AssetInfo::fromAAsset(assetManager, "game.love", asset);
 		return true;
 		return true;
 	}
 	}
 
 
@@ -883,6 +865,15 @@ void *getIOFromFD(int fd)
 	return io;
 	return io;
 }
 }
 
 
+const char *getArg0()
+{
+	static PHYSFS_AndroidInit androidInit = {
+		SDL_AndroidGetJNIEnv(),
+		SDL_AndroidGetActivity()
+	};
+	return (const char *) &androidInit;
+}
+
 } // android
 } // android
 } // love
 } // love
 
 

+ 6 - 0
src/common/android.h

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

+ 13 - 4
src/common/runtime.cpp

@@ -30,6 +30,7 @@
 // C++
 // C++
 #include <algorithm>
 #include <algorithm>
 #include <iostream>
 #include <iostream>
+#include <cstdint>
 #include <cstdio>
 #include <cstdio>
 #include <cstddef>
 #include <cstddef>
 #include <cmath>
 #include <cmath>
@@ -98,6 +99,10 @@ static bool luax_isfulllightuserdatasupported(lua_State *L)
 	static bool checked = false;
 	static bool checked = false;
 	static bool supported = false;
 	static bool supported = false;
 
 
+	if (sizeof(void*) == 4)
+		// 32-bit platforms always supports full-lightuserdata.
+		return true;
+
 	if (!checked)
 	if (!checked)
 	{
 	{
 		lua_pushcclosure(L, [](lua_State *L) -> int
 		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
 	// 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.
 	// 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
 	// 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;
 	uintptr_t key = (uintptr_t) object;
 
 
 	if ((key & (minalign - 1)) != 0)
 	if ((key & (minalign - 1)) != 0)
 	{
 	{
 		luaL_error(L, "Cannot push love object to Lua: unexpected alignment "
 		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;
 	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 checked = false;
 	static bool supported = false;
 	static bool supported = false;
 
 
+	if (sizeof(void*) == 4)
+		// 32-bit platforms always supports full-lightuserdata.
+		return true;
+
 	if (!checked)
 	if (!checked)
 	{
 	{
 		lua_pushcclosure(L, [](lua_State* L) -> int
 		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
 	// 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
 	// be more than 1. For example an alignment requirement of 8 means we can
 	// shift the pointer's bits by 3.
 	// 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;
 	uintptr_t key = (uintptr_t) peer;
 
 
 	if ((key & (minalign - 1)) != 0)
 	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)
 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))
 	if (supports_full_lightuserdata(L))
 		lua_pushlightuserdata(L, (void*) key);
 		lua_pushlightuserdata(L, (void*) key);
+#if UINTPTR_MAX == 0xffffffffffffffff
 	else if (key > 0x20000000000000ULL) // 2^53
 	else if (key > 0x20000000000000ULL) // 2^53
 		luaL_error(L, "Cannot push enet peer to Lua: pointer value %p is too large", key);
 		luaL_error(L, "Cannot push enet peer to Lua: pointer value %p is too large", key);
+#endif
 	else
 	else
 		lua_pushnumber(L, (lua_Number) key);
 		lua_pushnumber(L, (lua_Number) key);
 }
 }

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

@@ -12,8 +12,6 @@
 #include "physfs_internal.h"
 #include "physfs_internal.h"
 
 
 #if defined(_MSC_VER)
 #if defined(_MSC_VER)
-#include <stdarg.h>
-
 /* this code came from https://stackoverflow.com/a/8712996 */
 /* this code came from https://stackoverflow.com/a/8712996 */
 int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
 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;
     int retval;
     __PHYSFS_platformGrabMutex(stateLock);
     __PHYSFS_platformGrabMutex(stateLock);
+    *ptrval += val;
     retval = *ptrval;
     retval = *ptrval;
-    *ptrval = retval + val;
     __PHYSFS_platformReleaseMutex(stateLock);
     __PHYSFS_platformReleaseMutex(stateLock);
     return retval;
     return retval;
 } /* __PHYSFS_atomicAdd */
 } /* __PHYSFS_atomicAdd */
@@ -925,12 +923,12 @@ static DirHandle *openDirectory(PHYSFS_Io *io, const char *d, int forWriting)
             retval = tryOpenDir(io, *i, d, forWriting, &claimed);
             retval = tryOpenDir(io, *i, d, forWriting, &claimed);
     } /* else */
     } /* else */
 
 
-    errcode = currentErrorCode();
+    errcode = claimed ? currentErrorCode() : PHYSFS_ERR_UNSUPPORTED;
 
 
     if ((!retval) && (created_io))
     if ((!retval) && (created_io))
         io->destroy(io);
         io->destroy(io);
 
 
-    BAIL_IF(!retval, claimed ? errcode : PHYSFS_ERR_UNSUPPORTED, NULL);
+    BAIL_IF(!retval, errcode, NULL);
     return retval;
     return retval;
 } /* openDirectory */
 } /* openDirectory */
 
 
@@ -1097,6 +1095,8 @@ static int freeDirHandle(DirHandle *dh, FileHandle *openList)
         BAIL_IF(i->dirHandle == dh, PHYSFS_ERR_FILES_STILL_OPEN, 0);
         BAIL_IF(i->dirHandle == dh, PHYSFS_ERR_FILES_STILL_OPEN, 0);
 
 
     dh->funcs->closeArchive(dh->opaque);
     dh->funcs->closeArchive(dh->opaque);
+
+    if (dh->root) allocator.Free(dh->root);
     allocator.Free(dh->dirName);
     allocator.Free(dh->dirName);
     allocator.Free(dh->mountPoint);
     allocator.Free(dh->mountPoint);
     allocator.Free(dh);
     allocator.Free(dh);
@@ -1252,7 +1252,9 @@ int PHYSFS_init(const char *argv0)
     if (!userDir) goto initFailed;
     if (!userDir) goto initFailed;
 
 
     /* Platform layer is required to append a dirsep. */
     /* 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);
     assert(baseDir[strlen(baseDir) - 1] == __PHYSFS_platformDirSeparator);
+    #endif
     assert(userDir[strlen(userDir) - 1] == __PHYSFS_platformDirSeparator);
     assert(userDir[strlen(userDir) - 1] == __PHYSFS_platformDirSeparator);
 
 
     if (!initStaticArchivers()) goto initFailed;
     if (!initStaticArchivers()) goto initFailed;
@@ -1452,15 +1454,60 @@ char *__PHYSFS_strdup(const char *str)
 } /* __PHYSFS_strdup */
 } /* __PHYSFS_strdup */
 
 
 
 
-PHYSFS_uint32 __PHYSFS_hashString(const char *str, size_t len)
+PHYSFS_uint32 __PHYSFS_hashString(const char *str)
 {
 {
     PHYSFS_uint32 hash = 5381;
     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;
     return hash;
 } /* __PHYSFS_hashString */
 } /* __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! */
 /* MAKE SURE you hold stateLock before calling this! */
 static int doRegisterArchiver(const PHYSFS_Archiver *_archiver)
 static int doRegisterArchiver(const PHYSFS_Archiver *_archiver)
 {
 {
@@ -1752,10 +1799,10 @@ int PHYSFS_setRoot(const char *archive, const char *subdir)
                 if (i->root)
                 if (i->root)
                     allocator.Free(i->root);
                     allocator.Free(i->root);
                 i->root = ptr;
                 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 */
             } /* else */
 
 
             break;
             break;
@@ -2157,10 +2204,10 @@ static int verifyPath(DirHandle *h, char **_fname, int allowMissing)
     if (h->root)
     if (h->root)
     {
     {
         const int isempty = (*fname == '\0');
         const int isempty = (*fname == '\0');
-        fname -= h->rootlen - 1;
+        fname -= h->rootlen + (isempty ? 0 : 1);
         strcpy(fname, h->root);
         strcpy(fname, h->root);
         if (!isempty)
         if (!isempty)
-            fname[h->rootlen - 2] = '/';
+            fname[h->rootlen] = '/';
         *_fname = fname;
         *_fname = fname;
     } /* if */
     } /* 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);
             const int rc = h->funcs->stat(h->opaque, dname, &statbuf);
             if ((!rc) && (currentErrorCode() == PHYSFS_ERR_NOT_FOUND))
             if ((!rc) && (currentErrorCode() == PHYSFS_ERR_NOT_FOUND))
                 exists = 0;
                 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 */
 
 
         if (!exists)
         if (!exists)
@@ -2384,10 +2436,10 @@ static DirHandle *getRealDirHandle(const char *_fname)
     BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, NULL);
     BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, NULL);
 
 
     __PHYSFS_platformGrabMutex(stateLock);
     __PHYSFS_platformGrabMutex(stateLock);
-    len = strlen(_fname) + longest_root + 1;
+    len = strlen(_fname) + longest_root + 2;
     allocated_fname = __PHYSFS_smallAlloc(len);
     allocated_fname = __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, NULL);
     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))
     if (sanitizePlatformIndependentPath(_fname, fname))
     {
     {
         DirHandle *i;
         DirHandle *i;
@@ -2615,10 +2667,10 @@ int PHYSFS_enumerate(const char *_fn, PHYSFS_EnumerateCallback cb, void *data)
 
 
     __PHYSFS_platformGrabMutex(stateLock);
     __PHYSFS_platformGrabMutex(stateLock);
 
 
-    len = strlen(_fn) + longest_root + 1;
+    len = strlen(_fn) + longest_root + 2;
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
     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))
     if (!sanitizePlatformIndependentPath(_fn, fname))
         retval = PHYSFS_ENUM_STOP;
         retval = PHYSFS_ENUM_STOP;
     else
     else
@@ -2821,10 +2873,10 @@ PHYSFS_File *PHYSFS_openRead(const char *_fname)
 
 
     BAIL_IF_MUTEX(!searchPath, PHYSFS_ERR_NOT_FOUND, stateLock, 0);
     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);
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
     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))
     if (sanitizePlatformIndependentPath(_fname, fname))
     {
     {
@@ -2850,13 +2902,15 @@ PHYSFS_File *PHYSFS_openRead(const char *_fname)
                 io->destroy(io);
                 io->destroy(io);
                 PHYSFS_setErrorCode(PHYSFS_ERR_OUT_OF_MEMORY);
                 PHYSFS_setErrorCode(PHYSFS_ERR_OUT_OF_MEMORY);
             } /* if */
             } /* 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 */
     } /* if */
     } /* if */
 
 
@@ -3210,10 +3264,10 @@ int PHYSFS_stat(const char *_fname, PHYSFS_Stat *stat)
     stat->readonly = 1;
     stat->readonly = 1;
 
 
     __PHYSFS_platformGrabMutex(stateLock);
     __PHYSFS_platformGrabMutex(stateLock);
-    len = strlen(_fname) + longest_root + 1;
+    len = strlen(_fname) + longest_root + 2;
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     allocated_fname = (char *) __PHYSFS_smallAlloc(len);
     BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
     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))
     if (sanitizePlatformIndependentPath(_fname, fname))
     {
     {
@@ -3346,7 +3400,7 @@ static void setDefaultAllocator(void)
 } /* setDefaultAllocator */
 } /* 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' };
     static char rootpath[2] = { '/', '\0' };
     size_t alloclen;
     size_t alloclen;
@@ -3354,6 +3408,8 @@ int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen)
     assert(entrylen >= sizeof (__PHYSFS_DirTreeEntry));
     assert(entrylen >= sizeof (__PHYSFS_DirTreeEntry));
 
 
     memset(dt, '\0', sizeof (*dt));
     memset(dt, '\0', sizeof (*dt));
+    dt->case_sensitive = case_sensitive;
+    dt->only_usascii = only_usascii;
 
 
     dt->root = (__PHYSFS_DirTreeEntry *) allocator.Malloc(entrylen);
     dt->root = (__PHYSFS_DirTreeEntry *) allocator.Malloc(entrylen);
     BAIL_IF(!dt->root, PHYSFS_ERR_OUT_OF_MEMORY, 0);
     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 */
 } /* __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 */
 } /* 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. */
 /* Find the __PHYSFS_DirTreeEntry for a path in platform-independent notation. */
 void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path)
 void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path)
 {
 {
+    const int cs = dt->case_sensitive;
     PHYSFS_uint32 hashval;
     PHYSFS_uint32 hashval;
     __PHYSFS_DirTreeEntry *prev = NULL;
     __PHYSFS_DirTreeEntry *prev = NULL;
     __PHYSFS_DirTreeEntry *retval;
     __PHYSFS_DirTreeEntry *retval;
@@ -3447,7 +3505,8 @@ void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path)
     hashval = hashPathName(dt, path);
     hashval = hashPathName(dt, path);
     for (retval = dt->hash[hashval]; retval; retval = retval->hashnext)
     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 */
             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)
  *   - .ISO (ISO9660 files, CD-ROM images)
  *   - .GRP (Build Engine groupfile archives)
  *   - .GRP (Build Engine groupfile archives)
  *   - .PAK (Quake I/II archive format)
  *   - .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)
  *   - .MVL (Descent II movielib archives)
  *   - .WAD (DOOM engine archives)
  *   - .WAD (DOOM engine archives)
  *   - .VDF (Gothic I/II engine archives)
  *   - .VDF (Gothic I/II engine archives)
@@ -225,7 +225,9 @@ extern "C" {
 
 
 #if defined(PHYSFS_DECL)
 #if defined(PHYSFS_DECL)
 /* do nothing. */
 /* do nothing. */
-#elif defined(_MSC_VER)
+#elif defined(PHYSFS_STATIC)
+#define PHYSFS_DECL   /**/
+#elif defined(_WIN32) || defined(__OS2__)
 #define PHYSFS_DECL __declspec(dllexport)
 #define PHYSFS_DECL __declspec(dllexport)
 #elif defined(__SUNPRO_C)
 #elif defined(__SUNPRO_C)
 #define PHYSFS_DECL __global
 #define PHYSFS_DECL __global
@@ -433,7 +435,7 @@ typedef struct PHYSFS_Version
 
 
 #ifndef DOXYGEN_SHOULD_IGNORE_THIS
 #ifndef DOXYGEN_SHOULD_IGNORE_THIS
 #define PHYSFS_VER_MAJOR 3
 #define PHYSFS_VER_MAJOR 3
-#define PHYSFS_VER_MINOR 1
+#define PHYSFS_VER_MINOR 2
 #define PHYSFS_VER_PATCH 0
 #define PHYSFS_VER_PATCH 0
 #endif  /* DOXYGEN_SHOULD_IGNORE_THIS */
 #endif  /* DOXYGEN_SHOULD_IGNORE_THIS */
 
 
@@ -493,6 +495,14 @@ typedef struct PHYSFS_Version
 PHYSFS_DECL void PHYSFS_getLinkedVersion(PHYSFS_Version *ver);
 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)
  * \fn int PHYSFS_init(const char *argv0)
  * \brief Initialize the PhysicsFS library.
  * \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
  * This should be called prior to any attempts to change your process's
  *  current working directory.
  *  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.
  *   \param argv0 the argv[0] string passed to your program's mainline.
  *          This may be NULL on most platforms (such as ones without a
  *          This may be NULL on most platforms (such as ones without a
  *          standard main() function), but you should always try to pass
  *          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
  *  \return nonzero on success, zero on error. Specifics of the error can be
  *          gleaned from PHYSFS_getLastError().
  *          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.
  * 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.
  *  \return READ ONLY string of base dir in platform-dependent notation.
  *
  *
  * \sa PHYSFS_getPrefDir
  * \sa PHYSFS_getPrefDir
@@ -2710,10 +2740,10 @@ typedef PHYSFS_EnumerateCallbackResult (*PHYSFS_EnumerateCallback)(void *data,
  *
  *
  * \code
  * \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);
  *     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;
     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;
         const PHYSFS_uint32 count = info->db.NumFiles;
         PHYSFS_uint32 i;
         PHYSFS_uint32 i;
@@ -285,13 +285,16 @@ static PHYSFS_Io *SZIP_openRead(void *opaque, const char *path)
                         &blockIndex, &outBuffer, &outBufferSize, &offset,
                         &blockIndex, &outBuffer, &outBufferSize, &offset,
                         &outSizeProcessed, alloc, alloc);
                         &outSizeProcessed, alloc, alloc);
     GOTO_IF(rc != SZ_OK, szipErrorCode(rc), SZIP_openRead_failed);
     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->destroy(io);
     io = NULL;
     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);
     alloc->Free(alloc, outBuffer);
     outBuffer = NULL;
     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);
     BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL);
     count = PHYSFS_swapULE32(count);
     count = PHYSFS_swapULE32(count);
 
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
     if (!grpLoadEntries(io, count, unpkarc))
     if (!grpLoadEntries(io, count, unpkarc))

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

@@ -1,9 +1,9 @@
 /*
 /*
  * HOG support routines for PhysicsFS.
  * 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
  *   The file always starts with the 3-byte signature "DHF" (Descent
  *   HOG file). After that the files of a HOG are just attached after
  *   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/)
  * (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.
  * 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__
 #define __PHYSICSFS_INTERNAL__
@@ -34,7 +47,15 @@
 
 
 #if PHYSFS_SUPPORTS_HOG
 #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);
     const PHYSFS_uint64 iolen = io->length(io);
     PHYSFS_uint32 pos = 3;
     PHYSFS_uint32 pos = 3;
@@ -45,11 +66,10 @@ static int hogLoadEntries(PHYSFS_Io *io, void *arc)
         char name[13];
         char name[13];
 
 
         BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 13), 0);
         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. */
         name[12] = '\0';  /* just in case. */
         pos += 13 + 4;
         pos += 13 + 4;
 
 
-        size = PHYSFS_swapULE32(size);
         BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
         BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
         pos += size;
         pos += size;
 
 
@@ -60,24 +80,60 @@ static int hogLoadEntries(PHYSFS_Io *io, void *arc)
     return 1;
     return 1;
 } /* hogLoadEntries */
 } /* 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,
 static void *HOG_openArchive(PHYSFS_Io *io, const char *name,
                              int forWriting, int *claimed)
                              int forWriting, int *claimed)
 {
 {
     PHYSFS_uint8 buf[3];
     PHYSFS_uint8 buf[3];
     void *unpkarc = NULL;
     void *unpkarc = NULL;
+    int hog1 = 0;
 
 
     assert(io != NULL);  /* shouldn't ever happen. */
     assert(io != NULL);  /* shouldn't ever happen. */
     BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
     BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
     BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 3), 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;
     *claimed = 1;
 
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
-    if (!hogLoadEntries(io, unpkarc))
+    if (!(hog1 ? hog1LoadEntries(io, unpkarc) : hog2LoadEntries(io, unpkarc)))
     {
     {
         UNPK_abandonArchive(unpkarc);
         UNPK_abandonArchive(unpkarc);
         return NULL;
         return NULL;
@@ -92,7 +148,7 @@ const PHYSFS_Archiver __PHYSFS_Archiver_HOG =
     CURRENT_PHYSFS_ARCHIVER_API_VERSION,
     CURRENT_PHYSFS_ARCHIVER_API_VERSION,
     {
     {
         "HOG",
         "HOG",
-        "Descent I/II HOG file format",
+        "Descent I/II/III HOG file format",
         "Bradley Bell <[email protected]>",
         "Bradley Bell <[email protected]>",
         "https://icculus.org/physfs/",
         "https://icculus.org/physfs/",
         0,  /* supportsSymlinks */
         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 */
         pos += 2048;  /* each volume descriptor is 2048 bytes */
 
 
         BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &type, 1), 0);
         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? */
         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))
     if (!parseVolumeDescriptor(io, &rootpos, &len, &joliet, claimed))
         return NULL;
         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);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
     if (!iso9660LoadEntries(io, joliet, "", rootpos, rootpos + len, unpkarc))
     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);
     BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL);
     count = PHYSFS_swapULE32(count);
     count = PHYSFS_swapULE32(count);
 
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
     if (!mvlLoadEntries(io, count, unpkarc))
     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);
     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);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
     if (!qpakLoadEntries(io, count, unpkarc))
     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);
         BAIL_IF(backslash != '\\', PHYSFS_ERR_CORRUPT, 0);
 
 
         /* read the rest of the buffer, 63 bytes */
         /* 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 */
         name[63] = '\0'; /* in case the name lacks the null terminator */
 
 
         /* convert backslashes */
         /* convert backslashes */
@@ -94,7 +94,8 @@ static void *SLB_openArchive(PHYSFS_Io *io, const char *name,
     /* seek to the table of contents */
     /* seek to the table of contents */
     BAIL_IF_ERRPASS(!io->seek(io, tocPos), NULL);
     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);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
     if (!slbLoadEntries(io, count, unpkarc))
     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 */
 } /* 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));
     UNPKinfo *info = (UNPKinfo *) allocator.Malloc(sizeof (UNPKinfo));
     BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
     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);
         allocator.Free(info);
         return NULL;
         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);
     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);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
     if (!vdfLoadEntries(io, count, vdfDosTimeToEpoch(timestamp), unpkarc))
     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);
     BAIL_IF_ERRPASS(!io->seek(io, directoryOffset), 0);
 
 
-    unpkarc = UNPK_openArchive(io);
+    unpkarc = UNPK_openArchive(io, 0, 1);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
     BAIL_IF_ERRPASS(!unpkarc, NULL);
 
 
     if (!wadLoadEntries(io, count, unpkarc))
     if (!wadLoadEntries(io, count, unpkarc))

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

@@ -15,6 +15,11 @@
 #include <errno.h>
 #include <errno.h>
 #include <time.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"
 #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))
             if (!__PHYSFS_readAll(io, buf, maxread - 4))
                 return -1;
                 return -1;
-            memcpy(&buf[maxread - 4], &extra, sizeof (extra));
+            memcpy(&buf[maxread - 4], extra, sizeof (extra));
             totalread += maxread - 4;
             totalread += maxread - 4;
         } /* if */
         } /* if */
         else
         else
@@ -578,7 +583,7 @@ static PHYSFS_sint64 zip_find_end_of_central_dir(PHYSFS_Io *io, PHYSFS_sint64 *l
             totalread += maxread;
             totalread += maxread;
         } /* else */
         } /* else */
 
 
-        memcpy(&extra, buf, sizeof (extra));
+        memcpy(extra, buf, sizeof (extra));
 
 
         for (i = maxread - 4; i > 0; i--)
         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_ERRPASS(!readui32(io, &ui32), 0);
     BAIL_IF(ui32 != ZIP_LOCAL_FILE_SIG, PHYSFS_ERR_CORRUPT, 0);
     BAIL_IF(ui32 != ZIP_LOCAL_FILE_SIG, PHYSFS_ERR_CORRUPT, 0);
     BAIL_IF_ERRPASS(!readui16(io, &ui16), 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);  /* general bits. */
     BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
     BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
     BAIL_IF(ui16 != entry->compression_method, PHYSFS_ERR_CORRUPT, 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))
     if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &count))
         goto ZIP_openarchive_failed;
         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;
         goto ZIP_openarchive_failed;
 
 
     root = (ZIPentry *) info->tree.root;
     root = (ZIPentry *) info->tree.root;

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

@@ -38,7 +38,7 @@
 #include <malloc.h>
 #include <malloc.h>
 #endif
 #endif
 
 
-#ifdef PHYSFS_PLATFORM_SOLARIS
+#if defined(PHYSFS_PLATFORM_SOLARIS) || defined(PHYSFS_PLATFORM_LINUX)
 #include <alloca.h>
 #include <alloca.h>
 #endif
 #endif
 
 
@@ -69,7 +69,7 @@ extern "C" {
    All file-private symbols need to be marked "static".
    All file-private symbols need to be marked "static".
    Everything shared between PhysicsFS sources needs to be in this
    Everything shared between PhysicsFS sources needs to be in this
    file between the visibility pragma blocks. */
    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
 #define PHYSFS_HAVE_PRAGMA_VISIBILITY 1
 #endif
 #endif
 
 
@@ -95,6 +95,7 @@ extern const PHYSFS_Archiver __PHYSFS_Archiver_VDF;
 /* a real C99-compliant snprintf() is in Visual Studio 2015,
 /* a real C99-compliant snprintf() is in Visual Studio 2015,
    but just use this everywhere for binary compatibility. */
    but just use this everywhere for binary compatibility. */
 #if defined(_MSC_VER)
 #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_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap);
 int __PHYSFS_msvc_snprintf(char *outBuf, size_t size, const char *format, ...);
 int __PHYSFS_msvc_snprintf(char *outBuf, size_t size, const char *format, ...);
 #define vsnprintf __PHYSFS_msvc_vsnprintf
 #define vsnprintf __PHYSFS_msvc_vsnprintf
@@ -108,14 +109,24 @@ const void *__PHYSFS_winrtCalcPrefDir(void);
 #endif
 #endif
 
 
 /* atomic operations. */
 /* atomic operations. */
+/* increment/decrement operations return the final incremented/decremented value. */
 #if defined(_MSC_VER) && (_MSC_VER >= 1500)
 #if defined(_MSC_VER) && (_MSC_VER >= 1500)
 #include <intrin.h>
 #include <intrin.h>
 __PHYSFS_COMPILE_TIME_ASSERT(LongEqualsInt, sizeof (int) == sizeof (long));
 __PHYSFS_COMPILE_TIME_ASSERT(LongEqualsInt, sizeof (int) == sizeof (long));
 #define __PHYSFS_ATOMIC_INCR(ptrval) _InterlockedIncrement((long*)(ptrval))
 #define __PHYSFS_ATOMIC_INCR(ptrval) _InterlockedIncrement((long*)(ptrval))
 #define __PHYSFS_ATOMIC_DECR(ptrval) _InterlockedDecrement((long*)(ptrval))
 #define __PHYSFS_ATOMIC_DECR(ptrval) _InterlockedDecrement((long*)(ptrval))
 #elif defined(__clang__) || (defined(__GNUC__) && (((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100)) >= 40100))
 #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
 #else
 #define PHYSFS_NEED_ATOMIC_OP_FALLBACK 1
 #define PHYSFS_NEED_ATOMIC_OP_FALLBACK 1
 int __PHYSFS_ATOMIC_INCR(int *ptrval);
 int __PHYSFS_ATOMIC_INCR(int *ptrval);
@@ -213,6 +224,7 @@ extern void SZIP_global_init(void);
 /* The latest supported PHYSFS_Archiver::version value. */
 /* The latest supported PHYSFS_Archiver::version value. */
 #define CURRENT_PHYSFS_ARCHIVER_API_VERSION 0
 #define CURRENT_PHYSFS_ARCHIVER_API_VERSION 0
 
 
+
 /* This byteorder stuff was lifted from SDL. https://www.libsdl.org/ */
 /* This byteorder stuff was lifted from SDL. https://www.libsdl.org/ */
 #define PHYSFS_LIL_ENDIAN  1234
 #define PHYSFS_LIL_ENDIAN  1234
 #define PHYSFS_BIG_ENDIAN  4321
 #define PHYSFS_BIG_ENDIAN  4321
@@ -220,11 +232,26 @@ extern void SZIP_global_init(void);
 #ifdef __linux__
 #ifdef __linux__
 #include <endian.h>
 #include <endian.h>
 #define PHYSFS_BYTEORDER  __BYTE_ORDER
 #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__) || \
 #if defined(__hppa__) || \
     defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
     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__)
     defined(__sparc__)
 #define PHYSFS_BYTEORDER   PHYSFS_BIG_ENDIAN
 #define PHYSFS_BYTEORDER   PHYSFS_BIG_ENDIAN
 #else
 #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).
  * 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. */
 /* 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_abandonArchive(void *opaque);
 void UNPK_closeArchive(void *opaque);
 void UNPK_closeArchive(void *opaque);
-void *UNPK_openArchive(PHYSFS_Io *io);
 void *UNPK_addEntry(void *opaque, char *name, const int isdir,
 void *UNPK_addEntry(void *opaque, char *name, const int isdir,
                     const PHYSFS_sint64 ctime, const PHYSFS_sint64 mtime,
                     const PHYSFS_sint64 ctime, const PHYSFS_sint64 mtime,
                     const PHYSFS_uint64 pos, const PHYSFS_uint64 len);
                     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. */
     __PHYSFS_DirTreeEntry **hash;  /* all entries hashed for fast lookup. */
     size_t hashBuckets;            /* number of buckets in hash.          */
     size_t hashBuckets;            /* number of buckets in hash.          */
     size_t entrylen;    /* size in bytes of entries (including subclass). */
     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;
 } __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_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir);
 void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path);
 void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path);
 PHYSFS_EnumerateCallbackResult __PHYSFS_DirTreeEnumerate(void *opaque,
 PHYSFS_EnumerateCallbackResult __PHYSFS_DirTreeEnumerate(void *opaque,
@@ -715,6 +757,11 @@ int __PHYSFS_platformGrabMutex(void *mutex);
  */
  */
 void __PHYSFS_platformReleaseMutex(void *mutex);
 void __PHYSFS_platformReleaseMutex(void *mutex);
 
 
+
+/* !!! FIXME: move to public API? */
+PHYSFS_uint32 __PHYSFS_utf8codepoint(const char **_str);
+
+
 #if PHYSFS_HAVE_PRAGMA_VISIBILITY
 #if PHYSFS_HAVE_PRAGMA_VISIBILITY
 #pragma GCC visibility pop
 #pragma GCC visibility pop
 #endif
 #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
 #endif
 
 
 #if defined(MY_CPU_AMD64) \
 #if defined(MY_CPU_AMD64) \
+    || defined(_M_ARM64) \
     || defined(_M_IA64) \
     || defined(_M_IA64) \
     || defined(__AARCH64EL__) \
     || defined(__AARCH64EL__) \
     || defined(__AARCH64EB__) \
     || 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))
 #if defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64))
 #define MY_CPU_ARM_LE
 #define MY_CPU_ARM_LE
+#elif defined(_WIN64) && defined(_M_ARM64)
+#define MY_CPU_ARM_LE
 #endif
 #endif
 
 
 #if defined(_WIN32) && defined(_M_IA64)
 #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_alloc_func)(void *opaque, unsigned int items, unsigned int size);
 typedef void (*mz_free_func)(void *opaque, void *address);
 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)
 #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). */
 /* 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
 #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
 /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */
 /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */
 #define MINIZ_LITTLE_ENDIAN 1
 #define MINIZ_LITTLE_ENDIAN 1
 #endif
 #endif
+#endif /**/
 
 
 #if defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__)
 #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) */
 /* 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_MAX(a,b) (((a)>(b))?(a):(b))
 #define MZ_MIN(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_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
 #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
   #define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
   #define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
@@ -166,13 +170,17 @@ struct tinfl_decompressor_tag
     if (temp >= 0) { \
     if (temp >= 0) { \
       code_len = temp >> 9; \
       code_len = temp >> 9; \
       if ((code_len) && (num_bits >= code_len)) \
       if ((code_len) && (num_bits >= code_len)) \
-      break; \
+          break; \
     } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \
     } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \
        code_len = TINFL_FAST_LOOKUP_BITS; \
        code_len = TINFL_FAST_LOOKUP_BITS; \
        do { \
        do { \
           temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
           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);
   } 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 */
 /* 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
       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]; }
         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;
         r->m_table_sizes[2] = 19;
       }
       }
       for ( ; (int)r->m_type >= 0; r->m_type--)
       for ( ; (int)r->m_type >= 0; r->m_type--)
       {
       {
         int tree_next, tree_cur; tinfl_huff_table *pTable;
         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]]++;
         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;
         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)); }
         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
 #ifdef PHYSFS_PLATFORM_APPLE
 
 
 #include <Foundation/Foundation.h>
 #include <Foundation/Foundation.h>
+#include <dlfcn.h>
 
 
 #include "physfs_internal.h"
 #include "physfs_internal.h"
 
 
@@ -99,7 +100,7 @@ static int darwinIsWholeMedia(io_service_t service)
 } /* darwinIsWholeMedia */
 } /* darwinIsWholeMedia */
 
 
 
 
-static int darwinIsMountedDisc(char *bsdName, mach_port_t masterPort)
+static int darwinIsMountedDisc(char *bsdName, mach_port_t mainPort)
 {
 {
     int retval = 0;
     int retval = 0;
     CFMutableDictionaryRef matchingDict;
     CFMutableDictionaryRef matchingDict;
@@ -107,10 +108,10 @@ static int darwinIsMountedDisc(char *bsdName, mach_port_t masterPort)
     io_iterator_t iter;
     io_iterator_t iter;
     io_service_t service;
     io_service_t service;
 
 
-    if ((matchingDict = IOBSDNameMatching(masterPort, 0, bsdName)) == NULL)
+    if ((matchingDict = IOBSDNameMatching(mainPort, 0, bsdName)) == NULL)
         return 0;
         return 0;
 
 
-    rc = IOServiceGetMatchingServices(masterPort, matchingDict, &iter);
+    rc = IOServiceGetMatchingServices(mainPort, matchingDict, &iter);
     if ((rc != KERN_SUCCESS) || (!iter))
     if ((rc != KERN_SUCCESS) || (!iter))
         return 0;
         return 0;
 
 
@@ -158,13 +159,25 @@ static int darwinIsMountedDisc(char *bsdName, mach_port_t masterPort)
 void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
 void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
 {
 {
 #if !defined(PHYSFS_NO_CDROM_SUPPORT)
 #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 char *devPrefix = "/dev/";
     const int prefixLen = strlen(devPrefix);
     const int prefixLen = strlen(devPrefix);
-    mach_port_t masterPort = 0;
+    mach_port_t mainPort = 0;
     struct statfs *mntbufp;
     struct statfs *mntbufp;
     int i, mounts;
     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*/;
         BAIL(PHYSFS_ERR_OS_ERROR, ) /*return void*/;
 
 
     mounts = getmntinfo(&mntbufp, MNT_WAIT);  /* NOT THREAD SAFE! */
     mounts = getmntinfo(&mntbufp, MNT_WAIT);  /* NOT THREAD SAFE! */
@@ -176,7 +189,7 @@ void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
             continue;
             continue;
 
 
         dev += prefixLen;
         dev += prefixLen;
-        if (darwinIsMountedDisc(dev, masterPort))
+        if (darwinIsMountedDisc(dev, mainPort))
             cb(data, mnt);
             cb(data, mnt);
     } /* for */
     } /* for */
 #endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */
 #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). */
         if (ptr != NULL)  /* isolate element to find (fname is the start). */
             *ptr = '\0';
             *ptr = '\0';
 
 
-        rc = DosFindFirst((unsigned char *) spec, &hdir, FILE_DIRECTORY,
+        rc = DosFindFirst(spec, &hdir, FILE_DIRECTORY,
                           &fb, sizeof (fb), &count, FIL_STANDARD);
                           &fb, sizeof (fb), &count, FIL_STANDARD);
         if (rc == NO_ERROR)
         if (rc == NO_ERROR)
         {
         {
@@ -331,7 +331,7 @@ static int isCdRomDrive(ULONG drive)
     ULONG ul1, ul2;
     ULONG ul1, ul2;
     APIRET rc;
     APIRET rc;
     HFILE hfile = NULLHANDLE;
     HFILE hfile = NULLHANDLE;
-    unsigned char drivename[3] = { 0, ':', '\0' };
+    char drivename[3] = { 0, ':', '\0' };
 
 
     drivename[0] = 'A' + drive;
     drivename[0] = 'A' + drive;
 
 
@@ -443,7 +443,7 @@ PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
     __PHYSFS_smallFree(utf8);
     __PHYSFS_smallFree(utf8);
     BAIL_IF_ERRPASS(!cpspec, PHYSFS_ENUM_ERROR);
     BAIL_IF_ERRPASS(!cpspec, PHYSFS_ENUM_ERROR);
 
 
-    rc = DosFindFirst((unsigned char *) cpspec, &hdir,
+    rc = DosFindFirst(cpspec, &hdir,
                       FILE_DIRECTORY | FILE_ARCHIVED |
                       FILE_DIRECTORY | FILE_ARCHIVED |
                       FILE_READONLY | FILE_HIDDEN | FILE_SYSTEM,
                       FILE_READONLY | FILE_HIDDEN | FILE_SYSTEM,
                       &fb, sizeof (fb), &count, FIL_STANDARD);
                       &fb, sizeof (fb), &count, FIL_STANDARD);
@@ -535,7 +535,7 @@ int __PHYSFS_platformMkDir(const char *filename)
     APIRET rc;
     APIRET rc;
     char *cpstr = cvtUtf8ToCodepage(filename);
     char *cpstr = cvtUtf8ToCodepage(filename);
     BAIL_IF_ERRPASS(!cpstr, 0);
     BAIL_IF_ERRPASS(!cpstr, 0);
-    rc = DosCreateDir((unsigned char *) cpstr, NULL);
+    rc = DosCreateDir(cpstr, NULL);
     allocator.Free(cpstr);
     allocator.Free(cpstr);
     BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
     BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
     return 1;
     return 1;

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

@@ -6,8 +6,6 @@
  *  This file written by Ryan C. Gordon.
  *  This file written by Ryan C. Gordon.
  */
  */
 
 
-/* !!! FIXME: check for EINTR? */
-
 #define __PHYSICSFS_INTERNAL__
 #define __PHYSICSFS_INTERNAL__
 #include "physfs_platforms.h"
 #include "physfs_platforms.h"
 
 
@@ -157,19 +155,41 @@ int __PHYSFS_platformMkDir(const char *path)
 } /* __PHYSFS_platformMkDir */
 } /* __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)
 static void *doOpen(const char *filename, int mode)
 {
 {
     const int appending = (mode & O_APPEND);
     const int appending = (mode & O_APPEND);
     int fd;
     int fd;
     int *retval;
     int *retval;
+
     errno = 0;
     errno = 0;
 
 
     /* O_APPEND doesn't actually behave as we'd like. */
     /* O_APPEND doesn't actually behave as we'd like. */
     mode &= ~O_APPEND;
     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);
     BAIL_IF(fd < 0, errcodeFromErrno(), NULL);
 
 
+#if !defined(O_CLOEXEC) && defined(FD_CLOEXEC)
+    set_CLOEXEC(fd);
+#endif
+
     if (appending)
     if (appending)
     {
     {
         if (lseek(fd, 0, SEEK_END) < 0)
         if (lseek(fd, 0, SEEK_END) < 0)
@@ -219,7 +239,9 @@ PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buffer,
     if (!__PHYSFS_ui64FitsAddressSpace(len))
     if (!__PHYSFS_ui64FitsAddressSpace(len))
         BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
         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);
     BAIL_IF(rc == -1, errcodeFromErrno(), -1);
     assert(rc >= 0);
     assert(rc >= 0);
     assert(rc <= len);
     assert(rc <= len);
@@ -236,7 +258,9 @@ PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
     if (!__PHYSFS_ui64FitsAddressSpace(len))
     if (!__PHYSFS_ui64FitsAddressSpace(len))
         BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
         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);
     BAIL_IF(rc == -1, errcodeFromErrno(), rc);
     assert(rc >= 0);
     assert(rc >= 0);
     assert(rc <= len);
     assert(rc <= len);
@@ -275,8 +299,13 @@ PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
 int __PHYSFS_platformFlush(void *opaque)
 int __PHYSFS_platformFlush(void *opaque)
 {
 {
     const int fd = *((int *) 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;
     return 1;
 } /* __PHYSFS_platformFlush */
 } /* __PHYSFS_platformFlush */
 
 
@@ -284,7 +313,10 @@ int __PHYSFS_platformFlush(void *opaque)
 void __PHYSFS_platformClose(void *opaque)
 void __PHYSFS_platformClose(void *opaque)
 {
 {
     const int fd = *((int *) 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);
     allocator.Free(opaque);
 } /* __PHYSFS_platformClose */
 } /* __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)
         if (sysctl(mib, 4, fullpath, &buflen, NULL, 0) != -1)
             retval = __PHYSFS_strdup(fullpath);
             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
     #endif
 
 
     /* If there's a Linux-like /proc filesystem, you can get the full path to
     /* 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");
         retval = readSymLink("/proc/self/exe");
         if (!retval) retval = readSymLink("/proc/curproc/file");
         if (!retval) retval = readSymLink("/proc/curproc/file");
         if (!retval) retval = readSymLink("/proc/curproc/exe");
         if (!retval) retval = readSymLink("/proc/curproc/exe");
+        if (!retval) retval = readSymLink("/proc/self/path/a.out");
         if (retval == NULL)
         if (retval == NULL)
         {
         {
             /* older kernels don't have /proc/self ... try PID version... */
             /* older kernels don't have /proc/self ... try PID version... */
@@ -289,6 +284,15 @@ char *__PHYSFS_platformCalcBaseDir(const char *argv0)
         } /* if */
         } /* if */
     } /* 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. */
     if (retval != NULL)  /* chop off filename. */
     {
     {
         char *ptr = strrchr(retval, '/');
         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)
 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,
     return FindFirstFileExW(path, FindExInfoStandard, d,
                             FindExSearchNameMatch, NULL, 0);
                             FindExSearchNameMatch, NULL, 0);
     #else
     #else
@@ -111,7 +111,7 @@ static inline HANDLE winFindFirstFileW(const WCHAR *path, LPWIN32_FIND_DATAW d)
 
 
 static inline BOOL winInitializeCriticalSection(LPCRITICAL_SECTION lpcs)
 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);
     return InitializeCriticalSectionEx(lpcs, 2000, 0);
     #else
     #else
     InitializeCriticalSection(lpcs);
     InitializeCriticalSection(lpcs);
@@ -123,7 +123,7 @@ static inline HANDLE winCreateFileW(const WCHAR *wfname, const DWORD mode,
                                     const DWORD creation)
                                     const DWORD creation)
 {
 {
     const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE;
     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);
     return CreateFile2(wfname, mode, share, creation, NULL);
     #else
     #else
     return CreateFileW(wfname, mode, share, NULL, creation,
     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,
 static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos,
                               PHYSFS_sint64 *_newpos, const DWORD whence)
                               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 lipos;
     LARGE_INTEGER linewpos;
     LARGE_INTEGER linewpos;
     BOOL rc;
     BOOL rc;
@@ -158,7 +158,7 @@ static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos,
 
 
 static PHYSFS_sint64 winGetFileSize(HANDLE h)
 static PHYSFS_sint64 winGetFileSize(HANDLE h)
 {
 {
-    #ifdef PHYSFS_PLATFORM_WINRT
+    #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0600) // Windows Vista+
     FILE_STANDARD_INFO info;
     FILE_STANDARD_INFO info;
     const BOOL rc = GetFileInformationByHandleEx(h, FileStandardInfo,
     const BOOL rc = GetFileInformationByHandleEx(h, FileStandardInfo,
                                                  &info, sizeof (info));
                                                  &info, sizeof (info));

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

@@ -40,11 +40,11 @@
 #  define PHYSFS_PLATFORM_POSIX 1
 #  define PHYSFS_PLATFORM_POSIX 1
 #elif defined(macintosh)
 #elif defined(macintosh)
 #  error Classic Mac OS support was dropped from PhysicsFS 2.0. Move to OS X.
 #  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)
 #elif defined(__linux)
 #  define PHYSFS_PLATFORM_LINUX 1
 #  define PHYSFS_PLATFORM_LINUX 1
 #  define PHYSFS_PLATFORM_UNIX 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
  * 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
  *  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,
  *  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
  *  whereas utf8valid() will use the value to determine if a string has bad
  *  bits.
  *  bits.
@@ -35,7 +35,7 @@
  */
  */
 #define UNICODE_BOGUS_CHAR_CODEPOINT '?'
 #define UNICODE_BOGUS_CHAR_CODEPOINT '?'
 
 
-static PHYSFS_uint32 utf8codepoint(const char **_str)
+PHYSFS_uint32 __PHYSFS_utf8codepoint(const char **_str)
 {
 {
     const char *str = *_str;
     const char *str = *_str;
     PHYSFS_uint32 retval = 0;
     PHYSFS_uint32 retval = 0;
@@ -188,6 +188,11 @@ static PHYSFS_uint32 utf8codepoint(const char **_str)
     } /* else if */
     } /* else if */
 
 
     return UNICODE_BOGUS_CHAR_VALUE;
     return UNICODE_BOGUS_CHAR_VALUE;
+} /* __PHYSFS_utf8codepoint */
+
+static inline PHYSFS_uint32 utf8codepoint(const char **_str)
+{
+    return __PHYSFS_utf8codepoint(_str);
 } /* utf8codepoint */
 } /* utf8codepoint */
 
 
 static PHYSFS_uint32 utf16codepoint(const PHYSFS_uint16 **_str)
 static PHYSFS_uint32 utf16codepoint(const PHYSFS_uint16 **_str)
@@ -210,7 +215,7 @@ static PHYSFS_uint32 utf16codepoint(const PHYSFS_uint16 **_str)
         else
         else
         {
         {
             src++;  /* eat the other surrogate. */
             src++;  /* eat the other surrogate. */
-            cp = (((cp - 0xD800) << 10) | (pair - 0xDC00));
+            cp = 0x10000 + (((cp - 0xD800) << 10) | (pair - 0xDC00));
         } /* else */
         } /* else */
     } /* else if */
     } /* 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. */
     len -= sizeof (PHYSFS_uint32);   /* save room for null char. */
     while (len >= sizeof (PHYSFS_uint32))
     while (len >= sizeof (PHYSFS_uint32))
     {
     {
-        PHYSFS_uint32 cp = utf8codepoint(&src);
+        PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
         if (cp == 0)
         if (cp == 0)
             break;
             break;
         else if (cp == UNICODE_BOGUS_CHAR_VALUE)
         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. */
     len -= sizeof (PHYSFS_uint16);   /* save room for null char. */
     while (len >= sizeof (PHYSFS_uint16))
     while (len >= sizeof (PHYSFS_uint16))
     {
     {
-        PHYSFS_uint32 cp = utf8codepoint(&src);
+        PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
         if (cp == 0)
         if (cp == 0)
             break;
             break;
         else if (cp == UNICODE_BOGUS_CHAR_VALUE)
         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. */
     len -= sizeof (PHYSFS_uint16);   /* save room for null char. */
     while (len >= sizeof (PHYSFS_uint16))
     while (len >= sizeof (PHYSFS_uint16))
     {
     {
-        PHYSFS_uint32 cp = utf8codepoint(&src);
+        PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
         if (cp == 0)
         if (cp == 0)
             break;
             break;
         else if (cp == UNICODE_BOGUS_CHAR_VALUE)
         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())
 	if (isRecording())
 		stop();
 		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);
 	device = alcCaptureOpenDevice(name.c_str(), sampleRate, format, samples);
+#endif
 	if (device == nullptr)
 	if (device == nullptr)
 		return false;
 		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));
 	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
  * The following implementation is based on the pseudocode provided by multiple
  * authors on wikipedia: https://en.wikipedia.org/wiki/MD5
  * authors on wikipedia: https://en.wikipedia.org/wiki/MD5
@@ -80,25 +87,22 @@ public:
 		uint32 c0 = 0x98badcfe;
 		uint32 c0 = 0x98badcfe;
 		uint32 d0 = 0x10325476;
 		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);
 		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 A = a0;
 			uint32 B = b0;
 			uint32 B = b0;
@@ -201,29 +205,25 @@ public:
 			0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0
 			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);
 		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
 		// Allocate our extended words
 		uint32 words[80];
 		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++)
 			for (int j = 0; j < 16; j++)
 			{
 			{
 				char *c = (char*) &words[j];
 				char *c = (char*) &words[j];
@@ -304,22 +304,18 @@ public:
 		if (!isSupported(function))
 		if (!isSupported(function))
 			throw love::Exception("Hash function not supported by SHA-224/SHA-256 implementation");
 			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);
 		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];
 		uint32 intermediate[8];
 		if (function == FUNCTION_SHA224)
 		if (function == FUNCTION_SHA224)
@@ -330,9 +326,9 @@ public:
 		// Allocate our extended words
 		// Allocate our extended words
 		uint32 words[64];
 		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++)
 			for (int j = 0; j < 16; j++)
 			{
 			{
 				char *c = (char*) &words[j];
 				char *c = (char*) &words[j];
@@ -460,31 +456,26 @@ public:
 		else
 		else
 			memcpy(intermediates, initial512, sizeof(intermediates));
 			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);
 		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
 		// 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
 		// Allocate our extended words
 		uint64 words[80];
 		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)
 			for (int j = 0; j < 16; ++j)
 			{
 			{
 				char *c = (char*) &words[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);
 		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
 #ifndef LOVE_MACOS
 static void normalizedToDPICoords(double *x, double *y)
 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 y = (double) e.motion.y;
 			double xrel = (double) e.motion.xrel;
 			double xrel = (double) e.motion.xrel;
 			double yrel = (double) e.motion.yrel;
 			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(&x, &y);
 			windowToDPICoords(&xrel, &yrel);
 			windowToDPICoords(&xrel, &yrel);
+
 			vargs.emplace_back(x);
 			vargs.emplace_back(x);
 			vargs.emplace_back(y);
 			vargs.emplace_back(y);
 			vargs.emplace_back(xrel);
 			vargs.emplace_back(xrel);
@@ -280,7 +295,10 @@ Message *Event::convert(const SDL_Event &e)
 
 
 			double px = (double) e.button.x;
 			double px = (double) e.button.x;
 			double py = (double) e.button.y;
 			double py = (double) e.button.y;
+
+			clampToWindow(&px, &py);
 			windowToDPICoords(&px, &py);
 			windowToDPICoords(&px, &py);
+
 			vargs.emplace_back(px);
 			vargs.emplace_back(px);
 			vargs.emplace_back(py);
 			vargs.emplace_back(py);
 			vargs.emplace_back((double) button);
 			vargs.emplace_back((double) button);

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

@@ -27,6 +27,7 @@
 
 
 #include "Filesystem.h"
 #include "Filesystem.h"
 #include "File.h"
 #include "File.h"
+#include "PhysfsIo.h"
 
 
 // PhysFS
 // PhysFS
 #include "libraries/physfs/physfs.h"
 #include "libraries/physfs/physfs.h"
@@ -134,6 +135,10 @@ const char *Filesystem::getName() const
 
 
 void Filesystem::init(const char *arg0)
 void Filesystem::init(const char *arg0)
 {
 {
+#ifdef LOVE_ANDROID
+	arg0 = love::android::getArg0();
+#endif
+
 	if (!PHYSFS_init(arg0))
 	if (!PHYSFS_init(arg0))
 		throw love::Exception("Failed to initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
 		throw love::Exception("Failed to initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
 
 
@@ -156,7 +161,7 @@ bool Filesystem::isFused() const
 	return fused;
 	return fused;
 }
 }
 
 
-bool Filesystem::setIdentity(const char *ident, bool appendToPath) 
+bool Filesystem::setIdentity(const char *ident, bool appendToPath)
 {
 {
 	if (!PHYSFS_isInit())
 	if (!PHYSFS_isInit())
 		return false;
 		return false;
@@ -279,7 +284,27 @@ bool Filesystem::setSource(const char *source)
 
 
 	// Add the directory.
 	// Add the directory.
 	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
 	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.
 	// Save the game source.
 	gameSource = new_search_path;
 	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.
 	// Matches SDL_GameControllerAddMappingsFromRW.
 	if (mappingstr.find_last_of(',') != mappingstr.length() - 1)
 	if (mappingstr.find_last_of(',') != mappingstr.length() - 1)
 		mappingstr += ",";
 		mappingstr += ",";
-	mappingstr += "platform:" + std::string(SDL_GetPlatform());
+
+	if (mappingstr.find("platform:") == std::string::npos)
+		mappingstr += "platform:" + std::string(SDL_GetPlatform());
 
 
 	return mappingstr;
 	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)
 		if (endpos == std::string::npos)
 			endpos = mapstr.length() - 1;
 			endpos = mapstr.length() - 1;
 
 
-		mapstr.replace(findpos + 1, endpos - findpos + 1, insertstr);
+		mapstr.replace(findpos + 1, endpos - findpos, insertstr);
 	}
 	}
 	else
 	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.
 	// 1 == added, 0 == updated, -1 == error.
@@ -465,7 +470,9 @@ std::string JoystickModule::getGamepadMappingString(const std::string &guid) con
 	// Matches SDL_GameControllerAddMappingsFromRW.
 	// Matches SDL_GameControllerAddMappingsFromRW.
 	if (mapping.find_last_of(',') != mapping.length() - 1)
 	if (mapping.find_last_of(',') != mapping.length() - 1)
 		mapping += ",";
 		mapping += ",";
-	mapping += "platform:" + std::string(SDL_GetPlatform());
+
+	if (mapping.find("platform:") == std::string::npos)
+		mapping += "platform:" + std::string(SDL_GetPlatform());
 
 
 	return mapping;
 	return mapping;
 }
 }
@@ -489,8 +496,10 @@ std::string JoystickModule::saveGamepadMappings()
 			mapping += ",";
 			mapping += ",";
 
 
 		// Matches SDL_GameControllerAddMappingsFromRW.
 		// 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;
 	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);
 		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
 const char *Mouse::getName() const
 {
 {
 	return "love.mouse.sdl";
 	return "love.mouse.sdl";
@@ -119,24 +126,16 @@ bool Mouse::isCursorSupported() const
 
 
 double Mouse::getX() 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
 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
 void Mouse::getPosition(double &x, double &y) const
@@ -146,6 +145,14 @@ void Mouse::getPosition(double &x, double &y) const
 
 
 	x = (double) mx;
 	x = (double) mx;
 	y = (double) my;
 	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);
 	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);
 		th_decode_ycbcr_out(decoder, bufferinfo);
 		hasFrame = true;
 		hasFrame = true;
 
 
-		ogg_int64_t granulePosition;
+		ogg_int64_t decoderPosition;
 		do
 		do
 		{
 		{
 			if (demuxer.readPacket(packet))
 			if (demuxer.readPacket(packet))
 				return;
 				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;
 		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
 	// 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 getPixelWidth() const = 0;
 	virtual int getPixelHeight() 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
 	// Note: window-space coordinates are not necessarily the same as
 	// density-independent units (which toPixels and fromPixels use.)
 	// density-independent units (which toPixels and fromPixels use.)
 	virtual void windowToPixelCoords(double *x, double *y) const = 0;
 	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;
 	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
 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 getPixelWidth() const override;
 	int getPixelHeight() const override;
 	int getPixelHeight() const override;
 
 
+	void clampPositionInWindow(double *wx, double *wy) const override;
+
 	void windowToPixelCoords(double *x, double *y) const override;
 	void windowToPixelCoords(double *x, double *y) const override;
 	void pixelToWindowCoords(double *x, double *y) const override;
 	void pixelToWindowCoords(double *x, double *y) const override;