diff --git a/build/Makefile.common b/build/Makefile.common index b8b8c19..ed0b27b 100644 --- a/build/Makefile.common +++ b/build/Makefile.common @@ -58,6 +58,7 @@ SOURCES_C += $(LIBRETRO_COMM_DIR)/file/retro_dirent.c \ $(LIBRETRO_COMM_DIR)/compat/compat_strl.c \ $(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \ $(LIBRETRO_COMM_DIR)/compat/compat_snprintf.c \ + $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \ $(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \ $(LIBRETRO_COMM_DIR)/string/stdstring.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \ diff --git a/libretro/libretro-common/compat/compat_snprintf.c b/libretro/libretro-common/compat/compat_snprintf.c index 074cf8e..b69ad04 100644 --- a/libretro/libretro-common/compat/compat_snprintf.c +++ b/libretro/libretro-common/compat/compat_snprintf.c @@ -23,10 +23,7 @@ /* THIS FILE HAS NOT BEEN VALIDATED ON PLATFORMS BESIDES MSVC */ #ifdef _MSC_VER -#include -#if _MSC_VER >= 1900 -#include /* added for _vsnprintf_s and _vscprintf on VS2015 and VS2017 */ -#endif +#include #include #if _MSC_VER < 1800 @@ -54,14 +51,23 @@ int c99_vsnprintf_retro__(char *outBuf, size_t size, const char *format, va_list int count = -1; if (size != 0) + { #if (_MSC_VER <= 1310) - count = _vsnprintf(outBuf, size, format, ap); + count = _vsnprintf(outBuf, size - 1, format, ap); #else - count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + count = _vsnprintf_s(outBuf, size, size - 1, format, ap); #endif + } + if (count == -1) count = _vscprintf(format, ap); + if (count == size) + { + /* there was no room for a NULL, so truncate the last character */ + outBuf[size - 1] = '\0'; + } + return count; } diff --git a/libretro/libretro-common/encodings/encoding_utf.c b/libretro/libretro-common/encodings/encoding_utf.c index f7c533f..b6ad2f9 100644 --- a/libretro/libretro-common/encodings/encoding_utf.c +++ b/libretro/libretro-common/encodings/encoding_utf.c @@ -211,10 +211,7 @@ size_t utf8len(const char *string) return ret; } -static uint8_t utf8_walkbyte(const char **string) -{ - return *((*string)++); -} +#define utf8_walkbyte(string) (*((*(string))++)) /* Does not validate the input, returns garbage if it's not UTF-8. */ uint32_t utf8_walk(const char **string) @@ -227,14 +224,16 @@ uint32_t utf8_walk(const char **string) ret = (ret << 6) | (utf8_walkbyte(string) & 0x3F); if (first >= 0xE0) + { ret = (ret << 6) | (utf8_walkbyte(string) & 0x3F); - if (first >= 0xF0) - ret = (ret << 6) | (utf8_walkbyte(string) & 0x3F); - - if (first >= 0xF0) - return ret | (first & 7) << 18; - if (first >= 0xE0) + if (first >= 0xF0) + { + ret = (ret << 6) | (utf8_walkbyte(string) & 0x3F); + return ret | (first & 7) << 18; + } return ret | (first & 15) << 12; + } + return ret | (first & 31) << 6; } @@ -273,37 +272,25 @@ bool utf16_to_char_string(const uint16_t *in, char *s, size_t len) return ret; } +#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE) /* Returned pointer MUST be freed by the caller if non-NULL. */ -static char* mb_to_mb_string_alloc(const char *str, +static char *mb_to_mb_string_alloc(const char *str, enum CodePage cp_in, enum CodePage cp_out) { char *path_buf = NULL; wchar_t *path_buf_wide = NULL; int path_buf_len = 0; - int path_buf_wide_len = 0; - - if (!str || !*str) - return NULL; - - (void)path_buf; - (void)path_buf_wide; - (void)path_buf_len; - (void)path_buf_wide_len; - -#if !defined(_WIN32) || defined(_XBOX) - /* assume string needs no modification if not on Windows */ - return strdup(str); -#else -#ifdef UNICODE - /* TODO/FIXME: Not implemented. */ - return strdup(str); -#else - - /* Windows 95 will return 0 from these functions with a UTF8 codepage set without MSLU. From an unknown MSDN version (others omit this info): - * - CP_UTF8 Windows 98/Me, Windows NT 4.0 and later: Translate using UTF-8. When this is set, dwFlags must be zero. - * - Windows 95: Under the Microsoft Layer for Unicode, MultiByteToWideChar also supports CP_UTF7 and CP_UTF8. + int path_buf_wide_len = MultiByteToWideChar(cp_in, 0, str, -1, NULL, 0); + + /* Windows 95 will return 0 from these functions with + * a UTF8 codepage set without MSLU. + * + * From an unknown MSDN version (others omit this info): + * - CP_UTF8 Windows 98/Me, Windows NT 4.0 and later: + * Translate using UTF-8. When this is set, dwFlags must be zero. + * - Windows 95: Under the Microsoft Layer for Unicode, + * MultiByteToWideChar also supports CP_UTF7 and CP_UTF8. */ - path_buf_wide_len = MultiByteToWideChar(cp_in, 0, str, -1, NULL, 0); if (path_buf_wide_len) { @@ -355,20 +342,37 @@ static char* mb_to_mb_string_alloc(const char *str, free(path_buf_wide); return NULL; -#endif -#endif } +#endif /* Returned pointer MUST be freed by the caller if non-NULL. */ char* utf8_to_local_string_alloc(const char *str) { - return mb_to_mb_string_alloc(str, CODEPAGE_UTF8, CODEPAGE_LOCAL); + if (str && *str) + { +#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE) + return mb_to_mb_string_alloc(str, CODEPAGE_UTF8, CODEPAGE_LOCAL); +#else + /* assume string needs no modification if not on Windows */ + return strdup(str); +#endif + } + return NULL; } /* Returned pointer MUST be freed by the caller if non-NULL. */ char* local_to_utf8_string_alloc(const char *str) { - return mb_to_mb_string_alloc(str, CODEPAGE_LOCAL, CODEPAGE_UTF8); + if (str && *str) + { +#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE) + return mb_to_mb_string_alloc(str, CODEPAGE_LOCAL, CODEPAGE_UTF8); +#else + /* assume string needs no modification if not on Windows */ + return strdup(str); +#endif + } + return NULL; } /* Returned pointer MUST be freed by the caller if non-NULL. */ @@ -447,52 +451,44 @@ wchar_t* utf8_to_utf16_string_alloc(const char *str) char* utf16_to_utf8_string_alloc(const wchar_t *str) { #ifdef _WIN32 - int len = 0; - int out_len = 0; + int len = 0; #else - size_t len = 0; - size_t out_len = 0; + size_t len = 0; #endif - char *buf = NULL; + char *buf = NULL; if (!str || !*str) return NULL; #ifdef _WIN32 - len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); - - if (len) { + UINT code_page = CP_UTF8; + len = WideCharToMultiByte(code_page, + 0, str, -1, NULL, 0, NULL, NULL); + + /* fallback to ANSI codepage instead */ + if (!len) + { + code_page = CP_ACP; + len = WideCharToMultiByte(code_page, + 0, str, -1, NULL, 0, NULL, NULL); + } + buf = (char*)calloc(len, sizeof(char)); if (!buf) return NULL; - out_len = WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, len, NULL, NULL); - } - else - { - /* fallback to ANSI codepage instead */ - len = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL); - - if (len) + if (WideCharToMultiByte(code_page, + 0, str, -1, buf, len, NULL, NULL) < 0) { - buf = (char*)calloc(len, sizeof(char)); - - if (!buf) - return NULL; - - out_len = WideCharToMultiByte(CP_ACP, 0, str, -1, buf, len, NULL, NULL); + free(buf); + return NULL; } } - - if (out_len < 0) - { - free(buf); - return NULL; - } #else - /* NOTE: For now, assume non-Windows platforms' locale is already UTF-8. */ + /* NOTE: For now, assume non-Windows platforms' + * locale is already UTF-8. */ len = wcstombs(NULL, str, 0) + 1; if (len) @@ -502,13 +498,11 @@ char* utf16_to_utf8_string_alloc(const wchar_t *str) if (!buf) return NULL; - out_len = wcstombs(buf, str, len); - } - - if (out_len == (size_t)-1) - { - free(buf); - return NULL; + if (wcstombs(buf, str, len) == (size_t)-1) + { + free(buf); + return NULL; + } } #endif diff --git a/libretro/libretro-common/file/file_path.c b/libretro/libretro-common/file/file_path.c index 0b4c4b1..9b85d76 100644 --- a/libretro/libretro-common/file/file_path.c +++ b/libretro/libretro-common/file/file_path.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2018 The RetroArch team +/* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (file_path.c). @@ -32,7 +32,10 @@ #include #include #include +#define VFS_FRONTEND +#include +/* TODO: There are probably some unnecessary things on this huge include list now but I'm too afraid to touch it */ #ifdef __APPLE__ #include #endif @@ -79,6 +82,11 @@ #include #endif +#if defined(PS2) +#include +#include +#endif + #if defined(__CELLOS_LV2__) #include #endif @@ -87,10 +95,16 @@ #define FIO_S_ISDIR SCE_S_ISDIR #endif -#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP) +#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP) || defined(PS2) #include /* stat() is defined here */ #endif +#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) +#ifdef __WINRT__ +#include +#endif +#endif + /* Assume W-functions do not work below Win2K and Xbox platforms */ #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX) @@ -100,98 +114,31 @@ #endif -enum stat_mode -{ - IS_DIRECTORY = 0, - IS_CHARACTER_SPECIAL, - IS_VALID -}; +static retro_vfs_stat_t path_stat_cb = NULL; +static retro_vfs_mkdir_t path_mkdir_cb = NULL; -static bool path_stat(const char *path, enum stat_mode mode, int32_t *size) +void path_vfs_init(const struct retro_vfs_interface_info* vfs_info) { -#if defined(VITA) || defined(PSP) - SceIoStat buf; - char *tmp = strdup(path); - size_t len = strlen(tmp); - if (tmp[len-1] == '/') - tmp[len-1]='\0'; - - if (sceIoGetstat(tmp, &buf) < 0) - { - free(tmp); - return false; - } - free(tmp); - -#elif defined(__CELLOS_LV2__) - CellFsStat buf; - if (cellFsStat(path, &buf) < 0) - return false; -#elif defined(_WIN32) - struct _stat buf; - char *path_local; - wchar_t *path_wide; - DWORD file_info; - - if (!path || !*path) - return false; + const struct retro_vfs_interface* + vfs_iface = vfs_info->iface; - (void)path_wide; - (void)path_local; - (void)file_info; + path_stat_cb = NULL; + path_mkdir_cb = NULL; -#if defined(LEGACY_WIN32) - path_local = utf8_to_local_string_alloc(path); - file_info = GetFileAttributes(path_local); - - _stat(path_local, &buf); - - if (path_local) - free(path_local); -#else - path_wide = utf8_to_utf16_string_alloc(path); - file_info = GetFileAttributesW(path_wide); - - _wstat(path_wide, &buf); - - if (path_wide) - free(path_wide); -#endif + if (vfs_info->required_interface_version < PATH_REQUIRED_VFS_VERSION || !vfs_iface) + return; - if (file_info == INVALID_FILE_ATTRIBUTES) - return false; -#else - struct stat buf; - if (stat(path, &buf) < 0) - return false; -#endif + path_stat_cb = vfs_iface->stat; + path_mkdir_cb = vfs_iface->mkdir; +} - if (size) - *size = (int32_t)buf.st_size; +#define path_stat_internal(path, size) ((path_stat_cb != NULL) ? path_stat_cb((path), (size)) : retro_vfs_stat_impl((path), (size))) - switch (mode) - { - case IS_DIRECTORY: -#if defined(VITA) || defined(PSP) - return FIO_S_ISDIR(buf.st_mode); -#elif defined(__CELLOS_LV2__) - return ((buf.st_mode & S_IFMT) == S_IFDIR); -#elif defined(_WIN32) - return (file_info & FILE_ATTRIBUTE_DIRECTORY); -#else - return S_ISDIR(buf.st_mode); -#endif - case IS_CHARACTER_SPECIAL: -#if defined(VITA) || defined(PSP) || defined(__CELLOS_LV2__) || defined(_WIN32) - return false; -#else - return S_ISCHR(buf.st_mode); -#endif - case IS_VALID: - return true; - } +#define path_mkdir_norecurse(dir) ((path_mkdir_cb != NULL) ? path_mkdir_cb((dir)) : retro_vfs_mkdir_impl((dir))) - return false; +int path_stat(const char *path) +{ + return path_stat_internal(path, NULL); } /** @@ -204,39 +151,28 @@ static bool path_stat(const char *path, enum stat_mode mode, int32_t *size) */ bool path_is_directory(const char *path) { - return path_stat(path, IS_DIRECTORY, NULL); + return (path_stat_internal(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0; } bool path_is_character_special(const char *path) { - return path_stat(path, IS_CHARACTER_SPECIAL, NULL); + return (path_stat_internal(path, NULL) & RETRO_VFS_STAT_IS_CHARACTER_SPECIAL) != 0; } bool path_is_valid(const char *path) { - return path_stat(path, IS_VALID, NULL); + return (path_stat_internal(path, NULL) & RETRO_VFS_STAT_IS_VALID) != 0; } int32_t path_get_size(const char *path) { int32_t filesize = 0; - if (path_stat(path, IS_VALID, &filesize)) + if (path_stat_internal(path, &filesize) != 0) return filesize; return -1; } -static bool path_mkdir_error(int ret) -{ -#if defined(VITA) - return (ret == SCE_ERROR_ERRNO_EEXIST); -#elif defined(PSP) || defined(_3DS) || defined(WIIU) - return (ret == -1); -#else - return (ret < 0 && errno == EEXIST); -#endif -} - /** * path_mkdir: * @dir : directory @@ -247,77 +183,65 @@ static bool path_mkdir_error(int ret) **/ bool path_mkdir(const char *dir) { - /* Use heap. Real chance of stack overflow if we recurse too hard. */ - const char *target = NULL; bool sret = false; bool norecurse = false; char *basedir = NULL; - if (dir && *dir) - basedir = strdup(dir); + if (!(dir && *dir)) + return false; + + /* Use heap. Real chance of stack + * overflow if we recurse too hard. */ + basedir = strdup(dir); if (!basedir) - return false; + return false; path_parent_dir(basedir); + if (!*basedir || !strcmp(basedir, dir)) - goto end; + { + free(basedir); + return false; + } - if (path_is_directory(basedir)) +#if defined(GEKKO) { - target = dir; - norecurse = true; + size_t len = strlen(basedir); + + /* path_parent_dir() keeps the trailing slash. + * On Wii, mkdir() fails if the path has a + * trailing slash... + * We must therefore remove it. */ + if (len > 0) + if (basedir[len - 1] == '/') + basedir[len - 1] = '\0'; } +#endif + + if (path_is_directory(basedir)) + norecurse = true; else { - target = basedir; sret = path_mkdir(basedir); if (sret) - { - target = dir; norecurse = true; - } } + free(basedir); + if (norecurse) { -#if defined(_WIN32) -#ifdef LEGACY_WIN32 - int ret = _mkdir(dir); -#else - wchar_t *dirW = utf8_to_utf16_string_alloc(dir); - int ret = -1; - - if (dirW) - { - ret = _wmkdir(dirW); - free(dirW); - } -#endif -#elif defined(IOS) - int ret = mkdir(dir, 0755); -#elif defined(VITA) || defined(PSP) - int ret = sceIoMkdir(dir, 0777); -#elif defined(__QNX__) - int ret = mkdir(dir, 0777); -#else - int ret = mkdir(dir, 0750); -#endif + int ret = path_mkdir_norecurse(dir); /* Don't treat this as an error. */ - if (path_mkdir_error(ret) && path_is_directory(dir)) - ret = 0; + if (ret == -2 && path_is_directory(dir)) + return true; - if (ret < 0) - printf("mkdir(%s) error: %s.\n", dir, strerror(errno)); - sret = (ret == 0); + return (ret == 0); } -end: - if (target && !sret) - printf("Failed to create directory: \"%s\".\n", target); - free(basedir); return sret; } @@ -336,19 +260,20 @@ const char *path_get_archive_delim(const char *path) const char *last = find_last_slash(path); const char *delim = NULL; - if (last) - { - delim = strcasestr(last, ".zip#"); + if (!last) + return NULL; - if (!delim) - delim = strcasestr(last, ".apk#"); - } + /* Test if it's .zip */ + delim = strcasestr(last, ".zip#"); + + if (!delim) /* If it's not a .zip, test if it's .apk */ + delim = strcasestr(last, ".apk#"); if (delim) return delim + 4; - if (last) - delim = strcasestr(last, ".7z#"); + /* If it's not a .zip or .apk file, test if it's .7z */ + delim = strcasestr(last, ".7z#"); if (delim) return delim + 3; @@ -367,32 +292,35 @@ const char *path_get_archive_delim(const char *path) */ const char *path_get_extension(const char *path) { - const char *ext = !string_is_empty(path) - ? strrchr(path_basename(path), '.') : NULL; - if (!ext) - return ""; - return ext + 1; + const char *ext; + if (!string_is_empty(path) && ((ext = strrchr(path_basename(path), '.')))) + return ext + 1; + return ""; } /** * path_remove_extension: * @path : path * - * Removes the extension from the path and returns the result. - * Removes all text after and including the last '.'. + * Mutates path by removing its extension. Removes all + * text after and including the last '.'. * Only '.'s after the last slash are considered. * - * Returns: path with the extension part removed. + * Returns: + * 1) If path has an extension, returns path with the + * extension removed. + * 2) If there is no extension, returns NULL. + * 3) If path is empty or NULL, returns NULL */ char *path_remove_extension(char *path) { - char *last = !string_is_empty(path) + char *last = !string_is_empty(path) ? (char*)strrchr(path_basename(path), '.') : NULL; if (!last) return NULL; if (*last) *last = '\0'; - return last; + return path; } /** @@ -478,10 +406,9 @@ char *find_last_slash(const char *str) #ifdef _WIN32 const char *backslash = strrchr(str, '\\'); - if (backslash && ((slash && backslash > slash) || !slash)) - slash = backslash; + if (!slash || (backslash > slash)) + return (char*)backslash; #endif - return (char*)slash; } @@ -495,11 +422,18 @@ char *find_last_slash(const char *str) **/ void fill_pathname_slash(char *path, size_t size) { - size_t path_len = strlen(path); + size_t path_len; const char *last_slash = find_last_slash(path); + if (!last_slash) + { + strlcat(path, path_default_slash(), size); + return; + } + + path_len = strlen(path); /* Try to preserve slash type. */ - if (last_slash && (last_slash != (path + path_len - 1))) + if (last_slash != (path + path_len - 1)) { char join_str[2]; @@ -508,8 +442,6 @@ void fill_pathname_slash(char *path, size_t size) strlcpy(join_str, last_slash, sizeof(join_str)); strlcat(path, join_str, size); } - else if (!last_slash) - strlcat(path, path_default_slash(), size); } /** @@ -558,13 +490,15 @@ void fill_pathname_base(char *out, const char *in_path, size_t size) strlcpy(out, ptr, size); } -void fill_pathname_base_noext(char *out, const char *in_path, size_t size) +void fill_pathname_base_noext(char *out, + const char *in_path, size_t size) { fill_pathname_base(out, in_path, size); path_remove_extension(out); } -void fill_pathname_base_ext(char *out, const char *in_path, const char *ext, +void fill_pathname_base_ext(char *out, + const char *in_path, const char *ext, size_t size) { fill_pathname_base_noext(out, in_path, size); @@ -609,25 +543,28 @@ void fill_pathname_basedir_noext(char *out_dir, bool fill_pathname_parent_dir_name(char *out_dir, const char *in_dir, size_t size) { - char *temp = strdup(in_dir); - char *last = find_last_slash(temp); - bool ret = false; + bool success = false; + char *temp = strdup(in_dir); + char *last = find_last_slash(temp); - *last = '\0'; + if (last && last[1] == 0) + { + *last = '\0'; + last = find_last_slash(temp); + } - in_dir = find_last_slash(temp); + if (last) + *last = '\0'; - if (in_dir && in_dir + 1) - { + in_dir = find_last_slash(temp); + + success = in_dir && in_dir[1]; + + if (success) strlcpy(out_dir, in_dir + 1, size); - ret = true; - } - else - ret = false; free(temp); - - return ret; + return success; } /** @@ -638,6 +575,7 @@ bool fill_pathname_parent_dir_name(char *out_dir, * * Copies parent directory of @in_dir into @out_dir. * Assumes @in_dir is a directory. Keeps trailing '/'. + * If the path was already at the root directory, @out_dir will be an empty string. **/ void fill_pathname_parent_dir(char *out_dir, const char *in_dir, size_t size) @@ -662,10 +600,11 @@ void fill_pathname_parent_dir(char *out_dir, void fill_dated_filename(char *out_filename, const char *ext, size_t size) { - time_t cur_time = time(NULL); + time_t cur_time = time(NULL); + const struct tm* tm_ = localtime(&cur_time); strftime(out_filename, size, - "RetroArch-%m%d-%H%M%S.", localtime(&cur_time)); + "RetroArch-%m%d-%H%M%S", tm_); strlcat(out_filename, ext, size); } @@ -686,15 +625,24 @@ void fill_str_dated_filename(char *out_filename, const char *in_str, const char *ext, size_t size) { char format[256]; - time_t cur_time = time(NULL); + time_t cur_time = time(NULL); + const struct tm* tm_ = localtime(&cur_time); - format[0] = '\0'; + format[0] = '\0'; - strftime(format, sizeof(format), "-%y%m%d-%H%M%S.", localtime(&cur_time)); + if (string_is_empty(ext)) + { + strftime(format, sizeof(format), "-%y%m%d-%H%M%S", tm_); + fill_pathname_noext(out_filename, in_str, format, size); + } + else + { + strftime(format, sizeof(format), "-%y%m%d-%H%M%S.", tm_); - fill_pathname_join_concat_noext(out_filename, - in_str, format, ext, - size); + fill_pathname_join_concat_noext(out_filename, + in_str, format, ext, + size); + } } /** @@ -724,12 +672,34 @@ void path_basedir(char *path) * * Extracts parent directory by mutating path. * Assumes that path is a directory. Keeps trailing '/'. + * If the path was already at the root directory, returns empty string **/ void path_parent_dir(char *path) { - size_t len = strlen(path); + size_t len = 0; + + if (!path) + return; + + len = strlen(path); + if (len && path_char_is_slash(path[len - 1])) + { + bool path_was_absolute = path_is_absolute(path); + path[len - 1] = '\0'; + + if (path_was_absolute && !find_last_slash(path)) + { + /* We removed the only slash from what used to be an absolute path. + * On Linux, this goes from "/" to an empty string and everything works fine, + * but on Windows, we went from C:\ to C:, which is not a valid path and that later + * gets errornously treated as a relative one by path_basedir and returns "./". + * What we really wanted is an empty string. */ + path[0] = '\0'; + return; + } + } path_basedir(path); } @@ -743,16 +713,17 @@ void path_parent_dir(char *path) **/ const char *path_basename(const char *path) { - /* We cut either at the first compression-related hash - * or the last slash; whichever comes last */ - const char *last = find_last_slash(path); + /* We cut at the first compression-related hash */ const char *delim = path_get_archive_delim(path); - if (delim) return delim + 1; - if (last) - return last + 1; + { + /* We cut at the last slash */ + const char *last = find_last_slash(path); + if (last) + return last + 1; + } return path; } @@ -788,7 +759,8 @@ bool path_is_absolute(const char *path) * @buf : buffer for path * @size : size of buffer * - * Turns relative paths into absolute path. + * Turns relative paths into absolute paths and + * resolves use of "." and ".." in absolute paths. * If relative, rebases on current working dir. **/ void path_resolve_realpath(char *buf, size_t size) @@ -815,6 +787,51 @@ void path_resolve_realpath(char *buf, size_t size) #endif } +/** + * path_relative_to: + * @out : buffer to write the relative path to + * @path : path to be expressed relatively + * @base : base directory to start out on + * @size : size of output buffer + * + * Turns @path into a path relative to @base and writes it to @out. + * + * @base is assumed to be a base directory, i.e. a path ending with '/' or '\'. + * Both @path and @base are assumed to be absolute paths without "." or "..". + * + * E.g. path /a/b/e/f.cg with base /a/b/c/d/ turns into ../../e/f.cg + **/ +void path_relative_to(char *out, + const char *path, const char *base, size_t size) +{ + unsigned i; + const char *trimmed_path, *trimmed_base; + +#ifdef _WIN32 + /* For different drives, return absolute path */ + if (strlen(path) >= 2 && strlen(base) >= 2 + && path[1] == ':' && base[1] == ':' + && path[0] != base[0]) + { + out[0] = '\0'; + strlcat(out, path, size); + } +#endif + + /* Trim common beginning */ + for (i = 0; path[i] && base[i] && path[i] == base[i]; ) + i++; + trimmed_path = path+i; + trimmed_base = base+i; + + /* Each segment of base turns into ".." */ + out[0] = '\0'; + for (i = 0; trimmed_base[i]; i++) + if (trimmed_base[i] == '/' || trimmed_base[i] == '\\') + strlcat(out, "../", size); /* Use '/' as universal separator */ + strlcat(out, trimmed_path, size); +} + /** * fill_pathname_resolve_relative: * @out_path : output path @@ -838,6 +855,7 @@ void fill_pathname_resolve_relative(char *out_path, fill_pathname_basedir(out_path, in_refpath, size); strlcat(out_path, in_path, size); + path_resolve_realpath(out_path, size); } /** @@ -876,8 +894,7 @@ void fill_pathname_join_special_ext(char *out_path, strlcat(out_path, ext, size); } -void fill_pathname_join_concat_noext( - char *out_path, +void fill_pathname_join_concat_noext(char *out_path, const char *dir, const char *path, const char *concat, size_t size) @@ -902,7 +919,6 @@ void fill_pathname_join_noext(char *out_path, path_remove_extension(out_path); } - /** * fill_pathname_join_delim: * @out_path : output path @@ -917,12 +933,18 @@ void fill_pathname_join_noext(char *out_path, void fill_pathname_join_delim(char *out_path, const char *dir, const char *path, const char delim, size_t size) { - size_t copied = strlcpy(out_path, dir, size); + size_t copied; + /* behavior of strlcpy is undefined if dst and src overlap */ + if (out_path == dir) + copied = strlen(dir); + else + copied = strlcpy(out_path, dir, size); out_path[copied] = delim; out_path[copied+1] = '\0'; - strlcat(out_path, path, size); + if (path) + strlcat(out_path, path, size); } void fill_pathname_join_delim_concat(char *out_path, const char *dir, @@ -972,45 +994,67 @@ void fill_pathname_expand_special(char *out_path, const char *in_path, size_t size) { #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) - if (*in_path == '~') + if (in_path[0] == '~') { - const char *home = getenv("HOME"); - if (home) + char *home_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + + home_dir[0] = '\0'; + + fill_pathname_home_dir(home_dir, + PATH_MAX_LENGTH * sizeof(char)); + + if (*home_dir) { - size_t src_size = strlcpy(out_path, home, size); + size_t src_size = strlcpy(out_path, home_dir, size); retro_assert(src_size < size); out_path += src_size; size -= src_size; - in_path++; + + if (!path_char_is_slash(out_path[-1])) + { + src_size = strlcpy(out_path, path_default_slash(), size); + retro_assert(src_size < size); + + out_path += src_size; + size -= src_size; + } + + in_path += 2; } + + free(home_dir); } - else if ((in_path[0] == ':') && - ( - (in_path[1] == '/') -#ifdef _WIN32 - || (in_path[1] == '\\') -#endif - ) - ) + else if (in_path[0] == ':') { - size_t src_size; char *application_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); application_dir[0] = '\0'; - fill_pathname_application_path(application_dir, + fill_pathname_application_dir(application_dir, PATH_MAX_LENGTH * sizeof(char)); - path_basedir_wrapper(application_dir); - src_size = strlcpy(out_path, application_dir, size); - retro_assert(src_size < size); + if (*application_dir) + { + size_t src_size = strlcpy(out_path, application_dir, size); + retro_assert(src_size < size); + + out_path += src_size; + size -= src_size; - free(application_dir); + if (!path_char_is_slash(out_path[-1])) + { + src_size = strlcpy(out_path, path_default_slash(), size); + retro_assert(src_size < size); - out_path += src_size; - size -= src_size; - in_path += 2; + out_path += src_size; + size -= src_size; + } + + in_path += 2; + } + + free(application_dir); } #endif @@ -1025,7 +1069,7 @@ void fill_pathname_abbreviate_special(char *out_path, const char *candidates[3]; const char *notations[3]; char *application_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); - const char *home = getenv("HOME"); + char *home_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); application_dir[0] = '\0'; @@ -1037,16 +1081,17 @@ void fill_pathname_abbreviate_special(char *out_path, /* ugly hack - use application_dir pointer * before filling it in. C89 reasons */ candidates[0] = application_dir; - candidates[1] = home; + candidates[1] = home_dir; candidates[2] = NULL; notations [0] = ":"; notations [1] = "~"; notations [2] = NULL; - fill_pathname_application_path(application_dir, + fill_pathname_application_dir(application_dir, + PATH_MAX_LENGTH * sizeof(char)); + fill_pathname_home_dir(home_dir, PATH_MAX_LENGTH * sizeof(char)); - path_basedir_wrapper(application_dir); for (i = 0; candidates[i]; i++) { @@ -1074,6 +1119,7 @@ void fill_pathname_abbreviate_special(char *out_path, } free(application_dir); + free(home_dir); #endif retro_assert(strlcpy(out_path, in_path, size) < size); @@ -1115,7 +1161,7 @@ void fill_pathname_application_path(char *s, size_t len) CFBundleRef bundle = CFBundleGetMainBundle(); #endif #ifdef _WIN32 - DWORD ret; + DWORD ret = 0; wchar_t wstr[PATH_MAX_LENGTH] = {0}; #endif #ifdef __HAIKU__ @@ -1127,11 +1173,11 @@ void fill_pathname_application_path(char *s, size_t len) if (!len) return; -#ifdef _WIN32 +#if defined(_WIN32) #ifdef LEGACY_WIN32 - ret = GetModuleFileNameA(GetModuleHandle(NULL), s, len); + ret = GetModuleFileNameA(NULL, s, len); #else - ret = GetModuleFileNameW(GetModuleHandle(NULL), wstr, ARRAY_SIZE(wstr)); + ret = GetModuleFileNameW(NULL, wstr, ARRAY_SIZE(wstr)); if (*wstr) { @@ -1148,7 +1194,7 @@ void fill_pathname_application_path(char *s, size_t len) #elif defined(__APPLE__) if (bundle) { - CFURLRef bundle_url = CFBundleCopyBundleURL(bundle); + CFURLRef bundle_url = CFBundleCopyBundleURL(bundle); CFStringRef bundle_path = CFURLCopyPath(bundle_url); CFStringGetCString(bundle_path, s, len, kCFStringEncodingUTF8); CFRelease(bundle_path); @@ -1169,7 +1215,7 @@ void fill_pathname_application_path(char *s, size_t len) #elif defined(__QNX__) char *buff = malloc(len); - if(_cmdname(buff)) + if (_cmdname(buff)) strlcpy(s, buff, len); free(buff); @@ -1200,4 +1246,44 @@ void fill_pathname_application_path(char *s, size_t len) } #endif } + +void fill_pathname_application_dir(char *s, size_t len) +{ +#ifdef __WINRT__ + strlcpy(s, uwp_dir_install, len); +#else + fill_pathname_application_path(s, len); + path_basedir_wrapper(s); #endif +} + +void fill_pathname_home_dir(char *s, size_t len) +{ +#ifdef __WINRT__ + strlcpy(s, uwp_dir_data, len); +#else + const char *home = getenv("HOME"); + if (home) + strlcpy(s, home, len); + else + *s = 0; +#endif +} +#endif + +bool is_path_accessible_using_standard_io(const char *path) +{ +#ifdef __WINRT__ + bool result; + size_t path_sizeof = PATH_MAX_LENGTH * sizeof(char); + char *relative_path_abbrev = (char*)malloc(path_sizeof); + fill_pathname_abbreviate_special(relative_path_abbrev, path, path_sizeof); + + result = strlen(relative_path_abbrev) >= 2 && (relative_path_abbrev[0] == ':' || relative_path_abbrev[0] == '~') && path_char_is_slash(relative_path_abbrev[1]); + + free(relative_path_abbrev); + return result; +#else + return true; +#endif +} diff --git a/libretro/libretro-common/file/retro_dirent.c b/libretro/libretro-common/file/retro_dirent.c index 0b6ecad..752cbda 100644 --- a/libretro/libretro-common/file/retro_dirent.c +++ b/libretro/libretro-common/file/retro_dirent.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2018 The RetroArch team +/* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (retro_dirent.c). @@ -28,273 +28,91 @@ #include #include -#include -#include -#include +#define VFS_FRONTEND +#include -#if defined(_WIN32) -# ifdef _MSC_VER -# define setmode _setmode -# endif -#include -# ifdef _XBOX -# include -# define INVALID_FILE_ATTRIBUTES -1 -# else -# include -# include -# include -# include -# endif -#elif defined(VITA) -# include -# include -#include -#else -# if defined(PSP) -# include -# endif -# include -# include -# include -# include -#endif +static retro_vfs_opendir_t dirent_opendir_cb = NULL; +static retro_vfs_readdir_t dirent_readdir_cb = NULL; +static retro_vfs_dirent_get_name_t dirent_dirent_get_name_cb = NULL; +static retro_vfs_dirent_is_dir_t dirent_dirent_is_dir_cb = NULL; +static retro_vfs_closedir_t dirent_closedir_cb = NULL; -#ifdef __CELLOS_LV2__ -#include -#endif - -#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP) -#include /* stat() is defined here */ -#endif - -#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX) -#ifndef LEGACY_WIN32 -#define LEGACY_WIN32 -#endif -#endif - -struct RDIR -{ -#if defined(_WIN32) -#if defined(LEGACY_WIN32) - WIN32_FIND_DATA entry; -#else - WIN32_FIND_DATAW entry; -#endif - HANDLE directory; - bool next; - char path[PATH_MAX_LENGTH]; -#elif defined(VITA) || defined(PSP) - SceUID directory; - SceIoDirent entry; -#elif defined(__CELLOS_LV2__) - CellFsErrno error; - int directory; - CellFsDirent entry; -#else - DIR *directory; - const struct dirent *entry; -#endif -}; - -struct RDIR *retro_opendir(const char *name) +void dirent_vfs_init(const struct retro_vfs_interface_info* vfs_info) { -#if defined(_WIN32) - char path_buf[1024]; - char *path_local = NULL; - wchar_t *path_wide = NULL; - unsigned path_len; -#endif - struct RDIR *rdir; - - /*Reject null or empty string paths*/ - if (!name||(*name==0)) - return NULL; - - /*Allocate RDIR struct. Tidied later with retro_closedir*/ - rdir = (struct RDIR*)calloc(1, sizeof(*rdir)); - if (!rdir) - return NULL; + const struct retro_vfs_interface* vfs_iface; -#if defined(_WIN32) - (void)path_wide; - (void)path_local; + dirent_opendir_cb = NULL; + dirent_readdir_cb = NULL; + dirent_dirent_get_name_cb = NULL; + dirent_dirent_is_dir_cb = NULL; + dirent_closedir_cb = NULL; - path_buf[0] = '\0'; - path_len = strlen(name); + vfs_iface = vfs_info->iface; - /* Non-NT platforms don't like extra slashes in the path */ - if (name[path_len - 1] == '\\') - snprintf(path_buf, sizeof(path_buf), "%s*", name); - else - snprintf(path_buf, sizeof(path_buf), "%s\\*", name); - -#if defined(LEGACY_WIN32) - path_local = utf8_to_local_string_alloc(path_buf); - rdir->directory = FindFirstFile(path_local, &rdir->entry); - - if (path_local) - free(path_local); -#else - path_wide = utf8_to_utf16_string_alloc(path_buf); - rdir->directory = FindFirstFileW(path_wide, &rdir->entry); - - if (path_wide) - free(path_wide); -#endif + if (vfs_info->required_interface_version < DIRENT_REQUIRED_VFS_VERSION || !vfs_iface) + return; -#elif defined(VITA) || defined(PSP) - rdir->directory = sceIoDopen(name); -#elif defined(_3DS) - rdir->directory = !string_is_empty(name) ? opendir(name) : NULL; - rdir->entry = NULL; -#elif defined(__CELLOS_LV2__) - rdir->error = cellFsOpendir(name, &rdir->directory); -#else - rdir->directory = opendir(name); - rdir->entry = NULL; -#endif + dirent_opendir_cb = vfs_iface->opendir; + dirent_readdir_cb = vfs_iface->readdir; + dirent_dirent_get_name_cb = vfs_iface->dirent_get_name; + dirent_dirent_is_dir_cb = vfs_iface->dirent_is_dir; + dirent_closedir_cb = vfs_iface->closedir; +} - if (rdir->directory) - return rdir; +struct RDIR *retro_opendir_include_hidden(const char *name, bool include_hidden) +{ + if (dirent_opendir_cb != NULL) + return (struct RDIR *)dirent_opendir_cb(name, include_hidden); + return (struct RDIR *)retro_vfs_opendir_impl(name, include_hidden); +} - free(rdir); - return NULL; +struct RDIR *retro_opendir(const char *name) +{ + return retro_opendir_include_hidden(name, false); } bool retro_dirent_error(struct RDIR *rdir) { -#if defined(_WIN32) - return (rdir->directory == INVALID_HANDLE_VALUE); -#elif defined(VITA) || defined(PSP) - return (rdir->directory < 0); -#elif defined(__CELLOS_LV2__) - return (rdir->error != CELL_FS_SUCCEEDED); -#else - return !(rdir->directory); -#endif + /* Left for compatibility */ + return false; } int retro_readdir(struct RDIR *rdir) { -#if defined(_WIN32) - if(rdir->next) -#if defined(LEGACY_WIN32) - return (FindNextFile(rdir->directory, &rdir->entry) != 0); -#else - return (FindNextFileW(rdir->directory, &rdir->entry) != 0); -#endif - - rdir->next = true; - return (rdir->directory != INVALID_HANDLE_VALUE); -#elif defined(VITA) || defined(PSP) - return (sceIoDread(rdir->directory, &rdir->entry) > 0); -#elif defined(__CELLOS_LV2__) - uint64_t nread; - rdir->error = cellFsReaddir(rdir->directory, &rdir->entry, &nread); - return (nread != 0); -#else - return ((rdir->entry = readdir(rdir->directory)) != NULL); -#endif + if (dirent_readdir_cb != NULL) + return dirent_readdir_cb((struct retro_vfs_dir_handle *)rdir); + return retro_vfs_readdir_impl((struct retro_vfs_dir_handle *)rdir); } const char *retro_dirent_get_name(struct RDIR *rdir) { -#if defined(_WIN32) -#if defined(LEGACY_WIN32) - char *name_local = local_to_utf8_string_alloc(rdir->entry.cFileName); - memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName)); - strlcpy(rdir->entry.cFileName, name_local, sizeof(rdir->entry.cFileName)); - - if (name_local) - free(name_local); -#else - char *name = utf16_to_utf8_string_alloc(rdir->entry.cFileName); - memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName)); - strlcpy((char*)rdir->entry.cFileName, name, sizeof(rdir->entry.cFileName)); - - if (name) - free(name); -#endif - return (char*)rdir->entry.cFileName; -#elif defined(VITA) || defined(PSP) || defined(__CELLOS_LV2__) - return rdir->entry.d_name; -#else - - return rdir->entry->d_name; -#endif + if (dirent_dirent_get_name_cb != NULL) + return dirent_dirent_get_name_cb((struct retro_vfs_dir_handle *)rdir); + return retro_vfs_dirent_get_name_impl((struct retro_vfs_dir_handle *)rdir); } /** * * retro_dirent_is_dir: * @rdir : pointer to the directory entry. - * @path : path to the directory entry. + * @unused : deprecated, included for compatibility reasons, pass NULL * * Is the directory listing entry a directory? * * Returns: true if directory listing entry is * a directory, false if not. */ -bool retro_dirent_is_dir(struct RDIR *rdir, const char *path) +bool retro_dirent_is_dir(struct RDIR *rdir, const char *unused) { -#if defined(_WIN32) - const WIN32_FIND_DATA *entry = (const WIN32_FIND_DATA*)&rdir->entry; - return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; -#elif defined(PSP) || defined(VITA) - const SceIoDirent *entry = (const SceIoDirent*)&rdir->entry; -#if defined(PSP) - return (entry->d_stat.st_attr & FIO_SO_IFDIR) == FIO_SO_IFDIR; -#elif defined(VITA) - return SCE_S_ISDIR(entry->d_stat.st_mode); -#endif -#elif defined(__CELLOS_LV2__) - CellFsDirent *entry = (CellFsDirent*)&rdir->entry; - return (entry->d_type == CELL_FS_TYPE_DIRECTORY); -#else - struct stat buf; -#if defined(DT_DIR) - const struct dirent *entry = (const struct dirent*)rdir->entry; - if (entry->d_type == DT_DIR) - return true; - /* This can happen on certain file systems. */ - if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) - return false; -#endif - /* dirent struct doesn't have d_type, do it the slow way ... */ - if (stat(path, &buf) < 0) - return false; - return S_ISDIR(buf.st_mode); -#endif -} - -void retro_dirent_include_hidden(struct RDIR *rdir, bool include_hidden) -{ -#ifdef _WIN32 - if (include_hidden) - rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN; - else - rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN; -#endif + if (dirent_dirent_is_dir_cb != NULL) + return dirent_dirent_is_dir_cb((struct retro_vfs_dir_handle *)rdir); + return retro_vfs_dirent_is_dir_impl((struct retro_vfs_dir_handle *)rdir); } void retro_closedir(struct RDIR *rdir) { - if (!rdir) - return; - -#if defined(_WIN32) - if (rdir->directory != INVALID_HANDLE_VALUE) - FindClose(rdir->directory); -#elif defined(VITA) || defined(PSP) - sceIoDclose(rdir->directory); -#elif defined(__CELLOS_LV2__) - rdir->error = cellFsClosedir(rdir->directory); -#else - if (rdir->directory) - closedir(rdir->directory); -#endif - - free(rdir); + if (dirent_closedir_cb != NULL) + dirent_closedir_cb((struct retro_vfs_dir_handle *)rdir); + else + retro_vfs_closedir_impl((struct retro_vfs_dir_handle *)rdir); } diff --git a/libretro/libretro-common/include/compat/msvc.h b/libretro/libretro-common/include/compat/msvc.h index 822c973..4681b12 100644 --- a/libretro/libretro-common/include/compat/msvc.h +++ b/libretro/libretro-common/include/compat/msvc.h @@ -39,8 +39,8 @@ extern "C" { int c99_snprintf_retro__(char *outBuf, size_t size, const char *format, ...); #endif -/* Pre-MSVC 2010 compilers don't implement vsnprintf in a cross-platform manner? Not sure about this one. */ -#if _MSC_VER < 1600 +/* Pre-MSVC 2008 compilers don't implement vsnprintf in a cross-platform manner? Not sure about this one. */ +#if _MSC_VER < 1500 #include #include #ifndef vsnprintf @@ -56,6 +56,8 @@ extern "C" { #undef UNICODE /* Do not bother with UNICODE at this time. */ #include #include + +#define _USE_MATH_DEFINES #include /* Python headers defines ssize_t and sets HAVE_SSIZE_T. @@ -125,4 +127,3 @@ typedef int ssize_t; #endif #endif - diff --git a/libretro/libretro-common/include/compat/msvc/stdint.h b/libretro/libretro-common/include/compat/msvc/stdint.h index c791176..7c91a68 100644 --- a/libretro/libretro-common/include/compat/msvc/stdint.h +++ b/libretro/libretro-common/include/compat/msvc/stdint.h @@ -67,7 +67,6 @@ extern "C" { # endif #endif - /* 7.18.1 Integer types. */ /* 7.18.1.1 Exact-width integer types. */ @@ -94,7 +93,6 @@ extern "C" { typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; - /* 7.18.1.2 Minimum-width integer types. */ typedef int8_t int_least8_t; typedef int16_t int_least16_t; @@ -255,4 +253,3 @@ typedef uint64_t uintmax_t; #endif #endif - diff --git a/libretro/libretro-common/include/compat/posix_string.h b/libretro/libretro-common/include/compat/posix_string.h index 9f56322..f4380c3 100644 --- a/libretro/libretro-common/include/compat/posix_string.h +++ b/libretro/libretro-common/include/compat/posix_string.h @@ -29,6 +29,10 @@ #include #endif +#if defined(PS2) +#include +#endif + RETRO_BEGIN_DECLS #ifdef _WIN32 @@ -55,7 +59,6 @@ int isblank(int c); #endif - RETRO_END_DECLS #endif diff --git a/libretro/libretro-common/include/compat/strcasestr.h b/libretro/libretro-common/include/compat/strcasestr.h index f849593..c26de9e 100644 --- a/libretro/libretro-common/include/compat/strcasestr.h +++ b/libretro/libretro-common/include/compat/strcasestr.h @@ -25,6 +25,10 @@ #include +#if defined(PS2) +#include +#endif + #if defined(RARCH_INTERNAL) && defined(HAVE_CONFIG_H) #include "../../../config.h" #endif @@ -46,4 +50,3 @@ RETRO_END_DECLS #endif #endif - diff --git a/libretro/libretro-common/include/compat/strl.h b/libretro/libretro-common/include/compat/strl.h index 290498d..c70f119 100644 --- a/libretro/libretro-common/include/compat/strl.h +++ b/libretro/libretro-common/include/compat/strl.h @@ -57,4 +57,3 @@ char *strldup(const char *s, size_t n); RETRO_END_DECLS #endif - diff --git a/libretro/libretro-common/include/file/file_path.h b/libretro/libretro-common/include/file/file_path.h index a7ee1e7..31b3ce8 100644 --- a/libretro/libretro-common/include/file/file_path.h +++ b/libretro/libretro-common/include/file/file_path.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2018 The RetroArch team +/* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (file_path.h). @@ -28,12 +28,17 @@ #include #include +#include #include #include RETRO_BEGIN_DECLS +#define PATH_REQUIRED_VFS_VERSION 3 + +void path_vfs_init(const struct retro_vfs_interface_info* vfs_info); + /* Order in this enum is equivalent to negative sort order in filelist * (i.e. DIRECTORY is on top of PLAIN_FILE) */ enum @@ -46,7 +51,6 @@ enum RARCH_FILE_UNSUPPORTED }; - /** * path_is_compressed_file: * @path : path @@ -100,11 +104,15 @@ const char *path_get_extension(const char *path); * path_remove_extension: * @path : path * - * Removes the extension from the path and returns the result. - * Removes all text after and including the last '.'. + * Mutates path by removing its extension. Removes all + * text after and including the last '.'. * Only '.'s after the last slash are considered. * - * Returns: path with the extension part removed. + * Returns: + * 1) If path has an extension, returns path with the + * extension removed. + * 2) If there is no extension, returns NULL. + * 3) If path is empty or NULL, returns NULL */ char *path_remove_extension(char *path); @@ -133,6 +141,7 @@ void path_basedir(char *path); * * Extracts parent directory by mutating path. * Assumes that path is a directory. Keeps trailing '/'. + * If the path was already at the root directory, returns empty string **/ void path_parent_dir(char *path); @@ -141,11 +150,28 @@ void path_parent_dir(char *path); * @buf : buffer for path * @size : size of buffer * - * Turns relative paths into absolute path. + * Turns relative paths into absolute paths and + * resolves use of "." and ".." in absolute paths. * If relative, rebases on current working dir. **/ void path_resolve_realpath(char *buf, size_t size); +/** + * path_relative_to: + * @out : buffer to write the relative path to + * @path : path to be expressed relatively + * @base : relative to this + * @size : size of output buffer + * + * Turns @path into a path relative to @base and writes it to @out. + * + * @base is assumed to be a base directory, i.e. a path ending with '/' or '\'. + * Both @path and @base are assumed to be absolute paths without "." or "..". + * + * E.g. path /a/b/e/f.cgp with base /a/b/c/d/ turns into ../../e/f.cgp + **/ +void path_relative_to(char *out, const char *path, const char *base, size_t size); + /** * path_is_absolute: * @path : path @@ -311,6 +337,7 @@ bool fill_pathname_parent_dir_name(char *out_dir, * * Copies parent directory of @in_dir into @out_dir. * Assumes @in_dir is a directory. Keeps trailing '/'. + * If the path was already at the root directory, @out_dir will be an empty string. **/ void fill_pathname_parent_dir(char *out_dir, const char *in_dir, size_t size); @@ -349,8 +376,7 @@ void fill_pathname_join_special_ext(char *out_path, const char *last, const char *ext, size_t size); -void fill_pathname_join_concat_noext( - char *out_path, +void fill_pathname_join_concat_noext(char *out_path, const char *dir, const char *path, const char *concat, size_t size); @@ -432,7 +458,7 @@ void path_basedir_wrapper(char *path); #endif /** - * path_default_slash: + * path_default_slash and path_default_slash_c: * * Gets the default slash separator. * @@ -440,8 +466,10 @@ void path_basedir_wrapper(char *path); */ #ifdef _WIN32 #define path_default_slash() "\\" +#define path_default_slash_c() '\\' #else #define path_default_slash() "/" +#define path_default_slash_c() '/' #endif /** @@ -456,6 +484,8 @@ void fill_pathname_slash(char *path, size_t size); #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) void fill_pathname_application_path(char *buf, size_t size); +void fill_pathname_application_dir(char *buf, size_t size); +void fill_pathname_home_dir(char *buf, size_t size); #endif /** @@ -480,10 +510,14 @@ bool path_is_directory(const char *path); bool path_is_character_special(const char *path); +int path_stat(const char *path); + bool path_is_valid(const char *path); int32_t path_get_size(const char *path); +bool is_path_accessible_using_standard_io(const char *path); + RETRO_END_DECLS #endif diff --git a/libretro/libretro-common/include/retro_assert.h b/libretro/libretro-common/include/retro_assert.h index 9f3abde..13db8cf 100644 --- a/libretro/libretro-common/include/retro_assert.h +++ b/libretro/libretro-common/include/retro_assert.h @@ -26,6 +26,7 @@ #include #ifdef RARCH_INTERNAL +#include #define retro_assert(cond) do { \ if (!(cond)) { printf("Assertion failed at %s:%d.\n", __FILE__, __LINE__); abort(); } \ } while(0) diff --git a/libretro/libretro-common/include/retro_common.h b/libretro/libretro-common/include/retro_common.h index e4804fa..9a1fd5f 100644 --- a/libretro/libretro-common/include/retro_common.h +++ b/libretro/libretro-common/include/retro_common.h @@ -34,4 +34,3 @@ in a public API, you may need this. #include #endif - diff --git a/libretro/libretro-common/include/retro_common_api.h b/libretro/libretro-common/include/retro_common_api.h index c3b3f49..fc4f745 100644 --- a/libretro/libretro-common/include/retro_common_api.h +++ b/libretro/libretro-common/include/retro_common_api.h @@ -113,6 +113,5 @@ Of course, another school of thought is that you should do as little damage as p in as few places as possible... */ - /* _LIBRETRO_COMMON_RETRO_COMMON_API_H */ #endif diff --git a/libretro/libretro-common/include/retro_dirent.h b/libretro/libretro-common/include/retro_dirent.h index c345073..8a2591b 100644 --- a/libretro/libretro-common/include/retro_dirent.h +++ b/libretro/libretro-common/include/retro_dirent.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2018 The RetroArch team +/* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (retro_dirent.h). @@ -23,6 +23,7 @@ #ifndef __RETRO_DIRENT_H #define __RETRO_DIRENT_H +#include #include #include @@ -30,6 +31,10 @@ RETRO_BEGIN_DECLS +#define DIRENT_REQUIRED_VFS_VERSION 3 + +void dirent_vfs_init(const struct retro_vfs_interface_info* vfs_info); + typedef struct RDIR RDIR; /** @@ -44,25 +49,27 @@ typedef struct RDIR RDIR; */ struct RDIR *retro_opendir(const char *name); +struct RDIR *retro_opendir_include_hidden(const char *name, bool include_hidden); + int retro_readdir(struct RDIR *rdir); +/* Deprecated, returns false, left for compatibility */ bool retro_dirent_error(struct RDIR *rdir); -void retro_dirent_include_hidden(struct RDIR *rdir, bool include_hidden); - const char *retro_dirent_get_name(struct RDIR *rdir); /** * * retro_dirent_is_dir: * @rdir : pointer to the directory entry. + * @unused : deprecated, included for compatibility reasons, pass NULL * * Is the directory listing entry a directory? * * Returns: true if directory listing entry is * a directory, false if not. */ -bool retro_dirent_is_dir(struct RDIR *rdir, const char *path); +bool retro_dirent_is_dir(struct RDIR *rdir, const char *unused); void retro_closedir(struct RDIR *rdir); diff --git a/libretro/libretro-common/include/retro_environment.h b/libretro/libretro-common/include/retro_environment.h index 1a18cd6..4a68046 100644 --- a/libretro/libretro-common/include/retro_environment.h +++ b/libretro/libretro-common/include/retro_environment.h @@ -73,6 +73,42 @@ printf("This is C++, version %d.\n", __cplusplus); /* This is not standard C. __STDC__ is not defined. */ #endif +#if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +/* Try to find out if we're compiling for WinRT or non-WinRT */ +#if defined(_MSC_VER) && defined(__has_include) +#if __has_include() +#define HAVE_WINAPIFAMILY_H 1 +#else +#define HAVE_WINAPIFAMILY_H 0 +#endif + +/* If _USING_V110_SDK71_ is defined it means we are using the Windows XP toolset. */ +#elif defined(_MSC_VER) && (_MSC_VER >= 1700 && !_USING_V110_SDK71_) /* _MSC_VER == 1700 for Visual Studio 2012 */ +#define HAVE_WINAPIFAMILY_H 1 +#else +#define HAVE_WINAPIFAMILY_H 0 +#endif + +#if HAVE_WINAPIFAMILY_H +#include +#define WINAPI_FAMILY_WINRT (!WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)) +#else +#define WINAPI_FAMILY_WINRT 0 +#endif /* HAVE_WINAPIFAMILY_H */ + +#if WINAPI_FAMILY_WINRT +#undef __WINRT__ +#define __WINRT__ 1 +#endif +/* MSVC obviously has to have some non-standard constants... */ +#if _M_IX86_FP == 1 +#define __SSE__ 1 +#elif _M_IX86_FP == 2 || (defined(_M_AMD64) || defined(_M_X64)) +#define __SSE__ 1 +#define __SSE2__ 1 +#endif + +#endif #endif diff --git a/libretro/libretro-common/include/retro_miscellaneous.h b/libretro/libretro-common/include/retro_miscellaneous.h index afcb885..3893416 100644 --- a/libretro/libretro-common/include/retro_miscellaneous.h +++ b/libretro/libretro-common/include/retro_miscellaneous.h @@ -77,7 +77,7 @@ static INLINE bool bits_any_set(uint32_t* ptr, uint32_t count) #ifndef PATH_MAX_LENGTH #if defined(__CELLOS_LV2__) #define PATH_MAX_LENGTH CELL_FS_MAX_FS_PATH_LENGTH -#elif defined(_XBOX1) || defined(_3DS) || defined(PSP) || defined(GEKKO)|| defined(WIIU) +#elif defined(_XBOX1) || defined(_3DS) || defined(PSP) || defined(PS2) || defined(GEKKO)|| defined(WIIU) || defined(ORBIS) #define PATH_MAX_LENGTH 512 #else #define PATH_MAX_LENGTH 4096 @@ -155,4 +155,28 @@ typedef struct uint32_t data[8]; } retro_bits_t; +#ifdef _WIN32 +# ifdef _WIN64 +# define PRI_SIZET PRIu64 +# else +# if _MSC_VER == 1800 +# define PRI_SIZET PRIu32 +# else +# define PRI_SIZET "u" +# endif +# endif +#elif PS2 +# define PRI_SIZET "u" +#else +# if (SIZE_MAX == 0xFFFF) +# define PRI_SIZET "hu" +# elif (SIZE_MAX == 0xFFFFFFFF) +# define PRI_SIZET "u" +# elif (SIZE_MAX == 0xFFFFFFFFFFFFFFFF) +# define PRI_SIZET "lu" +# else +# error PRI_SIZET: unknown SIZE_MAX +# endif +#endif + #endif diff --git a/libretro/libretro-common/include/string/stdstring.h b/libretro/libretro-common/include/string/stdstring.h index eb2954b..74c9bdc 100644 --- a/libretro/libretro-common/include/string/stdstring.h +++ b/libretro/libretro-common/include/string/stdstring.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2018 The RetroArch team +/* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (stdstring.h). @@ -37,32 +37,30 @@ RETRO_BEGIN_DECLS static INLINE bool string_is_empty(const char *data) { - return (data == NULL) || (*data == '\0'); + return !data || (*data == '\0'); } static INLINE bool string_is_equal(const char *a, const char *b) { - if (!a || !b) - return false; - while(*a && (*a == *b)) - a++, b++; - return (*(const unsigned char*)a - *(const unsigned char*)b) == 0; + return (a && b) ? !strcmp(a, b) : false; } -static INLINE bool string_is_not_equal(const char *a, const char *b) -{ - return !string_is_equal(a, b); -} +#define STRLEN_CONST(x) ((sizeof((x))-1)) + +#define string_is_not_equal(a, b) !string_is_equal((a), (b)) -#define string_add_pair_open(s, size) strlcat((s), " (", (size)) -#define string_add_pair_close(s, size) strlcat((s), ")", (size)) -#define string_add_bracket_open(s, size) strlcat((s), "{", (size)) +#define string_add_pair_open(s, size) strlcat((s), " (", (size)) +#define string_add_pair_close(s, size) strlcat((s), ")", (size)) +#define string_add_bracket_open(s, size) strlcat((s), "{", (size)) #define string_add_bracket_close(s, size) strlcat((s), "}", (size)) -#define string_add_single_quote(s, size) strlcat((s), "'", (size)) -#define string_add_quote(s, size) strlcat((s), "\"", (size)) -#define string_add_colon(s, size) strlcat((s), ":", (size)) -#define string_add_glob_open(s, size) strlcat((s), "glob('*", (size)) -#define string_add_glob_close(s, size) strlcat((s), "*')", (size)) +#define string_add_single_quote(s, size) strlcat((s), "'", (size)) +#define string_add_quote(s, size) strlcat((s), "\"", (size)) +#define string_add_colon(s, size) strlcat((s), ":", (size)) +#define string_add_glob_open(s, size) strlcat((s), "glob('*", (size)) +#define string_add_glob_close(s, size) strlcat((s), "*')", (size)) + +#define string_is_not_equal_fast(a, b, size) (memcmp(a, b, size) != 0) +#define string_is_equal_fast(a, b, size) (memcmp(a, b, size) == 0) static INLINE void string_add_between_pairs(char *s, const char *str, size_t size) @@ -72,9 +70,6 @@ static INLINE void string_add_between_pairs(char *s, const char *str, string_add_pair_close(s, size); } -#define string_is_not_equal_fast(a, b, size) (memcmp(a, b, size) != 0) -#define string_is_equal_fast(a, b, size) (memcmp(a, b, size) == 0) - static INLINE bool string_is_equal_case_insensitive(const char *a, const char *b) { @@ -116,7 +111,7 @@ char *string_to_upper(char *s); char *string_to_lower(char *s); -char *string_ucwords(char* s); +char *string_ucwords(char *s); char *string_replace_substring(const char *in, const char *pattern, const char *by); @@ -130,7 +125,9 @@ char *string_trim_whitespace_right(char *const s); /* Remove leading and trailing whitespaces */ char *string_trim_whitespace(char *const s); -char *word_wrap(char* buffer, const char *string, int line_width, bool unicode); +/* max_lines == 0 means no limit */ +char *word_wrap(char *buffer, const char *string, + int line_width, bool unicode, unsigned max_lines); RETRO_END_DECLS diff --git a/libretro/libretro-common/include/vfs/vfs.h b/libretro/libretro-common/include/vfs/vfs.h new file mode 100644 index 0000000..5f046fe --- /dev/null +++ b/libretro/libretro-common/include/vfs/vfs.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2010-2019 The RetroArch team +* +* --------------------------------------------------------------------------------------- +* The following license statement only applies to this file (vfs_implementation.h). +* --------------------------------------------------------------------------------------- +* +* Permission is hereby granted, free of charge, +* to any person obtaining a copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +* and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __LIBRETRO_SDK_VFS_H +#define __LIBRETRO_SDK_VFS_H + +#include + +#ifdef RARCH_INTERNAL +#ifndef VFS_FRONTEND +#define VFS_FRONTEND +#endif +#endif + +RETRO_BEGIN_DECLS + +#ifdef _WIN32 +typedef void* HANDLE; +#endif + +#ifdef HAVE_CDROM +typedef struct +{ + char *cue_buf; + size_t cue_len; + int64_t byte_pos; + char drive; + unsigned char cur_min; + unsigned char cur_sec; + unsigned char cur_frame; + unsigned char cur_track; + unsigned cur_lba; +} vfs_cdrom_t; +#endif + +enum vfs_scheme +{ + VFS_SCHEME_NONE = 0, + VFS_SCHEME_CDROM +}; + +#ifndef __WINRT__ +#ifdef VFS_FRONTEND +struct retro_vfs_file_handle +#else +struct libretro_vfs_implementation_file +#endif +{ + int fd; + unsigned hints; + int64_t size; + char *buf; + FILE *fp; +#ifdef _WIN32 + HANDLE fh; +#endif + char* orig_path; + uint64_t mappos; + uint64_t mapsize; + uint8_t *mapped; + enum vfs_scheme scheme; +#ifdef HAVE_CDROM + vfs_cdrom_t cdrom; +#endif +}; +#endif + +/* Replace the following symbol with something appropriate + * to signify the file is being compiled for a front end instead of a core. + * This allows the same code to act as reference implementation + * for VFS and as fallbacks for when the front end does not provide VFS functionality. + */ + +#ifdef VFS_FRONTEND +typedef struct retro_vfs_file_handle libretro_vfs_implementation_file; +#else +typedef struct libretro_vfs_implementation_file libretro_vfs_implementation_file; +#endif + +#ifdef VFS_FRONTEND +typedef struct retro_vfs_dir_handle libretro_vfs_implementation_dir; +#else +typedef struct libretro_vfs_implementation_dir libretro_vfs_implementation_dir; +#endif + +RETRO_END_DECLS + +#endif diff --git a/libretro/libretro-common/include/vfs/vfs_implementation.h b/libretro/libretro-common/include/vfs/vfs_implementation.h new file mode 100644 index 0000000..c981cf7 --- /dev/null +++ b/libretro/libretro-common/include/vfs/vfs_implementation.h @@ -0,0 +1,76 @@ +/* Copyright (C) 2010-2019 The RetroArch team +* +* --------------------------------------------------------------------------------------- +* The following license statement only applies to this file (vfs_implementation.h). +* --------------------------------------------------------------------------------------- +* +* Permission is hereby granted, free of charge, +* to any person obtaining a copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +* and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __LIBRETRO_SDK_VFS_IMPLEMENTATION_H +#define __LIBRETRO_SDK_VFS_IMPLEMENTATION_H + +#include +#include +#include +#include +#include + +RETRO_BEGIN_DECLS + +libretro_vfs_implementation_file *retro_vfs_file_open_impl(const char *path, unsigned mode, unsigned hints); + +int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream); + +int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream); + +int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream); + +int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length); + +int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream); + +int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream, int64_t offset, int seek_position); + +int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream, void *s, uint64_t len); + +int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len); + +int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream); + +int retro_vfs_file_remove_impl(const char *path); + +int retro_vfs_file_rename_impl(const char *old_path, const char *new_path); + +const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *stream); + +int retro_vfs_stat_impl(const char *path, int32_t *size); + +int retro_vfs_mkdir_impl(const char *dir); + +libretro_vfs_implementation_dir *retro_vfs_opendir_impl(const char *dir, bool include_hidden); + +bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *dirstream); + +const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *dirstream); + +bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *dirstream); + +int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *dirstream); + +RETRO_END_DECLS + +#endif diff --git a/libretro/libretro-common/include/vfs/vfs_implementation_cdrom.h b/libretro/libretro-common/include/vfs/vfs_implementation_cdrom.h new file mode 100644 index 0000000..3996fbe --- /dev/null +++ b/libretro/libretro-common/include/vfs/vfs_implementation_cdrom.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (vfs_implementation_cdrom.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __LIBRETRO_SDK_VFS_IMPLEMENTATION_CDROM_H +#define __LIBRETRO_SDK_VFS_IMPLEMENTATION_CDROM_H + +#include +#include + +RETRO_BEGIN_DECLS + +int64_t retro_vfs_file_seek_cdrom(libretro_vfs_implementation_file *stream, int64_t offset, int whence); + +void retro_vfs_file_open_cdrom( + libretro_vfs_implementation_file *stream, + const char *path, unsigned mode, unsigned hints); + +int retro_vfs_file_close_cdrom(libretro_vfs_implementation_file *stream); + +int64_t retro_vfs_file_tell_cdrom(libretro_vfs_implementation_file *stream); + +int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, + void *s, uint64_t len); + +int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream); + +const cdrom_toc_t* retro_vfs_file_get_cdrom_toc(void); + +const vfs_cdrom_t* retro_vfs_file_get_cdrom_position(const libretro_vfs_implementation_file *stream); + +RETRO_END_DECLS + +#endif diff --git a/libretro/libretro-common/string/stdstring.c b/libretro/libretro-common/string/stdstring.c index 6e1555d..4405a78 100644 --- a/libretro/libretro-common/string/stdstring.c +++ b/libretro/libretro-common/string/stdstring.c @@ -82,6 +82,10 @@ char *string_replace_substring(const char *in, outlen = strlen(in) - pattern_len*numhits + replacement_len*numhits; out = (char *)malloc(outlen+1); + + if (!out) + return NULL; + outat = out; inat = in; inprev = in; @@ -105,15 +109,17 @@ char *string_trim_whitespace_left(char *const s) { if(s && *s) { - size_t len = strlen(s); - char *cur = s; + size_t len = strlen(s); + char *current = s; - while(*cur && isspace((unsigned char)*cur)) - ++cur, --len; - - if(s != cur) - memmove(s, cur, len + 1); + while(*current && isspace((unsigned char)*current)) + { + ++current; + --len; + } + if(s != current) + memmove(s, current, len + 1); } return s; @@ -124,13 +130,16 @@ char *string_trim_whitespace_right(char *const s) { if(s && *s) { - size_t len = strlen(s); - char *cur = s + len - 1; + size_t len = strlen(s); + char *current = s + len - 1; - while(cur != s && isspace((unsigned char)*cur)) - --cur, --len; + while(current != s && isspace((unsigned char)*current)) + { + --current; + --len; + } - cur[isspace((unsigned char)*cur) ? 0 : 1] = '\0'; + current[isspace((unsigned char)*current) ? 0 : 1] = '\0'; } return s; @@ -145,14 +154,16 @@ char *string_trim_whitespace(char *const s) return s; } -char *word_wrap(char* buffer, const char *string, int line_width, bool unicode) +char *word_wrap(char* buffer, const char *string, int line_width, bool unicode, unsigned max_lines) { - unsigned i = 0; - unsigned len = (unsigned)strlen(string); + unsigned i = 0; + unsigned len = (unsigned)strlen(string); + unsigned lines = 1; while (i < len) { unsigned counter; + int pos = (int)(&buffer[i] - buffer); /* copy string until the end of the line is reached */ for (counter = 1; counter <= (unsigned)line_width; counter++) @@ -184,14 +195,21 @@ char *word_wrap(char* buffer, const char *string, int line_width, bool unicode) /* check for newlines embedded in the original input * and reset the index */ if (buffer[j] == '\n') + { + lines++; counter = 1; + } } /* check for whitespace */ if (string[i] == ' ') { - buffer[i] = '\n'; - i++; + if ((max_lines == 0 || lines < max_lines)) + { + buffer[i] = '\n'; + i++; + lines++; + } } else { @@ -200,14 +218,18 @@ char *word_wrap(char* buffer, const char *string, int line_width, bool unicode) /* check for nearest whitespace back in string */ for (k = i; k > 0; k--) { - if (string[k] != ' ') + if (string[k] != ' ' || (max_lines != 0 && lines >= max_lines)) continue; buffer[k] = '\n'; /* set string index back to character after this one */ i = k + 1; + lines++; break; } + + if (&buffer[i] - buffer == pos) + return buffer; } } diff --git a/libretro/libretro-common/vfs/vfs_implementation.c b/libretro/libretro-common/vfs/vfs_implementation.c new file mode 100644 index 0000000..3842007 --- /dev/null +++ b/libretro/libretro-common/vfs/vfs_implementation.c @@ -0,0 +1,1321 @@ +/* Copyright (C) 2010-2019 The RetroArch team +* +* --------------------------------------------------------------------------------------- +* The following license statement only applies to this file (vfs_implementation.c). +* --------------------------------------------------------------------------------------- +* +* Permission is hereby granted, free of charge, +* to any person obtaining a copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +* and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(_WIN32) +# ifdef _MSC_VER +# define setmode _setmode +# endif +#include +# ifdef _XBOX +# include +# define INVALID_FILE_ATTRIBUTES -1 +# else + +# include +# include +# include +# endif +# include +#else +# if defined(PSP) +# include +# endif +# if defined(PS2) +# include +# include +# endif +# include +# include +# if !defined(VITA) +# include +# endif +# include +# if defined(ORBIS) +# include +# include +# include +# endif +#endif + +#ifdef __CELLOS_LV2__ +#include +#define O_RDONLY CELL_FS_O_RDONLY +#define O_WRONLY CELL_FS_O_WRONLY +#define O_CREAT CELL_FS_O_CREAT +#define O_TRUNC CELL_FS_O_TRUNC +#define O_RDWR CELL_FS_O_RDWR +#else +#include +#endif + +/* TODO: Some things are duplicated but I'm really afraid of breaking other platforms by touching this */ +#if defined(VITA) +# include +# include +# include +#elif defined(ORBIS) +# include +# include +# include +# include +#elif !defined(_WIN32) +# if defined(PSP) +# include +# endif +# if defined(PS2) +# include +# endif +# include +# include +# include +# include +#endif + +#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP) || defined(PS2) +#include /* stat() is defined here */ +#endif + +#ifdef __APPLE__ +#include +#endif +#ifdef __HAIKU__ +#include +#endif +#ifndef __MACH__ +#include +#include +#endif +#include +#include +#include + +#if defined(_WIN32) +#ifndef _XBOX +#if defined(_MSC_VER) && _MSC_VER <= 1200 +#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif +#endif +#elif defined(VITA) +#define SCE_ERROR_ERRNO_EEXIST 0x80010011 +#include +#include +#include +#else +#include +#include +#include +#endif + +#if defined(ORBIS) +#include +#include +#include +#endif +#if defined(PSP) +#include +#endif + +#if defined(PS2) +#include +#include +#endif + +#if defined(__CELLOS_LV2__) +#include +#endif + +#if defined(VITA) +#define FIO_S_ISDIR SCE_S_ISDIR +#endif + +#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP) +#include /* stat() is defined here */ +#endif + +/* Assume W-functions do not work below Win2K and Xbox platforms */ +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX) + +#ifndef LEGACY_WIN32 +#define LEGACY_WIN32 +#endif + +#endif + +#if defined(_WIN32) && !defined(_XBOX) +#if !defined(_MSC_VER) || (defined(_MSC_VER) && _MSC_VER >= 1400) +#define ATLEAST_VC2005 +#endif +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CDROM +#include +#endif + +#define RFILE_HINT_UNBUFFERED (1 << 8) + +int64_t retro_vfs_file_seek_internal(libretro_vfs_implementation_file *stream, int64_t offset, int whence) +{ + if (!stream) + return -1; + + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) + { +/* VC2005 and up have a special 64-bit fseek */ +#ifdef ATLEAST_VC2005 + return _fseeki64(stream->fp, offset, whence); +#elif defined(__CELLOS_LV2__) || defined(_MSC_VER) && _MSC_VER <= 1310 + return fseek(stream->fp, (long)offset, whence); +#elif defined(PS2) + int64_t ret = fileXioLseek(fileno(stream->fp), (off_t)offset, whence); + /* fileXioLseek could return positive numbers */ + if (ret > 0) + return 0; + return ret; +#elif defined(ORBIS) + int ret = orbisLseek(stream->fd, offset, whence); + if (ret < 0) + return -1; + return 0; +#else +#ifdef HAVE_CDROM + if (stream->scheme == VFS_SCHEME_CDROM) + return retro_vfs_file_seek_cdrom(stream, offset, whence); + else +#endif + return fseeko(stream->fp, (off_t)offset, whence); +#endif + } +#ifdef HAVE_MMAP + /* Need to check stream->mapped because this function is + * called in filestream_open() */ + if (stream->mapped && stream->hints & + RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS) + { + /* fseek() returns error on under/overflow but + * allows cursor > EOF for + read-only file descriptors. */ + switch (whence) + { + case SEEK_SET: + if (offset < 0) + return -1; + + stream->mappos = offset; + break; + + case SEEK_CUR: + if ( (offset < 0 && stream->mappos + offset > stream->mappos) || + (offset > 0 && stream->mappos + offset < stream->mappos)) + return -1; + + stream->mappos += offset; + break; + + case SEEK_END: + if (stream->mapsize + offset < stream->mapsize) + return -1; + + stream->mappos = stream->mapsize + offset; + break; + } + return stream->mappos; + } +#endif + + if (lseek(stream->fd, offset, whence) < 0) + return -1; + + return 0; +} + +/** + * retro_vfs_file_open_impl: + * @path : path to file + * @mode : file mode to use when opening (read/write) + * @hints : + * + * Opens a file for reading or writing, depending on the requested mode. + * Returns a pointer to an RFILE if opened successfully, otherwise NULL. + **/ + +libretro_vfs_implementation_file *retro_vfs_file_open_impl( + const char *path, unsigned mode, unsigned hints) +{ + int flags = 0; + const char *mode_str = NULL; + int path_len = (int)strlen(path); + libretro_vfs_implementation_file *stream = (libretro_vfs_implementation_file*) + calloc(1, sizeof(*stream)); + +#ifdef VFS_FRONTEND + const char *dumb_prefix = "vfsonly://"; + size_t dumb_prefix_siz = strlen(dumb_prefix); + int dumb_prefix_len = (int)dumb_prefix_siz; + + if (path_len >= dumb_prefix_len) + { + if (!memcmp(path, dumb_prefix, dumb_prefix_len)) + path += dumb_prefix_siz; + } +#endif + +#ifdef HAVE_CDROM + { + const char *cdrom_prefix = "cdrom://"; + size_t cdrom_prefix_siz = strlen(cdrom_prefix); + int cdrom_prefix_len = (int)cdrom_prefix_siz; + + if (path_len > cdrom_prefix_len) + { + if (!memcmp(path, cdrom_prefix, cdrom_prefix_len)) + { + path += cdrom_prefix_siz; + stream->scheme = VFS_SCHEME_CDROM; + } + } + } +#endif + + if (!stream) + return NULL; + + (void)flags; + + stream->hints = hints; + stream->orig_path = strdup(path); + +#ifdef HAVE_MMAP + if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS && mode == RETRO_VFS_FILE_ACCESS_READ) + stream->hints |= RFILE_HINT_UNBUFFERED; + else +#endif + stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS; + + switch (mode) + { + case RETRO_VFS_FILE_ACCESS_READ: + mode_str = "rb"; + + flags = O_RDONLY; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + break; + + case RETRO_VFS_FILE_ACCESS_WRITE: + mode_str = "wb"; + + flags = O_WRONLY | O_CREAT | O_TRUNC; +#if !defined(ORBIS) +#if defined(PS2) + flags |= FIO_S_IRUSR | FIO_S_IWUSR; +#elif !defined(_WIN32) + flags |= S_IRUSR | S_IWUSR; +#else + flags |= O_BINARY; +#endif +#endif + break; + + case RETRO_VFS_FILE_ACCESS_READ_WRITE: + mode_str = "w+b"; + flags = O_RDWR | O_CREAT | O_TRUNC; +#if !defined(ORBIS) +#if defined(PS2) + flags |= FIO_S_IRUSR | FIO_S_IWUSR; +#elif !defined(_WIN32) + flags |= S_IRUSR | S_IWUSR; +#else + flags |= O_BINARY; +#endif +#endif + break; + + case RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING: + case RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING: + mode_str = "r+b"; + + flags = O_RDWR; +#if !defined(ORBIS) +#if defined(PS2) + flags |= FIO_S_IRUSR | FIO_S_IWUSR; +#elif !defined(_WIN32) + flags |= S_IRUSR | S_IWUSR; +#else + flags |= O_BINARY; +#endif +#endif + break; + + default: + goto error; + } + + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) + { +#ifdef ORBIS + int fd = orbisOpen(path, flags, 0644); + if (fd < 0) + { + stream->fd = -1; + goto error; + } + stream->fd = fd; +#else + FILE *fp; +#ifdef HAVE_CDROM + if (stream->scheme == VFS_SCHEME_CDROM) + { + retro_vfs_file_open_cdrom(stream, path, mode, hints); +#if defined(_WIN32) && !defined(_XBOX) + if (!stream->fh) + goto error; +#else + if (!stream->fp) + goto error; +#endif + } + else +#endif + { + fp = (FILE*)fopen_utf8(path, mode_str); + + if (!fp) + goto error; + + stream->fp = fp; + } + /* Regarding setvbuf: + * + * https://www.freebsd.org/cgi/man.cgi?query=setvbuf&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE&arch=default&format=html + * + * If the size argument is not zero but buf is NULL, a buffer of the given size will be allocated immediately, and + * released on close. This is an extension to ANSI C. + * + * Since C89 does not support specifying a null buffer with a non-zero size, we create and track our own buffer for it. + */ + /* TODO: this is only useful for a few platforms, find which and add ifdef */ +#if !defined(PS2) && !defined(PSP) + if (stream->scheme != VFS_SCHEME_CDROM) + { + stream->buf = (char*)calloc(1, 0x4000); + if (stream->fp) + setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000); + } +#endif +#endif + } + else + { +#if defined(_WIN32) && !defined(_XBOX) +#if defined(LEGACY_WIN32) + char *path_local = utf8_to_local_string_alloc(path); + stream->fd = open(path_local, flags, 0); + if (path_local) + free(path_local); +#else + wchar_t * path_wide = utf8_to_utf16_string_alloc(path); + stream->fd = _wopen(path_wide, flags, 0); + if (path_wide) + free(path_wide); +#endif +#else + stream->fd = open(path, flags, 0); +#endif + + if (stream->fd == -1) + goto error; + +#ifdef HAVE_MMAP + if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS) + { + stream->mappos = 0; + stream->mapped = NULL; + stream->mapsize = retro_vfs_file_seek_internal(stream, 0, SEEK_END); + + if (stream->mapsize == (uint64_t)-1) + goto error; + + retro_vfs_file_seek_internal(stream, 0, SEEK_SET); + + stream->mapped = (uint8_t*)mmap((void*)0, + stream->mapsize, PROT_READ, MAP_SHARED, stream->fd, 0); + + if (stream->mapped == MAP_FAILED) + stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS; + } +#endif + } +#ifdef ORBIS + stream->size = orbisLseek(stream->fd, 0, SEEK_END); + orbisLseek(stream->fd, 0, SEEK_SET); +#else +#ifdef HAVE_CDROM + if (stream->scheme == VFS_SCHEME_CDROM) + { + retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET); + retro_vfs_file_seek_cdrom(stream, 0, SEEK_END); + + stream->size = retro_vfs_file_tell_impl(stream); + + retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET); + } + else +#endif + { + retro_vfs_file_seek_internal(stream, 0, SEEK_SET); + retro_vfs_file_seek_internal(stream, 0, SEEK_END); + + stream->size = retro_vfs_file_tell_impl(stream); + + retro_vfs_file_seek_internal(stream, 0, SEEK_SET); + } +#endif + return stream; + +error: + retro_vfs_file_close_impl(stream); + return NULL; +} + +int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream) +{ + if (!stream) + return -1; + +#ifdef HAVE_CDROM + if (stream->scheme == VFS_SCHEME_CDROM) + { + retro_vfs_file_close_cdrom(stream); + goto end; + } +#endif + + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) + { + if (stream->fp) + { + fclose(stream->fp); + } + } + else + { +#ifdef HAVE_MMAP + if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS) + munmap(stream->mapped, stream->mapsize); +#endif + } + + if (stream->fd > 0) + { +#ifdef ORBIS + orbisClose(stream->fd); + stream->fd = -1; +#else + close(stream->fd); +#endif + } +#ifdef HAVE_CDROM +end: + if (stream->cdrom.cue_buf) + free(stream->cdrom.cue_buf); +#endif + if (stream->buf) + free(stream->buf); + + if (stream->orig_path) + free(stream->orig_path); + + free(stream); + + return 0; +} + +int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream) +{ +#ifdef ORBIS + /* TODO/FIXME - implement this? */ + return 0; +#endif +#ifdef HAVE_CDROM + if (stream->scheme == VFS_SCHEME_CDROM) + return retro_vfs_file_error_cdrom(stream); +#endif + return ferror(stream->fp); +} + +int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream) +{ + if (stream) + return stream->size; + return 0; +} + +int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length) +{ + if (!stream) + return -1; + +#ifdef _WIN32 + if (_chsize(_fileno(stream->fp), length) != 0) + return -1; +#elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) && (!defined(SWITCH) || defined(HAVE_LIBNX)) + if (ftruncate(fileno(stream->fp), length) != 0) + return -1; +#endif + + return 0; +} + +int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream) +{ + if (!stream) + return -1; + + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) + { +#ifdef HAVE_CDROM + if (stream->scheme == VFS_SCHEME_CDROM) + return retro_vfs_file_tell_cdrom(stream); +#endif +#ifdef ORBIS + int64_t ret = orbisLseek(stream->fd, 0, SEEK_CUR); + if (ret < 0) + return -1; + return ret; +#else +/* VC2005 and up have a special 64-bit ftell */ +#ifdef ATLEAST_VC2005 + return _ftelli64(stream->fp); +#else + return ftell(stream->fp); +#endif +#endif + } +#ifdef HAVE_MMAP + /* Need to check stream->mapped because this function + * is called in filestream_open() */ + if (stream->mapped && stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS) + return stream->mappos; +#endif + if (lseek(stream->fd, 0, SEEK_CUR) < 0) + return -1; + + return 0; +} + +int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream, + int64_t offset, int seek_position) +{ + int whence = -1; + switch (seek_position) + { + case RETRO_VFS_SEEK_POSITION_START: + whence = SEEK_SET; + break; + case RETRO_VFS_SEEK_POSITION_CURRENT: + whence = SEEK_CUR; + break; + case RETRO_VFS_SEEK_POSITION_END: + whence = SEEK_END; + break; + } + + return retro_vfs_file_seek_internal(stream, offset, whence); +} + +int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream, + void *s, uint64_t len) +{ + if (!stream || !s) + return -1; + + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) + { +#ifdef ORBIS + if (orbisRead(stream->fd, s, (size_t)len) < 0) + return -1; + return 0; +#else +#ifdef HAVE_CDROM + if (stream->scheme == VFS_SCHEME_CDROM) + return retro_vfs_file_read_cdrom(stream, s, len); + else +#endif + return fread(s, 1, (size_t)len, stream->fp); +#endif + } +#ifdef HAVE_MMAP + if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS) + { + if (stream->mappos > stream->mapsize) + return -1; + + if (stream->mappos + len > stream->mapsize) + len = stream->mapsize - stream->mappos; + + memcpy(s, &stream->mapped[stream->mappos], len); + stream->mappos += len; + + return len; + } +#endif + + return read(stream->fd, s, (size_t)len); +} + +int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len) +{ + if (!stream) + return -1; + + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) + { +#ifdef ORBIS + if (orbisWrite(stream->fd, s, (size_t)len) < 0) + return -1; + return 0; +#else + return fwrite(s, 1, (size_t)len, stream->fp); +#endif + } + +#ifdef HAVE_MMAP + if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS) + return -1; +#endif + return write(stream->fd, s, (size_t)len); +} + +int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream) +{ + if (!stream) + return -1; +#ifdef ORBIS + return 0; +#else + return fflush(stream->fp) == 0 ? 0 : -1; +#endif +} + +int retro_vfs_file_remove_impl(const char *path) +{ +#if defined(_WIN32) && !defined(_XBOX) + /* Win32 (no Xbox) */ + +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 + char *path_local = NULL; +#else + wchar_t *path_wide = NULL; +#endif + if (!path || !*path) + return -1; +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 + path_local = utf8_to_local_string_alloc(path); + + if (path_local) + { + int ret = remove(path_local); + free(path_local); + + if (ret == 0) + return 0; + } +#else + path_wide = utf8_to_utf16_string_alloc(path); + + if (path_wide) + { + int ret = _wremove(path_wide); + free(path_wide); + + if (ret == 0) + return 0; + } +#endif + return -1; +#elif defined(ORBIS) + /* Orbis + * TODO/FIXME - stub for now */ + return 0; +#else + if (remove(path) == 0) + return 0; + return -1; +#endif +} + +int retro_vfs_file_rename_impl(const char *old_path, const char *new_path) +{ +#if defined(_WIN32) && !defined(_XBOX) + /* Win32 (no Xbox) */ + int ret = -1; +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 + char *old_path_local = NULL; +#else + wchar_t *old_path_wide = NULL; +#endif + + if (!old_path || !*old_path || !new_path || !*new_path) + return -1; + +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 + old_path_local = utf8_to_local_string_alloc(old_path); + + if (old_path_local) + { + char *new_path_local = utf8_to_local_string_alloc(new_path); + + if (new_path_local) + { + if (rename(old_path_local, new_path_local) == 0) + ret = 0; + free(new_path_local); + } + + free(old_path_local); + } +#else + old_path_wide = utf8_to_utf16_string_alloc(old_path); + + if (old_path_wide) + { + wchar_t *new_path_wide = utf8_to_utf16_string_alloc(new_path); + + if (new_path_wide) + { + if (_wrename(old_path_wide, new_path_wide) == 0) + ret = 0; + free(new_path_wide); + } + + free(old_path_wide); + } +#endif + return ret; + +#elif defined(ORBIS) + /* Orbis */ + /* TODO/FIXME - Stub for now */ + if (!old_path || !*old_path || !new_path || !*new_path) + return -1; + return 0; + +#else + /* Every other platform */ + if (!old_path || !*old_path || !new_path || !*new_path) + return -1; + return rename(old_path, new_path) == 0 ? 0 : -1; +#endif +} + +const char *retro_vfs_file_get_path_impl( + libretro_vfs_implementation_file *stream) +{ + /* should never happen, do something noisy so caller can be fixed */ + if (!stream) + abort(); + return stream->orig_path; +} + +int retro_vfs_stat_impl(const char *path, int32_t *size) +{ +#if defined(VITA) || defined(PSP) + /* Vita / PSP */ + SceIoStat buf; + int stat_ret; + bool is_dir = false; + bool is_character_special = false; + char *tmp = NULL; + size_t len = 0; + + if (!path || !*path) + return 0; + + tmp = strdup(path); + len = strlen(tmp); + if (tmp[len-1] == '/') + tmp[len-1] = '\0'; + + stat_ret = sceIoGetstat(tmp, &buf); + free(tmp); + if (stat_ret < 0) + return 0; + + if (size) + *size = (int32_t)buf.st_size; + + is_dir = FIO_S_ISDIR(buf.st_mode); + + return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0); + +#elif defined(ORBIS) + /* Orbis */ + bool is_dir, is_character_special; + int dir_ret; + + if (!path || !*path) + return 0; + + if (size) + *size = (int32_t)buf.st_size; + + dir_ret = orbisDopen(path); + is_dir = dir_ret > 0; + orbisDclose(dir_ret); + + is_character_special = S_ISCHR(buf.st_mode); + + return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0); + +#elif defined(PS2) + /* PS2 */ + iox_stat_t buf; + bool is_dir; + bool is_character_special = false; + char *tmp = NULL; + size_t len = 0; + + if (!path || !*path) + return 0; + + tmp = strdup(path); + len = strlen(tmp); + if (tmp[len-1] == '/') + tmp[len-1] = '\0'; + + fileXioGetStat(tmp, &buf); + free(tmp); + + if (size) + *size = (int32_t)buf.size; + + if (!buf.mode) + { + /* if fileXioGetStat fails */ + int dir_ret = fileXioDopen(path); + is_dir = dir_ret > 0; + fileXioDclose(dir_ret); + } + else + is_dir = FIO_S_ISDIR(buf.mode); + + return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0); + +#elif defined(__CELLOS_LV2__) + /* CellOS Lv2 */ + bool is_dir; + bool is_character_special = false; + CellFsStat buf; + + if (!path || !*path) + return 0; + if (cellFsStat(path, &buf) < 0) + return 0; + + if (size) + *size = (int32_t)buf.st_size; + + is_dir = ((buf.st_mode & S_IFMT) == S_IFDIR); + + return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0); + +#elif defined(_WIN32) + /* Windows */ + bool is_dir; + DWORD file_info; + struct _stat buf; + bool is_character_special = false; +#if defined(LEGACY_WIN32) + char *path_local = NULL; +#else + wchar_t *path_wide = NULL; +#endif + + if (!path || !*path) + return 0; + +#if defined(LEGACY_WIN32) + path_local = utf8_to_local_string_alloc(path); + file_info = GetFileAttributes(path_local); + + if (!string_is_empty(path_local)) + _stat(path_local, &buf); + + if (path_local) + free(path_local); +#else + path_wide = utf8_to_utf16_string_alloc(path); + file_info = GetFileAttributesW(path_wide); + + _wstat(path_wide, &buf); + + if (path_wide) + free(path_wide); +#endif + + if (file_info == INVALID_FILE_ATTRIBUTES) + return 0; + + if (size) + *size = (int32_t)buf.st_size; + + is_dir = (file_info & FILE_ATTRIBUTE_DIRECTORY); + + return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0); + +#else + /* Every other platform */ + bool is_dir, is_character_special; + struct stat buf; + + if (!path || !*path) + return 0; + if (stat(path, &buf) < 0) + return 0; + + if (size) + *size = (int32_t)buf.st_size; + + is_dir = S_ISDIR(buf.st_mode); + is_character_special = S_ISCHR(buf.st_mode); + + return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0); +#endif +} + +#if defined(VITA) +#define path_mkdir_error(ret) (((ret) == SCE_ERROR_ERRNO_EEXIST)) +#elif defined(PSP) || defined(PS2) || defined(_3DS) || defined(WIIU) || defined(SWITCH) || defined(ORBIS) +#define path_mkdir_error(ret) ((ret) == -1) +#else +#define path_mkdir_error(ret) ((ret) < 0 && errno == EEXIST) +#endif + +int retro_vfs_mkdir_impl(const char *dir) +{ +#if defined(_WIN32) +#ifdef LEGACY_WIN32 + int ret = _mkdir(dir); +#else + wchar_t *dirW = utf8_to_utf16_string_alloc(dir); + int ret = -1; + + if (dirW) + { + ret = _wmkdir(dirW); + free(dirW); + } +#endif +#elif defined(IOS) + int ret = mkdir(dir, 0755); +#elif defined(VITA) || defined(PSP) + int ret = sceIoMkdir(dir, 0777); +#elif defined(PS2) + int ret = fileXioMkdir(dir, 0777); +#elif defined(ORBIS) + int ret = orbisMkdir(dir, 0755); +#elif defined(__QNX__) + int ret = mkdir(dir, 0777); +#else + int ret = mkdir(dir, 0750); +#endif + + if (path_mkdir_error(ret)) + return -2; + return ret < 0 ? -1 : 0; +} + +#ifdef VFS_FRONTEND +struct retro_vfs_dir_handle +#else +struct libretro_vfs_implementation_dir +#endif +{ + char* orig_path; +#if defined(_WIN32) +#if defined(LEGACY_WIN32) + WIN32_FIND_DATA entry; +#else + WIN32_FIND_DATAW entry; +#endif + HANDLE directory; + bool next; + char path[PATH_MAX_LENGTH]; +#elif defined(VITA) || defined(PSP) + SceUID directory; + SceIoDirent entry; +#elif defined(PS2) + int directory; + iox_dirent_t entry; +#elif defined(__CELLOS_LV2__) + CellFsErrno error; + int directory; + CellFsDirent entry; +#elif defined(ORBIS) + int directory; + struct dirent entry; +#else + DIR *directory; + const struct dirent *entry; +#endif +}; + +static bool dirent_check_error(libretro_vfs_implementation_dir *rdir) +{ +#if defined(_WIN32) + return (rdir->directory == INVALID_HANDLE_VALUE); +#elif defined(VITA) || defined(PSP) || defined(PS2) || defined(ORBIS) + return (rdir->directory < 0); +#elif defined(__CELLOS_LV2__) + return (rdir->error != CELL_FS_SUCCEEDED); +#else + return !(rdir->directory); +#endif +} + +libretro_vfs_implementation_dir *retro_vfs_opendir_impl(const char *name, bool include_hidden) +{ +#if defined(_WIN32) + unsigned path_len; + char path_buf[1024]; +#if defined(LEGACY_WIN32) + char *path_local = NULL; +#else + wchar_t *path_wide = NULL; +#endif +#endif + libretro_vfs_implementation_dir *rdir; + + /*Reject null or empty string paths*/ + if (!name || (*name == 0)) + return NULL; + + /*Allocate RDIR struct. Tidied later with retro_closedir*/ + rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir)); + if (!rdir) + return NULL; + + rdir->orig_path = strdup(name); + +#if defined(_WIN32) + path_buf[0] = '\0'; + path_len = strlen(name); + + /* Non-NT platforms don't like extra slashes in the path */ + if (name[path_len - 1] == '\\') + snprintf(path_buf, sizeof(path_buf), "%s*", name); + else + snprintf(path_buf, sizeof(path_buf), "%s\\*", name); + +#if defined(LEGACY_WIN32) + path_local = utf8_to_local_string_alloc(path_buf); + rdir->directory = FindFirstFile(path_local, &rdir->entry); + + if (path_local) + free(path_local); +#else + path_wide = utf8_to_utf16_string_alloc(path_buf); + rdir->directory = FindFirstFileW(path_wide, &rdir->entry); + + if (path_wide) + free(path_wide); +#endif + +#elif defined(VITA) || defined(PSP) + rdir->directory = sceIoDopen(name); +#elif defined(PS2) + rdir->directory = ps2fileXioDopen(name); +#elif defined(_3DS) + rdir->directory = !string_is_empty(name) ? opendir(name) : NULL; + rdir->entry = NULL; +#elif defined(__CELLOS_LV2__) + rdir->error = cellFsOpendir(name, &rdir->directory); +#elif defined(ORBIS) + rdir->directory = orbisDopen(name); +#else + rdir->directory = opendir(name); + rdir->entry = NULL; +#endif + +#ifdef _WIN32 + if (include_hidden) + rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN; + else + rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN; +#endif + + if (rdir->directory && !dirent_check_error(rdir)) + return rdir; + + retro_vfs_closedir_impl(rdir); + return NULL; +} + +bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir) +{ +#if defined(_WIN32) + if (rdir->next) +#if defined(LEGACY_WIN32) + return (FindNextFile(rdir->directory, &rdir->entry) != 0); +#else + return (FindNextFileW(rdir->directory, &rdir->entry) != 0); +#endif + + rdir->next = true; + return (rdir->directory != INVALID_HANDLE_VALUE); +#elif defined(VITA) || defined(PSP) + return (sceIoDread(rdir->directory, &rdir->entry) > 0); +#elif defined(PS2) + iox_dirent_t record; + int ret = ps2fileXioDread(rdir->directory, &record); + rdir->entry = record; + return ( ret > 0); +#elif defined(__CELLOS_LV2__) + uint64_t nread; + rdir->error = cellFsReaddir(rdir->directory, &rdir->entry, &nread); + return (nread != 0); +#elif defined(ORBIS) + return (orbisDread(rdir->directory, &rdir->entry) > 0); +#else + return ((rdir->entry = readdir(rdir->directory)) != NULL); +#endif +} + +const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir) +{ +#if defined(_WIN32) +#if defined(LEGACY_WIN32) + char *name_local = local_to_utf8_string_alloc(rdir->entry.cFileName); + memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName)); + strlcpy(rdir->entry.cFileName, name_local, sizeof(rdir->entry.cFileName)); + + if (name_local) + free(name_local); +#else + char *name = utf16_to_utf8_string_alloc(rdir->entry.cFileName); + memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName)); + strlcpy((char*)rdir->entry.cFileName, name, sizeof(rdir->entry.cFileName)); + + if (name) + free(name); +#endif + return (char*)rdir->entry.cFileName; +#elif defined(VITA) || defined(PSP) || defined(__CELLOS_LV2__) || defined(ORBIS) + return rdir->entry.d_name; +#elif defined(PS2) + return rdir->entry.name; +#else + + return rdir->entry->d_name; +#endif +} + +bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir) +{ +#if defined(_WIN32) + const WIN32_FIND_DATA *entry = (const WIN32_FIND_DATA*)&rdir->entry; + return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; +#elif defined(PSP) || defined(VITA) + const SceIoDirent *entry = (const SceIoDirent*)&rdir->entry; +#if defined(PSP) + return (entry->d_stat.st_attr & FIO_SO_IFDIR) == FIO_SO_IFDIR; +#elif defined(VITA) + return SCE_S_ISDIR(entry->d_stat.st_mode); +#endif +#elif defined(PS2) + const iox_dirent_t *entry = (const iox_dirent_t*)&rdir->entry; + return FIO_S_ISDIR(entry->stat.mode); +#elif defined(__CELLOS_LV2__) + CellFsDirent *entry = (CellFsDirent*)&rdir->entry; + return (entry->d_type == CELL_FS_TYPE_DIRECTORY); +#elif defined(ORBIS) + const struct dirent *entry = &rdir->entry; + if (entry->d_type == DT_DIR) + return true; + if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) + return false; +#else + struct stat buf; + char path[PATH_MAX_LENGTH]; +#if defined(DT_DIR) + const struct dirent *entry = (const struct dirent*)rdir->entry; + if (entry->d_type == DT_DIR) + return true; + /* This can happen on certain file systems. */ + if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) + return false; +#endif + /* dirent struct doesn't have d_type, do it the slow way ... */ + path[0] = '\0'; + fill_pathname_join(path, rdir->orig_path, retro_vfs_dirent_get_name_impl(rdir), sizeof(path)); + if (stat(path, &buf) < 0) + return false; + return S_ISDIR(buf.st_mode); +#endif +} + +int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir) +{ + if (!rdir) + return -1; + +#if defined(_WIN32) + if (rdir->directory != INVALID_HANDLE_VALUE) + FindClose(rdir->directory); +#elif defined(VITA) || defined(PSP) + sceIoDclose(rdir->directory); +#elif defined(PS2) + ps2fileXioDclose(rdir->directory); +#elif defined(__CELLOS_LV2__) + rdir->error = cellFsClosedir(rdir->directory); +#elif defined(ORBIS) + orbisDclose(rdir->directory); +#else + if (rdir->directory) + closedir(rdir->directory); +#endif + + if (rdir->orig_path) + free(rdir->orig_path); + free(rdir); + return 0; +} diff --git a/libretro/libretro-common/vfs/vfs_implementation_cdrom.c b/libretro/libretro-common/vfs/vfs_implementation_cdrom.c new file mode 100644 index 0000000..acf9d0d --- /dev/null +++ b/libretro/libretro-common/vfs/vfs_implementation_cdrom.c @@ -0,0 +1,423 @@ +/* Copyright (C) 2010-2019 The RetroArch team +* +* --------------------------------------------------------------------------------------- +* The following license statement only applies to this file (vfs_implementation_cdrom.c). +* --------------------------------------------------------------------------------------- +* +* Permission is hereby granted, free of charge, +* to any person obtaining a copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +* and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +#if defined(_WIN32) && !defined(_XBOX) +#include +#endif + +static cdrom_toc_t vfs_cdrom_toc = {0}; + +const cdrom_toc_t* retro_vfs_file_get_cdrom_toc(void) +{ + return &vfs_cdrom_toc; +} + +int64_t retro_vfs_file_seek_cdrom(libretro_vfs_implementation_file *stream, int64_t offset, int whence) +{ + const char *ext = path_get_extension(stream->orig_path); + + if (string_is_equal_noncase(ext, "cue")) + { + switch (whence) + { + case SEEK_SET: + stream->cdrom.byte_pos = offset; + break; + case SEEK_CUR: + stream->cdrom.byte_pos += offset; + break; + case SEEK_END: + stream->cdrom.byte_pos = (stream->cdrom.cue_len - 1) + offset; + break; + } + +#ifdef CDROM_DEBUG + printf("[CDROM] Seek: Path %s Offset %" PRIu64 " is now at %" PRIu64 "\n", stream->orig_path, offset, stream->cdrom.byte_pos); + fflush(stdout); +#endif + } + else if (string_is_equal_noncase(ext, "bin")) + { + int lba = (offset / 2352); + unsigned char min = 0; + unsigned char sec = 0; + unsigned char frame = 0; + const char *seek_type = "SEEK_SET"; + + (void)seek_type; + + switch (whence) + { + case SEEK_CUR: + { + unsigned new_lba; + + stream->cdrom.byte_pos += offset; + new_lba = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352); + seek_type = "SEEK_CUR"; + + cdrom_lba_to_msf(new_lba, &min, &sec, &frame); + + break; + } + case SEEK_END: + { + ssize_t pregap_lba_len = (vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].audio ? 0 : (vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba_start)); + ssize_t lba_len = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_size - pregap_lba_len; + + cdrom_lba_to_msf(lba_len + lba, &min, &sec, &frame); + + stream->cdrom.byte_pos = lba_len * 2352; + seek_type = "SEEK_END"; + + break; + } + case SEEK_SET: + default: + { + seek_type = "SEEK_SET"; + stream->cdrom.byte_pos = offset; + cdrom_lba_to_msf(vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352), &min, &sec, &frame); + break; + } + } + + stream->cdrom.cur_min = min; + stream->cdrom.cur_sec = sec; + stream->cdrom.cur_frame = frame; + stream->cdrom.cur_lba = cdrom_msf_to_lba(min, sec, frame); + +#ifdef CDROM_DEBUG + printf("[CDROM] Seek %s: Path %s Offset %" PRIu64 " is now at %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)...\n", seek_type, stream->orig_path, offset, stream->cdrom.byte_pos, (unsigned)stream->cdrom.cur_min, (unsigned)stream->cdrom.cur_sec, (unsigned)stream->cdrom.cur_frame, stream->cdrom.cur_lba); + fflush(stdout); +#endif + } + else + return -1; + + return 0; +} + +void retro_vfs_file_open_cdrom( + libretro_vfs_implementation_file *stream, + const char *path, unsigned mode, unsigned hints) +{ +#if defined(__linux__) && !defined(ANDROID) + char cdrom_path[] = "/dev/sg1"; + size_t path_len = strlen(path); + const char *ext = path_get_extension(path); + + stream->cdrom.cur_track = 1; + + if (!string_is_equal_noncase(ext, "cue") && !string_is_equal_noncase(ext, "bin")) + return; + + if (path_len >= strlen("drive1-track01.bin")) + { + if (!memcmp(path, "drive", strlen("drive"))) + { + if (!memcmp(path + 6, "-track", strlen("-track"))) + { + if (sscanf(path + 12, "%02u", (unsigned*)&stream->cdrom.cur_track)) + { +#ifdef CDROM_DEBUG + printf("[CDROM] Opening track %d\n", stream->cdrom.cur_track); + fflush(stdout); +#endif + } + } + } + } + + if (path_len >= strlen("drive1.cue")) + { + if (!memcmp(path, "drive", strlen("drive"))) + { + if (path[5] >= '0' && path[5] <= '9') + { + cdrom_path[7] = path[5]; + stream->cdrom.drive = path[5]; + vfs_cdrom_toc.drive = stream->cdrom.drive; + } + } + } + +#ifdef CDROM_DEBUG + printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path); + fflush(stdout); +#endif + stream->fp = (FILE*)fopen_utf8(cdrom_path, "r+b"); + + if (!stream->fp) + return; + + if (string_is_equal_noncase(ext, "cue")) + { + if (stream->cdrom.cue_buf) + { + free(stream->cdrom.cue_buf); + stream->cdrom.cue_buf = NULL; + } + + cdrom_write_cue(stream, &stream->cdrom.cue_buf, &stream->cdrom.cue_len, stream->cdrom.drive, &vfs_cdrom_toc.num_tracks, &vfs_cdrom_toc); + cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); + +#ifdef CDROM_DEBUG + if (string_is_empty(stream->cdrom.cue_buf)) + { + printf("[CDROM] Error writing cue sheet.\n"); + fflush(stdout); + } + else + { + printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom.cue_buf); + fflush(stdout); + } +#endif + } +#endif +#if defined(_WIN32) && !defined(_XBOX) + char cdrom_path[] = "\\\\.\\D:"; + size_t path_len = strlen(path); + const char *ext = path_get_extension(path); + + if (!string_is_equal_noncase(ext, "cue") && !string_is_equal_noncase(ext, "bin")) + return; + + if (path_len >= strlen("d:/drive-track01.bin")) + { + if (!memcmp(path + 1, ":/drive-track", strlen(":/drive-track"))) + { + if (sscanf(path + 14, "%02u", (unsigned*)&stream->cdrom.cur_track)) + { +#ifdef CDROM_DEBUG + printf("[CDROM] Opening track %d\n", stream->cdrom.cur_track); + fflush(stdout); +#endif + } + } + } + + if (path_len >= strlen("d:/drive.cue")) + { + if (!memcmp(path + 1, ":/drive", strlen(":/drive"))) + { + if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) + { + cdrom_path[4] = path[0]; + stream->cdrom.drive = path[0]; + vfs_cdrom_toc.drive = stream->cdrom.drive; + } + } + } + +#ifdef CDROM_DEBUG + printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path); + fflush(stdout); +#endif + stream->fh = CreateFile(cdrom_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (stream->fh == INVALID_HANDLE_VALUE) + return; + + if (string_is_equal_noncase(ext, "cue")) + { + if (stream->cdrom.cue_buf) + { + free(stream->cdrom.cue_buf); + stream->cdrom.cue_buf = NULL; + } + + cdrom_write_cue(stream, &stream->cdrom.cue_buf, &stream->cdrom.cue_len, stream->cdrom.drive, &vfs_cdrom_toc.num_tracks, &vfs_cdrom_toc); + cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); + +#ifdef CDROM_DEBUG + if (string_is_empty(stream->cdrom.cue_buf)) + { + printf("[CDROM] Error writing cue sheet.\n"); + fflush(stdout); + } + else + { + printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom.cue_buf); + fflush(stdout); + } +#endif + } +#endif + if (vfs_cdrom_toc.num_tracks > 1 && stream->cdrom.cur_track) + { + stream->cdrom.cur_min = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].min; + stream->cdrom.cur_sec = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].sec; + stream->cdrom.cur_frame = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].frame; + stream->cdrom.cur_lba = cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame); + } + else + { + stream->cdrom.cur_min = vfs_cdrom_toc.track[0].min; + stream->cdrom.cur_sec = vfs_cdrom_toc.track[0].sec; + stream->cdrom.cur_frame = vfs_cdrom_toc.track[0].frame; + stream->cdrom.cur_lba = cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame); + } +} + +int retro_vfs_file_close_cdrom(libretro_vfs_implementation_file *stream) +{ +#ifdef CDROM_DEBUG + printf("[CDROM] Close: Path %s\n", stream->orig_path); + fflush(stdout); +#endif + +#if defined(_WIN32) && !defined(_XBOX) + if (!stream->fh || !CloseHandle(stream->fh)) + return -1; +#else + if (!stream->fp || fclose(stream->fp)) + return -1; +#endif + + return 0; +} + +int64_t retro_vfs_file_tell_cdrom(libretro_vfs_implementation_file *stream) +{ + const char *ext = NULL; + if (!stream) + return -1; + + ext = path_get_extension(stream->orig_path); + + if (string_is_equal_noncase(ext, "cue")) + { +#ifdef CDROM_DEBUG + printf("[CDROM] (cue) Tell: Path %s Position %" PRIu64 "\n", stream->orig_path, stream->cdrom.byte_pos); + fflush(stdout); +#endif + return stream->cdrom.byte_pos; + } + else if (string_is_equal_noncase(ext, "bin")) + { +#ifdef CDROM_DEBUG + printf("[CDROM] (bin) Tell: Path %s Position %" PRId64 "\n", stream->orig_path, stream->cdrom.byte_pos); + fflush(stdout); +#endif + return stream->cdrom.byte_pos; + } + + return -1; +} + +int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, + void *s, uint64_t len) +{ + int rv; + const char *ext = path_get_extension(stream->orig_path); + + if (string_is_equal_noncase(ext, "cue")) + { + if (len < stream->cdrom.cue_len - stream->cdrom.byte_pos) + { +#ifdef CDROM_DEBUG + printf("[CDROM] Read: Reading %" PRIu64 " bytes from cuesheet starting at %" PRIu64 "...\n", len, stream->cdrom.byte_pos); + fflush(stdout); +#endif + memcpy(s, stream->cdrom.cue_buf + stream->cdrom.byte_pos, len); + stream->cdrom.byte_pos += len; + + return len; + } + else + { +#ifdef CDROM_DEBUG + printf("[CDROM] Read: Reading %" PRIu64 " bytes from cuesheet starting at %" PRIu64 " failed.\n", len, stream->cdrom.byte_pos); + fflush(stdout); +#endif + return 0; + } + } + else if (string_is_equal_noncase(ext, "bin")) + { + size_t skip = stream->cdrom.byte_pos % 2352; + unsigned char min = 0; + unsigned char sec = 0; + unsigned char frame = 0; + unsigned char rmin = 0; + unsigned char rsec = 0; + unsigned char rframe = 0; + + if (stream->cdrom.byte_pos >= vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes) + return 0; + + if (stream->cdrom.byte_pos + len > vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes) + len -= (stream->cdrom.byte_pos + len) - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes; + + cdrom_lba_to_msf(stream->cdrom.cur_lba, &min, &sec, &frame); + cdrom_lba_to_msf(stream->cdrom.cur_lba - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba, &rmin, &rsec, &rframe); + +#ifdef CDROM_DEBUG + printf("[CDROM] Read: Reading %" PRIu64 " bytes from %s starting at byte offset %" PRIu64 " (rMSF %02u:%02u:%02u aMSF %02u:%02u:%02u) (LBA %u) skip %" PRIu64 "...\n", len, stream->orig_path, stream->cdrom.byte_pos, (unsigned)rmin, (unsigned)rsec, (unsigned)rframe, (unsigned)min, (unsigned)sec, (unsigned)frame, stream->cdrom.cur_lba, skip); + fflush(stdout); +#endif + + rv = cdrom_read(stream, &vfs_cdrom_toc.timeouts, min, sec, frame, s, (size_t)len, skip); + /*rv = cdrom_read_lba(stream, stream->cdrom.cur_lba, s, (size_t)len, skip);*/ + + if (rv) + { +#ifdef CDROM_DEBUG + printf("[CDROM] Failed to read %" PRIu64 " bytes from CD.\n", len); + fflush(stdout); +#endif + return 0; + } + + stream->cdrom.byte_pos += len; + stream->cdrom.cur_lba = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352); + + cdrom_lba_to_msf(stream->cdrom.cur_lba, &stream->cdrom.cur_min, &stream->cdrom.cur_sec, &stream->cdrom.cur_frame); + +#ifdef CDROM_DEBUG + printf("[CDROM] read %" PRIu64 " bytes, position is now: %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)\n", len, stream->cdrom.byte_pos, (unsigned)stream->cdrom.cur_min, (unsigned)stream->cdrom.cur_sec, (unsigned)stream->cdrom.cur_frame, cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame)); + fflush(stdout); +#endif + + return len; + } + + return 0; +} + +int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream) +{ + return 0; +} + +const vfs_cdrom_t* retro_vfs_file_get_cdrom_position(const libretro_vfs_implementation_file *stream) +{ + return &stream->cdrom; +} diff --git a/libretro/libretro-common/vfs/vfs_implementation_uwp.cpp b/libretro/libretro-common/vfs/vfs_implementation_uwp.cpp new file mode 100644 index 0000000..f0acba4 --- /dev/null +++ b/libretro/libretro-common/vfs/vfs_implementation_uwp.cpp @@ -0,0 +1,827 @@ +/* Copyright (C) 2018-2019 The RetroArch team +* +* --------------------------------------------------------------------------------------- +* The following license statement only applies to this file (vfs_implementation_uwp.cpp). +* --------------------------------------------------------------------------------------- +* +* Permission is hereby granted, free of charge, +* to any person obtaining a copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +* and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Storage; +using namespace Windows::Storage::Streams; +using namespace Windows::Storage::FileProperties; + +#ifdef RARCH_INTERNAL +#ifndef VFS_FRONTEND +#define VFS_FRONTEND +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + /* Dear Microsoft + * I really appreciate all the effort you took to not provide any + * synchronous file APIs and block all attempts to synchronously + * wait for the results of async tasks for "smooth user experience", + * but I'm not going to run and rewrite all RetroArch cores for + * async I/O. I hope you like this hack I made instead. + */ + template + T RunAsync(std::function()> func) + { + volatile bool finished = false; + Platform::Exception^ exception = nullptr; + T result; + + func().then([&finished, &exception, &result](concurrency::task t) { + try + { + result = t.get(); + } + catch (Platform::Exception^ exception_) + { + exception = exception_; + } + finished = true; + }); + + /* Don't stall the UI thread - prevents a deadlock */ + Windows::UI::Core::CoreWindow^ corewindow = Windows::UI::Core::CoreWindow::GetForCurrentThread(); + while (!finished) + { + if (corewindow) { + corewindow->Dispatcher->ProcessEvents(Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent); + } + } + + if (exception != nullptr) + throw exception; + return result; + } + + template + T RunAsyncAndCatchErrors(std::function()> func, T valueOnError) + { + try + { + return RunAsync(func); + } + catch (Platform::Exception^ e) + { + return valueOnError; + } + } + + void windowsize_path(wchar_t* path) + { + /* UWP deals with paths containing / instead of \ way worse than normal Windows */ + /* and RetroArch may sometimes mix them (e.g. on archive extraction) */ + if (!path) + return; + while (*path) + { + if (*path == '/') + *path = '\\'; + ++path; + } + } +} + +namespace +{ + /* Damn you, UWP, why no functions for that either */ + template + concurrency::task GetItemFromPathAsync(Platform::String^ path) + { + static_assert(false, "StorageFile and StorageFolder only"); + } + template<> + concurrency::task GetItemFromPathAsync(Platform::String^ path) + { + return concurrency::create_task(StorageFile::GetFileFromPathAsync(path)); + } + template<> + concurrency::task GetItemFromPathAsync(Platform::String^ path) + { + return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path)); + } + + template + concurrency::task GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path) + { + static_assert(false, "StorageFile and StorageFolder only"); + } + template<> + concurrency::task GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path) + { + if (path->IsEmpty()) + retro_assert(false); /* Attempt to read a folder as a file - this really should have been caught earlier */ + return concurrency::create_task(folder->GetFileAsync(path)); + } + template<> + concurrency::task GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path) + { + if (path->IsEmpty()) + return concurrency::create_task(concurrency::create_async([folder]() { return folder; })); + return concurrency::create_task(folder->GetFolderAsync(path)); + } +} + +namespace +{ + /* A list of all StorageFolder objects returned using from the file picker */ + Platform::Collections::Vector accessible_directories; + + concurrency::task TriggerPickerAddDialog() + { + auto folderPicker = ref new Windows::Storage::Pickers::FolderPicker(); + folderPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::Desktop; + folderPicker->FileTypeFilter->Append("*"); + + return concurrency::create_task(folderPicker->PickSingleFolderAsync()).then([](StorageFolder^ folder) { + if (folder == nullptr) + throw ref new Platform::Exception(E_ABORT, L"Operation cancelled by user"); + + /* TODO: check for duplicates */ + accessible_directories.Append(folder); + return folder->Path; + }); + } + + template + concurrency::task LocateStorageItem(Platform::String^ path) + { + /* Look for a matching directory we can use */ + for each (StorageFolder^ folder in accessible_directories) + { + std::wstring folder_path = folder->Path->Data(); + /* Could be C:\ or C:\Users\somebody - remove the trailing slash to unify them */ + if (folder_path[folder_path.size() - 1] == '\\') + folder_path.erase(folder_path.size() - 1); + std::wstring file_path = path->Data(); + if (file_path.find(folder_path) == 0) + { + /* Found a match */ + file_path = file_path.length() > folder_path.length() ? file_path.substr(folder_path.length() + 1) : L""; + return concurrency::create_task(GetItemInFolderFromPathAsync(folder, ref new Platform::String(file_path.data()))); + } + } + + /* No matches - try accessing directly, and fallback to user prompt */ + return concurrency::create_task(GetItemFromPathAsync(path)).then([&](concurrency::task item) { + try + { + T^ storageItem = item.get(); + return concurrency::create_task(concurrency::create_async([storageItem]() { return storageItem; })); + } + catch (Platform::AccessDeniedException^ e) + { + Windows::UI::Popups::MessageDialog^ dialog = + ref new Windows::UI::Popups::MessageDialog("Path \"" + path + "\" is not currently accessible. Please open any containing directory to access it."); + dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("Open file picker")); + dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("Cancel")); + return concurrency::create_task(dialog->ShowAsync()).then([path](Windows::UI::Popups::IUICommand^ cmd) { + if (cmd->Label == "Open file picker") + { + return TriggerPickerAddDialog().then([path](Platform::String^ added_path) { + /* Retry */ + return LocateStorageItem(path); + }); + } + else + { + throw ref new Platform::Exception(E_ABORT, L"Operation cancelled by user"); + } + }); + } + }); + } + + IStorageItem^ LocateStorageFileOrFolder(Platform::String^ path) + { + if (!path || path->IsEmpty()) + return nullptr; + + if (*(path->End() - 1) == '\\') + { + /* Ends with a slash, so it's definitely a directory */ + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(LocateStorageItem(path)); + }, nullptr); + } + else + { + /* No final slash - probably a file (since RetroArch usually slash-terminates dirs), but there is still a chance it's a directory */ + IStorageItem^ item; + item = RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(LocateStorageItem(path)); + }, nullptr); + if (!item) + { + item = RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(LocateStorageItem(path)); + }, nullptr); + } + return item; + } + } +} + + +/* This is some pure magic and I have absolutely no idea how it works */ +/* Wraps a raw buffer into a WinRT object */ +/* https://stackoverflow.com/questions/10520335/how-to-wrap-a-char-buffer-in-a-winrt-ibuffer-in-c */ +class NativeBuffer : + public Microsoft::WRL::RuntimeClass, + ABI::Windows::Storage::Streams::IBuffer, + Windows::Storage::Streams::IBufferByteAccess> +{ +public: + virtual ~NativeBuffer() + { + } + + HRESULT __stdcall RuntimeClassInitialize(byte *buffer, uint32_t capacity, uint32_t length) + { + m_buffer = buffer; + m_capacity = capacity; + m_length = length; + return S_OK; + } + + HRESULT __stdcall Buffer(byte **value) + { + if (m_buffer == nullptr) + return E_INVALIDARG; + *value = m_buffer; + return S_OK; + } + + HRESULT __stdcall get_Capacity(uint32_t *value) + { + *value = m_capacity; + return S_OK; + } + + HRESULT __stdcall get_Length(uint32_t *value) + { + *value = m_length; + return S_OK; + } + + HRESULT __stdcall put_Length(uint32_t value) + { + if (value > m_capacity) + return E_INVALIDARG; + m_length = value; + return S_OK; + } + +private: + byte *m_buffer; + uint32_t m_capacity; + uint32_t m_length; +}; + +IBuffer^ CreateNativeBuffer(void* buf, uint32_t capacity, uint32_t length) +{ + Microsoft::WRL::ComPtr nativeBuffer; + Microsoft::WRL::Details::MakeAndInitialize(&nativeBuffer, (byte *)buf, capacity, length); + auto iinspectable = (IInspectable *)reinterpret_cast(nativeBuffer.Get()); + IBuffer ^buffer = reinterpret_cast(iinspectable); + return buffer; +} + +#ifdef VFS_FRONTEND +struct retro_vfs_file_handle +#else +struct libretro_vfs_implementation_file +#endif +{ + IRandomAccessStream^ fp; + IBuffer^ bufferp; + char* buffer; + char* orig_path; + size_t buffer_size; + int buffer_left; + size_t buffer_fill; +}; + +libretro_vfs_implementation_file *retro_vfs_file_open_impl(const char *path, unsigned mode, unsigned hints) +{ + if (!path || !*path) + return NULL; + + if (!path_is_absolute(path)) + { + RARCH_WARN("Something tried to access files from current directory ('%s'). This is not allowed on UWP.\n", path); + return NULL; + } + + if (path_char_is_slash(path[strlen(path) - 1])) + { + RARCH_WARN("Trying to open a directory as file?! ('%s')\n", path); + return NULL; + } + + char* dirpath = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + fill_pathname_basedir(dirpath, path, PATH_MAX_LENGTH); + wchar_t *dirpath_wide = utf8_to_utf16_string_alloc(dirpath); + windowsize_path(dirpath_wide); + Platform::String^ dirpath_str = ref new Platform::String(dirpath_wide); + free(dirpath_wide); + free(dirpath); + + char* filename = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + fill_pathname_base(filename, path, PATH_MAX_LENGTH); + wchar_t *filename_wide = utf8_to_utf16_string_alloc(filename); + Platform::String^ filename_str = ref new Platform::String(filename_wide); + free(filename_wide); + free(filename); + + retro_assert(!dirpath_str->IsEmpty() && !filename_str->IsEmpty()); + + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(LocateStorageItem(dirpath_str)).then([&](StorageFolder^ dir) { + if (mode == RETRO_VFS_FILE_ACCESS_READ) + return dir->GetFileAsync(filename_str); + else + return dir->CreateFileAsync(filename_str, (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0 ? + CreationCollisionOption::OpenIfExists : CreationCollisionOption::ReplaceExisting); + }).then([&](StorageFile^ file) { + FileAccessMode accessMode = mode == RETRO_VFS_FILE_ACCESS_READ ? + FileAccessMode::Read : FileAccessMode::ReadWrite; + return file->OpenAsync(accessMode); + }).then([&](IRandomAccessStream^ fpstream) { + libretro_vfs_implementation_file *stream = (libretro_vfs_implementation_file*)calloc(1, sizeof(*stream)); + if (!stream) + return (libretro_vfs_implementation_file*)NULL; + + stream->orig_path = strdup(path); + stream->fp = fpstream; + stream->fp->Seek(0); + // Preallocate a small buffer for manually buffered IO, makes short read faster + int buf_size = 8 * 1024; + stream->buffer = (char*)malloc(buf_size); + stream->bufferp = CreateNativeBuffer(stream->buffer, buf_size, 0); + stream->buffer_left = 0; + stream->buffer_fill = 0; + stream->buffer_size = buf_size; + return stream; + }); + }, NULL); +} + +int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream) +{ + if (!stream || !stream->fp) + return -1; + + /* Apparently, this is how you close a file in WinRT */ + /* Yes, really */ + stream->fp = nullptr; + free(stream->buffer); + + return 0; +} + +int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream) +{ + return false; /* TODO */ +} + +int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream) +{ + if (!stream || !stream->fp) + return 0; + return stream->fp->Size; +} + +int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length) +{ + if (!stream || !stream->fp) + return -1; + stream->fp->Size = length; + return 0; +} + +int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream) +{ + if (!stream || !stream->fp) + return -1; + return stream->fp->Position - stream->buffer_left; +} + +int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream, int64_t offset, int seek_position) +{ + if (!stream || !stream->fp) + return -1; + + switch (seek_position) + { + case RETRO_VFS_SEEK_POSITION_START: + stream->fp->Seek(offset); + break; + + case RETRO_VFS_SEEK_POSITION_CURRENT: + stream->fp->Seek(retro_vfs_file_tell_impl(stream) + offset); + break; + + case RETRO_VFS_SEEK_POSITION_END: + stream->fp->Seek(stream->fp->Size - offset); + break; + } + + // For simplicity always flush the buffer on seek + stream->buffer_left = 0; + + return 0; +} + +int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream, void *s, uint64_t len) +{ + if (!stream || !stream->fp || !s) + return -1; + + int64_t bytes_read = 0; + + if (len <= stream->buffer_size) { + // Small read, use manually buffered IO + if (stream->buffer_left < len) { + // Exhaust the buffer + memcpy(s, &stream->buffer[stream->buffer_fill - stream->buffer_left], stream->buffer_left); + len -= stream->buffer_left; + bytes_read += stream->buffer_left; + stream->buffer_left = 0; + + // Fill buffer + stream->buffer_left = RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(stream->fp->ReadAsync(stream->bufferp, stream->bufferp->Capacity, InputStreamOptions::None)).then([&](IBuffer^ buf) { + retro_assert(stream->bufferp == buf); + return (int64_t)stream->bufferp->Length; + }); + }, -1); + stream->buffer_fill = stream->buffer_left; + + if (stream->buffer_left == -1) { + stream->buffer_left = 0; + stream->buffer_fill = 0; + return -1; + } + + if (stream->buffer_left < len) { + // EOF + memcpy(&((char*)s)[bytes_read], stream->buffer, stream->buffer_left); + bytes_read += stream->buffer_left; + stream->buffer_left = 0; + return bytes_read; + } + + memcpy(&((char*)s)[bytes_read], stream->buffer, len); + bytes_read += len; + stream->buffer_left -= len; + return bytes_read; + } + + // Internal buffer already contains requested amount + memcpy(s, &stream->buffer[stream->buffer_fill - stream->buffer_left], len); + stream->buffer_left -= len; + return len; + } + + // Big read exceeding buffer size, exhaust small buffer and read rest in one go + memcpy(s, &stream->buffer[stream->buffer_fill - stream->buffer_left], stream->buffer_left); + len -= stream->buffer_left; + bytes_read += stream->buffer_left; + stream->buffer_left = 0; + + IBuffer^ buffer = CreateNativeBuffer(&((char*)s)[bytes_read], len, 0); + + int64_t ret = RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(stream->fp->ReadAsync(buffer, buffer->Capacity - bytes_read, InputStreamOptions::None)).then([&](IBuffer^ buf) { + retro_assert(buf == buffer); + return (int64_t)buffer->Length; + }); + }, -1); + + if (ret == -1) { + return -1; + } + return bytes_read + ret; +} + +int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len) +{ + if (!stream || !stream->fp || !s) + return -1; + + // const_cast to remove const modifier is undefined behaviour, but the buffer is only read, should be safe + IBuffer^ buffer = CreateNativeBuffer(const_cast(s), len, len); + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(stream->fp->WriteAsync(buffer)).then([&](unsigned int written) { + return (int64_t)written; + }); + }, -1); +} + +int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream) +{ + if (!stream || !stream->fp) + return -1; + + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(stream->fp->FlushAsync()).then([&](bool this_value_is_not_even_documented_wtf) { + /* The bool value may be reporting an error or something, but just leave it alone for now */ + /* https://github.com/MicrosoftDocs/winrt-api/issues/841 */ + return 0; + }); + }, -1); +} + +int retro_vfs_file_remove_impl(const char *path) +{ + if (!path || !*path) + return -1; + + wchar_t *path_wide = utf8_to_utf16_string_alloc(path); + windowsize_path(path_wide); + Platform::String^ path_str = ref new Platform::String(path_wide); + free(path_wide); + + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(LocateStorageItem(path_str)).then([&](StorageFile^ file) { + return file->DeleteAsync(StorageDeleteOption::PermanentDelete); + }).then([&]() { + return 0; + }); + }, -1); +} + +/* TODO: this may not work if trying to move a directory */ +int retro_vfs_file_rename_impl(const char *old_path, const char *new_path) +{ + if (!old_path || !*old_path || !new_path || !*new_path) + return -1; + + wchar_t* old_path_wide = utf8_to_utf16_string_alloc(old_path); + Platform::String^ old_path_str = ref new Platform::String(old_path_wide); + free(old_path_wide); + + char* new_dir_path = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + fill_pathname_basedir(new_dir_path, new_path, PATH_MAX_LENGTH); + wchar_t *new_dir_path_wide = utf8_to_utf16_string_alloc(new_dir_path); + windowsize_path(new_dir_path_wide); + Platform::String^ new_dir_path_str = ref new Platform::String(new_dir_path_wide); + free(new_dir_path_wide); + free(new_dir_path); + + char* new_file_name = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + fill_pathname_base(new_file_name, new_path, PATH_MAX_LENGTH); + wchar_t *new_file_name_wide = utf8_to_utf16_string_alloc(new_file_name); + Platform::String^ new_file_name_str = ref new Platform::String(new_file_name_wide); + free(new_file_name_wide); + free(new_file_name); + + retro_assert(!old_path_str->IsEmpty() && !new_dir_path_str->IsEmpty() && !new_file_name_str->IsEmpty()); + + return RunAsyncAndCatchErrors([&]() { + concurrency::task old_file_task = concurrency::create_task(LocateStorageItem(old_path_str)); + concurrency::task new_dir_task = concurrency::create_task(LocateStorageItem(new_dir_path_str)); + return concurrency::create_task([&] { + /* Run these two tasks in parallel */ + /* TODO: There may be some cleaner way to express this */ + concurrency::task_group group; + group.run([&] { return old_file_task; }); + group.run([&] { return new_dir_task; }); + group.wait(); + }).then([&]() { + return old_file_task.get()->MoveAsync(new_dir_task.get(), new_file_name_str, NameCollisionOption::ReplaceExisting); + }).then([&]() { + return 0; + }); + }, -1); +} + +const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *stream) +{ + /* should never happen, do something noisy so caller can be fixed */ + if (!stream) + abort(); + return stream->orig_path; +} + +int retro_vfs_stat_impl(const char *path, int32_t *size) +{ + if (!path || !*path) + return 0; + + wchar_t *path_wide = utf8_to_utf16_string_alloc(path); + windowsize_path(path_wide); + Platform::String^ path_str = ref new Platform::String(path_wide); + free(path_wide); + + IStorageItem^ item = LocateStorageFileOrFolder(path_str); + if (!item) + return 0; + + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(item->GetBasicPropertiesAsync()).then([&](BasicProperties^ properties) { + if (size) + *size = properties->Size; + return item->IsOfType(StorageItemTypes::Folder) ? RETRO_VFS_STAT_IS_VALID | RETRO_VFS_STAT_IS_DIRECTORY : RETRO_VFS_STAT_IS_VALID; + }); + }, 0); +} + +int retro_vfs_mkdir_impl(const char *dir) +{ + if (!dir || !*dir) + return -1; + + char* dir_local = strdup(dir); + /* If the path ends with a slash, we have to remove it for basename to work */ + char* tmp = dir_local + strlen(dir_local) - 1; + if (path_char_is_slash(*tmp)) + *tmp = 0; + + char* dir_name = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + fill_pathname_base(dir_name, dir_local, PATH_MAX_LENGTH); + wchar_t *dir_name_wide = utf8_to_utf16_string_alloc(dir_name); + Platform::String^ dir_name_str = ref new Platform::String(dir_name_wide); + free(dir_name_wide); + free(dir_name); + + char* parent_path = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + fill_pathname_parent_dir(parent_path, dir_local, PATH_MAX_LENGTH); + wchar_t *parent_path_wide = utf8_to_utf16_string_alloc(parent_path); + windowsize_path(parent_path_wide); + Platform::String^ parent_path_str = ref new Platform::String(parent_path_wide); + free(parent_path_wide); + free(parent_path); + + retro_assert(!dir_name_str->IsEmpty() && !parent_path_str->IsEmpty()); + + free(dir_local); + + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(LocateStorageItem(parent_path_str)).then([&](StorageFolder^ parent) { + return parent->CreateFolderAsync(dir_name_str); + }).then([&](concurrency::task new_dir) { + try + { + new_dir.get(); + } + catch (Platform::COMException^ e) + { + if (e->HResult == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) + return -2; + throw; + } + return 0; + }); + }, -1); +} + +#ifdef VFS_FRONTEND +struct retro_vfs_dir_handle +#else +struct libretro_vfs_implementation_dir +#endif +{ + IVectorView^ directory; + IIterator^ entry; + char *entry_name; +}; + +libretro_vfs_implementation_dir *retro_vfs_opendir_impl(const char *name, bool include_hidden) +{ + libretro_vfs_implementation_dir *rdir; + + if (!name || !*name) + return NULL; + + rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir)); + if (!rdir) + return NULL; + + wchar_t *name_wide = utf8_to_utf16_string_alloc(name); + windowsize_path(name_wide); + Platform::String^ name_str = ref new Platform::String(name_wide); + free(name_wide); + + rdir->directory = RunAsyncAndCatchErrors^>([&]() { + return concurrency::create_task(LocateStorageItem(name_str)).then([&](StorageFolder^ folder) { + return folder->GetItemsAsync(); + }); + }, nullptr); + + if (rdir->directory) + return rdir; + + free(rdir); + return NULL; +} + +bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir) +{ + if (!rdir->entry) + { + rdir->entry = rdir->directory->First(); + return rdir->entry->HasCurrent; + } + else + { + return rdir->entry->MoveNext(); + } +} + +const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir) +{ + if (rdir->entry_name) + free(rdir->entry_name); + rdir->entry_name = utf16_to_utf8_string_alloc(rdir->entry->Current->Name->Data()); + return rdir->entry_name; +} + +bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir) +{ + return rdir->entry->Current->IsOfType(StorageItemTypes::Folder); +} + +int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir) +{ + if (!rdir) + return -1; + + if (rdir->entry_name) + free(rdir->entry_name); + rdir->entry = nullptr; + rdir->directory = nullptr; + + free(rdir); + return 0; +} + +bool uwp_drive_exists(const char *path) +{ + if (!path || !*path) + return 0; + + wchar_t *path_wide = utf8_to_utf16_string_alloc(path); + Platform::String^ path_str = ref new Platform::String(path_wide); + free(path_wide); + + return RunAsyncAndCatchErrors([&]() { + return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path_str)).then([](StorageFolder^ properties) { + return true; + }); + }, false); +} + +char* uwp_trigger_picker(void) +{ + return RunAsyncAndCatchErrors([&]() { + return TriggerPickerAddDialog().then([](Platform::String^ path) { + return utf16_to_utf8_string_alloc(path->Data()); + }); + }, NULL); +}