From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/storage/SDL_storage.c | 410 +++++++++++++++++++++ contrib/SDL-3.2.8/src/storage/SDL_sysstorage.h | 51 +++ .../src/storage/generic/SDL_genericstorage.c | 351 ++++++++++++++++++ .../SDL-3.2.8/src/storage/steam/SDL_steamstorage.c | 215 +++++++++++ .../src/storage/steam/SDL_steamstorage_proc.h | 14 + 5 files changed, 1041 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/storage/SDL_storage.c create mode 100644 contrib/SDL-3.2.8/src/storage/SDL_sysstorage.h create mode 100644 contrib/SDL-3.2.8/src/storage/generic/SDL_genericstorage.c create mode 100644 contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage.c create mode 100644 contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage_proc.h (limited to 'contrib/SDL-3.2.8/src/storage') diff --git a/contrib/SDL-3.2.8/src/storage/SDL_storage.c b/contrib/SDL-3.2.8/src/storage/SDL_storage.c new file mode 100644 index 0000000..75952ff --- /dev/null +++ b/contrib/SDL-3.2.8/src/storage/SDL_storage.c @@ -0,0 +1,410 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "SDL_sysstorage.h" +#include "../filesystem/SDL_sysfilesystem.h" + +// Available title storage drivers +static TitleStorageBootStrap *titlebootstrap[] = { + &GENERIC_titlebootstrap, + NULL +}; + +// Available user storage drivers +static UserStorageBootStrap *userbootstrap[] = { +#ifdef SDL_STORAGE_STEAM + &STEAM_userbootstrap, +#endif + &GENERIC_userbootstrap, + NULL +}; + +struct SDL_Storage +{ + SDL_StorageInterface iface; + void *userdata; +}; + +#define CHECK_STORAGE_MAGIC() \ + if (!storage) { \ + return SDL_SetError("Invalid storage container"); \ + } + +#define CHECK_STORAGE_MAGIC_RET(result) \ + if (!storage) { \ + SDL_SetError("Invalid storage container"); \ + return result; \ + } + +// we don't make any effort to convert path separators here, because a) +// everything including Windows will accept a '/' separator and b) that +// conversion should probably happen in the storage backend anyhow. + +static bool ValidateStoragePath(const char *path) +{ + if (SDL_strchr(path, '\\')) { + return SDL_SetError("Windows-style path separators ('\\') not permitted, use '/' instead."); + } + + const char *ptr; + const char *prev = path; + while ((ptr = SDL_strchr(prev, '/')) != NULL) { + if ((SDL_strncmp(prev, "./", 2) == 0) || (SDL_strncmp(prev, "../", 3) == 0)) { + return SDL_SetError("Relative paths not permitted"); + } + prev = ptr + 1; + } + + // check the last path element (or the only path element). + if ((SDL_strcmp(prev, ".") == 0) || (SDL_strcmp(prev, "..") == 0)) { + return SDL_SetError("Relative paths not permitted"); + } + + return true; +} + +SDL_Storage *SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props) +{ + SDL_Storage *storage = NULL; + int i = 0; + + // Select the proper storage driver + const char *driver_name = SDL_GetHint(SDL_HINT_STORAGE_TITLE_DRIVER); + if (driver_name && *driver_name != 0) { + const char *driver_attempt = driver_name; + while (driver_attempt && *driver_attempt != 0 && !storage) { + const char *driver_attempt_end = SDL_strchr(driver_attempt, ','); + size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt) + : SDL_strlen(driver_attempt); + + for (i = 0; titlebootstrap[i]; ++i) { + if ((driver_attempt_len == SDL_strlen(titlebootstrap[i]->name)) && + (SDL_strncasecmp(titlebootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) { + storage = titlebootstrap[i]->create(override, props); + break; + } + } + + driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL; + } + } else { + for (i = 0; titlebootstrap[i]; ++i) { + storage = titlebootstrap[i]->create(override, props); + if (storage) { + break; + } + } + } + if (!storage) { + if (driver_name) { + SDL_SetError("%s not available", driver_name); + } else { + SDL_SetError("No available title storage driver"); + } + } + return storage; +} + +SDL_Storage *SDL_OpenUserStorage(const char *org, const char *app, SDL_PropertiesID props) +{ + SDL_Storage *storage = NULL; + int i = 0; + + // Select the proper storage driver + const char *driver_name = SDL_GetHint(SDL_HINT_STORAGE_USER_DRIVER); + if (driver_name && *driver_name != 0) { + const char *driver_attempt = driver_name; + while (driver_attempt && *driver_attempt != 0 && !storage) { + const char *driver_attempt_end = SDL_strchr(driver_attempt, ','); + size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt) + : SDL_strlen(driver_attempt); + + for (i = 0; userbootstrap[i]; ++i) { + if ((driver_attempt_len == SDL_strlen(userbootstrap[i]->name)) && + (SDL_strncasecmp(userbootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) { + storage = userbootstrap[i]->create(org, app, props); + break; + } + } + + driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL; + } + } else { + for (i = 0; userbootstrap[i]; ++i) { + storage = userbootstrap[i]->create(org, app, props); + if (storage) { + break; + } + } + } + if (!storage) { + if (driver_name) { + SDL_SetError("%s not available", driver_name); + } else { + SDL_SetError("No available user storage driver"); + } + } + return storage; +} + +SDL_Storage *SDL_OpenFileStorage(const char *path) +{ + return GENERIC_OpenFileStorage(path); +} + +SDL_Storage *SDL_OpenStorage(const SDL_StorageInterface *iface, void *userdata) +{ + SDL_Storage *storage; + + if (!iface) { + SDL_InvalidParamError("iface"); + return NULL; + } + if (iface->version < sizeof(*iface)) { + // Update this to handle older versions of this interface + SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()"); + return NULL; + } + + storage = (SDL_Storage *)SDL_calloc(1, sizeof(*storage)); + if (storage) { + SDL_copyp(&storage->iface, iface); + storage->userdata = userdata; + } + return storage; +} + +bool SDL_CloseStorage(SDL_Storage *storage) +{ + bool result = true; + + CHECK_STORAGE_MAGIC() + + if (storage->iface.close) { + result = storage->iface.close(storage->userdata); + } + SDL_free(storage); + return result; +} + +bool SDL_StorageReady(SDL_Storage *storage) +{ + CHECK_STORAGE_MAGIC_RET(false) + + if (storage->iface.ready) { + return storage->iface.ready(storage->userdata); + } + return true; +} + +bool SDL_GetStorageFileSize(SDL_Storage *storage, const char *path, Uint64 *length) +{ + SDL_PathInfo info; + + if (SDL_GetStoragePathInfo(storage, path, &info)) { + if (length) { + *length = info.size; + } + return true; + } else { + if (length) { + *length = 0; + } + return false; + } +} + +bool SDL_ReadStorageFile(SDL_Storage *storage, const char *path, void *destination, Uint64 length) +{ + CHECK_STORAGE_MAGIC() + + if (!path) { + return SDL_InvalidParamError("path"); + } else if (!ValidateStoragePath(path)) { + return false; + } else if (!storage->iface.read_file) { + return SDL_Unsupported(); + } + + return storage->iface.read_file(storage->userdata, path, destination, length); +} + +bool SDL_WriteStorageFile(SDL_Storage *storage, const char *path, const void *source, Uint64 length) +{ + CHECK_STORAGE_MAGIC() + + if (!path) { + return SDL_InvalidParamError("path"); + } else if (!ValidateStoragePath(path)) { + return false; + } else if (!storage->iface.write_file) { + return SDL_Unsupported(); + } + + return storage->iface.write_file(storage->userdata, path, source, length); +} + +bool SDL_CreateStorageDirectory(SDL_Storage *storage, const char *path) +{ + CHECK_STORAGE_MAGIC() + + if (!path) { + return SDL_InvalidParamError("path"); + } else if (!ValidateStoragePath(path)) { + return false; + } else if (!storage->iface.mkdir) { + return SDL_Unsupported(); + } + + return storage->iface.mkdir(storage->userdata, path); +} + +bool SDL_EnumerateStorageDirectory(SDL_Storage *storage, const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata) +{ + CHECK_STORAGE_MAGIC() + + if (!path) { + path = ""; // we allow NULL to mean "root of the storage tree". + } + + if (!ValidateStoragePath(path)) { + return false; + } else if (!storage->iface.enumerate) { + return SDL_Unsupported(); + } + + return storage->iface.enumerate(storage->userdata, path, callback, userdata); +} + +bool SDL_RemoveStoragePath(SDL_Storage *storage, const char *path) +{ + CHECK_STORAGE_MAGIC() + + if (!path) { + return SDL_InvalidParamError("path"); + } else if (!ValidateStoragePath(path)) { + return false; + } else if (!storage->iface.remove) { + return SDL_Unsupported(); + } + + return storage->iface.remove(storage->userdata, path); +} + +bool SDL_RenameStoragePath(SDL_Storage *storage, const char *oldpath, const char *newpath) +{ + CHECK_STORAGE_MAGIC() + + if (!oldpath) { + return SDL_InvalidParamError("oldpath"); + } else if (!newpath) { + return SDL_InvalidParamError("newpath"); + } else if (!ValidateStoragePath(oldpath)) { + return false; + } else if (!ValidateStoragePath(newpath)) { + return false; + } else if (!storage->iface.rename) { + return SDL_Unsupported(); + } + + return storage->iface.rename(storage->userdata, oldpath, newpath); +} + +bool SDL_CopyStorageFile(SDL_Storage *storage, const char *oldpath, const char *newpath) +{ + CHECK_STORAGE_MAGIC() + + if (!oldpath) { + return SDL_InvalidParamError("oldpath"); + } else if (!newpath) { + return SDL_InvalidParamError("newpath"); + } else if (!ValidateStoragePath(oldpath)) { + return false; + } else if (!ValidateStoragePath(newpath)) { + return false; + } else if (!storage->iface.copy) { + return SDL_Unsupported(); + } + + return storage->iface.copy(storage->userdata, oldpath, newpath); +} + +bool SDL_GetStoragePathInfo(SDL_Storage *storage, const char *path, SDL_PathInfo *info) +{ + SDL_PathInfo dummy; + + if (!info) { + info = &dummy; + } + SDL_zerop(info); + + CHECK_STORAGE_MAGIC() + + if (!path) { + return SDL_InvalidParamError("path"); + } else if (!ValidateStoragePath(path)) { + return false; + } else if (!storage->iface.info) { + return SDL_Unsupported(); + } + + return storage->iface.info(storage->userdata, path, info); +} + +Uint64 SDL_GetStorageSpaceRemaining(SDL_Storage *storage) +{ + CHECK_STORAGE_MAGIC_RET(0) + + if (!storage->iface.space_remaining) { + SDL_Unsupported(); + return 0; + } + + return storage->iface.space_remaining(storage->userdata); +} + +static bool GlobStorageDirectoryGetPathInfo(const char *path, SDL_PathInfo *info, void *userdata) +{ + return SDL_GetStoragePathInfo((SDL_Storage *) userdata, path, info); +} + +static bool GlobStorageDirectoryEnumerator(const char *path, SDL_EnumerateDirectoryCallback cb, void *cbuserdata, void *userdata) +{ + return SDL_EnumerateStorageDirectory((SDL_Storage *) userdata, path, cb, cbuserdata); +} + +char **SDL_GlobStorageDirectory(SDL_Storage *storage, const char *path, const char *pattern, SDL_GlobFlags flags, int *count) +{ + CHECK_STORAGE_MAGIC_RET(NULL) + + if (!path) { + path = ""; // we allow NULL to mean "root of the storage tree". + } + + if (!ValidateStoragePath(path)) { + return NULL; + } + + return SDL_InternalGlobDirectory(path, pattern, flags, count, GlobStorageDirectoryEnumerator, GlobStorageDirectoryGetPathInfo, storage); +} + diff --git a/contrib/SDL-3.2.8/src/storage/SDL_sysstorage.h b/contrib/SDL-3.2.8/src/storage/SDL_sysstorage.h new file mode 100644 index 0000000..57d60d6 --- /dev/null +++ b/contrib/SDL-3.2.8/src/storage/SDL_sysstorage.h @@ -0,0 +1,51 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_sysstorage_h_ +#define SDL_sysstorage_h_ + +#include "SDL_internal.h" + +typedef struct TitleStorageBootStrap +{ + const char *name; + const char *desc; + SDL_Storage *(*create)(const char*, SDL_PropertiesID); +} TitleStorageBootStrap; + +typedef struct UserStorageBootStrap +{ + const char *name; + const char *desc; + SDL_Storage *(*create)(const char*, const char*, SDL_PropertiesID); +} UserStorageBootStrap; + +// Not all of these are available in a given build. Use #ifdefs, etc. + +extern TitleStorageBootStrap GENERIC_titlebootstrap; +// Steam does not have title storage APIs + +extern UserStorageBootStrap GENERIC_userbootstrap; +extern UserStorageBootStrap STEAM_userbootstrap; + +extern SDL_Storage *GENERIC_OpenFileStorage(const char *path); + +#endif // SDL_sysstorage_h_ diff --git a/contrib/SDL-3.2.8/src/storage/generic/SDL_genericstorage.c b/contrib/SDL-3.2.8/src/storage/generic/SDL_genericstorage.c new file mode 100644 index 0000000..cc0804f --- /dev/null +++ b/contrib/SDL-3.2.8/src/storage/generic/SDL_genericstorage.c @@ -0,0 +1,351 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "../SDL_sysstorage.h" + + +static char *GENERIC_INTERNAL_CreateFullPath(const char *base, const char *relative) +{ + char *result = NULL; + SDL_asprintf(&result, "%s%s", base ? base : "", relative); + return result; +} + +static bool GENERIC_CloseStorage(void *userdata) +{ + SDL_free(userdata); + return true; +} + +typedef struct GenericEnumerateData +{ + size_t base_len; + SDL_EnumerateDirectoryCallback real_callback; + void *real_userdata; +} GenericEnumerateData; + +static SDL_EnumerationResult SDLCALL GENERIC_EnumerateDirectory(void *userdata, const char *dirname, const char *fname) +{ + // SDL_EnumerateDirectory will return the full path, so for Storage we + // can take the base directory and add its length to the dirname string, + // effectively trimming the root without having to strdup anything. + const GenericEnumerateData *wrap_data = (GenericEnumerateData *)userdata; + + dirname += wrap_data->base_len; // skip the base, just return the part inside of the Storage. + + #ifdef SDL_PLATFORM_WINDOWS + char *dirnamecpy = NULL; + const size_t slen = SDL_strlen(dirname); + if (slen && (dirname[slen - 1] == '\\')) { + dirnamecpy = SDL_strdup(dirname); + if (!dirnamecpy) { + return SDL_ENUM_FAILURE; + } + dirnamecpy[slen - 1] = '/'; // storage layer always uses '/' path separators. + dirname = dirnamecpy; + } + const SDL_EnumerationResult retval = wrap_data->real_callback(wrap_data->real_userdata, dirname, fname); + SDL_free(dirnamecpy); + return retval; + #else + return wrap_data->real_callback(wrap_data->real_userdata, dirname, fname); + #endif +} + +static bool GENERIC_EnumerateStorageDirectory(void *userdata, const char *path, SDL_EnumerateDirectoryCallback callback, void *callback_userdata) +{ + bool result = false; + GenericEnumerateData wrap_data; + + char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path); + if (fullpath) { + wrap_data.base_len = SDL_strlen((char *)userdata); + wrap_data.real_callback = callback; + wrap_data.real_userdata = callback_userdata; + + result = SDL_EnumerateDirectory(fullpath, GENERIC_EnumerateDirectory, &wrap_data); + + SDL_free(fullpath); + } + return result; +} + +static bool GENERIC_GetStoragePathInfo(void *userdata, const char *path, SDL_PathInfo *info) +{ + bool result = false; + + char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path); + if (fullpath) { + result = SDL_GetPathInfo(fullpath, info); + + SDL_free(fullpath); + } + return result; +} + +static bool GENERIC_ReadStorageFile(void *userdata, const char *path, void *destination, Uint64 length) +{ + bool result = false; + + if (length > SDL_SIZE_MAX) { + return SDL_SetError("Read size exceeds SDL_SIZE_MAX"); + } + + char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path); + if (fullpath) { + SDL_IOStream *stream = SDL_IOFromFile(fullpath, "rb"); + if (stream) { + // FIXME: Should SDL_ReadIO use u64 now...? + if (SDL_ReadIO(stream, destination, (size_t)length) == length) { + result = true; + } else { + SDL_SetError("File length did not exactly match the destination length"); + } + SDL_CloseIO(stream); + } + SDL_free(fullpath); + } + return result; +} + +static bool GENERIC_WriteStorageFile(void *userdata, const char *path, const void *source, Uint64 length) +{ + // TODO: Recursively create subdirectories with SDL_CreateDirectory + bool result = false; + + if (length > SDL_SIZE_MAX) { + return SDL_SetError("Write size exceeds SDL_SIZE_MAX"); + } + + char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path); + if (fullpath) { + SDL_IOStream *stream = SDL_IOFromFile(fullpath, "wb"); + + if (stream) { + // FIXME: Should SDL_WriteIO use u64 now...? + if (SDL_WriteIO(stream, source, (size_t)length) == length) { + result = true; + } else { + SDL_SetError("Resulting file length did not exactly match the source length"); + } + SDL_CloseIO(stream); + } + SDL_free(fullpath); + } + return result; +} + +static bool GENERIC_CreateStorageDirectory(void *userdata, const char *path) +{ + // TODO: Recursively create subdirectories with SDL_CreateDirectory + bool result = false; + + char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path); + if (fullpath) { + result = SDL_CreateDirectory(fullpath); + + SDL_free(fullpath); + } + return result; +} + +static bool GENERIC_RemoveStoragePath(void *userdata, const char *path) +{ + bool result = false; + + char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path); + if (fullpath) { + result = SDL_RemovePath(fullpath); + + SDL_free(fullpath); + } + return result; +} + +static bool GENERIC_RenameStoragePath(void *userdata, const char *oldpath, const char *newpath) +{ + bool result = false; + + char *fulloldpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, oldpath); + char *fullnewpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, newpath); + if (fulloldpath && fullnewpath) { + result = SDL_RenamePath(fulloldpath, fullnewpath); + } + SDL_free(fulloldpath); + SDL_free(fullnewpath); + + return result; +} + +static bool GENERIC_CopyStorageFile(void *userdata, const char *oldpath, const char *newpath) +{ + bool result = false; + + char *fulloldpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, oldpath); + char *fullnewpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, newpath); + if (fulloldpath && fullnewpath) { + result = SDL_CopyFile(fulloldpath, fullnewpath); + } + SDL_free(fulloldpath); + SDL_free(fullnewpath); + + return result; +} + +static Uint64 GENERIC_GetStorageSpaceRemaining(void *userdata) +{ + // TODO: There's totally a way to query a folder root's quota... + return SDL_MAX_UINT64; +} + +static const SDL_StorageInterface GENERIC_title_iface = { + sizeof(SDL_StorageInterface), + GENERIC_CloseStorage, + NULL, // ready + GENERIC_EnumerateStorageDirectory, + GENERIC_GetStoragePathInfo, + GENERIC_ReadStorageFile, + NULL, // write_file + NULL, // mkdir + NULL, // remove + NULL, // rename + NULL, // copy + NULL // space_remaining +}; + +static SDL_Storage *GENERIC_Title_Create(const char *override, SDL_PropertiesID props) +{ + SDL_Storage *result = NULL; + char *basepath = NULL; + + if (override != NULL) { + // make sure override has a path separator at the end. If you're not on Windows and used '\\', that's on you. + const size_t slen = SDL_strlen(override); + const bool need_sep = (!slen || ((override[slen-1] != '/') && (override[slen-1] != '\\'))); + if (SDL_asprintf(&basepath, "%s%s", override, need_sep ? "/" : "") == -1) { + return NULL; + } + } else { + const char *base = SDL_GetBasePath(); + basepath = base ? SDL_strdup(base) : NULL; + } + + if (basepath != NULL) { + result = SDL_OpenStorage(&GENERIC_title_iface, basepath); + if (result == NULL) { + SDL_free(basepath); // otherwise CloseStorage will free it. + } + } + + return result; +} + +TitleStorageBootStrap GENERIC_titlebootstrap = { + "generic", + "SDL generic title storage driver", + GENERIC_Title_Create +}; + +static const SDL_StorageInterface GENERIC_user_iface = { + sizeof(SDL_StorageInterface), + GENERIC_CloseStorage, + NULL, // ready + GENERIC_EnumerateStorageDirectory, + GENERIC_GetStoragePathInfo, + GENERIC_ReadStorageFile, + GENERIC_WriteStorageFile, + GENERIC_CreateStorageDirectory, + GENERIC_RemoveStoragePath, + GENERIC_RenameStoragePath, + GENERIC_CopyStorageFile, + GENERIC_GetStorageSpaceRemaining +}; + +static SDL_Storage *GENERIC_User_Create(const char *org, const char *app, SDL_PropertiesID props) +{ + SDL_Storage *result; + char *prefpath = SDL_GetPrefPath(org, app); + if (prefpath == NULL) { + return NULL; + } + + result = SDL_OpenStorage(&GENERIC_user_iface, prefpath); + if (result == NULL) { + SDL_free(prefpath); // otherwise CloseStorage will free it. + } + return result; +} + +UserStorageBootStrap GENERIC_userbootstrap = { + "generic", + "SDL generic user storage driver", + GENERIC_User_Create +}; + +static const SDL_StorageInterface GENERIC_file_iface = { + sizeof(SDL_StorageInterface), + GENERIC_CloseStorage, + NULL, // ready + GENERIC_EnumerateStorageDirectory, + GENERIC_GetStoragePathInfo, + GENERIC_ReadStorageFile, + GENERIC_WriteStorageFile, + GENERIC_CreateStorageDirectory, + GENERIC_RemoveStoragePath, + GENERIC_RenameStoragePath, + GENERIC_CopyStorageFile, + GENERIC_GetStorageSpaceRemaining +}; + +SDL_Storage *GENERIC_OpenFileStorage(const char *path) +{ + SDL_Storage *result; + size_t len = 0; + char *basepath = NULL; + + if (path) { + len += SDL_strlen(path); + } + if (len > 0) { + #ifdef SDL_PLATFORM_WINDOWS + const bool appended_separator = (path[len-1] == '/') || (path[len-1] == '\\'); + #else + const bool appended_separator = (path[len-1] == '/'); + #endif + if (appended_separator) { + basepath = SDL_strdup(path); + if (!basepath) { + return NULL; + } + } else { + if (SDL_asprintf(&basepath, "%s/", path) < 0) { + return NULL; + } + } + } + result = SDL_OpenStorage(&GENERIC_file_iface, basepath); + if (result == NULL) { + SDL_free(basepath); + } + return result; +} diff --git a/contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage.c b/contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage.c new file mode 100644 index 0000000..8f735f8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage.c @@ -0,0 +1,215 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "../SDL_sysstorage.h" + +// !!! FIXME: Async API can use SteamRemoteStorage_ReadFileAsync +// !!! FIXME: Async API can use SteamRemoteStorage_WriteFileAsync + +#define STEAM_PROC(ret, func, parms) \ + typedef ret (*steamfntype_##func) parms; +#include "SDL_steamstorage_proc.h" + +typedef struct STEAM_RemoteStorage +{ + SDL_SharedObject *libsteam_api; + #define STEAM_PROC(ret, func, parms) \ + steamfntype_##func func; + #include "SDL_steamstorage_proc.h" +} STEAM_RemoteStorage; + +static bool STEAM_CloseStorage(void *userdata) +{ + bool result = true; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + result = SDL_SetError("SteamRemoteStorage unavailable"); + } else if (!steam->SteamAPI_ISteamRemoteStorage_EndFileWriteBatch(steamremotestorage)) { + result = SDL_SetError("SteamRemoteStorage()->EndFileWriteBatch() failed"); + } + SDL_UnloadObject(steam->libsteam_api); + SDL_free(steam); + return result; +} + +static bool STEAM_StorageReady(void *userdata) +{ + return true; +} + +static bool STEAM_GetStoragePathInfo(void *userdata, const char *path, SDL_PathInfo *info) +{ + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + return SDL_SetError("SteamRemoteStorage unavailable"); + } + + if (info) { + SDL_zerop(info); + info->type = SDL_PATHTYPE_FILE; + info->size = steam->SteamAPI_ISteamRemoteStorage_GetFileSize(steamremotestorage, path); + } + return true; +} + +static bool STEAM_ReadStorageFile(void *userdata, const char *path, void *destination, Uint64 length) +{ + bool result = false; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + return SDL_SetError("SteamRemoteStorage unavailable"); + } + if (length > SDL_MAX_SINT32) { + return SDL_SetError("SteamRemoteStorage only supports INT32_MAX read size"); + } + if (steam->SteamAPI_ISteamRemoteStorage_FileRead(steamremotestorage, path, destination, (Sint32) length) == length) { + result = true; + } else { + SDL_SetError("SteamAPI_ISteamRemoteStorage_FileRead() failed"); + } + return result; +} + +static bool STEAM_WriteStorageFile(void *userdata, const char *path, const void *source, Uint64 length) +{ + bool result = false; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + return SDL_SetError("SteamRemoteStorage unavailable"); + } + if (length > SDL_MAX_SINT32) { + return SDL_SetError("SteamRemoteStorage only supports INT32_MAX write size"); + } + if (steam->SteamAPI_ISteamRemoteStorage_FileWrite(steamremotestorage, path, source, (Sint32) length) == length) { + result = true; + } else { + SDL_SetError("SteamAPI_ISteamRemoteStorage_FileRead() failed"); + } + return result; +} + +static Uint64 STEAM_GetStorageSpaceRemaining(void *userdata) +{ + Uint64 total, remaining; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + SDL_SetError("SteamRemoteStorage unavailable"); + return 0; + } + if (!steam->SteamAPI_ISteamRemoteStorage_GetQuota(steamremotestorage, &total, &remaining)) { + SDL_SetError("SteamRemoteStorage()->GetQuota failed"); + return 0; + } + return remaining; +} + +static const SDL_StorageInterface STEAM_user_iface = { + sizeof(SDL_StorageInterface), + STEAM_CloseStorage, + STEAM_StorageReady, + NULL, // enumerate + STEAM_GetStoragePathInfo, + STEAM_ReadStorageFile, + STEAM_WriteStorageFile, + NULL, // mkdir + NULL, // remove + NULL, // rename + NULL, // copy + STEAM_GetStorageSpaceRemaining +}; + +static SDL_Storage *STEAM_User_Create(const char *org, const char *app, SDL_PropertiesID props) +{ + SDL_Storage *result; + STEAM_RemoteStorage *steam; + void *steamremotestorage; + + steam = (STEAM_RemoteStorage*) SDL_malloc(sizeof(STEAM_RemoteStorage)); + if (steam == NULL) { + return NULL; + } + + steam->libsteam_api = SDL_LoadObject( +#if defined(_WIN64) + "steam_api64.dll" +#elif defined(_WIN32) + "steam_api.dll" +#elif defined(__APPLE__) + "libsteam_api.dylib" +#else + "libsteam_api.so" +#endif + ); + if (steam->libsteam_api == NULL) { + SDL_free(steam); + return NULL; + } + + #define STEAM_PROC(ret, func, parms) \ + steam->func = (steamfntype_##func) SDL_LoadFunction(steam->libsteam_api, #func); \ + if (steam->func == NULL) { \ + SDL_SetError("Could not load function " #func); \ + goto steamfail; \ + } + #include "SDL_steamstorage_proc.h" + + steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + SDL_SetError("SteamRemoteStorage unavailable"); + goto steamfail; + } + if (!steam->SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount(steamremotestorage)) { + SDL_SetError("Steam cloud is disabled for this user"); + goto steamfail; + } + if (!steam->SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp(steamremotestorage)) { + SDL_SetError("Steam cloud is disabled for this application"); + goto steamfail; + } + if (!steam->SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch(steamremotestorage)) { + SDL_SetError("SteamRemoteStorage()->BeginFileWriteBatch failed"); + goto steamfail; + } + + result = SDL_OpenStorage(&STEAM_user_iface, steam); + if (result == NULL) { + goto steamfail; + } + return result; + +steamfail: + SDL_UnloadObject(steam->libsteam_api); + SDL_free(steam); + return NULL; +} + +UserStorageBootStrap STEAM_userbootstrap = { + "steam", + "SDL Steam user storage driver", + STEAM_User_Create +}; diff --git a/contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage_proc.h b/contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage_proc.h new file mode 100644 index 0000000..9d259e7 --- /dev/null +++ b/contrib/SDL-3.2.8/src/storage/steam/SDL_steamstorage_proc.h @@ -0,0 +1,14 @@ +STEAM_PROC(void*, SteamAPI_SteamRemoteStorage_v016, (void)) + +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount, (void*)) +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp, (void*)) + +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch, (void*)) +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_EndFileWriteBatch, (void*)) + +STEAM_PROC(Sint32, SteamAPI_ISteamRemoteStorage_GetFileSize, (void*, const char*)) +STEAM_PROC(Sint32, SteamAPI_ISteamRemoteStorage_FileRead, (void*, const char*, void*, Sint32)) +STEAM_PROC(Sint32, SteamAPI_ISteamRemoteStorage_FileWrite, (void*, const char*, const void*, Sint32)) +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_GetQuota, (void*, Uint64*, Uint64*)) + +#undef STEAM_PROC -- cgit v1.2.3