From 2213b3bcfd0e9558fb63a21bd059f48cdff144e5 Mon Sep 17 00:00:00 2001 From: mcc Date: Tue, 13 Nov 2018 15:20:04 -0500 Subject: [PATCH] Allow the mountpoint to be set "inside" a zip file, using an --inside parameter. This is used in the oculus mobile driver, allowing replacement of the (dubious) recursive-copy-from-zip code. In order for this to work, the argument parsing must be beefed up a bit and also PhysFS must be updated to the newest master in order to get a new PHYSFS_setRoot. The github submodule source has been changed to one which updates more frequently to get this. --- .gitmodules | 2 +- deps/physfs | 2 +- src/api/filesystem.c | 13 ++-- src/filesystem/filesystem.c | 17 +++-- src/filesystem/filesystem.h | 4 +- src/headset/oculus_mobile.c | 130 +++--------------------------------- src/lib/glfw.h | 1 + src/main.c | 31 ++++++++- 8 files changed, 64 insertions(+), 136 deletions(-) diff --git a/.gitmodules b/.gitmodules index a6d4b4ff..6898800c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,7 +24,7 @@ url = https://github.com/ValveSoftware/openvr [submodule "deps/physfs"] path = deps/physfs - url = https://github.com/criptych/physfs + url = https://github.com/Didstopia/physfs.git [submodule "deps/assimp"] path = deps/assimp url = https://github.com/assimp/assimp diff --git a/deps/physfs b/deps/physfs index 68beaa11..3ae84ee5 160000 --- a/deps/physfs +++ b/deps/physfs @@ -1 +1 @@ -Subproject commit 68beaa110991cb5002c8dc3a3d61718845046e79 +Subproject commit 3ae84ee5d0a0af72a6a808a32b63e1ea0076f2be diff --git a/src/api/filesystem.c b/src/api/filesystem.c index d56d9a08..144bef9d 100644 --- a/src/api/filesystem.c +++ b/src/api/filesystem.c @@ -263,7 +263,8 @@ static int l_lovrFilesystemMount(lua_State* L) { const char* path = luaL_checkstring(L, 1); const char* mountpoint = luaL_optstring(L, 2, NULL); bool append = lua_isnoneornil(L, 3) ? 0 : lua_toboolean(L, 3); - lua_pushboolean(L, !lovrFilesystemMount(path, mountpoint, append)); + const char* mountpointInner = luaL_optstring(L, 4, NULL); + lua_pushboolean(L, !lovrFilesystemMount(path, mountpoint, append, mountpointInner)); return 1; } @@ -362,14 +363,16 @@ int luaopen_lovr_filesystem(lua_State* L) { lua_getglobal(L, "arg"); if (lua_istable(L, -1)) { lua_rawgeti(L, -1, -1); + const char* argExe = lua_tostring(L, -1); lua_rawgeti(L, -2, 0); - const char* argExe = lua_tostring(L, -2); const char* argGame = lua_tostring(L, -1); - lovrFilesystemInit(argExe, argGame); - lua_pop(L, 3); + lua_getfield(L, -3, "inside"); + const char* argGameInner = luaL_optstring(L, -1, NULL); + lovrFilesystemInit(argExe, argGame, argGameInner); + lua_pop(L, 4); } else { lua_pop(L, 1); - lovrFilesystemInit(NULL, NULL); + lovrFilesystemInit(NULL, NULL, NULL); } luax_registerloader(L, moduleLoader, 2); diff --git a/src/filesystem/filesystem.c b/src/filesystem/filesystem.c index 8704404f..80e122eb 100644 --- a/src/filesystem/filesystem.c +++ b/src/filesystem/filesystem.c @@ -31,7 +31,7 @@ const char lovrDirSep = '/'; static FilesystemState state; -void lovrFilesystemInit(const char* argExe, const char* argGame) { +void lovrFilesystemInit(const char* argExe, const char* argGame, const char* argGameInner) { if (state.initialized) return; state.initialized = true; @@ -49,17 +49,17 @@ void lovrFilesystemInit(const char* argExe, const char* argGame) { // Try to mount either an archive fused to the executable or an archive from the command line lovrFilesystemGetExecutablePath(state.source, LOVR_PATH_MAX); - if (lovrFilesystemMount(state.source, NULL, 1)) { + if (lovrFilesystemMount(state.source, NULL, 1, argGameInner)) { // Attempt to load fused. If that fails... state.isFused = false; if (argGame) { strncpy(state.source, argGame, LOVR_PATH_MAX); - if (!lovrFilesystemMount(state.source, NULL, 1)) { + if (!lovrFilesystemMount(state.source, NULL, 1, argGameInner)) { // Attempt to load from arg. If success, init is done return; } } - free(state.source); + free(state.source); // Couldn't load from argProject, so apparently it isn't the source state.source = NULL; } } @@ -201,8 +201,13 @@ bool lovrFilesystemIsFused() { return state.isFused; } -int lovrFilesystemMount(const char* path, const char* mountpoint, bool append) { - return !PHYSFS_mount(path, mountpoint, append); +// Returns zero on success, nonzero on failure +int lovrFilesystemMount(const char* path, const char* mountpoint, bool append, const char* mountpointInner) { + bool success = PHYSFS_mount(path, mountpoint, append); + if (success && mountpointInner) { + success = PHYSFS_setRoot(path, mountpointInner); + } + return !success; } void* lovrFilesystemRead(const char* path, size_t* bytesRead) { diff --git a/src/filesystem/filesystem.h b/src/filesystem/filesystem.h index b9c9e16c..e4cb6735 100644 --- a/src/filesystem/filesystem.h +++ b/src/filesystem/filesystem.h @@ -22,7 +22,7 @@ typedef struct { vec_str_t requirePattern[2]; } FilesystemState; -void lovrFilesystemInit(const char* argExe, const char* argGame); +void lovrFilesystemInit(const char* argExe, const char* argGame, const char* argGameInner); void lovrFilesystemDestroy(); int lovrFilesystemCreateDirectory(const char* path); int lovrFilesystemGetAppdataDirectory(char* dest, unsigned int size); @@ -41,7 +41,7 @@ int lovrFilesystemGetWorkingDirectory(char* dest, unsigned int size); bool lovrFilesystemIsDirectory(const char* path); bool lovrFilesystemIsFile(const char* path); bool lovrFilesystemIsFused(); -int lovrFilesystemMount(const char* path, const char* mountpoint, bool append); +int lovrFilesystemMount(const char* path, const char* mountpoint, bool append, const char *mountpointInner); void* lovrFilesystemRead(const char* path, size_t* bytesRead); int lovrFilesystemRemove(const char* path); int lovrFilesystemSetIdentity(const char* identity); diff --git a/src/headset/oculus_mobile.c b/src/headset/oculus_mobile.c index b1a2b043..c6819aaa 100644 --- a/src/headset/oculus_mobile.c +++ b/src/headset/oculus_mobile.c @@ -330,63 +330,6 @@ int lovr_luaB_print_override (lua_State *L); #define SDS(...) sdscatfmt(sdsempty(), __VA_ARGS__) -// Recursively copy a subdirectory out of PhysFS onto disk -static void physCopyFiles(sds toDir, sds fromDir) { - char **filesOrig = PHYSFS_enumerateFiles(fromDir); - char **files = filesOrig; - - if (!files) { - __android_log_print(ANDROID_LOG_ERROR, "LOVR", "COULD NOT READ DIRECTORY SOMEHOW: [%s]", fromDir); - return; - } - - mkdir(toDir, 0777); - - while (*files) { - sds fromPath = SDS("%S/%s", fromDir, *files); - sds toPath = SDS("%S/%s", toDir, *files); - PHYSFS_Stat stat; - PHYSFS_stat(fromPath, &stat); - - if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) { - __android_log_print(ANDROID_LOG_DEBUG, "LOVR", "DIR: [%s] INTO: [%s]", fromPath, toPath); - physCopyFiles(toPath, fromPath); - } else { - __android_log_print(ANDROID_LOG_DEBUG, "LOVR", "FILE: [%s] INTO: [%s]", fromPath, toPath); - - PHYSFS_File *fromFile = PHYSFS_openRead(fromPath); - - if (!fromFile) { - __android_log_print(ANDROID_LOG_ERROR, "LOVR", "COULD NOT OPEN TO READ: [%s]", fromPath); - - } else { - FILE *toFile = fopen(toPath, "w"); - - if (!toFile) { - __android_log_print(ANDROID_LOG_ERROR, "LOVR", "COULD NOT OPEN TO WRITE: [%s]", toPath); - - } else { -#define CPBUFSIZE (1024*8) - while(1) { - char buffer[CPBUFSIZE]; - int written = PHYSFS_readBytes(fromFile, buffer, CPBUFSIZE); - if (written > 0) - fwrite(buffer, 1, written, toFile); - if (PHYSFS_eof(fromFile)) - break; - } - fclose(toFile); - } - PHYSFS_close(fromFile); - } - } - files++; - sdsfree(fromPath); - sdsfree(toPath); - } - PHYSFS_freeList(filesOrig); -} - static void android_vthrow(lua_State* L, const char* format, ...) { #define MAX_ERROR_LENGTH 1024 char lovrErrorMessage[MAX_ERROR_LENGTH]; @@ -416,64 +359,6 @@ void bridgeLovrInit(BridgeLovrInitData *initData) { mkdir(lovrOculusMobileWritablePath, 0777); } - // This is a bit fancy. We want to run files off disk instead of out of the zip file. - // This is for two reasons: Because PhysFS won't let us mount "within" a zip; - // and because if we run the files out of a temp data directory, we can overwrite them - // with "adb push" to debug. - // As a TODO, when PHYSFS_mountSubdir lands, this path should change to only run in debug mode. - sds programPath = SDS("%s/program", initData->writablePath); - { - // We will store the last apk change time in this "lastprogram" file. - // We will copy all the files out of the zip into the temp dir, but ONLY if they've changed. - sds timePath = SDS("%s/lastprogram.dat", initData->writablePath); - - // When did APK last change? - struct stat apkstat; - int statfail = stat(initData->apkPath, &apkstat); - if (statfail) { - __android_log_print(ANDROID_LOG_ERROR, "LOVR", "CAN'T FIND APK [%s]\n", initData->apkPath); - assert(0); - } - - // When did we last do a file copy? - struct timespec previoussec; - FILE *timeFile = fopen(timePath, "r"); - bool copyFiles = !timeFile; // If no lastprogram.dat, we've never copied - if (timeFile) { - fread(&previoussec.tv_sec, sizeof(previoussec.tv_sec), 1, timeFile); - fread(&previoussec.tv_nsec, sizeof(previoussec.tv_nsec), 1, timeFile); - fclose(timeFile); - - copyFiles = apkstat.st_mtim.tv_sec != previoussec.tv_sec || // If timestamp differs, apk changed - apkstat.st_mtim.tv_nsec != previoussec.tv_nsec; - } - - if (copyFiles) { - __android_log_print(ANDROID_LOG_ERROR, "LOVR", "APK CHANGED [%s] WILL UNPACK\n", initData->apkPath); - - // PhysFS hasn't been inited, so we can temporarily use it as an unzip utility if we deinit afterward - PHYSFS_init("lovr"); - int success = PHYSFS_mount(initData->apkPath, NULL, 1); - if (!success) { - __android_log_print(ANDROID_LOG_ERROR, "LOVR", "FAILED TO MOUNT APK [%s]\n", initData->apkPath); - assert(0); - } else { - sds rootFromDir = sdsnew("/assets"); - physCopyFiles(programPath, rootFromDir); - sdsfree(rootFromDir); - } - PHYSFS_deinit(); - - // Save timestamp in a new lastprogram.dat file - timeFile = fopen(timePath, "w"); - fwrite(&apkstat.st_mtim.tv_sec, sizeof(apkstat.st_mtim.tv_sec), 1, timeFile); - fwrite(&apkstat.st_mtim.tv_nsec, sizeof(apkstat.st_mtim.tv_nsec), 1, timeFile); - fclose(timeFile); - } - - sdsfree(timePath); - } - // Unpack init data bridgeLovrMobileData.displayDimensions = initData->suggestedEyeTexture; bridgeLovrMobileData.updateData.displayTime = initData->zeroDisplayTime; @@ -505,13 +390,20 @@ void bridgeLovrInit(BridgeLovrInitData *initData) { lua_newtable(L); lua_pushstring(L, "lovr"); lua_rawseti(L, -2, -1); - lua_pushstring(L, programPath); + lua_pushstring(L, initData->apkPath); lua_rawseti(L, -2, 0); + + // Mimic the arguments "--inside /assets" as parsed by lovrInit + lua_pushstring(L, "--inside"); + lua_rawseti(L, -2, -2); + lua_pushstring(L, "/assets"); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "inside"); + lua_rawseti(L, -2, -3); + lua_setglobal(L, "arg"); } - sdsfree(programPath); - // Register loaders for internal packages (since dynamic load does not seem to work on Android) luax_preloadmodule(L, "lovr", luaopen_lovr); luax_preloadmodule(L, "lovr.audio", luaopen_lovr_audio); @@ -540,7 +432,7 @@ void bridgeLovrInit(BridgeLovrInitData *initData) { Lcoroutine = lua_newthread(L); // Leave L clear to be used by the draw function coroutineRef = luaL_ref(L, LUA_REGISTRYINDEX); // Hold on to the Lua-side coroutine object so it isn't GC'd - __android_log_print(ANDROID_LOG_DEBUG, "LOVR", "\n BRIDGE INIT COMPLETE top %d\n", (int)lua_gettop(L)); + __android_log_print(ANDROID_LOG_DEBUG, "LOVR", "\n BRIDGE INIT COMPLETE\n"); } void bridgeLovrUpdate(BridgeLovrUpdateData *updateData) { diff --git a/src/lib/glfw.h b/src/lib/glfw.h index 3821d078..290c920f 100644 --- a/src/lib/glfw.h +++ b/src/lib/glfw.h @@ -17,6 +17,7 @@ typedef void GLFWwindow; #define GLFWAPI #define GLFWAPI_IS_FAKE +GLFWAPI int glfwInit(); GLFWAPI void glfwPollEvents(); GLFWAPI void glfwGetCursorPos(GLFWwindow* window, double* xpos, double* ypos); GLFWAPI GLFWwindow* glfwGetCurrentContext(void); diff --git a/src/main.c b/src/main.c index eea722a8..afacec54 100644 --- a/src/main.c +++ b/src/main.c @@ -101,11 +101,17 @@ static void onGlfwError(int code, const char* description) { lovrThrow(description); } -typedef enum { +typedef enum { // What is the argument parser doing? argparse_exe, // Interpreter + argparse_flags, argparse_game // Game path and arguments } ArgParseState; +typedef enum { // In state argparse_flag, what flag is being searched for? + argflag_none, // Not processing a flag + argflag_inside +} ArgFlag; + lua_State* lovrInit(lua_State* L, int argc, char** argv) { glfwSetErrorCallback(onGlfwError); lovrAssert(glfwInit(), "Error initializing GLFW"); @@ -117,24 +123,45 @@ lua_State* lovrInit(lua_State* L, int argc, char** argv) { // The interpreter will always be at arg[-1] and always non-nil. // * The "script" (in the case of Lovr, the "game") is at arg[0]. This may be nil (for a fused exe) // * Arguments intended for the script (the "game") are at positive indexes starting with 1. + // Named arguments recognized by lovr will also be stored in the table at their names. This is not standard. lua_newtable(L); // push dummy "lovr" in case argv is empty lua_pushstring(L, "lovr"); lua_rawseti(L, -2, -1); ArgParseState parseState = argparse_exe; + ArgFlag currentFlag = argflag_none; int lovrArgs = 0, userArgs = 0; for (int i = 0; i < argc; i++) { + // Handle flags first, so we can immediately reinterpret this as the game path if it isn't a flag + if (parseState == argparse_flags) { + // This argument is an argument to a -- flag + if (currentFlag == argflag_inside) { + lua_pushstring(L, argv[i]); + lua_setfield(L, -2, "inside"); + currentFlag = argflag_none; + + // This argument is a -- flag + } else if (!strcmp(argv[1], "--inside") || !strcmp(argv[1], "-i")) { + currentFlag = argflag_inside; + + // No flags, this is the game path + } else { + parseState = argparse_game; + } + } + lua_pushstring(L, argv[i]); lua_rawseti(L, -2, parseState == argparse_game ? (0 + userArgs) : (-1 - lovrArgs)); + // Count flags if (parseState == argparse_game) { userArgs++; } else { lovrArgs++; if (parseState == argparse_exe) { - parseState = argparse_game; + parseState = argparse_flags; } } }