diff --git a/src/common/android.cpp b/src/common/android.cpp index ffa0a9ae5..08fd2338c 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -19,6 +19,7 @@ **/ #include "android.h" +#include "Object.h" #ifdef LOVE_ANDROID @@ -122,34 +123,6 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right) return hasSafeArea; } -const char *getSelectedGameFile() -{ - static const char *path = NULL; - - if (path) - { - delete path; - path = NULL; - } - - JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jclass activity = env->FindClass("org/love2d/android/GameActivity"); - - jmethodID getGamePath = env->GetStaticMethodID(activity, "getGamePath", "()Ljava/lang/String;"); - jstring gamePath = (jstring) env->CallStaticObjectMethod(activity, getGamePath); - const char *utf = env->GetStringUTFChars(gamePath, 0); - if (utf) - { - path = SDL_strdup(utf); - env->ReleaseStringUTFChars(gamePath, utf); - } - - env->DeleteLocalRef(gamePath); - env->DeleteLocalRef(activity); - - return path; -} - bool openURL(const std::string &url) { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); @@ -192,37 +165,6 @@ void freeGameArchiveMemory(void *ptr) delete[] game_love_data; } -bool loadGameArchiveToMemory(const char* filename, char **ptr, size_t *size) -{ - SDL_RWops *asset_game_file = SDL_RWFromFile(filename, "rb"); - if (!asset_game_file) { - SDL_Log("Could not find %s", filename); - return false; - } - - Sint64 file_size = asset_game_file->size(asset_game_file); - if (file_size <= 0) { - SDL_Log("Could not load game from %s. File has invalid file size: %d.", filename, (int) file_size); - return false; - } - - *ptr = new char[file_size]; - if (!*ptr) { - SDL_Log("Could not allocate memory for in-memory game archive"); - return false; - } - - size_t bytes_copied = asset_game_file->read(asset_game_file, (void*) *ptr, sizeof(char), (size_t) file_size); - if (bytes_copied != file_size) { - SDL_Log("Incomplete copy of in-memory game archive!"); - delete[] *ptr; - return false; - } - - *size = (size_t) file_size; - return true; -} - bool directoryExists(const char *path) { struct stat s; @@ -766,7 +708,7 @@ bool checkFusedGame(void **physfsIO_Out) io = nullptr; return true; } - + // If there's no main.lua inside assets/ try game.love asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM); if (asset) @@ -821,6 +763,124 @@ const char *getCRequirePath() return path; } +int getFDFromLoveProtocol(const char *path) +{ + constexpr const char PROTOCOL[] = "love2d://fd/"; + constexpr size_t PROTOCOL_LEN = sizeof(PROTOCOL) - 1; + + if (*path == '/') + path++; + + if (memcmp(path, PROTOCOL, PROTOCOL_LEN) == 0) + { + try + { + return std::stoi(path + PROTOCOL_LEN, nullptr, 10); + } + catch (std::logic_error &) + { } + } + + return -1; +} + +class FileDescriptorTracker: public love::Object +{ +public: + FileDescriptorTracker(int fd): Object(), fd(fd) {} + ~FileDescriptorTracker() { close(fd); } + int getFd() { return fd; } +private: + int fd; +}; + +struct FileDescriptorIO +{ + FileDescriptorTracker *fd; + off64_t size; + off64_t offset; +}; + +void *getIOFromFD(int fd) +{ + if (fd == -1) + return nullptr; + + // Create file descriptor IO structure + FileDescriptorIO *fdio = new FileDescriptorIO(); + fdio->size = lseek64(fd, 0, SEEK_END); + fdio->offset = 0; + lseek64(fd, 0, SEEK_SET); + + if (fdio->size == -1) + { + // Cannot get size + delete fdio; + return nullptr; + } + + fdio->fd = new FileDescriptorTracker(fd); + + PHYSFS_Io *io = new PHYSFS_Io(); + io->version = 0; + io->opaque = fdio; + io->read = [](PHYSFS_Io *io, void *buf, PHYSFS_uint64 size) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + ssize_t ret = pread64(fdio->fd->getFd(), buf, (size_t) size, fdio->offset); + + if (ret == -1) + PHYSFS_setErrorCode(PHYSFS_ERR_OTHER_ERROR); + else + fdio->offset = std::min(fdio->offset + (off64_t) ret, fdio->size); + + return (PHYSFS_sint64) ret; + }; + io->write = nullptr; + io->seek = [](PHYSFS_Io *io, PHYSFS_uint64 offset) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + fdio->offset = std::min(std::max((off64_t) offset, 0), fdio->size); + // Always success + return 1; + }; + io->tell = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + return (PHYSFS_sint64) fdio->offset; + }; + io->length = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + return (PHYSFS_sint64) fdio->size; + }; + io->duplicate = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + FileDescriptorIO *fdio2 = new FileDescriptorIO(); + PHYSFS_Io *io2 = new PHYSFS_Io(); + + fdio->fd->retain(); + + // Copy data + *fdio2 = *fdio; + *io2 = *io; + io2->opaque = fdio2; + + return io2; + }; + io->flush = nullptr; + io->destroy = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + fdio->fd->release(); + delete fdio; + delete io; + }; + + return io; +} + } // android } // love diff --git a/src/common/android.h b/src/common/android.h index 543a94a9e..25eb1621e 100644 --- a/src/common/android.h +++ b/src/common/android.h @@ -50,11 +50,6 @@ double getScreenScale(); **/ bool getSafeArea(int &top, int &left, int &bottom, int &right); -/** - * Gets the selected love file in the device filesystem. - **/ -const char *getSelectedGameFile(); - bool openURL(const std::string &url); void vibrate(double seconds); @@ -64,8 +59,6 @@ void vibrate(double seconds); */ void freeGameArchiveMemory(void *ptr); -bool loadGameArchiveToMemory(const char *filename, char **ptr, size_t *size); - bool directoryExists(const char *path); bool mkdir(const char *path); @@ -103,6 +96,20 @@ bool checkFusedGame(void **physfsIO_Out); const char *getCRequirePath(); +/** + * Attempt to parse "(/)love2d://fd/" from path. + * @param path Potentially special path. + * @return File descriptor passed if successful, -1 if path is not valid. + */ +int getFDFromLoveProtocol(const char *path); + +/** + * Create PHYSFS_Io from file descriptor. + * @param fd File descriptor + * @return PHYSFS_Io casted to void*. + */ +void *getIOFromFD(int fd); + } // android } // love diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index 27f58e826..a548f2f49 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -236,8 +236,6 @@ bool Filesystem::setSource(const char *source) if (!love::android::createStorageDirectories()) SDL_Log("Error creating storage directories!"); - new_search_path = ""; - PHYSFS_Io *gameLoveIO; bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO); bool isAAssetMounted = false; @@ -263,38 +261,24 @@ bool Filesystem::setSource(const char *source) if (!isAAssetMounted) { - 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)) + // Is this love2d://fd/ URIs? + int fd = love::android::getFDFromLoveProtocol(new_search_path.c_str()); + if (fd != -1) { - // 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)) + PHYSFS_Io *io = (PHYSFS_Io *) love::android::getIOFromFD(fd); + + if (PHYSFS_mountIo(io, "LOVE.FD", nullptr, 0)) { - 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; + gameSource = new_search_path; + return true; } } } -#else +#endif + // Add the directory. if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) return false; -#endif // Save the game source. gameSource = new_search_path;