From 300f1b791956f864a6d4333c0c2edb6b56097ab5 Mon Sep 17 00:00:00 2001 From: Misa Date: Mon, 5 Apr 2021 11:32:10 -0700 Subject: [PATCH] Switch assets mounting to dedicated directory This fixes an issue where you would be able to mount things other than custom assets in per-level custom asset directories and zips. To be fair, the effects of this issue were fairly limited - about the only thing I could do with it was to override a user-made quicksave of a custom level with one of my own. However, since the quicksave check happens before assets are mounted, if the user didn't have an existing quicksave then they wouldn't be able load my quicksave. Furthermore, mounting things like settings.vvv simply doesn't work because assets only get mounted when the level gets loaded, but the game only reads from settings.vvv on startup. Still, this is an issue, and just because it only has one effect doesn't mean we should single-case patch that one effect only. So what can we do? I was thinking that we should (1) mount custom assets in a dedicated directory, and then from there (2) mount each specific asset directly - namely, mount the graphics/ and sounds/ folders, and mount the vvvvvvmusic.vvv and mmmmmm.vvv files. For (1), assets are now mounted at a (non-existent) location named .vvv-mnt/assets/. However, (2) doesn't fully work due to how PhysFS works. What DOES work is being able to mount the graphics/ and sounds/ folders, but only if the custom assets directory is a directory. And, you actually have to use the real directory where those graphics/ and sounds/ folders are located, and not the mounted directory, because PHYSFS_mount() only accepts real directories. (In which case why bother mounting the directory in the first place if we have to use real directories anyway?) So already this seems like having different directory and zip mounting paths, which I don't want... I tried to unify the directory and zip paths and get around the real directory limitation. So for mounting each individual asset (i.e. graphics/, sounds/, but especially vvvvvvmusic.vvv and mmmmmm.vvv), I tried doing PHYSFS_openRead() followed by PHYSFS_mountHandle() with that PHYSFS_File, but this simply doesn't work, because PHYSFS_mountHandle() will always create a PHYSFS_Io object, and pass it to a PhysFS internal helper function named openDirectory() which will only attempt to treat it as a directory if the PHYSFS_Io* passed is NULL. Since PHYSFS_mountHandle() always passes a non-NULL PHYSFS_Io*, openDirectory() will always treat it like a zip file and never as a directory - in contrast, PHYSFS_mount() will always pass a NULL PHYSFS_Io* to openDirectory(), so PHYSFS_mount() is the only function that works for mounting directories. (And even if this did work, having to keep the file open (because of the PHYSFS_openRead()) results in the user being unable to touch the file on Windows until it gets closed, which I also don't want.) As for zip files, PHYSFS_mount() works just fine on them, but then we run into the issue of accessing the individual assets inside it. As covered above, PHYSFS_mount() only accepts real directories, so we can't use it to access the assets inside, but then if we do the PHYSFS_openRead() and PHYSFS_mountHandle() approach, PHYSFS_mountHandle() will treat the assets inside as zip files instead of just mounting them normally! So in short, PhysFS only seems to be able to mount directories and zip files, and not any loose individual files (like vvvvvvmusic.vvv and mmmmmm.vvv). Furthermore, directories inside directories works, but directories inside zip files doesn't (only zip files inside zip files work). It seems like our asset paths don't really work well with PhysFS's design. Currently, graphics/, sounds/, vvvvvvmusic.vvv, and mmmmmm.vvv all live at the root directory of the VVVVVV folder. But what would work better is if all of those items were organized into a subfolder, for example, a folder named assets/. So the previous assets mounting system before this patch would just have mounted assets/ and be done with it, and there would be no risk of mounting extraneous files that could do bad things. However, due to our unorganized asset paths, the previous system has to mount assets at the root of the VVVVVV folder, which invites the possibility of those extraneous bad files being mounted. Well, we can't change the asset paths now, that would be a pretty big API break (maybe it should be a 2.4 thing). So what can we do? What I've done is, after mounting the assets at .vvv-mnt/assets/, when the game loads an asset, it checks if there's an override available inside .vvv-mnt/assets/, and if so, the game will load that asset instead of the regular one. This is basically reimplementing what PhysFS SHOULD be able to do for us, but can't. This fixes the issue of being able to mount a quicksave for a custom level inside its asset directory. I should also note, the unorganized asset paths issue also means that for .zip files (which contain the level file), the level file itself is also technically mounted at .vvv-mnt/assets/. This is harmless (because when we load a level file, we never load it as an asset) but it's still a bit ugly. Changing the asset paths now seems more and more like a good thing to do... --- desktop_version/src/FileSystemUtils.cpp | 55 ++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/desktop_version/src/FileSystemUtils.cpp b/desktop_version/src/FileSystemUtils.cpp index 3c21c5fd..741fbf79 100644 --- a/desktop_version/src/FileSystemUtils.cpp +++ b/desktop_version/src/FileSystemUtils.cpp @@ -9,6 +9,7 @@ #include "Exit.h" #include "Graphics.h" +#include "Maths.h" #include "UtilityClass.h" /* These are needed for PLATFORM_* crap */ @@ -27,6 +28,7 @@ static char saveDir[MAX_PATH] = {'\0'}; static char levelDir[MAX_PATH] = {'\0'}; static char assetDir[MAX_PATH] = {'\0'}; +static char virtualMountPath[MAX_PATH] = {'\0'}; static void PLATFORM_getOSDirectory(char* output); static void PLATFORM_migrateSaveData(char* output); @@ -213,6 +215,33 @@ static bool FILESYSTEM_exists(const char *fname) return PHYSFS_exists(fname); } +static void generateVirtualMountPath(char* path, const size_t path_size) +{ + char random[6 + 1] = {'\0'}; + size_t i; + for (i = 0; i < SDL_arraysize(random) - 1; ++i) + { + /* Generate a-z0-9 (base 36) */ + char randchar = fRandom() * 36; + if (randchar <= 26) + { + randchar += 'a'; + } + else + { + randchar -= 26; + randchar += '0'; + } + random[i] = randchar; + } + SDL_snprintf( + path, + path_size, + ".vvv-mnt-virtual-%s/custom-assets/", + random + ); +} + static bool FILESYSTEM_mountAssetsFrom(const char *fname) { const char* real_dir = PHYSFS_getRealDir(fname); @@ -229,7 +258,9 @@ static bool FILESYSTEM_mountAssetsFrom(const char *fname) SDL_snprintf(path, sizeof(path), "%s/%s", real_dir, fname); - if (!PHYSFS_mount(path, NULL, 0)) + generateVirtualMountPath(virtualMountPath, sizeof(virtualMountPath)); + + if (!PHYSFS_mount(path, virtualMountPath, 0)) { printf( "Error mounting %s: %s\n", @@ -452,8 +483,28 @@ void FILESYSTEM_loadAssetToMemory( const bool addnull ) { const char* path; + const bool assets_mounted = assetDir[0] != '\0'; + char mounted_path[MAX_PATH]; - path = name; + if (assets_mounted) + { + SDL_snprintf( + mounted_path, + sizeof(mounted_path), + "%s%s", + virtualMountPath, + name + ); + } + + if (assets_mounted && PHYSFS_exists(mounted_path)) + { + path = mounted_path; + } + else + { + path = name; + } FILESYSTEM_loadFileToMemory(path, mem, len, addnull); }