diff --git a/desktop_version/src/FileSystemUtils.cpp b/desktop_version/src/FileSystemUtils.cpp index 1160f63f..419cf834 100644 --- a/desktop_version/src/FileSystemUtils.cpp +++ b/desktop_version/src/FileSystemUtils.cpp @@ -11,6 +11,7 @@ #include "Exit.h" #include "Graphics.h" #include "Maths.h" +#include "Unused.h" #include "UtilityClass.h" /* These are needed for PLATFORM_* crap */ @@ -309,10 +310,148 @@ static bool FILESYSTEM_mountAssetsFrom(const char *fname) return true; } +struct ArchiveState +{ + const char* filename; + bool has_extension; + bool other_level_files; +}; + +static PHYSFS_EnumerateCallbackResult zipCheckCallback( + void* data, + const char* origdir, + const char* filename +) { + struct ArchiveState* state = (struct ArchiveState*) data; + const bool has_extension = endsWith(filename, ".vvvvvv"); + UNUSED(origdir); + + if (!state->has_extension) + { + state->has_extension = has_extension; + } + if (!state->other_level_files && has_extension) + { + state->other_level_files = SDL_strcmp( + state->filename, + filename + ) != 0; + } + + if (state->has_extension && state->other_level_files) + { + /* We don't need to check any more files. */ + return PHYSFS_ENUM_STOP; + } + return PHYSFS_ENUM_OK; +} + +/* For technical reasons, the level file inside a zip named LEVELNAME.zip must + * be named LEVELNAME.vvvvvv, else its custom assets won't work; + * if there are .vvvvvv files other than LEVELNAME.vvvvvv, they would be loaded + * too but they won't load any assets + * + * For user-friendliness, we check this upfront and reject all zips that don't + * conform to this (regardless of them containing assets or not) - otherwise a + * level zip with assets can be played but its assets mysteriously won't work + */ +static bool checkZipStructure(const char* filename) +{ + const char* real_dir = PHYSFS_getRealDir(filename); + char base_name[MAX_PATH]; + char real_path[MAX_PATH]; + char mount_path[MAX_PATH]; + char check_path[MAX_PATH]; + char random_str[6 + 1]; + bool success; + struct ArchiveState zip_state; + + if (real_dir == NULL) + { + printf( + "Could not check %s: real directory doesn't exist\n", + filename + ); + return false; + } + + SDL_snprintf(real_path, sizeof(real_path), "%s/%s", real_dir, filename); + + generateBase36(random_str, sizeof(random_str)); + SDL_snprintf(mount_path, sizeof(mount_path), ".vvv-mnt-temp-%s/", random_str); + + if (!PHYSFS_mount(real_path, mount_path, 1)) + { + printf( + "Error mounting and checking %s: %s\n", + filename, + PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()) + ); + return false; + } + + VVV_between(filename, "levels/", base_name, ".zip"); + + SDL_snprintf( + check_path, + sizeof(check_path), + "%s%s.vvvvvv", + mount_path, + base_name + ); + + success = PHYSFS_exists(check_path); + + SDL_zero(zip_state); + zip_state.filename = check_path; + + PHYSFS_enumerate(mount_path, zipCheckCallback, (void*) &zip_state); + + /* If no .vvvvvv files in zip, don't print warning. */ + if (!success && zip_state.has_extension) + { + /* FIXME: How do we print this for non-terminal users? */ + printf( + "%s.zip is not structured correctly! It is missing %s.vvvvvv.\n", + base_name, + base_name + ); + } + + success &= !zip_state.other_level_files; + + /* ...But if other .vvvvvv file(s), do print warning. */ + if (zip_state.other_level_files) + { + /* FIXME: How do we print this for non-terminal users? */ + printf( + "%s.zip is not structured correctly! It has .vvvvvv file(s) other than %s.vvvvvv.\n", + base_name, + base_name + ); + } + + if (!PHYSFS_unmount(real_path)) + { + printf( + "Could not unmount %s: %s\n", + mount_path, + PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()) + ); + } + + return success; +} + void FILESYSTEM_loadZip(const char* filename) { PHYSFS_File* zip = PHYSFS_openRead(filename); + if (!checkZipStructure(filename)) + { + return; + } + if (!PHYSFS_mountHandle(zip, filename, "levels", 1)) { printf(