Browse Source

Merge branch 'master' into 12.0-development

Alex Szpakowski 4 years ago
parent
commit
04d59274ef
4 changed files with 523 additions and 34 deletions
  1. 401 3
      src/common/android.cpp
  2. 21 0
      src/common/android.h
  3. 52 12
      src/common/runtime.cpp
  4. 49 19
      src/modules/filesystem/physfs/Filesystem.cpp

+ 401 - 3
src/common/android.cpp

@@ -22,13 +22,20 @@
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
 
 
-#include "SDL.h"
-#include "jni.h"
+#include <cerrno>
+#include <unordered_map>
+
+#include <SDL.h>
+
+#include <jni.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
 
 
 #include <sys/stat.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <unistd.h>
-#include <errno.h>
+
+#include "physfs.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -326,6 +333,397 @@ void showRecordingPermissionMissingDialog()
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(activity);
 }
 }
 
 
+/*
+ * Helper functions to aid new fusing method
+ */
+static AAssetManager *getAssetManager()
+{
+	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
+	jobject self = (jobject) SDL_AndroidGetActivity();
+	jclass activity = env->GetObjectClass(self);
+	jmethodID method = env->GetMethodID(activity, "getAssetManager", "()Landroid/content/res/AssetManager;");
+
+	jobject assetManager = env->CallObjectMethod(self, method);
+	AAssetManager *assetManagerObject = AAssetManager_fromJava(env, assetManager);
+
+	env->DeleteLocalRef(assetManager);
+	env->DeleteLocalRef(activity);
+	env->DeleteLocalRef(self);
+
+	return assetManagerObject;
+}
+
+namespace aasset
+{
+namespace io
+{
+
+struct AssetInfo
+{
+	AAssetManager *assetManager;
+	AAsset *asset;
+	char *filename;
+	size_t size;
+};
+
+static std::unordered_map<std::string, PHYSFS_FileType> fileTree;
+
+// Workaround AAsset can't detect whetever something is a directory or doesn't exist
+static void buildFileLookup(
+	AAssetManager *assetManager,
+	std::unordered_map<std::string, PHYSFS_FileType> &out,
+	const std::string &path = ""
+)
+{
+	if (!path.empty())
+	{
+		AAsset *test = AAssetManager_open(assetManager, path.c_str(), AASSET_MODE_STREAMING);
+		if (test)
+		{
+			AAsset_close(test);
+			out[path] = PHYSFS_FILETYPE_REGULAR;
+			return;
+		}
+
+		out[path] = PHYSFS_FILETYPE_DIRECTORY;
+	}
+
+	AAssetDir *dir = AAssetManager_openDir(assetManager, path.c_str());
+	const char *file;
+
+	while ((file = AAssetDir_getNextFileName(dir)) != nullptr)
+		buildFileLookup(assetManager, out, file);
+
+	AAssetDir_close(dir);
+}
+
+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;
+}
+
+PHYSFS_sint64 write(PHYSFS_Io *io, const void *buf, PHYSFS_uint64 len)
+{
+	LOVE_UNUSED(io);
+	LOVE_UNUSED(buf);
+	LOVE_UNUSED(len);
+
+	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+	return -1;
+}
+
+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;
+
+	PHYSFS_setErrorCode(success ? PHYSFS_ERR_OK : PHYSFS_ERR_OS_ERROR);
+	return success;
+}
+
+PHYSFS_sint64 tell(PHYSFS_Io *io)
+{
+	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
+	off64_t len = AAsset_getLength64(asset);
+	off64_t remain = AAsset_getRemainingLength64(asset);
+
+	return len - remain;
+}
+
+PHYSFS_sint64 length(PHYSFS_Io *io)
+{
+	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
+	return AAsset_getLength64(asset);
+}
+
+// Forward declaration
+PHYSFS_Io *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset);
+
+PHYSFS_Io *duplicate(PHYSFS_Io *io)
+{
+	AssetInfo *assetInfo = (AssetInfo *) io->opaque;
+	AAsset *asset = AAssetManager_open(assetInfo->assetManager, assetInfo->filename, AASSET_MODE_RANDOM);
+
+	if (asset == nullptr)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return nullptr;
+	}
+
+	AAsset_seek64(asset, tell(io), SEEK_SET);
+	return fromAAsset(assetInfo->assetManager, assetInfo->filename, asset);
+}
+
+void destroy(PHYSFS_Io *io)
+{
+	AssetInfo *assetInfo = (AssetInfo *) io->opaque;
+	AAsset_close(assetInfo->asset);
+	delete[] assetInfo->filename;
+	delete assetInfo;
+	delete io;
+}
+
+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);
+
+	// 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;
+
+	return io;
+}
+
+}
+
+void *openArchive(PHYSFS_Io *io, const char *name, int forWrite, int *claimed)
+{
+	if (io->opaque == nullptr || memcmp(io->opaque, "ASET", 4) != 0)
+		return nullptr;
+
+	// It's our archive
+	*claimed = 1;
+	AAssetManager *assetManager = getAssetManager();
+
+	if (io::fileTree.empty())
+		io::buildFileLookup(assetManager, io::fileTree);
+
+	return assetManager;
+}
+
+PHYSFS_EnumerateCallbackResult enumerate(
+	void *opaque,
+	const char *dirname,
+	PHYSFS_EnumerateCallback cb,
+	const char *origdir,
+	void *callbackdata
+)
+{
+	const char *path = dirname;
+	if (path == nullptr || (path[0] == '/' && path[1] == 0))
+		path = "";
+
+	AAssetManager *assetManager = (AAssetManager *) opaque;
+	AAssetDir *dir = AAssetManager_openDir(assetManager, path);
+
+	if (dir == nullptr)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+		return PHYSFS_ENUM_ERROR;
+	}
+
+	PHYSFS_EnumerateCallbackResult ret = PHYSFS_ENUM_OK;
+
+	while (ret == PHYSFS_ENUM_OK)
+	{
+		const char *name = AAssetDir_getNextFileName(dir);
+
+		// No more files?
+		if (name == nullptr)
+			break;
+
+		ret = cb(callbackdata, origdir, name);
+	}
+
+	AAssetDir_close(dir);
+	return ret;
+}
+
+PHYSFS_Io *openRead(void *opaque, const char *name)
+{
+	AAssetManager *assetManager = (AAssetManager *) opaque;
+	AAsset *file = AAssetManager_open(assetManager, name, AASSET_MODE_UNKNOWN);
+
+	if (file == nullptr)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+		return nullptr;
+	}
+
+	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+	return io::fromAAsset(assetManager, name, file);
+}
+
+PHYSFS_Io *openWriteAppend(void *opaque, const char *name)
+{
+	LOVE_UNUSED(opaque);
+	LOVE_UNUSED(name);
+
+	// AAsset doesn't support modification
+	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+	return nullptr;
+}
+
+int removeMkdir(void *opaque, const char *name)
+{
+	LOVE_UNUSED(opaque);
+	LOVE_UNUSED(name);
+
+	// AAsset doesn't support modification
+	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+	return 0;
+}
+
+int stat(void *opaque, const char *name, PHYSFS_Stat *out)
+{
+	LOVE_UNUSED(opaque);
+
+	auto result = io::fileTree.find(name);
+
+	if (result != io::fileTree.end())
+	{
+		out->filetype = result->second;
+		out->modtime = -1;
+		out->createtime = -1;
+		out->accesstime = -1;
+		out->readonly = 1;
+
+		PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+		return 1;
+	}
+	else
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+		return 0;
+	}
+}
+
+void closeArchive(void *opaque)
+{
+	// Do nothing
+	LOVE_UNUSED(opaque);
+	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+}
+
+static PHYSFS_Archiver g_AAssetArchiver = {
+	0,
+	{
+		"AASSET",
+		"Android AAsset Wrapper",
+		"LOVE Development Team",
+		"https://developer.android.com/ndk/reference/group/asset",
+		0
+	},
+	openArchive,
+	enumerate,
+	openRead,
+	openWriteAppend,
+	openWriteAppend,
+	removeMkdir,
+	removeMkdir,
+	stat,
+	closeArchive
+};
+
+static PHYSFS_sint64 dummyReturn0(PHYSFS_Io *io)
+{
+	LOVE_UNUSED(io);
+	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+	return 0;
+}
+
+static PHYSFS_Io *getDummyIO(PHYSFS_Io *io);
+
+static char dummyOpaque[] = "ASET";
+static PHYSFS_Io dummyIo = {
+	0,
+	dummyOpaque,
+	nullptr,
+	nullptr,
+	[](PHYSFS_Io *io, PHYSFS_uint64 offset) -> int
+	{
+		PHYSFS_setErrorCode(offset == 0 ? PHYSFS_ERR_OK : PHYSFS_ERR_PAST_EOF);
+		return offset == 0;
+	},
+	dummyReturn0,
+	dummyReturn0,
+	getDummyIO,
+	nullptr,
+	[](PHYSFS_Io *io) { LOVE_UNUSED(io); }
+};
+
+static PHYSFS_Io *getDummyIO(PHYSFS_Io *io)
+{
+	return &dummyIo;
+}
+
+}
+
+static bool isVirtualArchiveInitialized = false;
+
+bool initializeVirtualArchive()
+{
+	if (isVirtualArchiveInitialized)
+		return true;
+
+	if (!PHYSFS_registerArchiver(&aasset::g_AAssetArchiver))
+		return false;
+	if (!PHYSFS_mountIo(&aasset::dummyIo, "ASET.AASSET", nullptr, 0))
+	{
+		PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
+		return false;
+	}
+
+	isVirtualArchiveInitialized = true;
+	return true;
+}
+
+void deinitializeVirtualArchive()
+{
+	if (isVirtualArchiveInitialized)
+	{
+		PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
+		isVirtualArchiveInitialized = false;
+	}
+}
+
+bool checkFusedGame(void **physfsIO_Out)
+{
+	// TODO: Reorder the loading in 12.0
+	PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out;
+	AAssetManager *assetManager = getAssetManager();
+
+	// Prefer game.love inside assets/ folder
+	AAsset *asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
+	if (asset)
+	{
+		io = aasset::io::fromAAsset(assetManager, "game.love", asset);
+		return true;
+	}
+
+	// If there's no game.love inside assets/ try main.lua
+	asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
+
+	if (asset)
+	{
+		AAsset_close(asset);
+		io = nullptr;
+		return true;
+	}
+
+	// Not found
+	return false;
+}
+
 } // android
 } // android
 } // love
 } // love
 
 

+ 21 - 0
src/common/android.h

@@ -80,6 +80,27 @@ void requestRecordingPermission();
 
 
 void showRecordingPermissionMissingDialog();
 void showRecordingPermissionMissingDialog();
 
 
+/**
+ * Initialize Android AAsset virtual archive.
+ * @return true if successful.
+ */
+bool initializeVirtualArchive();
+
+/**
+ * Deinitialize Android AAsset virtual archive.
+ * @return true if successful.
+ */
+void deinitializeVirtualArchive();
+
+/**
+ * Retrieve the fused game inside the APK
+ * @param physfsIO_Out Pointer to PHYSFS_Io* struct
+ * @return true if there's game inside the APK. If physfsIO_Out is not null, then it contains
+ * the game.love which needs to be mounted to root. false if it's not fused, in which case
+ * physfsIO_Out is undefined.
+ */
+bool checkFusedGame(void **physfsIO_Out);
+
 } // android
 } // android
 } // love
 } // love
 
 

+ 52 - 12
src/common/runtime.cpp

@@ -93,11 +93,42 @@ static int w__eq(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+typedef uint64 ObjectKey;
+
+static bool luax_isfulllightuserdatasupported(lua_State *L)
+{
+	// LuaJIT prior to commit e9af1abec542e6f9851ff2368e7f196b6382a44c doesn't
+	// support lightuserdata > 48 bits. This is not a problem with Android,
+	// Windows, macOS, and iOS as they'll use updated LuaJIT or won't use
+	// pointers > 48 bits, but this is not the case for Linux. So check for
+	// this capability first!
+	static bool checked = false;
+	static bool supported = false;
+
+	if (!checked)
+	{
+		lua_pushcclosure(L, [](lua_State *L) -> int
+		{
+			// Try to push pointer with all bits set.
+			lua_pushlightuserdata(L, (void *) (~((size_t) 0)));
+			return 1;
+		}, 0);
+
+		supported = lua_pcall(L, 0, 1, 0) == 0;
+		checked = true;
+
+		lua_pop(L, 1);
+	}
+
+	return supported;
+}
+
 // For use with the love object pointer -> Proxy pointer registry.
 // For use with the love object pointer -> Proxy pointer registry.
 // Using the pointer directly via lightuserdata would be ideal, but LuaJIT
 // Using the pointer directly via lightuserdata would be ideal, but LuaJIT
-// cannot use lightuserdata with more than 47 bits whereas some newer arm64
-// architectures allow pointers which use more than that.
-static lua_Number luax_computeloveobjectkey(lua_State *L, love::Object *object)
+// (before a commit to 2.1 in 2020) cannot use lightuserdata with more than 47
+// bits whereas some newer arm64 architectures allow pointers which use more
+// than that.
+static ObjectKey luax_computeloveobjectkey(lua_State *L, love::Object *object)
 {
 {
 	// love objects should be allocated on the heap, and thus are subject
 	// love objects should be allocated on the heap, and thus are subject
 	// to the alignment rules of operator new / malloc. Lua numbers (doubles)
 	// to the alignment rules of operator new / malloc. Lua numbers (doubles)
@@ -118,11 +149,20 @@ static lua_Number luax_computeloveobjectkey(lua_State *L, love::Object *object)
 
 
 	key >>= shift;
 	key >>= shift;
 
 
-	// Make sure our key isn't larger than 2^53.
-	if (key > 0x20000000000000ULL)
-		luaL_error(L, "Cannot push love object to Lua: pointer value %p is too large", object);
+	return (ObjectKey) key;
+}
 
 
-	return (lua_Number) key;
+static void luax_pushloveobjectkey(lua_State *L, ObjectKey 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 (luax_isfulllightuserdatasupported(L))
+		lua_pushlightuserdata(L, (void *) key);
+	else if (key > 0x20000000000000ULL) // 2^53
+		luaL_error(L, "Cannot push love object to Lua: pointer value %p is too large", key);
+	else
+		lua_pushnumber(L, (lua_Number) key);
 }
 }
 
 
 static int w__release(lua_State *L)
 static int w__release(lua_State *L)
@@ -141,8 +181,8 @@ static int w__release(lua_State *L)
 		if (lua_istable(L, -1))
 		if (lua_istable(L, -1))
 		{
 		{
 			// loveobjects[object] = nil
 			// loveobjects[object] = nil
-			lua_Number objectkey = luax_computeloveobjectkey(L, object);
-			lua_pushnumber(L, objectkey);
+			ObjectKey objectkey = luax_computeloveobjectkey(L, object);
+			luax_pushloveobjectkey(L, objectkey);
 			lua_pushnil(L);
 			lua_pushnil(L);
 			lua_settable(L, -3);
 			lua_settable(L, -3);
 		}
 		}
@@ -622,10 +662,10 @@ void luax_pushtype(lua_State *L, love::Type &type, love::Object *object)
 		return luax_rawnewtype(L, type, object);
 		return luax_rawnewtype(L, type, object);
 	}
 	}
 
 
-	lua_Number objectkey = luax_computeloveobjectkey(L, object);
+	ObjectKey objectkey = luax_computeloveobjectkey(L, object);
 
 
 	// Get the value of loveobjects[object] on the stack.
 	// Get the value of loveobjects[object] on the stack.
-	lua_pushnumber(L, objectkey);
+	luax_pushloveobjectkey(L, objectkey);
 	lua_gettable(L, -2);
 	lua_gettable(L, -2);
 
 
 	// If the Proxy userdata isn't in the instantiated types table yet, add it.
 	// If the Proxy userdata isn't in the instantiated types table yet, add it.
@@ -635,7 +675,7 @@ void luax_pushtype(lua_State *L, love::Type &type, love::Object *object)
 
 
 		luax_rawnewtype(L, type, object);
 		luax_rawnewtype(L, type, object);
 
 
-		lua_pushnumber(L, objectkey);
+		luax_pushloveobjectkey(L, objectkey);
 		lua_pushvalue(L, -2);
 		lua_pushvalue(L, -2);
 
 
 		// loveobjects[object] = Proxy.
 		// loveobjects[object] = Proxy.

+ 49 - 19
src/modules/filesystem/physfs/Filesystem.cpp

@@ -118,6 +118,10 @@ Filesystem::Filesystem()
 
 
 Filesystem::~Filesystem()
 Filesystem::~Filesystem()
 {
 {
+#ifdef LOVE_ANDROID
+	love::android::deinitializeVirtualArchive();
+#endif
+
 	if (PHYSFS_isInit())
 	if (PHYSFS_isInit())
 		PHYSFS_deinit();
 		PHYSFS_deinit();
 }
 }
@@ -232,30 +236,56 @@ bool Filesystem::setSource(const char *source)
 	if (!love::android::createStorageDirectories())
 	if (!love::android::createStorageDirectories())
 		SDL_Log("Error creating storage directories!");
 		SDL_Log("Error creating storage directories!");
 
 
-	new_search_path = love::android::getSelectedGameFile();
+	new_search_path = "";
 
 
-	// try mounting first, if that fails, load to memory and mount
-	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
+	PHYSFS_Io *gameLoveIO;
+	bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO);
+
+	if (hasFusedGame)
 	{
 	{
-		// PHYSFS cannot yet mount a zip file inside an .apk
-		SDL_Log("Mounting %s did not work. Loading to memory.",
-				new_search_path.c_str());
-		char* game_archive_ptr = NULL;
-		size_t game_archive_size = 0;
-		if (!love::android::loadGameArchiveToMemory(
-					new_search_path.c_str(), &game_archive_ptr,
-					&game_archive_size))
+		if (gameLoveIO)
 		{
 		{
-			SDL_Log("Failure memory loading archive %s", new_search_path.c_str());
-			return false;
+			// Actually we should just be able to mount gameLoveIO, but that's experimental.
+			gameLoveIO->destroy(gameLoveIO);
+			goto oldschool;
+		}
+		else
+		{
+			if (!love::android::initializeVirtualArchive())
+			{
+				SDL_Log("Unable to mount AAsset: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+				return false;
+			}
 		}
 		}
-		if (!PHYSFS_mountMemory(
-			    game_archive_ptr, game_archive_size,
-			    love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
+	}
+	else
+	{
+	oldschool:
+		new_search_path = love::android::getSelectedGameFile();
+
+		// try mounting first, if that fails, load to memory and mount
+		if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
 		{
 		{
-			SDL_Log("Failure mounting in-memory archive.");
-			love::android::freeGameArchiveMemory(game_archive_ptr);
-			return false;
+			// PHYSFS cannot yet mount a zip file inside an .apk
+			SDL_Log("Mounting %s did not work. Loading to memory.",
+					new_search_path.c_str());
+			char* game_archive_ptr = NULL;
+			size_t game_archive_size = 0;
+			if (!love::android::loadGameArchiveToMemory(
+						new_search_path.c_str(), &game_archive_ptr,
+						&game_archive_size))
+			{
+				SDL_Log("Failure memory loading archive %s", new_search_path.c_str());
+				return false;
+			}
+			if (!PHYSFS_mountMemory(
+					game_archive_ptr, game_archive_size,
+					love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
+			{
+				SDL_Log("Failure mounting in-memory archive.");
+				love::android::freeGameArchiveMemory(game_archive_ptr);
+				return false;
+			}
 		}
 		}
 	}
 	}
 #else
 #else