mirror of
https://github.com/love2d/love.git
synced 2026-08-16 00:02:12 +02:00
Merge branch 'master' into 12.0-development
This commit is contained in:
+401
-3
@@ -22,13 +22,20 @@
|
||||
|
||||
#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/types.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "physfs.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
@@ -326,6 +333,397 @@ void showRecordingPermissionMissingDialog()
|
||||
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
|
||||
} // love
|
||||
|
||||
|
||||
@@ -80,6 +80,27 @@ void requestRecordingPermission();
|
||||
|
||||
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
|
||||
} // love
|
||||
|
||||
|
||||
+52
-12
@@ -93,11 +93,42 @@ static int w__eq(lua_State *L)
|
||||
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.
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
// 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)
|
||||
@@ -141,8 +181,8 @@ static int w__release(lua_State *L)
|
||||
if (lua_istable(L, -1))
|
||||
{
|
||||
// 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_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);
|
||||
}
|
||||
|
||||
lua_Number objectkey = luax_computeloveobjectkey(L, object);
|
||||
ObjectKey objectkey = luax_computeloveobjectkey(L, object);
|
||||
|
||||
// Get the value of loveobjects[object] on the stack.
|
||||
lua_pushnumber(L, objectkey);
|
||||
luax_pushloveobjectkey(L, objectkey);
|
||||
lua_gettable(L, -2);
|
||||
|
||||
// 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);
|
||||
|
||||
lua_pushnumber(L, objectkey);
|
||||
luax_pushloveobjectkey(L, objectkey);
|
||||
lua_pushvalue(L, -2);
|
||||
|
||||
// loveobjects[object] = Proxy.
|
||||
|
||||
Reference in New Issue
Block a user