1
0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-06-30 16:38:29 +02:00
VVVVVV/desktop_version/src/FileSystemUtils.cpp
Misa 9e4076a418 Add FILESYSTEM_isMounted()
This returns if the file given is mounted or not. 2.3 added level zip
support, so whenever the game loads level metadata, it will mount any
zip files in the levels directory; this function can be used to check if
any of those files have been mounted, and ignore them if so.
2021-03-04 20:07:47 -05:00

713 lines
16 KiB
C++

#include <iostream>
#include <iterator>
#include <physfs.h>
#include <SDL.h>
#include <stdio.h>
#include <string>
#include <tinyxml2.h>
#include <vector>
#include "Exit.h"
#include "Graphics.h"
#include "UtilityClass.h"
/* These are needed for PLATFORM_* crap */
#if defined(_WIN32)
#include <windows.h>
#include <shlobj.h>
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__DragonFly__)
#include <unistd.h>
#include <dirent.h>
#include <limits.h>
#include <sys/stat.h>
#define MAX_PATH PATH_MAX
#endif
static char saveDir[MAX_PATH] = {'\0'};
static char levelDir[MAX_PATH] = {'\0'};
static void PLATFORM_getOSDirectory(char* output);
static void PLATFORM_migrateSaveData(char* output);
static void PLATFORM_copyFile(const char *oldLocation, const char *newLocation);
static void* bridged_malloc(PHYSFS_uint64 size)
{
return SDL_malloc(size);
}
static void* bridged_realloc(void* ptr, PHYSFS_uint64 size)
{
return SDL_realloc(ptr, size);
}
static const PHYSFS_Allocator allocator = {
NULL,
NULL,
bridged_malloc,
bridged_realloc,
SDL_free
};
int FILESYSTEM_init(char *argvZero, char* baseDir, char *assetsPath)
{
char output[MAX_PATH];
int mkdirResult;
const char* pathSep = PHYSFS_getDirSeparator();
PHYSFS_setAllocator(&allocator);
PHYSFS_init(argvZero);
PHYSFS_permitSymbolicLinks(1);
/* Determine the OS user directory */
if (baseDir && baseDir[0] != '\0')
{
/* We later append to this path and assume it ends in a slash */
bool trailing_pathsep = SDL_strcmp(baseDir + SDL_strlen(baseDir) - SDL_strlen(pathSep), pathSep) == 0;
SDL_snprintf(output, sizeof(output), "%s%s",
baseDir,
!trailing_pathsep ? pathSep : ""
);
}
else
{
PLATFORM_getOSDirectory(output);
}
/* Create base user directory, mount */
mkdirResult = PHYSFS_mkdir(output);
/* Mount our base user directory */
PHYSFS_mount(output, NULL, 0);
PHYSFS_setWriteDir(output);
printf("Base directory: %s\n", output);
/* Create the save/level folders */
mkdirResult |= PHYSFS_mkdir("saves");
mkdirResult |= PHYSFS_mkdir("levels");
/* Store full save directory */
SDL_snprintf(saveDir, sizeof(saveDir), "%s%s%s",
output,
"saves",
PHYSFS_getDirSeparator()
);
printf("Save directory: %s\n", saveDir);
/* Store full level directory */
SDL_snprintf(levelDir, sizeof(levelDir), "%s%s%s",
output,
"levels",
PHYSFS_getDirSeparator()
);
printf("Level directory: %s\n", levelDir);
/* We didn't exist until now, migrate files! */
if (mkdirResult == 0)
{
PLATFORM_migrateSaveData(output);
}
/* Mount the stock content last */
if (assetsPath)
{
SDL_strlcpy(output, assetsPath, sizeof(output));
}
else
{
SDL_snprintf(output, sizeof(output), "%s%s",
PHYSFS_getBaseDir(),
"data.zip"
);
}
if (!PHYSFS_mount(output, NULL, 1))
{
puts("Error: data.zip missing!");
puts("You do not have data.zip!");
puts("Grab it from your purchased copy of the game,");
puts("or get it from the free Make and Play Edition.");
SDL_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
"data.zip missing!",
"You do not have data.zip!"
"\n\nGrab it from your purchased copy of the game,"
"\nor get it from the free Make and Play Edition.",
NULL
);
return 0;
}
SDL_snprintf(output, sizeof(output), "%s%s", PHYSFS_getBaseDir(), "gamecontrollerdb.txt");
if (SDL_GameControllerAddMappingsFromFile(output) < 0)
{
printf("gamecontrollerdb.txt not found!\n");
}
return 1;
}
void FILESYSTEM_deinit(void)
{
if (PHYSFS_isInit())
{
PHYSFS_deinit();
}
}
char *FILESYSTEM_getUserSaveDirectory(void)
{
return saveDir;
}
char *FILESYSTEM_getUserLevelDirectory(void)
{
return levelDir;
}
bool FILESYSTEM_isFile(const char* filename)
{
PHYSFS_Stat stat;
bool success = PHYSFS_stat(filename, &stat);
if (!success)
{
printf(
"Could not stat file: %s\n",
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
);
return false;
}
/* We unfortunately cannot follow symlinks (PhysFS limitation).
* Let the caller deal with them.
*/
return stat.filetype == PHYSFS_FILETYPE_REGULAR
|| stat.filetype == PHYSFS_FILETYPE_SYMLINK;
}
bool FILESYSTEM_isMounted(const char* filename)
{
return PHYSFS_getMountPoint(filename) != NULL;
}
static bool FILESYSTEM_exists(const char *fname)
{
return PHYSFS_exists(fname);
}
void FILESYSTEM_mount(const char *fname)
{
const char* real_dir = PHYSFS_getRealDir(fname);
const char* dir_separator;
char path[MAX_PATH];
if (real_dir == NULL)
{
printf(
"Could not mount %s: real directory doesn't exist\n",
fname
);
return;
}
dir_separator = PHYSFS_getDirSeparator();
SDL_snprintf(path, sizeof(path), "%s%s%s", real_dir, dir_separator, fname);
if (!PHYSFS_mount(path, NULL, 0))
{
printf("Error mounting: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
else
{
graphics.assetdir = std::string(path);
}
}
void FILESYSTEM_loadZip(const char* filename)
{
PHYSFS_File* zip = PHYSFS_openRead(filename);
if (!PHYSFS_mountHandle(zip, filename, "levels", 1))
{
printf(
"Could not mount %s: %s\n",
filename,
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
);
}
}
bool FILESYSTEM_assetsmounted = false;
void FILESYSTEM_mountassets(const char* path)
{
const size_t path_size = SDL_strlen(path);
char filename[MAX_PATH];
char zip_data[MAX_PATH];
const char* zip_normal;
char dir[MAX_PATH];
/* path is going to look like "levels/LEVELNAME.vvvvvv".
* We want LEVELNAME, which entails starting from index 7
* (which is how long "levels/" is)
* and then grabbing path_size-14 characters
* (14 chars because "levels/" and ".vvvvvv" are both 7 chars).
* We also add 1 when calculating the amount of bytes to grab
* to account for the null terminator.
*/
SDL_strlcpy(
filename,
&path[7],
VVV_min((path_size - 14) + 1, sizeof(filename))
);
SDL_snprintf(
zip_data,
sizeof(zip_data),
"levels/%s.data.zip",
filename
);
zip_normal = PHYSFS_getRealDir(path);
SDL_snprintf(
dir,
sizeof(dir),
"levels/%s/",
filename
);
if (FILESYSTEM_exists(zip_data))
{
printf("Custom asset directory is .data.zip at %s\n", zip_data);
FILESYSTEM_mount(zip_data);
graphics.reloadresources();
FILESYSTEM_assetsmounted = true;
}
else if (zip_normal != NULL && endsWith(zip_normal, ".zip"))
{
PHYSFS_File* zip = PHYSFS_openRead(zip_normal);
printf("Custom asset directory is .zip at %s\n", zip_normal);
SDL_snprintf(
zip_data,
sizeof(zip_data),
"%s.data.zip",
zip_normal
);
if (zip == NULL)
{
printf(
"Error loading .zip: %s\n",
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
);
}
else if (PHYSFS_mountHandle(zip, zip_data, "/", 0) == 0)
{
printf(
"Error mounting .zip: %s\n",
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
);
}
else
{
graphics.assetdir = std::string(zip_data);
}
FILESYSTEM_assetsmounted = true;
graphics.reloadresources();
}
else if (FILESYSTEM_exists(dir))
{
printf("Custom asset directory exists at %s\n", dir);
FILESYSTEM_mount(dir);
graphics.reloadresources();
FILESYSTEM_assetsmounted = true;
}
else
{
puts("Custom asset directory does not exist");
FILESYSTEM_assetsmounted = false;
}
}
void FILESYSTEM_unmountassets(void)
{
if (graphics.assetdir != "")
{
printf("Unmounting %s\n", graphics.assetdir.c_str());
PHYSFS_unmount(graphics.assetdir.c_str());
graphics.assetdir = "";
graphics.reloadresources();
}
else
{
printf("Cannot unmount when no asset directory is mounted\n");
}
FILESYSTEM_assetsmounted = false;
}
void FILESYSTEM_freeMemory(unsigned char **mem);
void FILESYSTEM_loadFileToMemory(
const char *name,
unsigned char **mem,
size_t *len,
bool addnull
) {
if (SDL_strcmp(name, "levels/special/stdin.vvvvvv") == 0)
{
// this isn't *technically* necessary when piping directly from a file, but checking for that is annoying
static std::vector<char> STDIN_BUFFER;
static bool STDIN_LOADED = false;
if (!STDIN_LOADED)
{
std::istreambuf_iterator<char> begin(std::cin), end;
STDIN_BUFFER.assign(begin, end);
STDIN_BUFFER.push_back(0); // there's no observable change in behavior if addnull is always true, but not vice versa
STDIN_LOADED = true;
}
size_t length = STDIN_BUFFER.size() - 1;
if (len != NULL)
{
*len = length;
}
++length;
*mem = static_cast<unsigned char*>(SDL_malloc(length)); // STDIN_BUFFER.data() causes double-free
if (*mem == NULL)
{
VVV_exit(1);
}
std::copy(STDIN_BUFFER.begin(), STDIN_BUFFER.end(), reinterpret_cast<char*>(*mem));
return;
}
PHYSFS_File *handle = PHYSFS_openRead(name);
if (handle == NULL)
{
return;
}
PHYSFS_sint64 length = PHYSFS_fileLength(handle);
if (len != NULL)
{
if (length < 0)
{
length = 0;
}
*len = length;
}
if (addnull)
{
*mem = (unsigned char *) SDL_malloc(length + 1);
if (*mem == NULL)
{
VVV_exit(1);
}
(*mem)[length] = 0;
}
else
{
*mem = (unsigned char*) SDL_malloc(length);
if (*mem == NULL)
{
VVV_exit(1);
}
}
PHYSFS_sint64 success = PHYSFS_readBytes(handle, *mem, length);
if (success == -1)
{
FILESYSTEM_freeMemory(mem);
}
PHYSFS_close(handle);
}
void FILESYSTEM_freeMemory(unsigned char **mem)
{
SDL_free(*mem);
*mem = NULL;
}
bool FILESYSTEM_saveTiXml2Document(const char *name, tinyxml2::XMLDocument& doc)
{
/* XMLDocument.SaveFile doesn't account for Unicode paths, PHYSFS does */
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
PHYSFS_File* handle = PHYSFS_openWrite(name);
if (handle == NULL)
{
return false;
}
PHYSFS_writeBytes(handle, printer.CStr(), printer.CStrSize() - 1); // subtract one because CStrSize includes terminating null
PHYSFS_close(handle);
return true;
}
bool FILESYSTEM_loadTiXml2Document(const char *name, tinyxml2::XMLDocument& doc)
{
/* XMLDocument.LoadFile doesn't account for Unicode paths, PHYSFS does */
unsigned char *mem = NULL;
FILESYSTEM_loadFileToMemory(name, &mem, NULL, true);
if (mem == NULL)
{
return false;
}
doc.Parse((const char*) mem);
FILESYSTEM_freeMemory(&mem);
return true;
}
static PHYSFS_EnumerateCallbackResult enumerateCallback(
void* data,
const char* origdir,
const char* filename
) {
void (*callback)(const char*) = (void (*)(const char*)) data;
char builtLocation[MAX_PATH];
SDL_snprintf(
builtLocation,
sizeof(builtLocation),
"%s/%s",
origdir,
filename
);
callback(builtLocation);
return PHYSFS_ENUM_OK;
}
void FILESYSTEM_enumerateLevelDirFileNames(
void (*callback)(const char* filename)
) {
int success = PHYSFS_enumerate("levels", enumerateCallback, (void*) callback);
if (success == 0)
{
printf(
"Could not get list of levels: %s\n",
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
);
}
}
static void PLATFORM_getOSDirectory(char* output)
{
#ifdef _WIN32
/* This block is here for compatibility, do not touch it! */
WCHAR utf16_path[MAX_PATH];
SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, utf16_path);
WideCharToMultiByte(CP_UTF8, 0, utf16_path, -1, output, MAX_PATH, NULL, NULL);
SDL_strlcat(output, "\\VVVVVV\\", MAX_PATH);
#else
SDL_strlcpy(output, PHYSFS_getPrefDir("distractionware", "VVVVVV"), MAX_PATH);
#endif
}
static void PLATFORM_migrateSaveData(char* output)
{
char oldLocation[MAX_PATH];
char newLocation[MAX_PATH];
char oldDirectory[MAX_PATH];
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__DragonFly__)
DIR *dir = NULL;
struct dirent *de = NULL;
DIR *subDir = NULL;
struct dirent *subDe = NULL;
char subDirLocation[MAX_PATH];
const char *homeDir = SDL_getenv("HOME");
if (homeDir == NULL)
{
/* Uhh, I don't want to get near this. -flibit */
return;
}
const char oldPath[] =
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__DragonFly__)
"/.vvvvvv/";
#elif defined(__APPLE__)
"/Documents/VVVVVV/";
#endif
SDL_snprintf(oldDirectory, sizeof(oldDirectory), "%s%s", homeDir, oldPath);
dir = opendir(oldDirectory);
if (!dir)
{
printf("Could not find directory %s\n", oldDirectory);
return;
}
printf("Migrating old savedata to new location...\n");
for (de = readdir(dir); de != NULL; de = readdir(dir))
{
if ( SDL_strcmp(de->d_name, "..") == 0 ||
SDL_strcmp(de->d_name, ".") == 0 )
{
continue;
}
#define COPY_SAVEFILE(name) \
else if (SDL_strcmp(de->d_name, name) == 0) \
{ \
SDL_snprintf(oldLocation, sizeof(oldLocation), "%s%s", oldDirectory, name); \
SDL_snprintf(newLocation, sizeof(newLocation), "%ssaves/%s", output, name); \
PLATFORM_copyFile(oldLocation, newLocation); \
}
COPY_SAVEFILE("unlock.vvv")
COPY_SAVEFILE("tsave.vvv")
COPY_SAVEFILE("qsave.vvv")
#undef COPY_SAVEFILE
else if (SDL_strstr(de->d_name, ".vvvvvv.vvv") != NULL)
{
SDL_snprintf(oldLocation, sizeof(oldLocation), "%s%s", oldDirectory, de->d_name);
SDL_snprintf(newLocation, sizeof(newLocation), "%ssaves/%s", output, de->d_name);
PLATFORM_copyFile(oldLocation, newLocation);
}
else if (SDL_strstr(de->d_name, ".vvvvvv") != NULL)
{
SDL_snprintf(oldLocation, sizeof(oldLocation), "%s%s", oldDirectory, de->d_name);
SDL_snprintf(newLocation, sizeof(newLocation), "%slevels/%s", output, de->d_name);
PLATFORM_copyFile(oldLocation, newLocation);
}
else if (SDL_strcmp(de->d_name, "Saves") == 0)
{
SDL_snprintf(subDirLocation, sizeof(subDirLocation), "%sSaves/", oldDirectory);
subDir = opendir(subDirLocation);
if (!subDir)
{
printf("Could not open Saves/ subdir!\n");
continue;
}
for (
subDe = readdir(subDir);
subDe != NULL;
subDe = readdir(subDir)
) {
#define COPY_SAVEFILE(name) \
(SDL_strcmp(subDe->d_name, name) == 0) \
{ \
SDL_snprintf(oldLocation, sizeof(oldLocation), "%s%s", subDirLocation, name); \
SDL_snprintf(newLocation, sizeof(newLocation), "%ssaves/%s", output, name); \
PLATFORM_copyFile(oldLocation, newLocation); \
}
if COPY_SAVEFILE("unlock.vvv")
else if COPY_SAVEFILE("tsave.vvv")
else if COPY_SAVEFILE("qsave.vvv")
#undef COPY_SAVEFILE
}
}
}
#elif defined(_WIN32)
WIN32_FIND_DATA findHandle;
HANDLE hFind = NULL;
char fileSearch[MAX_PATH];
/* Same place, different layout. */
SDL_strlcpy(oldDirectory, output, sizeof(oldDirectory));
SDL_snprintf(fileSearch, sizeof(fileSearch), "%s\\*.vvvvvv", oldDirectory);
hFind = FindFirstFile(fileSearch, &findHandle);
if (hFind == INVALID_HANDLE_VALUE)
{
printf("Could not find directory %s\n", oldDirectory);
}
else do
{
if ((findHandle.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
SDL_snprintf(oldLocation, sizeof(oldLocation), "%s%s", oldDirectory, findHandle.cFileName);
SDL_snprintf(newLocation, sizeof(newLocation), "%slevels\\%s", output, findHandle.cFileName);
PLATFORM_copyFile(oldLocation, newLocation);
}
} while (FindNextFile(hFind, &findHandle));
#else
#error See PLATFORM_migrateSaveData
#endif
}
static void PLATFORM_copyFile(const char *oldLocation, const char *newLocation)
{
char *data;
size_t length, bytes_read, bytes_written;
/* Read data */
FILE *file = fopen(oldLocation, "rb");
if (!file)
{
printf("Cannot open/copy %s\n", oldLocation);
return;
}
fseek(file, 0, SEEK_END);
length = ftell(file);
fseek(file, 0, SEEK_SET);
data = (char*) SDL_malloc(length);
if (data == NULL)
{
VVV_exit(1);
}
bytes_read = fread(data, 1, length, file);
fclose(file);
if (bytes_read != length)
{
printf("An error occurred when reading from %s\n", oldLocation);
SDL_free(data);
return;
}
/* Write data */
file = fopen(newLocation, "wb");
if (!file)
{
printf("Could not write to %s\n", newLocation);
SDL_free(data);
return;
}
bytes_written = fwrite(data, 1, length, file);
fclose(file);
SDL_free(data);
/* WTF did we just do */
printf("Copied:\n\tOld: %s\n\tNew: %s\n", oldLocation, newLocation);
if (bytes_written != length)
{
printf("Warning: an error occurred when writing to %s\n", newLocation);
}
}
bool FILESYSTEM_openDirectoryEnabled(void)
{
/* This is just a check to see if we're on a desktop or tenfoot setup.
* If you're working on a tenfoot-only build, add a def that always
* returns false!
*/
return !SDL_GetHintBoolean("SteamTenfoot", SDL_FALSE);
}
bool FILESYSTEM_openDirectory(const char *dname)
{
char url[MAX_PATH];
SDL_snprintf(url, sizeof(url), "file://%s", dname);
if (SDL_OpenURL(url) == -1)
{
printf("Error opening directory: %s\n", SDL_GetError());
return false;
}
return true;
}
bool FILESYSTEM_delete(const char *name)
{
return PHYSFS_delete(name) != 0;
}