Merge pull request #71 from mcclure/args-and-physfs

"Fix" arg standard and allow mounting inside a zip file
This commit is contained in:
Bjorn Swenson 2018-11-13 20:04:39 -08:00 committed by GitHub
commit 5ec3f7ccc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 151 deletions

2
.gitmodules vendored
View File

@ -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

View File

@ -574,7 +574,7 @@ elseif(APPLE)
elseif(EMSCRIPTEN)
target_sources(lovr PRIVATE src/platform/web.c)
elseif(ANDROID)
target_link_libraries(lovr log EGL)
target_link_libraries(lovr log EGL GLESv3)
target_sourceS(lovr PRIVATE src/platform/linux.c src/platform/android.c src/platform/print_override.c)
elseif(UNIX)
target_sourceS(lovr PRIVATE src/platform/linux.c)

2
deps/physfs vendored

@ -1 +1 @@
Subproject commit 68beaa110991cb5002c8dc3a3d61718845046e79
Subproject commit 3ae84ee5d0a0af72a6a808a32b63e1ea0076f2be

View File

@ -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* root = luaL_optstring(L, 4, NULL);
lua_pushboolean(L, !lovrFilesystemMount(path, mountpoint, append, root));
return 1;
}
@ -361,15 +362,17 @@ int luaopen_lovr_filesystem(lua_State* L) {
lua_getglobal(L, "arg");
if (lua_istable(L, -1)) {
lua_rawgeti(L, -1, -2);
lua_rawgeti(L, -2, 1);
const char* arg0 = lua_tostring(L, -2);
const char* arg1 = lua_tostring(L, -1);
lovrFilesystemInit(arg0, arg1);
lua_pop(L, 3);
lua_getfield(L, -1, "exe");
const char* argExe = lua_tostring(L, -1);
lua_rawgeti(L, -2, 0);
const char* argGame = lua_tostring(L, -1);
lua_getfield(L, -3, "root");
const char* argRoot = luaL_optstring(L, -1, NULL);
lovrFilesystemInit(argExe, argGame, argRoot);
lua_pop(L, 4);
} else {
lua_pop(L, 1);
lovrFilesystemInit(NULL, NULL);
lovrFilesystemInit(NULL, NULL, NULL);
}
luax_registerloader(L, moduleLoader, 2);

View File

@ -31,11 +31,11 @@ const char lovrDirSep = '/';
static FilesystemState state;
void lovrFilesystemInit(const char* arg0, const char* arg1) {
void lovrFilesystemInit(const char* argExe, const char* argGame, const char* argRoot) {
if (state.initialized) return;
state.initialized = true;
if (!PHYSFS_init(arg0)) {
if (!PHYSFS_init(argExe)) {
lovrThrow("Could not initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
@ -49,17 +49,17 @@ void lovrFilesystemInit(const char* arg0, const char* arg1) {
// 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, argRoot)) { // Attempt to load fused. If that fails...
state.isFused = false;
if (arg1) {
strncpy(state.source, arg1, LOVR_PATH_MAX);
if (!lovrFilesystemMount(state.source, NULL, 1)) {
if (argGame) {
strncpy(state.source, argGame, LOVR_PATH_MAX);
if (!lovrFilesystemMount(state.source, NULL, 1, argRoot)) { // 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* root) {
bool success = PHYSFS_mount(path, mountpoint, append);
if (success && root) {
success = PHYSFS_setRoot(path, root);
}
return !success;
}
void* lovrFilesystemRead(const char* path, size_t* bytesRead) {

View File

@ -22,7 +22,7 @@ typedef struct {
vec_str_t requirePattern[2];
} FilesystemState;
void lovrFilesystemInit(const char* arg0, const char* arg1);
void lovrFilesystemInit(const char* argExe, const char* argGame, const char* argRoot);
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 *root);
void* lovrFilesystemRead(const char* path, size_t* bytesRead);
int lovrFilesystemRemove(const char* path);
int lovrFilesystemSetIdentity(const char* identity);

View File

@ -313,63 +313,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, va_list args) {
#define MAX_ERROR_LENGTH 1024
char lovrErrorMessage[MAX_ERROR_LENGTH];
@ -407,64 +350,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;
@ -493,23 +378,28 @@ void bridgeLovrInit(BridgeLovrInitData *initData) {
glfwSetTime(0);
// Set "arg" global
// Set "arg" global (see main.c)
{
const char *argv[] = {"lovr", programPath};
int argc = 2;
lua_newtable(L);
lua_pushstring(L, "lovr");
lua_pushvalue(L, -1); // Double at named key
lua_setfield(L, -3, "exe");
lua_rawseti(L, -2, -3);
// Mimic the arguments "--root /assets" as parsed by lovrInit
lua_pushstring(L, "--root");
lua_rawseti(L, -2, -2);
lua_pushstring(L, "/assets");
lua_pushvalue(L, -1); // Double at named key
lua_setfield(L, -3, "root");
lua_rawseti(L, -2, -1);
for (int i = 0; i < argc; i++) {
lua_pushstring(L, argv[i]);
lua_rawseti(L, -2, i == 0 ? -2 : i);
}
lua_pushstring(L, initData->apkPath);
lua_rawseti(L, -2, 0);
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);
@ -539,7 +429,7 @@ void bridgeLovrInit(BridgeLovrInitData *initData) {
lua_atpanic(Lcoroutine, luax_custom_atpanic);
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) {

View File

@ -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);

View File

@ -112,19 +112,68 @@ static void onGlfwError(int code, const char* description) {
lovrThrow(description);
}
typedef enum { // What flag is being searched for?
ARGFLAG_NONE, // Not processing a flag
ARGFLAG_ROOT
} ArgFlag;
lua_State* lovrInit(lua_State* L, int argc, char** argv) {
glfwSetErrorCallback(onGlfwError);
lovrAssert(glfwInit(), "Error initializing GLFW");
glfwSetTime(0);
// arg
// arg table
// Args follow the lua standard https://en.wikibooks.org/wiki/Lua_Programming/command_line_parameter
// In this standard, the contents of argv are put in a global table named "arg", but with indices offset
// such that the "script" (in lovr, the game path) is at index 0. So all arguments (if any) intended for
// the script/game are at successive indices starting with 1, and the exe and its arguments are in normal
// order but stored in negative indices.
//
// Lovr can be run in ways normal lua can't. It can be run with an empty argv, or with no script (if fused).
// So in the case of lovr:
// * The script path will always be at index 0, but it can be nil.
// * For a fused executable, whatever is given in argv as script name is still at 0, but will be ignored.
// * The exe (argv[0]) will always be the lowest-integer index. If it's present, it will always be -1 or less.
// * Any named arguments parsed by Lovr will be stored in the arg table at their named keys.
// * An exe name will always be stored in the arg table at string key "exe", even if none was supplied in argv.
lua_newtable(L);
// push dummy "lovr" in case argv is empty
lua_pushstring(L, "lovr");
lua_rawseti(L, -2, -1);
lua_setfield(L, -2, "exe");
bool exeFound = false;
ArgFlag currentFlag = ARGFLAG_NONE;
int lovrArgs = 0; // Arg count up to but not including the game path
// One pass to parse flags
for (int i = 0; i < argc; i++) {
if (lovrArgs > 0) {
// This argument is an argument to a -- flag
if (currentFlag == ARGFLAG_ROOT) {
lua_pushstring(L, argv[i]);
lua_setfield(L, -2, "root");
currentFlag = ARGFLAG_NONE;
// This argument is a -- flag
} else if (!strcmp(argv[1], "--root") || !strcmp(argv[1], "-r")) {
currentFlag = ARGFLAG_ROOT;
// This is the game path
} else {
break;
}
} else { // Found exe name
lua_pushstring(L, argv[i]);
lua_setfield(L, -2, "exe");
}
lovrArgs++;
}
// Now that we know the negative offset to start at, copy all args in the table
for (int i = 0; i < argc; i++) {
lua_pushstring(L, argv[i]);
lua_rawseti(L, -2, i == 0 ? -2 : i);
lua_rawseti(L, -2, -lovrArgs + i);
}
lua_setglobal(L, "arg");
luax_registerloader(L, loadSelf, 1);