mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
File I/O: one path, libretro-common's filestream and VFS
Every file the core touches goes through libretro-common's filestream, path and dirent layers, taken from RetroArch: vfs_hybrid opens local paths with libretro-common's own implementation and hands URI paths (and, on Android, anything a local open cannot reach) to the frontend's VFS. File::IOFile and File::DirectIOFile sit on RFILE, and FileUtil, FileSearch and the directory walks on path_stat, path_mkdir and retro_opendir. Dolphin's fopen, POSIX and Win32 file code, std::filesystem and std::fstream are gone, with the dual VFS paths and the core option that chose between them. Files opened for reading alone are memory-mapped where the platform can: reads copy straight out of the page cache, DirectIOFile's offset reads on one mapped object are thread safe, and GetMappedPtr(), View() and Prefetch() lend and warm without a copy. Text files are read a line at a time through File::LineReader, lent from the mapping or a window refilled a block at a time, never a byte at a time; JSON is parsed straight from the mapping. Ini files and the cheat file are built in memory and written once, an ini only when it changed and then atomically.
This commit is contained in:
1 parent
bdd075aa7f
commit
2323f96cf4
75 files changed
+20456
-3426
No files matched your search
@@ -0,0 +1,117 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (compat_posix_string.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 <ctype.h>
|
||||
|
||||
#include <compat/posix_string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#undef strcasecmp
|
||||
#undef strdup
|
||||
#undef isblank
|
||||
#undef strtok_r
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <compat/strl.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* ASCII case folding, not tolower.
|
||||
*
|
||||
* tolower takes an int that must be representable as unsigned char or
|
||||
* EOF. A plain char sign-extends any byte above 0x7F into a negative
|
||||
* value, so passing one straight in is undefined - and these are
|
||||
* compared on file paths and user text, where such bytes are ordinary.
|
||||
* glibc happens to tolerate it because its table is offset for exactly
|
||||
* this mistake; nothing guarantees the next libc will.
|
||||
*
|
||||
* Folding only A-Z is also what the callers mean. Anything wider makes
|
||||
* a comparison depend on the user's locale, which for extensions,
|
||||
* driver names and identifiers is not wanted. */
|
||||
#define COMPAT_LOWER(c) \
|
||||
(((c) >= 'A' && (c) <= 'Z') ? ((c) + ('a' - 'A')) : (c))
|
||||
|
||||
int retro_strcasecmp__(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *b)
|
||||
{
|
||||
int a_ = COMPAT_LOWER((unsigned char)*a);
|
||||
int b_ = COMPAT_LOWER((unsigned char)*b);
|
||||
|
||||
if (a_ != b_)
|
||||
return a_ - b_;
|
||||
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
|
||||
return COMPAT_LOWER((unsigned char)*a) - COMPAT_LOWER((unsigned char)*b);
|
||||
}
|
||||
|
||||
char *retro_strdup__(const char *orig)
|
||||
{
|
||||
size_t _len = strlen(orig) + 1;
|
||||
char *ret = (char*)malloc(_len);
|
||||
if (!ret)
|
||||
return NULL;
|
||||
memcpy(ret, orig, _len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int retro_isblank__(int c)
|
||||
{
|
||||
return (c == ' ') || (c == '\t');
|
||||
}
|
||||
|
||||
char *retro_strtok_r__(char *str, const char *delim, char **saveptr)
|
||||
{
|
||||
char *first = NULL;
|
||||
if (!saveptr || !delim)
|
||||
return NULL;
|
||||
|
||||
if (str)
|
||||
*saveptr = str;
|
||||
|
||||
do
|
||||
{
|
||||
char *ptr = NULL;
|
||||
first = *saveptr;
|
||||
while (*first && strchr(delim, *first))
|
||||
*first++ = '\0';
|
||||
|
||||
if (*first == '\0')
|
||||
return NULL;
|
||||
|
||||
ptr = first + 1;
|
||||
|
||||
while (*ptr && !strchr(delim, *ptr))
|
||||
ptr++;
|
||||
|
||||
*saveptr = ptr + (*ptr ? 1 : 0);
|
||||
*ptr = '\0';
|
||||
} while (strlen(first) == 0);
|
||||
|
||||
return first;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (compat_strcasestr.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 <ctype.h>
|
||||
|
||||
#include <compat/strcasestr.h>
|
||||
|
||||
/* ASCII case folding, done here rather than through tolower.
|
||||
*
|
||||
* tolower takes an int whose value must be representable as unsigned
|
||||
* char or EOF; passing a plain char sign-extends anything above 0x7F
|
||||
* into a negative value, which is undefined. This is called on
|
||||
* arbitrary bytes - file paths, HTTP responses, config text - so that
|
||||
* was reachable. Folding only A-Z is also what every caller here
|
||||
* means: extensions, window manager names, config keys and header
|
||||
* fields are all ASCII, and locale-dependent folding of anything else
|
||||
* would make the result depend on the user's locale. */
|
||||
#define STRCASESTR_LOWER(c) \
|
||||
(((c) >= 'A' && (c) <= 'Z') ? ((c) + ('a' - 'A')) : (c))
|
||||
|
||||
/* Pretty much strncasecmp. */
|
||||
static int casencmp(const char *a, const char *b, size_t n)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
int a_lower = STRCASESTR_LOWER((unsigned char)a[i]);
|
||||
int b_lower = STRCASESTR_LOWER((unsigned char)b[i]);
|
||||
if (a_lower != b_lower)
|
||||
return a_lower - b_lower;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Let memchr find the candidate positions.
|
||||
*
|
||||
* Comparing at every offset means folding every byte of the haystack,
|
||||
* which measured some sixty times slower than strstr on the same
|
||||
* inputs - enough to matter where this is called per file over a
|
||||
* directory. Only the first character of the needle decides whether
|
||||
* an offset is worth examining, and memchr finds those far faster than
|
||||
* a byte loop can, so it does the skipping and the fold only runs
|
||||
* where a match is actually possible. A needle whose first character
|
||||
* has no case of its own - a digit, a punctuation mark - costs one
|
||||
* memchr rather than two. */
|
||||
char *compat_strcasestr(const char *haystack, const char *needle)
|
||||
{
|
||||
size_t nlen = strlen(needle);
|
||||
size_t hlen = strlen(haystack);
|
||||
const char *end = haystack + hlen;
|
||||
int lo;
|
||||
int up;
|
||||
|
||||
if (!nlen)
|
||||
return (char*)haystack;
|
||||
if (nlen > hlen)
|
||||
return NULL;
|
||||
|
||||
lo = STRCASESTR_LOWER((unsigned char)needle[0]);
|
||||
up = (lo >= 'a' && lo <= 'z') ? (lo - ('a' - 'A')) : lo;
|
||||
|
||||
while ((size_t)(end - haystack) >= nlen)
|
||||
{
|
||||
size_t left = (size_t)(end - haystack);
|
||||
const char *a = (const char*)memchr(haystack, lo, left);
|
||||
const char *b = (up != lo)
|
||||
? (const char*)memchr(haystack, up, left) : NULL;
|
||||
const char *p = !a ? b : (!b ? a : (a < b ? a : b));
|
||||
|
||||
if (!p || (size_t)(end - p) < nlen)
|
||||
return NULL;
|
||||
if (!casencmp(p + 1, needle + 1, nlen - 1))
|
||||
return (char*)p;
|
||||
haystack = p + 1;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (fopen_utf8.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 <compat/fopen_utf8.h>
|
||||
#include <encodings/utf.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <boolean.h>
|
||||
#if defined(__WINRT__)
|
||||
#include <stdint.h>
|
||||
#include <windows.h>
|
||||
#include <fileapifromapp.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef fopen
|
||||
|
||||
#if defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
||||
static void *fopen_utf8_ansi(const char * filename, const char * mode)
|
||||
{
|
||||
char * filename_local = utf8_to_local_string_alloc(filename);
|
||||
if (filename_local)
|
||||
{
|
||||
FILE *ret = fopen(filename_local, mode);
|
||||
free(filename_local);
|
||||
return ret;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
||||
static void *fopen_utf8_wide(const char * filename, const char * mode)
|
||||
{
|
||||
wchar_t * filename_w = utf8_to_utf16_string_alloc(filename);
|
||||
if (filename_w)
|
||||
{
|
||||
FILE *ret = NULL;
|
||||
#if defined(__WINRT__)
|
||||
HANDLE file_handle = INVALID_HANDLE_VALUE;
|
||||
DWORD desired_access = 0;
|
||||
DWORD creation_disposition = OPEN_EXISTING;
|
||||
int open_flags = O_BINARY;
|
||||
int fd = -1;
|
||||
bool append = mode && strchr(mode, 'a');
|
||||
bool write = mode && strchr(mode, 'w');
|
||||
bool plus = mode && strchr(mode, '+');
|
||||
wchar_t *path = filename_w;
|
||||
|
||||
while (*path)
|
||||
{
|
||||
if (*path == L'/')
|
||||
*path = L'\\';
|
||||
path++;
|
||||
}
|
||||
|
||||
if (mode && strchr(mode, 'r'))
|
||||
desired_access |= GENERIC_READ;
|
||||
if (write || append || plus)
|
||||
desired_access |= GENERIC_WRITE;
|
||||
if (plus)
|
||||
desired_access |= GENERIC_READ;
|
||||
|
||||
if (append)
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
else if (write)
|
||||
creation_disposition = CREATE_ALWAYS;
|
||||
|
||||
if (plus)
|
||||
open_flags |= O_RDWR;
|
||||
else if (append || write)
|
||||
open_flags |= O_WRONLY;
|
||||
else
|
||||
open_flags |= O_RDONLY;
|
||||
|
||||
if (append)
|
||||
open_flags |= O_APPEND;
|
||||
if (write || append)
|
||||
open_flags |= O_CREAT;
|
||||
if (write)
|
||||
open_flags |= O_TRUNC;
|
||||
|
||||
file_handle = CreateFile2FromAppW(filename_w, desired_access,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
creation_disposition, NULL);
|
||||
if (file_handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
fd = _open_osfhandle((intptr_t)file_handle, open_flags);
|
||||
if (fd != -1)
|
||||
ret = _fdopen(fd, mode);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
if (fd != -1)
|
||||
_close(fd);
|
||||
else
|
||||
CloseHandle(file_handle);
|
||||
}
|
||||
}
|
||||
#else
|
||||
wchar_t *mode_w = utf8_to_utf16_string_alloc(mode);
|
||||
if (mode_w)
|
||||
{
|
||||
ret = _wfopen(filename_w, mode_w);
|
||||
free(mode_w);
|
||||
}
|
||||
#endif
|
||||
free(filename_w);
|
||||
return ret;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void *fopen_utf8(const char * filename, const char * mode)
|
||||
{
|
||||
#if defined(LEGACY_WIN32_RUNTIME)
|
||||
if (win32_needs_local_encoding())
|
||||
return fopen_utf8_ansi(filename, mode);
|
||||
return fopen_utf8_wide(filename, mode);
|
||||
#elif defined(LEGACY_WIN32)
|
||||
return fopen_utf8_ansi(filename, mode);
|
||||
#else
|
||||
return fopen_utf8_wide(filename, mode);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
+1020
File diff suppressed because it is too large.
Load diff
+1594
File diff suppressed because it is too large.
Load diff
+321
@@ -0,0 +1,321 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (file_path_io.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 <retro_posix_source.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* MinGW's <sys/stat.h> defines stat (and in some configs mkdir) as
|
||||
* function-like macros that map to _stat64/_stati64/_mkdir. This must
|
||||
* be undone BEFORE including libretro.h below: otherwise the macro
|
||||
* rewrites the struct member name in the retro_vfs_interface definition
|
||||
* (retro_vfs_stat_t stat -> _stat64), and then the use sites
|
||||
* vfs_iface->stat / vfs_iface->mkdir no longer match it, breaking the
|
||||
* Windows build. The POSIX functions are still reachable via their
|
||||
* real names where this TU needs them (it doesn't call stat()/mkdir()
|
||||
* directly; all I/O goes through the VFS *_impl callbacks). */
|
||||
#ifdef stat
|
||||
#undef stat
|
||||
#endif
|
||||
#ifdef mkdir
|
||||
#undef mkdir
|
||||
#endif
|
||||
|
||||
#include <boolean.h>
|
||||
#include <file/file_path.h>
|
||||
#include <compat/strl.h>
|
||||
#include <compat/posix_string.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#define VFS_FRONTEND
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h> /* stat() is defined here */
|
||||
#endif
|
||||
|
||||
/* <direct.h> (MinGW, included just above) can re-establish the mkdir
|
||||
* macro after the earlier #undef, so drop it again here before the use
|
||||
* site below. Same rationale for stat, defensively. */
|
||||
#ifdef stat
|
||||
#undef stat
|
||||
#endif
|
||||
#ifdef mkdir
|
||||
#undef mkdir
|
||||
#endif
|
||||
|
||||
/* TODO/FIXME - globals */
|
||||
static retro_vfs_stat_t path_stat32_cb = retro_vfs_stat_impl;
|
||||
static retro_vfs_stat_64_t path_stat64_cb = retro_vfs_stat_64_impl;
|
||||
static retro_vfs_mkdir_t path_mkdir_cb = retro_vfs_mkdir_impl;
|
||||
/* VFS API v5. NULL when a frontend older than v5 is in use, so the
|
||||
* wrappers report failure instead of touching the local file system
|
||||
* behind a foreign frontend's back. */
|
||||
static retro_vfs_set_readonly_t path_set_readonly_cb = retro_vfs_set_readonly_impl;
|
||||
static retro_vfs_get_mtime_t path_get_mtime_cb = retro_vfs_get_mtime_impl;
|
||||
static retro_vfs_set_mtime_t path_set_mtime_cb = retro_vfs_set_mtime_impl;
|
||||
|
||||
void path_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
{
|
||||
const struct retro_vfs_interface*
|
||||
vfs_iface = vfs_info->iface;
|
||||
|
||||
path_stat32_cb = retro_vfs_stat_impl;
|
||||
path_stat64_cb = retro_vfs_stat_64_impl;
|
||||
path_mkdir_cb = retro_vfs_mkdir_impl;
|
||||
path_set_readonly_cb = retro_vfs_set_readonly_impl;
|
||||
path_get_mtime_cb = retro_vfs_get_mtime_impl;
|
||||
path_set_mtime_cb = retro_vfs_set_mtime_impl;
|
||||
|
||||
if (vfs_info->required_interface_version < PATH_REQUIRED_VFS_VERSION || !vfs_iface)
|
||||
return;
|
||||
|
||||
path_stat32_cb = vfs_iface->stat;
|
||||
path_mkdir_cb = vfs_iface->mkdir;
|
||||
|
||||
if (vfs_info->required_interface_version >= STAT64_REQUIRED_VFS_VERSION)
|
||||
path_stat64_cb = vfs_iface->stat_64;
|
||||
else
|
||||
path_stat64_cb = NULL;
|
||||
|
||||
/* Members a v5 frontend left NULL stay NULL: the wrappers then
|
||||
* report "unavailable" rather than dereferencing them. */
|
||||
path_set_readonly_cb = NULL;
|
||||
path_get_mtime_cb = NULL;
|
||||
path_set_mtime_cb = NULL;
|
||||
if (vfs_info->required_interface_version >= METADATA_REQUIRED_VFS_VERSION)
|
||||
{
|
||||
path_set_readonly_cb = vfs_iface->set_readonly;
|
||||
path_get_mtime_cb = vfs_iface->get_mtime;
|
||||
path_set_mtime_cb = vfs_iface->set_mtime;
|
||||
}
|
||||
}
|
||||
|
||||
bool path_is_readonly(const char *path)
|
||||
{
|
||||
if (path_stat64_cb)
|
||||
return (path_stat64_cb(path, NULL) & RETRO_VFS_STAT_IS_READONLY) != 0;
|
||||
return (path_stat32_cb(path, NULL) & RETRO_VFS_STAT_IS_READONLY) != 0;
|
||||
}
|
||||
|
||||
bool path_set_readonly(const char *path, bool readonly)
|
||||
{
|
||||
if (!path_set_readonly_cb)
|
||||
return false;
|
||||
return path_set_readonly_cb(path, readonly ? 1 : 0) == 0;
|
||||
}
|
||||
|
||||
bool path_get_mtime(const char *path, int64_t *mtime)
|
||||
{
|
||||
if (!path_get_mtime_cb || !mtime)
|
||||
return false;
|
||||
return path_get_mtime_cb(path, mtime) == 0;
|
||||
}
|
||||
|
||||
bool path_set_mtime(const char *path, int64_t mtime)
|
||||
{
|
||||
if (!path_set_mtime_cb)
|
||||
return false;
|
||||
return path_set_mtime_cb(path, mtime) == 0;
|
||||
}
|
||||
|
||||
int path_stat(const char *path)
|
||||
{
|
||||
/* Use 64‑bit stat if available, else fallback */
|
||||
return path_stat64_cb ? path_stat64_cb(path, NULL) : path_stat32_cb(path, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* path_is_directory:
|
||||
* @path : path
|
||||
*
|
||||
* Checks if path is a directory.
|
||||
*
|
||||
* @return true if path is a directory, otherwise false.
|
||||
*/
|
||||
bool path_is_directory(const char *path)
|
||||
{
|
||||
if (path_stat64_cb)
|
||||
return (path_stat64_cb(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0;
|
||||
return (path_stat32_cb(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0;
|
||||
}
|
||||
|
||||
bool path_is_character_special(const char *path)
|
||||
{
|
||||
if (path_stat64_cb)
|
||||
return (path_stat64_cb(path, NULL) & RETRO_VFS_STAT_IS_CHARACTER_SPECIAL) != 0;
|
||||
return (path_stat32_cb(path, NULL) & RETRO_VFS_STAT_IS_CHARACTER_SPECIAL) != 0;
|
||||
}
|
||||
|
||||
bool path_is_valid(const char *path)
|
||||
{
|
||||
if (path_stat64_cb)
|
||||
return (path_stat64_cb(path, NULL) & RETRO_VFS_STAT_IS_VALID) != 0;
|
||||
return (path_stat32_cb(path, NULL) & RETRO_VFS_STAT_IS_VALID) != 0;
|
||||
}
|
||||
|
||||
bool path_set_private(const char *path)
|
||||
{
|
||||
return retro_vfs_restrict_permissions_impl(path) == 0;
|
||||
}
|
||||
|
||||
int64_t path_get_size(const char *path)
|
||||
{
|
||||
int64_t filesize = 0;
|
||||
int32_t filesize32 = 0;
|
||||
|
||||
if (path_stat64_cb && path_stat64_cb(path, &filesize) != 0)
|
||||
return filesize;
|
||||
|
||||
/* Fallback: 32-bit stat */
|
||||
if (path_stat32_cb && path_stat32_cb(path, &filesize32) != 0)
|
||||
return (int64_t)filesize32;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* path_rmdir:
|
||||
* @dir : directory path.
|
||||
*
|
||||
* Removes the empty directory @dir. There is no libretro VFS entry
|
||||
* for this, so it always goes through the built-in implementation.
|
||||
*
|
||||
* @return true if the directory was removed, otherwise false -
|
||||
* including a directory that is not empty, and platforms with no
|
||||
* directory removal, where an empty directory is simply left.
|
||||
**/
|
||||
bool path_rmdir(const char *dir)
|
||||
{
|
||||
return retro_vfs_rmdir_impl(dir) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* path_mkdir:
|
||||
* @dir : directory
|
||||
*
|
||||
* Create directory on filesystem.
|
||||
*
|
||||
* Recursive function.
|
||||
*
|
||||
* @return true if directory could be created, otherwise false.
|
||||
**/
|
||||
/* True for "dev:name", "dev:/name" and "dev:\name" (a trailing
|
||||
* separator allowed): a directory whose parent is a device root. The
|
||||
* root cannot be probed everywhere - the PSP firmware refuses a stat of
|
||||
* "ms0:/" - and path_parent_dir() turns it into a relative "./", but a
|
||||
* mounted device's root always exists. */
|
||||
static bool path_parent_is_device_root(const char *p)
|
||||
{
|
||||
const char *colon = strchr(p, ':');
|
||||
const char *s;
|
||||
|
||||
if (!colon || colon == p)
|
||||
return false;
|
||||
for (s = p; s < colon; s++)
|
||||
if (*s == '/' || *s == '\\')
|
||||
return false;
|
||||
|
||||
s = colon + 1;
|
||||
if (*s == '/' || *s == '\\')
|
||||
s++;
|
||||
if (!*s || *s == '/' || *s == '\\')
|
||||
return false;
|
||||
while (*s && *s != '/' && *s != '\\')
|
||||
s++;
|
||||
if (*s)
|
||||
s++;
|
||||
return *s == '\0';
|
||||
}
|
||||
|
||||
bool path_mkdir(const char *dir)
|
||||
{
|
||||
bool norecurse = false;
|
||||
char *basedir = NULL;
|
||||
|
||||
if (!(dir && *dir))
|
||||
return false;
|
||||
|
||||
/* Nothing to do if it is already there.
|
||||
*
|
||||
* Without this the common case - which for archive extraction is
|
||||
* every member after the first in a given directory - still costs
|
||||
* a strdup, a path_parent_dir, a strcmp, a stat of the parent, a
|
||||
* mkdir that is guaranteed to fail with EEXIST, and then a stat of
|
||||
* the leaf to interpret that failure. Two directory probes and a
|
||||
* doomed syscall to answer a question one probe answers. On Win32
|
||||
* each of those probes is a UTF-16 conversion allocation plus both
|
||||
* GetFileAttributesW and _wstat64, so the saving there is larger
|
||||
* than the syscall count suggests.
|
||||
*
|
||||
* This is not a shortcut around the slow path's result: the slow
|
||||
* path already returns true for an existing directory, by way of
|
||||
* the mkdir -> -2 -> path_is_directory sequence at the bottom. The
|
||||
* one behavioural difference is at a filesystem root ("/", "C:\"),
|
||||
* where path_parent_dir empties the string and the !*basedir guard
|
||||
* below returns false today; such a directory does exist, so
|
||||
* reporting true for it is the correction, not a regression. */
|
||||
if (path_is_directory(dir))
|
||||
return true;
|
||||
|
||||
if (path_parent_is_device_root(dir))
|
||||
norecurse = true;
|
||||
else
|
||||
{
|
||||
/* Use heap. Real chance of stack
|
||||
* overflow if we recurse too hard. */
|
||||
if (!(basedir = strdup(dir)))
|
||||
return false;
|
||||
|
||||
path_parent_dir(basedir, strlen(basedir));
|
||||
|
||||
if (!*basedir || !strcmp(basedir, dir))
|
||||
{
|
||||
free(basedir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( path_is_directory(basedir)
|
||||
|| path_mkdir(basedir))
|
||||
norecurse = true;
|
||||
|
||||
free(basedir);
|
||||
}
|
||||
|
||||
if (norecurse)
|
||||
{
|
||||
int ret = path_mkdir_cb(dir);
|
||||
|
||||
/* Don't treat this as an error. */
|
||||
if (ret == -2 && path_is_directory(dir))
|
||||
return true;
|
||||
else if (ret == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (retro_dirent.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_common.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <retro_dirent.h>
|
||||
#define VFS_FRONTEND
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
/* TODO/FIXME - static globals */
|
||||
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;
|
||||
static retro_vfs_dirent_stat_t dirent_dirent_stat_cb = NULL;
|
||||
/* Set when a frontend older than VFS API v5 owns the directory
|
||||
* handles: the local _impl cannot be used on a foreign handle, so
|
||||
* retro_dirent_stat() reports "unavailable" instead. */
|
||||
static bool dirent_stat_unavailable = false;
|
||||
|
||||
void dirent_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
{
|
||||
const struct retro_vfs_interface* vfs_iface;
|
||||
|
||||
dirent_opendir_cb = NULL;
|
||||
dirent_readdir_cb = NULL;
|
||||
dirent_dirent_get_name_cb = NULL;
|
||||
dirent_dirent_is_dir_cb = NULL;
|
||||
dirent_closedir_cb = NULL;
|
||||
dirent_dirent_stat_cb = NULL;
|
||||
dirent_stat_unavailable = false;
|
||||
|
||||
vfs_iface = vfs_info->iface;
|
||||
|
||||
if (
|
||||
vfs_info->required_interface_version < DIRENT_REQUIRED_VFS_VERSION
|
||||
|| !vfs_iface)
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
/* A frontend that negotiated v5 but left the member NULL is treated
|
||||
* like an older one: the local _impl must not touch its handles. */
|
||||
if (vfs_info->required_interface_version >= DIRENT_STAT_REQUIRED_VFS_VERSION
|
||||
&& vfs_iface->dirent_stat)
|
||||
dirent_dirent_stat_cb = vfs_iface->dirent_stat;
|
||||
else
|
||||
dirent_stat_unavailable = true;
|
||||
}
|
||||
|
||||
struct RDIR *retro_opendir_include_hidden(
|
||||
const char *name, bool include_hidden)
|
||||
{
|
||||
if (dirent_opendir_cb)
|
||||
return (struct RDIR *)dirent_opendir_cb(name, include_hidden);
|
||||
return (struct RDIR *)retro_vfs_opendir_impl(name, include_hidden);
|
||||
}
|
||||
|
||||
struct RDIR *retro_opendir(const char *name)
|
||||
{
|
||||
return retro_opendir_include_hidden(name, false);
|
||||
}
|
||||
|
||||
bool retro_dirent_error(struct RDIR *rdir)
|
||||
{
|
||||
/* Left for compatibility */
|
||||
return false;
|
||||
}
|
||||
|
||||
int retro_readdir(struct RDIR *rdir)
|
||||
{
|
||||
if (dirent_readdir_cb)
|
||||
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 (dirent_dirent_get_name_cb)
|
||||
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.
|
||||
* @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 *unused)
|
||||
{
|
||||
if (dirent_dirent_is_dir_cb)
|
||||
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);
|
||||
}
|
||||
|
||||
int retro_dirent_stat(struct RDIR *rdir, int64_t *size, int64_t *mtime)
|
||||
{
|
||||
if (!rdir)
|
||||
return 0;
|
||||
if (dirent_dirent_stat_cb)
|
||||
return dirent_dirent_stat_cb((struct retro_vfs_dir_handle *)rdir, size, mtime);
|
||||
if (dirent_stat_unavailable)
|
||||
return 0;
|
||||
return retro_vfs_dirent_stat_impl((struct retro_vfs_dir_handle *)rdir, size, mtime);
|
||||
}
|
||||
|
||||
void retro_closedir(struct RDIR *rdir)
|
||||
{
|
||||
if (dirent_closedir_cb)
|
||||
dirent_closedir_cb((struct retro_vfs_dir_handle *)rdir);
|
||||
else
|
||||
retro_vfs_closedir_impl((struct retro_vfs_dir_handle *)rdir);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (fopen_utf8.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_COMPAT_FOPEN_UTF8_H
|
||||
#define __LIBRETRO_SDK_COMPAT_FOPEN_UTF8_H
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Defined to error rather than fopen_utf8, to make it clear to everyone reading the code that not worrying about utf16 is fine */
|
||||
/* TODO: enable */
|
||||
/* #define fopen (use fopen_utf8 instead) */
|
||||
void *fopen_utf8(const char * filename, const char * mode);
|
||||
#else
|
||||
#define fopen_utf8 fopen
|
||||
#endif
|
||||
#endif
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (msvc.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_COMPAT_MSVC_H
|
||||
#define __LIBRETRO_SDK_COMPAT_MSVC_H
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Pre-MSVC 2015 compilers don't implement snprintf, vsnprintf in a cross-platform manner. */
|
||||
#if _MSC_VER < 1900
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef snprintf
|
||||
#define snprintf c99_snprintf_retro__
|
||||
#endif
|
||||
int c99_snprintf_retro__(char *s, size_t len, const char *format, ...);
|
||||
|
||||
#ifndef vsnprintf
|
||||
#define vsnprintf c99_vsnprintf_retro__
|
||||
#endif
|
||||
int c99_vsnprintf_retro__(char *s, size_t len, const char *format, va_list ap);
|
||||
#endif
|
||||
|
||||
/* strtoll/strtoull are C99. The CRT grew _strtoi64/_strtoui64 in Visual
|
||||
* Studio 2005, and those take the same three arguments with the same
|
||||
* meaning, so for 2005 and later the mapping is exact. Visual C++ 6.0,
|
||||
* 2002 and 2003 have neither; _atoi64 is not a stand-in for them because
|
||||
* it accepts no base and reports no end pointer, so compat_strtoll.c
|
||||
* supplies a real implementation instead. */
|
||||
#if _MSC_VER >= 1900
|
||||
/* The UCRT has the real thing; do not shadow it with a macro,
|
||||
* which would also catch std::strtoll in C++ translation units. */
|
||||
#elif _MSC_VER >= 1400
|
||||
#ifndef strtoll
|
||||
#define strtoll _strtoi64
|
||||
#endif
|
||||
#ifndef strtoull
|
||||
#define strtoull _strtoui64
|
||||
#endif
|
||||
#else
|
||||
__int64 strtoll_retro__(const char *nptr, char **endptr, int base);
|
||||
unsigned __int64 strtoull_retro__(const char *nptr, char **endptr, int base);
|
||||
#ifndef strtoll
|
||||
#define strtoll strtoll_retro__
|
||||
#endif
|
||||
#ifndef strtoull
|
||||
#define strtoull strtoull_retro__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef UNICODE /* Do not bother with UNICODE at this time. */
|
||||
#include <direct.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
/* Python headers defines ssize_t and sets HAVE_SSIZE_T.
|
||||
* Cannot duplicate these efforts.
|
||||
*/
|
||||
#ifndef HAVE_SSIZE_T
|
||||
#if defined(_WIN64)
|
||||
typedef __int64 ssize_t;
|
||||
#elif defined(_WIN32)
|
||||
typedef int ssize_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define mkdir(dirname, unused) _mkdir(dirname)
|
||||
#undef strcasecmp
|
||||
#define strcasecmp _stricmp
|
||||
#undef strncasecmp
|
||||
#define strncasecmp _strnicmp
|
||||
|
||||
/* Disable some of the annoying warnings. */
|
||||
#pragma warning(disable : 4800)
|
||||
#pragma warning(disable : 4805)
|
||||
#pragma warning(disable : 4244)
|
||||
#pragma warning(disable : 4305)
|
||||
#pragma warning(disable : 4146)
|
||||
#pragma warning(disable : 4267)
|
||||
#pragma warning(disable : 4723)
|
||||
#pragma warning(disable : 4996)
|
||||
|
||||
/* roundf and va_copy is available since MSVC 2013 */
|
||||
#if _MSC_VER < 1800
|
||||
#define roundf(in) (in >= 0.0f ? floorf(in + 0.5f) : ceilf(in - 0.5f))
|
||||
#define va_copy(x, y) ((x) = (y))
|
||||
#endif
|
||||
|
||||
#if _MSC_VER <= 1310
|
||||
#ifndef __cplusplus
|
||||
/* VC6 math.h doesn't define some functions when in C mode.
|
||||
* Trying to define a prototype gives "undefined reference".
|
||||
* But providing an implementation then gives "function already has body".
|
||||
* So the equivalent of the implementations from math.h are used as
|
||||
* defines here instead, and it seems to work.
|
||||
*/
|
||||
#define cosf(x) ((float)cos((double)x))
|
||||
#define powf(x, y) ((float)pow((double)x, (double)y))
|
||||
#define sinf(x) ((float)sin((double)x))
|
||||
#define ceilf(x) ((float)ceil((double)x))
|
||||
#define floorf(x) ((float)floor((double)x))
|
||||
#define sqrtf(x) ((float)sqrt((double)x))
|
||||
#define fabsf(x) ((float)fabs((double)(x)))
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX _MAX_PATH
|
||||
#endif
|
||||
|
||||
#ifndef SIZE_MAX
|
||||
#define SIZE_MAX _UI32_MAX
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (posix_string.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_COMPAT_POSIX_STRING_H
|
||||
#define __LIBRETRO_SDK_COMPAT_POSIX_STRING_H
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
/**
|
||||
* @file posix_string.h
|
||||
*
|
||||
* Portable reimplementations of various string functions
|
||||
* that are normally provided by libc or POSIX.
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <compat/msvc.h>
|
||||
#endif
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
#if defined(_WIN32) || defined(DOXYGEN)
|
||||
#undef strtok_r
|
||||
#define strtok_r(str, delim, saveptr) retro_strtok_r__(str, delim, saveptr)
|
||||
|
||||
/**
|
||||
* Portable reimplementation of \c strtok_r().
|
||||
* The original function will be used if it's available.
|
||||
*
|
||||
* @see https://man7.org/linux/man-pages/man3/strtok.3.html
|
||||
*/
|
||||
char *strtok_r(char *str, const char *delim, char **saveptr);
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) || defined(DOXYGEN)
|
||||
#undef strcasecmp
|
||||
#undef strdup
|
||||
|
||||
#define strcasecmp(a, b) retro_strcasecmp__(a, b)
|
||||
#define strdup(orig) retro_strdup__(orig)
|
||||
/**
|
||||
* Portable reimplementation of \c strcasecmp().
|
||||
* The original function will be used if it's available.
|
||||
*
|
||||
* @see https://man7.org/linux/man-pages/man3/strcasecmp.3.html
|
||||
*/
|
||||
int strcasecmp(const char *a, const char *b);
|
||||
|
||||
/**
|
||||
* Portable reimplementation of \c strdup().
|
||||
* The original function will be used if it's available.
|
||||
*
|
||||
* @see https://man7.org/linux/man-pages/man3/strdup.3.html
|
||||
*/
|
||||
char *strdup(const char *orig);
|
||||
|
||||
/* isblank is available since MSVC 2013 */
|
||||
#if _MSC_VER < 1800
|
||||
#undef isblank
|
||||
#define isblank(c) retro_isblank__(c)
|
||||
/**
|
||||
* Portable reimplementation of \c isblank().
|
||||
* The original function will be used if it's available.
|
||||
*
|
||||
* @see https://en.cppreference.com/w/c/string/byte/isblank
|
||||
*/
|
||||
int isblank(int c);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (strcasestr.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_COMPAT_STRCASESTR_H
|
||||
#define __LIBRETRO_SDK_COMPAT_STRCASESTR_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if defined(RARCH_INTERNAL) && defined(HAVE_CONFIG_H)
|
||||
#include "../../../config.h"
|
||||
#endif
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Case-insensitive substring search.
|
||||
*
|
||||
* Called by its own name rather than shadowing strcasestr, and used
|
||||
* unconditionally rather than only where the C library lacks one. The
|
||||
* library's version is not reliably the better choice: glibc's measured
|
||||
* some fifty times slower than this on a representative search, and the
|
||||
* handheld targets that set HAVE_STRCASESTR are the slowest machines
|
||||
* RetroArch runs on and the least likely to have a good implementation.
|
||||
* Using one function everywhere also means one set of semantics, rather
|
||||
* than case folding that varies with the platform's locale.
|
||||
*
|
||||
* Folds ASCII only, which is what the callers mean - extensions, driver
|
||||
* names, search terms - and matches the behaviour of the C library
|
||||
* versions it replaces for every input that is not undefined.
|
||||
*
|
||||
* @param haystack String to search in.
|
||||
* @param needle String to search for.
|
||||
* @return Pointer to the first occurrence within \c haystack,
|
||||
* or NULL if there is none.
|
||||
*/
|
||||
char *compat_strcasestr(const char *haystack, const char *needle);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (utf.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_ENCODINGS_UTF_H
|
||||
#define _LIBRETRO_ENCODINGS_UTF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <boolean.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
#include <retro_inline.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
enum CodePage
|
||||
{
|
||||
CODEPAGE_LOCAL = 0, /* CP_ACP */
|
||||
CODEPAGE_UTF8 = 65001 /* CP_UTF8 */
|
||||
};
|
||||
|
||||
/**
|
||||
* utf8_conv_utf32:
|
||||
*
|
||||
* Simple implementation. Assumes the sequence is
|
||||
* properly synchronized and terminated.
|
||||
**/
|
||||
size_t utf8_conv_utf32(uint32_t *out, size_t out_chars,
|
||||
const char *in, size_t in_size);
|
||||
|
||||
/**
|
||||
* utf16_conv_utf8:
|
||||
*
|
||||
* Leaf function.
|
||||
**/
|
||||
bool utf16_conv_utf8(uint8_t *out, size_t *out_chars,
|
||||
const uint16_t *in, size_t in_size);
|
||||
|
||||
/**
|
||||
* utf8len:
|
||||
*
|
||||
* Leaf function.
|
||||
**/
|
||||
size_t utf8len(const char *string);
|
||||
|
||||
/**
|
||||
* utf8cpy:
|
||||
*
|
||||
* Acts mostly like strlcpy.
|
||||
*
|
||||
* Copies the given number of UTF-8 characters,
|
||||
* but at most @d_len bytes.
|
||||
*
|
||||
* Always NULL terminates. Does not copy half a character.
|
||||
* @s is assumed valid UTF-8.
|
||||
* Use only if @chars is considerably less than @d_len.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls memcpy
|
||||
*
|
||||
* @return Number of bytes.
|
||||
**/
|
||||
size_t utf8cpy(char *d, size_t d_len, const char *s, size_t chars);
|
||||
|
||||
/**
|
||||
* utf8skip:
|
||||
*
|
||||
* Leaf function
|
||||
**/
|
||||
const char *utf8skip(const char *str, size_t chars);
|
||||
|
||||
/**
|
||||
* utf8_walk:
|
||||
*
|
||||
* Does not validate the input.
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* @return Returns garbage if it's not UTF-8.
|
||||
**/
|
||||
uint32_t utf8_walk(const char **string);
|
||||
|
||||
/* utf8_walk with its ASCII case inline, for loops that walk text a
|
||||
* codepoint at a time every frame: a call per character is most of
|
||||
* what such a loop costs when the text is ASCII. */
|
||||
static INLINE uint32_t utf8_walk_inline(const char **string)
|
||||
{
|
||||
const uint8_t *s = (const uint8_t*)*string;
|
||||
if (*s < 0x80)
|
||||
{
|
||||
*string = (const char*)(s + 1);
|
||||
return *s;
|
||||
}
|
||||
return utf8_walk(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* utf16_to_char_string:
|
||||
**/
|
||||
bool utf16_to_char_string(const uint16_t *in, char *s, size_t len);
|
||||
|
||||
/**
|
||||
* utf8_to_local_string_alloc:
|
||||
*
|
||||
* @return Returned pointer MUST be freed by the caller if non-NULL.
|
||||
**/
|
||||
char *utf8_to_local_string_alloc(const char *str);
|
||||
|
||||
/**
|
||||
* local_to_utf8_string_alloc:
|
||||
*
|
||||
* @return Returned pointer MUST be freed by the caller if non-NULL.
|
||||
**/
|
||||
char *local_to_utf8_string_alloc(const char *str);
|
||||
|
||||
/**
|
||||
* local_to_utf8_string:
|
||||
*
|
||||
* Same conversion as local_to_utf8_string_alloc(), into a caller buffer.
|
||||
* Targets where the local codepage needs no conversion - Xbox, and any
|
||||
* UNICODE build - copy rather than allocate. Truncates like strlcpy().
|
||||
*
|
||||
* @return false only if the conversion itself failed.
|
||||
**/
|
||||
bool local_to_utf8_string(const char *in, char *s, size_t len);
|
||||
|
||||
/**
|
||||
* utf8_to_utf16_string_alloc:
|
||||
*
|
||||
* @return Returned pointer MUST be freed by the caller if non-NULL.
|
||||
**/
|
||||
wchar_t *utf8_to_utf16_string_alloc(const char *str);
|
||||
|
||||
/**
|
||||
* utf16_to_utf8_string_alloc:
|
||||
*
|
||||
* @return Returned pointer MUST be freed by the caller if non-NULL.
|
||||
**/
|
||||
char *utf16_to_utf8_string_alloc(const wchar_t *str);
|
||||
|
||||
|
||||
/* Which of the two conversion families above a Win32 call needs.
|
||||
*
|
||||
* The A entry points take a string in the local codepage
|
||||
* (utf8_to_local_string_alloc); the W ones take UTF-16
|
||||
* (utf8_to_utf16_string_alloc). Which is correct is a property of the
|
||||
* target, not a preference, and it lives here because choosing between
|
||||
* those two families is the whole of the question.
|
||||
*
|
||||
* LEGACY_WIN32 selects the ANSI entry points, and there are exactly
|
||||
* three ways to earn it.
|
||||
*
|
||||
* 1. Xbox, where the W entry points are not stubs - they are absent.
|
||||
* The 2002 XDK's WinBase.h declares CreateFileA and #defines
|
||||
* CreateFile to it; there is no CreateFileW anywhere in the header,
|
||||
* nor GetFileAttributesW, FindFirstFileW, DeleteFileW, MoveFileW,
|
||||
* CreateDirectoryW or RemoveDirectoryW. The only W entry points it
|
||||
* has are string helpers: lstrlenW, lstrcpyW, lstrcatW, lstrcmpW,
|
||||
* lstrcmpiW, lstrcpynW, wsprintfW, wvsprintfW, CharUpperW,
|
||||
* CharLowerW, IsBadStringPtrW, OutputDebugStringW. A W filesystem
|
||||
* call there does not fail at runtime, it fails to link.
|
||||
*
|
||||
* (The CRT is the odd one out: _wopen, _wsopen, _wcreat, _wfopen
|
||||
* and _wstat64 are all declared, and _wopen really does ship in
|
||||
* libcmt.lib. But the Win32 layer underneath is ANSI-only, so the
|
||||
* branch has to go ANSI as a whole.)
|
||||
*
|
||||
* 2. Windows 95/98/Me, where the W entry points exist but are stubs
|
||||
* that fail with ERROR_CALL_NOT_IMPLEMENTED - the reason Microsoft
|
||||
* shipped MSLU/unicows.dll at all. Only a small documented set
|
||||
* (the lstr* family, MultiByteToWideChar, WideCharToMultiByte, the
|
||||
* text-out calls) actually works.
|
||||
*
|
||||
* Detected by _WIN32_WINDOWS, which is the macro that means "this
|
||||
* build targets 9x" - w32api.h spells out the scale: Windows95
|
||||
* 0x0400, Windows98 0x0410, WindowsME 0x0500 - or by HAVE_WIN9X,
|
||||
* for build systems that would rather say it in one place.
|
||||
*
|
||||
* 3. Anything a build forces with -DLEGACY_WIN32.
|
||||
*
|
||||
* WinRT/UWP is never legacy, Xbox or not: the ANSI entry points are
|
||||
* outside the app API partition there, so it needs the W path.
|
||||
*
|
||||
* What this deliberately does NOT key off is _WIN32_WINNT, which is
|
||||
* what six separate copies of this test used to do:
|
||||
*
|
||||
* #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
|
||||
*
|
||||
* That is the NT scale, and reading it as a 9x test was wrong in both
|
||||
* directions. It caught NT 3.51 and NT 4.0 targets, which are
|
||||
* Unicode-native - W is the real entry point on NT and A is the
|
||||
* wrapper - so it forced ANSI on the one family that never needed it,
|
||||
* discarding every path not representable in the ANSI codepage to do
|
||||
* it. And it missed 9x unless someone happened to set an NT macro
|
||||
* low, which a 9x build has no reason to do: mingw-w64 does not
|
||||
* predefine _WIN32_WINNT, and sdkddkver.h defaults it to 0x0A00 when
|
||||
* the build is silent, so the default configuration took the W path on
|
||||
* the exact platform the test existed to protect.
|
||||
*
|
||||
* The two scales even collide: _WIN32_WINDOWS 0x0500 is Windows Me,
|
||||
* while _WIN32_WINNT 0x0500 is Windows 2000.
|
||||
*/
|
||||
|
||||
#if !defined(LEGACY_WIN32) && defined(_WIN32) && !defined(__WINRT__)
|
||||
#if defined(_XBOX) || defined(_WIN32_WINDOWS) || defined(HAVE_WIN9X)
|
||||
#define LEGACY_WIN32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Runtime selection, for the one binary that has to be both.
|
||||
*
|
||||
* Everything above is compile-time: a build is ANSI or it is wide, and
|
||||
* a 9x-capable executable gives up Unicode paths on NT to get there.
|
||||
* HAVE_WIN9X_RUNTIME compiles both branches and picks per call from a
|
||||
* cached predicate instead.
|
||||
*
|
||||
* Opt-in, because it is not free: both branches in the binary, and a
|
||||
* branch the other configurations do not execute. A build that does
|
||||
* not ask keeps exactly the compile-time selection above and pays
|
||||
* nothing - not the code, not the test.
|
||||
*
|
||||
* Never available where one of the two branches cannot exist:
|
||||
*
|
||||
* _XBOX - the W entry points are absent, so the wide branch
|
||||
* would not link, never mind not run.
|
||||
* __WINRT__ - the ANSI entry points are outside the app API
|
||||
* partition, so the legacy branch would not compile.
|
||||
* LEGACY_WIN32 forced - the build has already said what it wants.
|
||||
*
|
||||
* The predicate is inline in this header on purpose: a new .c file in
|
||||
* libretro-common is a line every consuming repo has to add to its own
|
||||
* build, for a feature almost none of them want. One cached int per
|
||||
* translation unit costs a probe each rather than one, which is 16 us
|
||||
* of startup in the only configuration that compiles it at all.
|
||||
*/
|
||||
#if defined(HAVE_WIN9X_RUNTIME) && defined(_WIN32) \
|
||||
&& !defined(_XBOX) && !defined(__WINRT__) && !defined(LEGACY_WIN32)
|
||||
#define LEGACY_WIN32_RUNTIME 1
|
||||
#endif
|
||||
|
||||
#ifdef LEGACY_WIN32_RUNTIME
|
||||
|
||||
/* _WINDOWS_ is the include guard both the Microsoft and the mingw-w64
|
||||
* windows.h define, so this pulls it in only where a translation unit
|
||||
* has not already. */
|
||||
#ifndef _WINDOWS_
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX /* MSVC defines min/max as macros in C++ too */
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
/* Non-zero when the W entry points are not usable, i.e. on 95/98/Me.
|
||||
*
|
||||
* A feature probe rather than a version check. GetVersionEx would
|
||||
* work - dwPlatformId is VER_PLATFORM_WIN32_WINDOWS only on 9x, and
|
||||
* that field is not touched by the version lie a manifest-less process
|
||||
* gets on 8.1 and up - but it answers the question next to the one
|
||||
* being asked. What matters is not which Windows this is, it is
|
||||
* whether the W entry points do anything, and that is directly
|
||||
* observable: on 9x they are stubs that set ERROR_CALL_NOT_IMPLEMENTED.
|
||||
* Asking the thing itself also credits a wrapper that implements them
|
||||
* - MSLU/unicows, wine, anything later - rather than talking past it.
|
||||
*
|
||||
* GetFileAttributesW on "." is the cheapest probe there is: no handle,
|
||||
* no allocation, a path that exists everywhere. Its return value is
|
||||
* discarded, since INVALID_FILE_ATTRIBUTES is a perfectly good answer
|
||||
* from a real implementation in some sandbox; only the error code
|
||||
* carries the signal.
|
||||
*
|
||||
* The cache is a plain int with no interlock. Two threads racing both
|
||||
* call the same stub, compute the same value and store it; losing the
|
||||
* race costs a second probe and nothing else. */
|
||||
static INLINE int win32_needs_local_encoding(void)
|
||||
{
|
||||
static int cached = -1;
|
||||
|
||||
if (cached < 0)
|
||||
{
|
||||
SetLastError(NO_ERROR);
|
||||
GetFileAttributesW(L".");
|
||||
cached = (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) ? 1 : 0;
|
||||
}
|
||||
|
||||
return cached;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Migration aid for out-of-tree builds that expressed "old Windows"
|
||||
* the only way this used to understand. Silent for anything that has
|
||||
* already said what it means, and it never changes the outcome - the
|
||||
* decision above is already made. */
|
||||
#if !defined(LEGACY_WIN32) && !defined(LEGACY_WIN32_NO_MIGRATION_NOTE)
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 && !defined(_XBOX) && !defined(__WINRT__)
|
||||
#if defined(_MSC_VER)
|
||||
#pragma message("libretro-common: a low _WIN32_WINNT no longer implies the ANSI Win32 path. NT is Unicode-native; if this target runs on Windows 95/98/Me, define _WIN32_WINDOWS (or HAVE_WIN9X).")
|
||||
#elif defined(__GNUC__)
|
||||
#warning "libretro-common: a low _WIN32_WINNT no longer implies the ANSI Win32 path. NT is Unicode-native; if this target runs on Windows 95/98/Me, define _WIN32_WINDOWS (or HAVE_WIN9X)."
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
+760
@@ -0,0 +1,760 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (file_path.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_FILE_PATH_H
|
||||
#define __LIBRETRO_SDK_FILE_PATH_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <libretro.h>
|
||||
#include <retro_common_api.h>
|
||||
|
||||
#include <boolean.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
#define PATH_REQUIRED_VFS_VERSION 3
|
||||
#define STAT64_REQUIRED_VFS_VERSION 4
|
||||
#define METADATA_REQUIRED_VFS_VERSION 5
|
||||
|
||||
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
|
||||
{
|
||||
RARCH_FILETYPE_UNSET,
|
||||
RARCH_PLAIN_FILE,
|
||||
RARCH_COMPRESSED_FILE_IN_ARCHIVE,
|
||||
RARCH_COMPRESSED_ARCHIVE,
|
||||
RARCH_DIRECTORY,
|
||||
RARCH_FILE_UNSUPPORTED
|
||||
};
|
||||
|
||||
struct path_linked_list
|
||||
{
|
||||
char *path;
|
||||
struct path_linked_list *next;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new linked list with one item in it
|
||||
* The path on this item will be set to NULL
|
||||
**/
|
||||
struct path_linked_list* path_linked_list_new(void);
|
||||
|
||||
/* Free the entire linked list */
|
||||
void path_linked_list_free(struct path_linked_list *in_path_linked_list);
|
||||
|
||||
/**
|
||||
* Add a node to the linked list with this path
|
||||
* If the first node's path if it's not yet set,
|
||||
* set this instead
|
||||
**/
|
||||
void path_linked_list_add_path(struct path_linked_list *in_path_linked_list, char *path);
|
||||
|
||||
/**
|
||||
* path_is_compressed_file:
|
||||
* @path : path
|
||||
*
|
||||
* Checks if path is a compressed file.
|
||||
*
|
||||
* Returns: true (1) if path is a compressed file, otherwise false (0).
|
||||
**/
|
||||
bool path_is_compressed_file(const char *path);
|
||||
|
||||
/**
|
||||
* path_contains_compressed_file:
|
||||
* @path : path
|
||||
*
|
||||
* Checks if path contains a compressed file.
|
||||
*
|
||||
* Currently we only check for hash symbol (#) inside the pathname.
|
||||
* If path is ever expanded to a general URI, we should check for that here.
|
||||
*
|
||||
* Example: Somewhere in the path there might be a compressed file
|
||||
* E.g.: /path/to/file.7z#mygame.img
|
||||
*
|
||||
* Returns: true (1) if path contains compressed file, otherwise false (0).
|
||||
**/
|
||||
#define path_contains_compressed_file(path) (path_get_archive_delim((path)) != NULL)
|
||||
|
||||
/**
|
||||
* path_get_archive_delim:
|
||||
* @path : path
|
||||
*
|
||||
* Find delimiter of an archive file. Only the first '#'
|
||||
* after a compression extension is considered.
|
||||
*
|
||||
* @return pointer to the delimiter in the path if it contains
|
||||
* a path inside a compressed file, otherwise NULL.
|
||||
**/
|
||||
const char *path_get_archive_delim(const char *path);
|
||||
|
||||
/**
|
||||
* path_get_extension:
|
||||
* @path : path
|
||||
*
|
||||
* Gets extension of file. Only '.'s
|
||||
* after the last slash are considered.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strrchr
|
||||
*
|
||||
* @return extension part from the path.
|
||||
**/
|
||||
const char *path_get_extension(const char *path);
|
||||
|
||||
/**
|
||||
* path_get_extension_mutable:
|
||||
* @path : path
|
||||
*
|
||||
* Specialized version of path_get_extension(). Return
|
||||
* value is mutable.
|
||||
*
|
||||
* Gets extension of file. Only '.'s
|
||||
* after the last slash are considered.
|
||||
*
|
||||
* @return extension part from the path.
|
||||
**/
|
||||
char *path_get_extension_mutable(const char *path);
|
||||
|
||||
/**
|
||||
* path_remove_extension:
|
||||
* @path : path
|
||||
*
|
||||
* Mutates path by removing its extension. Removes all
|
||||
* text after and including the last '.'.
|
||||
* Only '.'s after the last slash are considered.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strrchr
|
||||
*
|
||||
* @return
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* path_basename:
|
||||
* @path : path
|
||||
*
|
||||
* Get basename from @path.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls path_get_archive_delim()
|
||||
*
|
||||
* @return basename from path.
|
||||
**/
|
||||
const char *path_basename(const char *path);
|
||||
|
||||
/**
|
||||
* path_basename_nocompression:
|
||||
* @path : path
|
||||
*
|
||||
* Specialized version of path_basename().
|
||||
* Get basename from @path.
|
||||
*
|
||||
* @return basename from path.
|
||||
**/
|
||||
const char *path_basename_nocompression(const char *path);
|
||||
|
||||
/**
|
||||
* path_basedir:
|
||||
* @path : path
|
||||
*
|
||||
* Extracts base directory by mutating path.
|
||||
* Keeps trailing '/'.
|
||||
**/
|
||||
size_t path_basedir(char *path);
|
||||
|
||||
/**
|
||||
* path_parent_dir:
|
||||
* @s : path
|
||||
* @len : size of buffer
|
||||
*
|
||||
* 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
|
||||
**/
|
||||
size_t path_parent_dir(char *s, size_t len);
|
||||
|
||||
/**
|
||||
* path_resolve_realpath:
|
||||
* @s : input and output buffer for path
|
||||
* @size : size of buffer
|
||||
* @resolve_symlinks : whether to resolve symlinks or not
|
||||
*
|
||||
* Resolves use of ".", "..", multiple slashes etc in absolute paths.
|
||||
*
|
||||
* Relative paths are rebased on the current working dir.
|
||||
*
|
||||
* @return @s if successful, NULL otherwise.
|
||||
* Note: Not implemented on consoles
|
||||
* Note: Symlinks are only resolved on Unix-likes
|
||||
* Note: The current working dir might not be what you expect,
|
||||
* e.g. on Android it is "/"
|
||||
* Use of fill_pathname_resolve_relative() should be preferred
|
||||
**/
|
||||
char *path_resolve_realpath(char *s, size_t len, bool resolve_symlinks);
|
||||
|
||||
/**
|
||||
* path_relative_to:
|
||||
* @s : buffer to write the relative path to
|
||||
* @path : path to be expressed relatively
|
||||
* @base : relative to this
|
||||
* @len : size of output buffer
|
||||
*
|
||||
* Turns @path into a path relative to @base and writes it to @s.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t path_relative_to(char *s, const char *path, const char *base,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* path_is_absolute:
|
||||
* @path : path
|
||||
*
|
||||
* Checks if @path is an absolute path or a relative path.
|
||||
*
|
||||
* @return true if path is absolute, false if path is relative.
|
||||
**/
|
||||
bool path_is_absolute(const char *path);
|
||||
|
||||
/**
|
||||
* fill_pathname:
|
||||
* @s : output path
|
||||
* @in_path : input path
|
||||
* @replace : what to replace
|
||||
* @len : buffer size of output path
|
||||
*
|
||||
* FIXME: Verify
|
||||
*
|
||||
* Replaces filename extension with 'replace' and outputs result to s.
|
||||
* The extension here is considered to be the string from the last '.'
|
||||
* to the end.
|
||||
*
|
||||
* Only '.'s after the last slash are considered as extensions.
|
||||
* If no '.' is present, in_path and replace will simply be concatenated.
|
||||
* 'len' is buffer size of 's'.
|
||||
* E.g.: in_path = "/foo/bar/baz/boo.c", replace = ".asm" =>
|
||||
* s = "/foo/bar/baz/boo.asm"
|
||||
* E.g.: in_path = "/foo/bar/baz/boo.c", replace = "" =>
|
||||
* s = "/foo/bar/baz/boo"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strlcpy 2x
|
||||
* - calls strrchr
|
||||
* - calls strlcat
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
*/
|
||||
size_t fill_pathname(char *s, const char *in_path,
|
||||
const char *replace, size_t len);
|
||||
|
||||
/**
|
||||
* fill_dated_filename:
|
||||
* @s : output filename
|
||||
* @ext : extension of output filename
|
||||
* @len : buffer size of output filename
|
||||
*
|
||||
* Creates a 'dated' filename prefixed by 'RetroArch', and
|
||||
* concatenates extension (@ext) to it.
|
||||
*
|
||||
* E.g.:
|
||||
* s = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls rtime_localtime()
|
||||
* - Calls strftime
|
||||
* - Calls strlcpy
|
||||
*
|
||||
**/
|
||||
size_t fill_dated_filename(char *s, const char *ext, size_t len);
|
||||
|
||||
/**
|
||||
* fill_str_dated_filename:
|
||||
* @s : output filename
|
||||
* @in_str : input string
|
||||
* @ext : extension of output filename
|
||||
* @len : buffer size of output filename
|
||||
*
|
||||
* Creates a 'dated' filename prefixed by the string @in_str, and
|
||||
* concatenates extension (@ext) to it.
|
||||
*
|
||||
* E.g.:
|
||||
* s = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls time
|
||||
* - Calls rtime_localtime()
|
||||
* - Calls strlcpy (at least once)
|
||||
* - Calls strftime
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t fill_str_dated_filename(char *s, const char *in_str, const char *ext, size_t len);
|
||||
|
||||
/**
|
||||
* find_last_slash:
|
||||
* @str : path
|
||||
*
|
||||
* Find last slash in path. Tries to find
|
||||
* a backslash on Windows too which takes precedence
|
||||
* over regular slash.
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* @return pointer to last slash/backslash found in @str.
|
||||
**/
|
||||
char *find_last_slash(const char *str);
|
||||
|
||||
/**
|
||||
* fill_pathname_dir:
|
||||
* @s : input directory path
|
||||
* @in_basename : input basename to be appended to @s
|
||||
* @replace : replacement to be appended to @in_basename
|
||||
* @len : size of buffer
|
||||
*
|
||||
* Appends basename of 'in_basename', to 's', along with 'replace'.
|
||||
* Basename of in_basename is the string after the last '/' or '\\',
|
||||
* i.e the filename without directories.
|
||||
*
|
||||
* If in_basename has no '/' or '\\', the whole 'in_basename' will be used.
|
||||
* 'len' is buffer size of 's'.
|
||||
*
|
||||
* E.g..: s = "/tmp/some_dir", in_basename = "/some_content/foo.c",
|
||||
* replace = ".asm" => s = "/tmp/some_dir/foo.c.asm"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls fill_pathname_slash()
|
||||
* - Calls path_basename()
|
||||
* - Calls strlcpy 2x
|
||||
**/
|
||||
size_t fill_pathname_dir(char *s, const char *in_basename,
|
||||
const char *replace, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_base:
|
||||
* @s : output path
|
||||
* @in_path : input path
|
||||
* @len : size of output path
|
||||
*
|
||||
* Copies basename of @in_path into @s.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls path_basename()
|
||||
* - Calls strlcpy
|
||||
*
|
||||
* @return length of the string copied into @s
|
||||
**/
|
||||
size_t fill_pathname_base(char *s, const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_basedir:
|
||||
* @s : output directory
|
||||
* @in_path : input path
|
||||
* @len : size of output directory
|
||||
*
|
||||
* Copies base directory of @in_path into @s.
|
||||
* If in_path is a path without any slashes (relative current directory),
|
||||
* @s will get path "./".
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strlcpy
|
||||
* - Calls path_basedir()
|
||||
**/
|
||||
size_t fill_pathname_basedir(char *s, const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_parent_dir_name:
|
||||
* @s : output string
|
||||
* @in_dir : input directory
|
||||
* @len : size of @s
|
||||
*
|
||||
* Copies only the parent directory name of @in_dir into @s.
|
||||
* The two buffers must not overlap. Removes trailing '/'.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Can call strlcpy
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t fill_pathname_parent_dir_name(char *s,
|
||||
const char *in_dir, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_parent_dir:
|
||||
* @s : output directory
|
||||
* @in_dir : input directory
|
||||
* @len : size of output directory
|
||||
*
|
||||
* Copies parent directory of @in_dir into @s.
|
||||
* Assumes @in_dir is a directory. Keeps trailing '/'.
|
||||
* If the path was already at the root directory, @s will be an empty string.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Can call strlcpy if (@s!= @in_dir)
|
||||
* - Calls strlen if (@s == @in_dir)
|
||||
* - Calls path_parent_dir()
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t fill_pathname_parent_dir(char *s,
|
||||
const char *in_dir, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_resolve_relative:
|
||||
* @s : output path
|
||||
* @in_refpath : input reference path
|
||||
* @in_path : input path
|
||||
* @len : size of @s
|
||||
*
|
||||
* Joins basedir of @in_refpath together with @in_path.
|
||||
* If @in_path is an absolute path, s = in_path.
|
||||
* E.g.: in_refpath = "/foo/bar/baz.a", in_path = "foobar.cg",
|
||||
* s = "/foo/bar/foobar.cg".
|
||||
**/
|
||||
void fill_pathname_resolve_relative(char *s, const char *in_refpath,
|
||||
const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_join:
|
||||
* @s : output path
|
||||
* @dir : directory
|
||||
* @path : path
|
||||
* @len : size of output path
|
||||
*
|
||||
* Joins a directory (@dir) and path (@path) together.
|
||||
* Makes sure not to get two consecutive slashes
|
||||
* between directory and path.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strlcpy at least once
|
||||
* - calls fill_pathname_slash()
|
||||
*
|
||||
* Deprecated. Use fill_pathname_join_special() instead
|
||||
* if you can ensure @dir != @s
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t fill_pathname_join(char *s, const char *dir,
|
||||
const char *path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_join_special:
|
||||
* @s : output path
|
||||
* @dir : directory
|
||||
* @path : path
|
||||
* @len : size of output path
|
||||
*
|
||||
* Macro alias of fill_pathname_join(); the two are identical.
|
||||
* Kept for source compatibility with existing callers in
|
||||
* RetroArch and downstream cores. Each argument expands exactly
|
||||
* once, so arguments with side effects are safe. The distinct
|
||||
* identifier fill_pathname_join_special_ext() is unaffected.
|
||||
*
|
||||
* @dir and @s may be identical (the join is then performed in
|
||||
* place); buffers that overlap without being identical are
|
||||
* undefined.
|
||||
*
|
||||
* Joins a directory (@dir) and path (@path) together.
|
||||
* Makes sure not to get two consecutive slashes
|
||||
* between directory and path.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strlcpy 2x
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
#define fill_pathname_join_special(s, dir, path, len) \
|
||||
fill_pathname_join((s), (dir), (path), (len))
|
||||
|
||||
size_t fill_pathname_join_special_ext(char *s,
|
||||
const char *dir, const char *path,
|
||||
const char *last, const char *ext,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_join_delim:
|
||||
* @s : output path
|
||||
* @dir : directory
|
||||
* @path : path
|
||||
* @delim : delimiter
|
||||
* @len : size of output path
|
||||
*
|
||||
* Joins a directory (@dir) and path (@path) together
|
||||
* using the given delimiter (@delim).
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - can call strlen
|
||||
* - can call strlcpy
|
||||
* - can call strlcat
|
||||
**/
|
||||
size_t fill_pathname_join_delim(char *s, const char *dir,
|
||||
const char *path, const char delim, size_t len);
|
||||
|
||||
size_t fill_pathname_expand_special(char *s,
|
||||
const char *in_path, size_t len);
|
||||
|
||||
size_t fill_pathname_abbreviate_special(char *s,
|
||||
const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_abbreviated_or_relative:
|
||||
*
|
||||
* Fills the supplied path with either the abbreviated path or
|
||||
* the relative path, which ever one has less depth / number of slashes
|
||||
*
|
||||
* If lengths of abbreviated and relative paths are the same,
|
||||
* the relative path will be used
|
||||
* @in_path can be an absolute, relative or abbreviated path
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t fill_pathname_abbreviated_or_relative(char *s,
|
||||
const char *in_refpath, const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* sanitize_path_part:
|
||||
*
|
||||
* @path_part : directory or filename
|
||||
* @len : length of path_part
|
||||
*
|
||||
* Takes single part of a path eg. single filename
|
||||
* or directory, and removes any special chars that are
|
||||
* unavailable.
|
||||
*
|
||||
* @returns newly allocated string that has been sanitized.
|
||||
* Caller is responsible for freeing the returned string.
|
||||
**/
|
||||
char *sanitize_path_part(const char *path_part, size_t len);
|
||||
|
||||
/**
|
||||
* pathname_conform_slashes_to_os:
|
||||
*
|
||||
* @path : path
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Changes the slashes to the correct kind for the os
|
||||
* So forward slash on linux and backslash on Windows
|
||||
**/
|
||||
void pathname_conform_slashes_to_os(char *s);
|
||||
|
||||
/**
|
||||
* pathname_make_slashes_portable:
|
||||
* @path : path
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Change all slashes to forward so they are more
|
||||
* portable between Windows and Linux
|
||||
**/
|
||||
void pathname_make_slashes_portable(char *s);
|
||||
|
||||
/**
|
||||
* path_basedir:
|
||||
* @path : path
|
||||
*
|
||||
* Extracts base directory by mutating path.
|
||||
* Keeps trailing '/'.
|
||||
**/
|
||||
void path_basedir_wrapper(char *s);
|
||||
|
||||
/**
|
||||
* path_char_is_slash:
|
||||
* @c : character
|
||||
*
|
||||
* Checks if character (@c) is a slash.
|
||||
*
|
||||
* @return true if character is a slash, otherwise false.
|
||||
**/
|
||||
#ifdef _WIN32
|
||||
#define PATH_CHAR_IS_SLASH(c) (((c) == '/') || ((c) == '\\'))
|
||||
#else
|
||||
#define PATH_CHAR_IS_SLASH(c) ((c) == '/')
|
||||
#endif
|
||||
|
||||
/**
|
||||
* path_default_slash and path_default_slash_c:
|
||||
*
|
||||
* Gets the default slash separator.
|
||||
*
|
||||
* @return default slash separator.
|
||||
**/
|
||||
#ifdef _WIN32
|
||||
#define PATH_DEFAULT_SLASH() "\\"
|
||||
#define PATH_DEFAULT_SLASH_C() '\\'
|
||||
#else
|
||||
#define PATH_DEFAULT_SLASH() "/"
|
||||
#define PATH_DEFAULT_SLASH_C() '/'
|
||||
#endif
|
||||
|
||||
/**
|
||||
* fill_pathname_slash:
|
||||
* @s : path
|
||||
* @len : size of path
|
||||
*
|
||||
* Assumes path is a directory. Appends a slash
|
||||
* if not already there.
|
||||
|
||||
* Hidden non-leaf function cost:
|
||||
* - can call strlcat once if it returns false
|
||||
* - calls strlen
|
||||
**/
|
||||
size_t fill_pathname_slash(char *s, size_t len);
|
||||
|
||||
#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
|
||||
size_t fill_pathname_application_path(char *s, size_t len);
|
||||
size_t fill_pathname_application_dir(char *s, size_t len);
|
||||
size_t fill_pathname_home_dir(char *s, size_t len);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* path_mkdir:
|
||||
* @dir : directory
|
||||
*
|
||||
* Create directory on filesystem.
|
||||
*
|
||||
* Recursive function.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strdup
|
||||
* - Calls path_parent_dir()
|
||||
* - Calls strcmp
|
||||
* - Calls path_is_directory()
|
||||
* - Calls path_mkdir()
|
||||
*
|
||||
* @return true if directory could be created, otherwise false.
|
||||
**/
|
||||
bool path_mkdir(const char *dir);
|
||||
|
||||
/**
|
||||
* path_rmdir:
|
||||
* @dir : directory path.
|
||||
*
|
||||
* Removes the empty directory @dir.
|
||||
*
|
||||
* @return true if the directory was removed, otherwise false -
|
||||
* including a directory that is not empty, and platforms with no
|
||||
* directory removal, where an empty directory is simply left.
|
||||
**/
|
||||
bool path_rmdir(const char *dir);
|
||||
|
||||
/**
|
||||
* path_is_directory:
|
||||
* @path : path
|
||||
*
|
||||
* Checks if path is a directory.
|
||||
*
|
||||
* @return true if path is a directory, otherwise false.
|
||||
*/
|
||||
bool path_is_directory(const char *path);
|
||||
|
||||
/* Time format strings with AM-PM designation require special
|
||||
* handling due to platform dependence
|
||||
* @return Length of the string written to @s
|
||||
*/
|
||||
size_t strftime_am_pm(char *s, size_t len, const char* format,
|
||||
const void* timeptr);
|
||||
|
||||
bool path_is_character_special(const char *path);
|
||||
|
||||
int path_stat(const char *path);
|
||||
|
||||
bool path_is_valid(const char *path);
|
||||
|
||||
/**
|
||||
* path_set_private:
|
||||
* @path : file
|
||||
*
|
||||
* Restricts @path to the owning user (0600 on POSIX). Use for files
|
||||
* that hold secrets. A no-op that returns true on platforms where
|
||||
* the location is already private or has no permission model.
|
||||
*
|
||||
* @return true if the permissions are restricted (or nothing needed
|
||||
* doing), false if the platform supports it and it failed.
|
||||
**/
|
||||
bool path_set_private(const char *path);
|
||||
|
||||
/**
|
||||
* path_is_readonly:
|
||||
* @path : path
|
||||
*
|
||||
* Whether the current user cannot write to @path. Reads the
|
||||
* RETRO_VFS_STAT_IS_READONLY flag, which frontends older than
|
||||
* VFS API v5 never set, so the answer there is always false.
|
||||
*
|
||||
* @return true if @path exists and is read-only.
|
||||
**/
|
||||
bool path_is_readonly(const char *path);
|
||||
|
||||
/**
|
||||
* path_set_readonly:
|
||||
* @path : path
|
||||
* @readonly : true to make read-only, false to make writable
|
||||
*
|
||||
* POSIX: toggles the write bits. Windows: FILE_ATTRIBUTE_READONLY.
|
||||
*
|
||||
* @return true on success, false if unsupported on this platform,
|
||||
* the file system, or the negotiated VFS version (< 5).
|
||||
**/
|
||||
bool path_set_readonly(const char *path, bool readonly);
|
||||
|
||||
/**
|
||||
* path_get_mtime:
|
||||
* @path : path
|
||||
* @mtime : receives seconds since 1970-01-01T00:00:00Z
|
||||
*
|
||||
* @return true on success, false if unavailable.
|
||||
**/
|
||||
bool path_get_mtime(const char *path, int64_t *mtime);
|
||||
|
||||
/**
|
||||
* path_set_mtime:
|
||||
* @path : path
|
||||
* @mtime : seconds since 1970-01-01T00:00:00Z
|
||||
*
|
||||
* @return true on success, false if unsupported or it failed.
|
||||
**/
|
||||
bool path_set_mtime(const char *path, int64_t mtime);
|
||||
|
||||
int64_t path_get_size(const char *path);
|
||||
|
||||
bool is_path_accessible_using_standard_io(const char *path);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (memmap.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_MEMMAP_H
|
||||
#define _LIBRETRO_MEMMAP_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <boolean.h>
|
||||
#include <retro_common_api.h>
|
||||
|
||||
#if defined(PSP) || defined(PS2) || defined(GEKKO) || defined(VITA) || defined(_XBOX) || defined(_3DS) || defined(WIIU) || defined(SWITCH) || defined(HAVE_LIBNX) || defined(__PS3__) || defined(__PSL1GHT__)
|
||||
/* No mman available */
|
||||
#elif defined(_WIN32) && !defined(_XBOX)
|
||||
/* MSVC's minwindef.h defines min and max as macros in C++ as well as C
|
||||
* -- MinGW's is guarded by #ifndef __cplusplus -- and they then break
|
||||
* every std::numeric_limits<>::max() in any C++ file that reaches this
|
||||
* header. A public header must not leak them. */
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <errno.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#define HAVE_MMAN
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
#if !defined(HAVE_MMAN) || defined(_WIN32)
|
||||
/* PROT_/MAP_ request bits for the shim platforms (real <sys/mman.h>
|
||||
* provides them elsewhere). Only the combinations the shim can
|
||||
* honour are defined. */
|
||||
#ifndef PROT_NONE
|
||||
#define PROT_NONE 0x0
|
||||
#endif
|
||||
#ifndef PROT_READ
|
||||
#define PROT_READ 0x1
|
||||
#endif
|
||||
#ifndef PROT_WRITE
|
||||
#define PROT_WRITE 0x2
|
||||
#endif
|
||||
#ifndef PROT_EXEC
|
||||
#define PROT_EXEC 0x4
|
||||
#endif
|
||||
#ifndef MAP_PRIVATE
|
||||
#define MAP_PRIVATE 0x02
|
||||
#endif
|
||||
#ifndef MAP_ANONYMOUS
|
||||
#define MAP_ANONYMOUS 0x20
|
||||
#endif
|
||||
#ifndef MAP_ANON
|
||||
#define MAP_ANON MAP_ANONYMOUS
|
||||
#endif
|
||||
#ifndef MAP_FAILED
|
||||
#define MAP_FAILED ((void *)-1)
|
||||
#endif
|
||||
void* mmap(void *addr, size_t len, int mmap_prot, int mmap_flags, int fildes, size_t off);
|
||||
|
||||
int munmap(void *addr, size_t len);
|
||||
|
||||
int mprotect(void *addr, size_t len, int prot);
|
||||
#endif
|
||||
|
||||
int memsync(void *start, void *end);
|
||||
|
||||
int memprotect(void *addr, size_t len);
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Reserve/commit
|
||||
*
|
||||
* A different model from mmap() above, and not expressible through it:
|
||||
* reserve a range of address space with no physical pages behind it,
|
||||
* then commit and decommit sub-ranges as they are needed. Windows has
|
||||
* this natively (MEM_RESERVE / MEM_COMMIT / MEM_DECOMMIT); on POSIX it
|
||||
* is PROT_NONE plus mprotect() and MADV_DONTNEED.
|
||||
*
|
||||
* The mmap() shim above cannot stand in for it: on Windows it maps
|
||||
* SEC_COMMIT sections, so the whole length is committed up front,
|
||||
* which is the one thing a reservation exists to avoid.
|
||||
*
|
||||
* Useful when a consumer needs one stable base covering a large range
|
||||
* while only a moving part of it is resident - a streaming window over
|
||||
* a file, say. Where the platform has no reservation at all,
|
||||
* memreserve() returns NULL and the caller is expected to fall back to
|
||||
* holding the range outright.
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* mempagesize:
|
||||
*
|
||||
* Returns: the granularity commit and decommit operate on. Ranges
|
||||
* passed to the functions below are rounded to it internally, so
|
||||
* callers only need it when they want to align their own bookkeeping.
|
||||
* Returns 0 if reservations are unsupported.
|
||||
*/
|
||||
size_t mempagesize(void);
|
||||
|
||||
/**
|
||||
* memreserve:
|
||||
* @len : bytes of address space to reserve
|
||||
*
|
||||
* Reserves @len bytes with nothing committed behind them. The range is
|
||||
* unreadable and unwritable until memcommit() covers it.
|
||||
*
|
||||
* Returns: the base of the reservation, or NULL if reservations are
|
||||
* unsupported or the request failed.
|
||||
*/
|
||||
void *memreserve(size_t len);
|
||||
|
||||
/**
|
||||
* memreserve_at:
|
||||
* @hint : preferred base, or NULL for any.
|
||||
* @len : bytes; rounded up to a page.
|
||||
*
|
||||
* As memreserve, at a preferred address -- a fastmem window that must
|
||||
* sit where the recompiler expects it. A hint, never forced: the
|
||||
* platform may return another address, and the caller compares.
|
||||
*/
|
||||
void *memreserve_at(void *hint, size_t len);
|
||||
|
||||
/**
|
||||
* memcommit:
|
||||
* @addr : start of the sub-range, within a memreserve() result
|
||||
* @len : bytes to make readable and writable
|
||||
*
|
||||
* Returns: true on success.
|
||||
*/
|
||||
bool memcommit(void *addr, size_t len);
|
||||
|
||||
/**
|
||||
* memrearm:
|
||||
* @addr : start of the sub-range, within a memreserve() result
|
||||
* @len : length of the sub-range
|
||||
*
|
||||
* Make a committed range unreadable again while KEEPING its physical
|
||||
* pages. This is the half of memdecommit() that costs nothing to
|
||||
* undo: memdecommit() also hands the pages back, so the next write to
|
||||
* the range faults them in again, whereas a range taken back with
|
||||
* memrearm() is re-armed by memcommit() without a fault.
|
||||
*
|
||||
* For recycling a reservation whose pages are about to be overwritten
|
||||
* anyway - the buffer pool in data_transfer - where the guard matters
|
||||
* and the page contents do not. Use memdecommit() when the point is
|
||||
* to give the memory back.
|
||||
*
|
||||
* Returns false where reservations are unsupported.
|
||||
*/
|
||||
bool memrearm(void *addr, size_t len);
|
||||
|
||||
/**
|
||||
* memdecommit:
|
||||
* @addr : start of the sub-range
|
||||
* @len : bytes to release the physical pages of
|
||||
* @strict : when true, also make the range fault on access. Costs
|
||||
* an extra syscall on POSIX; useful for proving a
|
||||
* consumer does not read behind itself.
|
||||
*
|
||||
* The address space stays reserved either way; only the pages go. What
|
||||
* a non-strict range reads back as afterwards is not defined: zeroes on
|
||||
* Linux and Windows, possibly the old contents on Darwin and the BSDs,
|
||||
* where the advice given is only advice. Use memzero_pages() for a
|
||||
* range whose contents must be zero.
|
||||
*/
|
||||
void memdecommit(void *addr, size_t len, bool strict);
|
||||
|
||||
/**
|
||||
* memrelease:
|
||||
* @addr : base returned by memreserve()
|
||||
* @len : the length passed to memreserve()
|
||||
*
|
||||
* Releases the reservation and everything committed in it.
|
||||
*/
|
||||
void memrelease(void *addr, size_t len);
|
||||
|
||||
/**
|
||||
* memshm_create:
|
||||
* @name : a name unique to this process, e.g. built from the pid.
|
||||
* @len : size in bytes.
|
||||
*
|
||||
* Creates an anonymous-backed shared memory region that can be mapped
|
||||
* at more than one address with memshm_map -- how an emulator mirrors
|
||||
* a guest RAM into several places in a large reserved window. The name
|
||||
* is unlinked at once where the platform links it, so it never outlives
|
||||
* the process. Windows uses a pagefile-backed file mapping, Android a
|
||||
* memfd (Bionic has no shm_open), everything else shm_open.
|
||||
*
|
||||
* Darwin caps a name at 31 characters and returns ENAMETOOLONG past it,
|
||||
* where Linux takes one ten times longer; a name that will not fit is
|
||||
* shortened from the front there, keeping the tail that distinguishes
|
||||
* one process from another, so the same caller works on both.
|
||||
*
|
||||
* Returns: a handle for memshm_map / memshm_destroy, or NULL. It is the
|
||||
* platform's own object -- the file descriptor on POSIX, the HANDLE on
|
||||
* Windows -- so a caller that must map the region a way memshm_map does
|
||||
* not offer, such as MAP_FIXED into a reservation it owns, can use it.
|
||||
* Never 0 on POSIX. On platforms with no mman this is always NULL.
|
||||
*/
|
||||
void *memshm_create(const char *name, size_t len);
|
||||
|
||||
/**
|
||||
* memshm_destroy:
|
||||
* @handle : from memshm_create.
|
||||
*
|
||||
* Closes the handle. Mappings made from it stay valid until unmapped.
|
||||
*/
|
||||
void memshm_destroy(void *handle);
|
||||
|
||||
/**
|
||||
* memshm_map:
|
||||
* @handle : from memshm_create.
|
||||
* @offset : byte offset into the region; page-aligned.
|
||||
* @hint : preferred address, or NULL. A hint, never forced: on a
|
||||
* taken address the platform picks another, and the
|
||||
* caller compares the result against what it asked for.
|
||||
* @len : bytes to map.
|
||||
* @prot : PROT_READ | PROT_WRITE | PROT_EXEC as memmap.h defines.
|
||||
*
|
||||
* Returns: the mapped address, or NULL.
|
||||
*/
|
||||
void *memshm_map(void *handle, size_t offset, void *hint, size_t len, int prot);
|
||||
|
||||
/**
|
||||
* memshm_unmap:
|
||||
* @addr : from memshm_map.
|
||||
* @len : the length passed to memshm_map.
|
||||
*/
|
||||
void memshm_unmap(void *addr, size_t len);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* A reservation that shared memory is mapped into at several places */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* A fastmem window: one contiguous reservation of address space that
|
||||
* pieces of a guest RAM are mapped into, at offsets the recompiler
|
||||
* computes, and unmapped from again as the guest remaps. The
|
||||
* reservation must stay reserved while individual pieces come and go,
|
||||
* which neither mmap nor VirtualAlloc gives directly:
|
||||
*
|
||||
* - POSIX: the reservation is PROT_NONE anonymous memory, a piece is
|
||||
* MAP_FIXED over it, and unmapping restores PROT_NONE anonymous
|
||||
* memory in its place. The kernel splits and merges the VMAs.
|
||||
* - Windows 10 1803 and later: the reservation is a placeholder, a
|
||||
* piece replaces part of it with MEM_REPLACE_PLACEHOLDER, and
|
||||
* unmapping restores the placeholder and coalesces it with its
|
||||
* neighbours. The area tracks the ranges itself, because the API
|
||||
* cannot be asked where they are.
|
||||
* - Windows 2000 through 10 1709: no placeholder APIs, and
|
||||
* MEM_RELEASE frees a whole reservation rather than part of one, so
|
||||
* the span is reserved one allocation-granularity slot at a time.
|
||||
* Mapping releases exactly the slots it covers and maps a view over
|
||||
* them; unmapping restores those reservations at once.
|
||||
*
|
||||
* Runtime is the same either way: a mapped view is a mapped view, and a
|
||||
* load through the window is the same instruction. The legacy path
|
||||
* costs one extra system call per map and carries a brief window where
|
||||
* the address is free, which it retries through -- both on a path that
|
||||
* runs at setup and when the guest remaps, not per access.
|
||||
*/
|
||||
typedef struct memshm_area memshm_area_t;
|
||||
|
||||
/**
|
||||
* memshm_area_create:
|
||||
* @len : size of the reservation in bytes.
|
||||
*
|
||||
* Returns: the area, or NULL where there is no address space for it, or
|
||||
* no way to reserve one. A caller that gets NULL runs without a fastmem
|
||||
* window rather than failing. @len must be a whole number of allocation
|
||||
* granularity units on Windows.
|
||||
*
|
||||
* On Windows the base is placed above 4 GB where possible: a 4 GB
|
||||
* window based below that either crosses the 32-bit boundary into the
|
||||
* system's own reservations or aliases mapped pages, and a fault there
|
||||
* cannot be told apart from a legitimate miss.
|
||||
*/
|
||||
memshm_area_t *memshm_area_create(size_t len);
|
||||
|
||||
/**
|
||||
* memshm_area_free:
|
||||
* Releases the reservation and everything still mapped in it.
|
||||
*/
|
||||
void memshm_area_free(memshm_area_t *area);
|
||||
|
||||
/**
|
||||
* memshm_area_base / memshm_area_size:
|
||||
*/
|
||||
unsigned char *memshm_area_base(const memshm_area_t *area);
|
||||
size_t memshm_area_size(const memshm_area_t *area);
|
||||
|
||||
/**
|
||||
* memshm_area_map:
|
||||
* @area : from memshm_area_create.
|
||||
* @handle : from memshm_create.
|
||||
* @offset : byte offset into the shared region; page-aligned.
|
||||
* @at : where in the area to map it, within the reservation.
|
||||
* @len : bytes.
|
||||
* @prot : PROT_READ | PROT_WRITE | PROT_EXEC.
|
||||
*
|
||||
* Refused when any part of the range is already mapped.
|
||||
*
|
||||
* Returns: @at on success, NULL on failure.
|
||||
*/
|
||||
unsigned char *memshm_area_map(memshm_area_t *area, void *handle,
|
||||
size_t offset, void *at, size_t len, int prot);
|
||||
|
||||
/**
|
||||
* memshm_area_unmap:
|
||||
* @at : an address a previous memshm_area_map returned.
|
||||
* @len : the length it was mapped with.
|
||||
*
|
||||
* Restores the reservation over that range. Refused, with nothing
|
||||
* changed, unless @at and @len are exactly a mapping the area holds:
|
||||
* part of one, a hole, or a range spanning several is not unmapped.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool memshm_area_unmap(memshm_area_t *area, void *at, size_t len);
|
||||
|
||||
/**
|
||||
* memjit_write_begin / memjit_write_end:
|
||||
*
|
||||
* On a platform whose JIT pages are write-xor-execute per thread --
|
||||
* Apple Silicon, where MAP_JIT memory is executable until the thread
|
||||
* asks otherwise -- these switch the calling thread to writing and back
|
||||
* (pthread_jit_write_protect_np). Nesting is counted, so a writer that
|
||||
* calls another writer is fine. Everywhere else they do nothing.
|
||||
*/
|
||||
void memjit_write_begin(void);
|
||||
void memjit_write_end(void);
|
||||
|
||||
/**
|
||||
* memjit_alloc / memjit_free:
|
||||
*
|
||||
* A mapping the caller both writes code into and executes, of @len bytes:
|
||||
* readable, writable and executable everywhere that grants it, and on
|
||||
* macOS on Apple Silicon the MAP_JIT kind, which is executable until the
|
||||
* writing thread brackets its writes with memjit_write_begin/end.
|
||||
* Returns NULL where no such mapping can be had, including the platforms
|
||||
* with no mmap at all. Free with memjit_free(), giving the same length.
|
||||
*/
|
||||
void *memjit_alloc(size_t len);
|
||||
void memjit_free(void *addr, size_t len);
|
||||
|
||||
/**
|
||||
* memzero_pages:
|
||||
*
|
||||
* Zeroes a page-aligned range of private anonymous memory as cheaply as
|
||||
* the system allows: by handing the pages back and taking fresh ones
|
||||
* where that is guaranteed to read as zeroes (Linux, Android, Windows),
|
||||
* by mapping fresh pages over the range where the advice would only be
|
||||
* advice (Darwin, the BSDs), and by memset where neither exists.
|
||||
*
|
||||
* Returns true when the range is zeroed and still mapped and readable.
|
||||
* Returns false only on Windows, when the pages were handed back but
|
||||
* fresh ones could not be committed in their place: the range is then
|
||||
* decommitted and must not be read or written; the caller should treat
|
||||
* the allocation as lost and release it. Every other path either
|
||||
* succeeds or falls back to memset and returns true.
|
||||
*/
|
||||
bool memzero_pages(void *addr, size_t len);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
+11
-27
@@ -1,7 +1,7 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (vfs_implementation_cdrom.h).
|
||||
* The following license statement only applies to this file (retro_common.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
@@ -20,33 +20,17 @@
|
||||
* 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
|
||||
#ifndef _LIBRETRO_COMMON_RETRO_COMMON_H
|
||||
#define _LIBRETRO_COMMON_RETRO_COMMON_H
|
||||
|
||||
#include <vfs/vfs.h>
|
||||
#include <cdrom/cdrom.h>
|
||||
/*!
|
||||
* @internal This file is designed to normalize the libretro-common compiling environment.
|
||||
* It is not to be used in public API headers, as they should be designed as leanly as possible.
|
||||
* Nonetheless.. in the meantime, if you do something like use ssize_t, which is not fully portable,
|
||||
* in a public API, you may need this.
|
||||
*/
|
||||
|
||||
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
|
||||
/* conditional compilation is handled inside here */
|
||||
#include <compat/msvc.h>
|
||||
|
||||
#endif
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (retro_dirent.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 __RETRO_DIRENT_H
|
||||
#define __RETRO_DIRENT_H
|
||||
|
||||
#include <libretro.h>
|
||||
#include <retro_common_api.h>
|
||||
|
||||
#include <boolean.h>
|
||||
|
||||
/** @defgroup dirent Directory Entries
|
||||
* @{
|
||||
*/
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* The minimum VFS version (as defined in \c retro_vfs_interface_info::required_interface_version)
|
||||
* required by the \c dirent functions.
|
||||
* If no acceptable VFS interface is provided,
|
||||
* all dirent functions will fall back to libretro-common's implementations.
|
||||
* @see retro_vfs_interface_info
|
||||
*/
|
||||
#define DIRENT_REQUIRED_VFS_VERSION 3
|
||||
#define DIRENT_STAT_REQUIRED_VFS_VERSION 5
|
||||
|
||||
/**
|
||||
* Installs a frontend-provided VFS interface for the dirent functions to use
|
||||
* instead of libretro-common's built-in implementations.
|
||||
*
|
||||
* @param vfs_info The VFS interface returned by the frontend.
|
||||
* The dirent functions will fall back to libretro-common's implementations
|
||||
* if \c vfs_info::required_interface_version is too low.
|
||||
* @see retro_vfs_interface_info
|
||||
* @see RETRO_ENVIRONMENT_GET_VFS_INTERFACE
|
||||
*/
|
||||
void dirent_vfs_init(const struct retro_vfs_interface_info* vfs_info);
|
||||
|
||||
/**
|
||||
* Opaque handle to a directory entry (aka "dirent").
|
||||
* It may name a file, directory, or other filesystem object.
|
||||
* @see retro_opendir
|
||||
*/
|
||||
typedef struct RDIR RDIR;
|
||||
|
||||
/**
|
||||
* Opens a directory for reading.
|
||||
*
|
||||
* @param name Path to a directory to open.
|
||||
* @return An \c RDIR representing the given directory if successful.
|
||||
* Returns \c NULL if \c name is \c NULL, the empty string, or does not name a directory.
|
||||
* @note The returned \c RDIR must be closed with \c retro_closedir.
|
||||
* @see retro_opendir_include_hidden
|
||||
* @see retro_closedir
|
||||
*/
|
||||
struct RDIR *retro_opendir(const char *name);
|
||||
|
||||
/**
|
||||
* @copybrief retro_opendir
|
||||
*
|
||||
* @param name Path to the directory to open.
|
||||
* @param include_hidden Whether to include hidden files and directories
|
||||
* when iterating over this directory with \c retro_readdir.
|
||||
* Platforms and filesystems have different notions of "hidden" files.
|
||||
* Setting this to \c false will not prevent this function from opening \c name.
|
||||
* @return An \c RDIR representing the given directory if successful.
|
||||
* Returns \c NULL if \c name is \c NULL, the empty string, or does not name a directory.
|
||||
* @note The returned \c RDIR must be closed with \c retro_closedir.
|
||||
* @see retro_opendir
|
||||
* @see retro_closedir
|
||||
*/
|
||||
struct RDIR *retro_opendir_include_hidden(const char *name, bool include_hidden);
|
||||
|
||||
/**
|
||||
* Reads the next entry in the given directory.
|
||||
*
|
||||
* Here's a usage example that prints the names of all files in the current directory:
|
||||
* @code
|
||||
* struct RDIR *rdir = retro_opendir(".");
|
||||
* if (rdir)
|
||||
* {
|
||||
* while (retro_readdir(rdir))
|
||||
* {
|
||||
* const char *name = retro_dirent_get_name(rdir);
|
||||
* printf("%s\n", name);
|
||||
* }
|
||||
* retro_closedir(rdir);
|
||||
* rdir = NULL;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param rdir The directory to iterate over.
|
||||
* Behavior is undefined if \c NULL.
|
||||
* @return \c true if the next entry was read successfully,
|
||||
* \c false if there are no more entries to read or if there was an error.
|
||||
* @note This may include "." and ".." on Unix-like platforms.
|
||||
* @see retro_dirent_get_name
|
||||
* @see retro_dirent_is_dir
|
||||
*/
|
||||
int retro_readdir(struct RDIR *rdir);
|
||||
|
||||
/**
|
||||
* @deprecated Left for compatibility.
|
||||
* @param rdir Ignored.
|
||||
* @return \c false.
|
||||
*/
|
||||
bool retro_dirent_error(struct RDIR *rdir);
|
||||
|
||||
/**
|
||||
* Gets the name of the dirent's current file or directory.
|
||||
*
|
||||
* @param rdir The dirent to get the name of.
|
||||
* Behavior is undefined if \c NULL.
|
||||
* @return The name of the directory entry (file, directory, etc.) that the dirent points to.
|
||||
* Will return \c NULL if there was an error,
|
||||
* \c retro_readdir has not been called on \c rdir,
|
||||
* or if there are no more entries to read.
|
||||
* @note This returns only a name, not a full path.
|
||||
* @warning The returned string is managed by the VFS implementation
|
||||
* and must not be modified or freed by the caller.
|
||||
* @warning The returned string is only valid until the next call to \c retro_readdir.
|
||||
* @see retro_readdir
|
||||
*/
|
||||
const char *retro_dirent_get_name(struct RDIR *rdir);
|
||||
|
||||
/**
|
||||
* Checks if the given \c RDIR's current dirent names a directory.
|
||||
*
|
||||
* @param rdir The directory entry to check.
|
||||
* Behavior is undefined if \c NULL.
|
||||
* @param unused Ignored for compatibility reasons. Pass \c NULL.
|
||||
* @return \c true if \c rdir refers to a directory, otherwise \c false.
|
||||
* @see retro_readdir
|
||||
*/
|
||||
bool retro_dirent_is_dir(struct RDIR *rdir, const char *unused);
|
||||
|
||||
/**
|
||||
* Gets size, modification time and stat flags for the current dirent
|
||||
* without opening it or building its path. Free on Windows (already in
|
||||
* the find data), one fstatat on POSIX.
|
||||
*
|
||||
* Only valid after a \c retro_readdir that returned \c true and before
|
||||
* the next \c retro_readdir or \c retro_closedir on the same handle.
|
||||
*
|
||||
* @param rdir The directory being enumerated.
|
||||
* @param size If non-NULL, receives the entry size in bytes (0 for directories).
|
||||
* @param mtime If non-NULL, receives seconds since 1970-01-01T00:00:00Z.
|
||||
* @return A bitmask of \c RETRO_VFS_STAT flags for the entry,
|
||||
* or 0 if unavailable (including frontends older than VFS API v5).
|
||||
* @see retro_readdir
|
||||
*/
|
||||
int retro_dirent_stat(struct RDIR *rdir, int64_t *size, int64_t *mtime);
|
||||
|
||||
/**
|
||||
* Closes an opened \c RDIR that was returned by \c retro_opendir.
|
||||
*
|
||||
* @param rdir The directory entry to close.
|
||||
* If \c NULL, this function does nothing.
|
||||
* @see retro_vfs_closedir_t
|
||||
*/
|
||||
void retro_closedir(struct RDIR *rdir);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,595 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (retro_endianness.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_ENDIANNESS_H
|
||||
#define __LIBRETRO_SDK_ENDIANNESS_H
|
||||
|
||||
#include <retro_inline.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER > 1200
|
||||
#define SWAP16 _byteswap_ushort
|
||||
#define SWAP32 _byteswap_ulong
|
||||
#else
|
||||
/**
|
||||
* Swaps the byte order of a 16-bit unsigned integer.
|
||||
* @param x The integer to byteswap.
|
||||
* @return \c with its two bytes swapped.
|
||||
*/
|
||||
static INLINE uint16_t SWAP16(uint16_t x)
|
||||
{
|
||||
return ((x & 0x00ff) << 8) |
|
||||
((x & 0xff00) >> 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the byte order of a 32-bit unsigned integer.
|
||||
* @param x The integer to byteswap.
|
||||
* @return \c with its bytes swapped.
|
||||
*/
|
||||
static INLINE uint32_t SWAP32(uint32_t x)
|
||||
{
|
||||
return ((x & 0x000000ff) << 24) |
|
||||
((x & 0x0000ff00) << 8) |
|
||||
((x & 0x00ff0000) >> 8) |
|
||||
((x & 0xff000000) >> 24);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1200
|
||||
static INLINE uint64_t SWAP64(uint64_t val)
|
||||
{
|
||||
return
|
||||
((val & 0x00000000000000ff) << 56)
|
||||
| ((val & 0x000000000000ff00) << 40)
|
||||
| ((val & 0x0000000000ff0000) << 24)
|
||||
| ((val & 0x00000000ff000000) << 8)
|
||||
| ((val & 0x000000ff00000000) >> 8)
|
||||
| ((val & 0x0000ff0000000000) >> 24)
|
||||
| ((val & 0x00ff000000000000) >> 40)
|
||||
| ((val & 0xff00000000000000) >> 56);
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* Swaps the byte order of a 64-bit unsigned integer.
|
||||
* @param x The integer to byteswap.
|
||||
* @return \c with its bytes swapped.
|
||||
*/
|
||||
static INLINE uint64_t SWAP64(uint64_t val)
|
||||
{
|
||||
return ((val & 0x00000000000000ffULL) << 56)
|
||||
| ((val & 0x000000000000ff00ULL) << 40)
|
||||
| ((val & 0x0000000000ff0000ULL) << 24)
|
||||
| ((val & 0x00000000ff000000ULL) << 8)
|
||||
| ((val & 0x000000ff00000000ULL) >> 8)
|
||||
| ((val & 0x0000ff0000000000ULL) >> 24)
|
||||
| ((val & 0x00ff000000000000ULL) >> 40)
|
||||
| ((val & 0xff00000000000000ULL) >> 56);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* MSVC pre-defines macros depending on target arch */
|
||||
#if defined (_M_IX86) || defined (_M_AMD64) || defined (_M_ARM) || defined (_M_ARM64)
|
||||
#ifndef LSB_FIRST
|
||||
#define LSB_FIRST 1
|
||||
#endif
|
||||
#elif _M_PPC
|
||||
#ifndef MSB_FIRST
|
||||
#define MSB_FIRST 1
|
||||
#endif
|
||||
#else
|
||||
/* MSVC can run on _M_ALPHA and _M_IA64 too, but they're both bi-endian; need to find what mode MSVC runs them at */
|
||||
#error "unknown platform, can't determine endianness"
|
||||
#endif
|
||||
#else
|
||||
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
#ifndef MSB_FIRST
|
||||
#define MSB_FIRST 1
|
||||
#endif
|
||||
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#ifndef LSB_FIRST
|
||||
#define LSB_FIRST 1
|
||||
#endif
|
||||
#else
|
||||
#error "Invalid endianness macros"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(MSB_FIRST) && defined(LSB_FIRST)
|
||||
# error "Bug in LSB_FIRST/MSB_FIRST definition"
|
||||
#endif
|
||||
|
||||
#if !defined(MSB_FIRST) && !defined(LSB_FIRST)
|
||||
# error "Bug in LSB_FIRST/MSB_FIRST definition"
|
||||
#endif
|
||||
|
||||
#ifdef MSB_FIRST
|
||||
# define RETRO_IS_BIG_ENDIAN 1
|
||||
# define RETRO_IS_LITTLE_ENDIAN 0
|
||||
/* For compatibility */
|
||||
# define WORDS_BIGENDIAN 1
|
||||
#else
|
||||
# define RETRO_IS_BIG_ENDIAN 0
|
||||
# define RETRO_IS_LITTLE_ENDIAN 1
|
||||
/* For compatibility */
|
||||
# undef WORDS_BIGENDIAN
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the current CPU is little-endian.
|
||||
*
|
||||
* @return \c true on little-endian CPUs,
|
||||
* \c false on big-endian CPUs.
|
||||
*/
|
||||
#define is_little_endian() RETRO_IS_LITTLE_ENDIAN
|
||||
|
||||
/**
|
||||
* Byte-swaps an unsigned 64-bit integer on big-endian CPUs.
|
||||
*
|
||||
* @param val The value to byteswap if necessary.
|
||||
* @return \c val byteswapped on big-endian CPUs,
|
||||
* \c val unchanged on little-endian CPUs.
|
||||
*/
|
||||
#if RETRO_IS_BIG_ENDIAN
|
||||
#define swap_if_big64(val) (SWAP64(val))
|
||||
#elif RETRO_IS_LITTLE_ENDIAN
|
||||
#define swap_if_big64(val) (val)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Byte-swaps an unsigned 32-bit integer on big-endian CPUs.
|
||||
*
|
||||
* @param val The value to byteswap if necessary.
|
||||
* @return \c val byteswapped on big-endian CPUs,
|
||||
* \c val unchanged on little-endian CPUs.
|
||||
*/
|
||||
#if RETRO_IS_BIG_ENDIAN
|
||||
#define swap_if_big32(val) (SWAP32(val))
|
||||
#elif RETRO_IS_LITTLE_ENDIAN
|
||||
#define swap_if_big32(val) (val)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Byte-swaps an unsigned 64-bit integer on little-endian CPUs.
|
||||
*
|
||||
* @param val The value to byteswap if necessary.
|
||||
* @return \c val byteswapped on little-endian CPUs,
|
||||
* \c val unchanged on big-endian CPUs.
|
||||
*/
|
||||
#if RETRO_IS_BIG_ENDIAN
|
||||
#define swap_if_little64(val) (val)
|
||||
#elif RETRO_IS_LITTLE_ENDIAN
|
||||
#define swap_if_little64(val) (SWAP64(val))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Byte-swaps an unsigned 32-bit integer on little-endian CPUs.
|
||||
*
|
||||
* @param val The value to byteswap if necessary.
|
||||
* @return \c val byteswapped on little-endian CPUs,
|
||||
* \c val unchanged on big-endian CPUs.
|
||||
*/
|
||||
#if RETRO_IS_BIG_ENDIAN
|
||||
#define swap_if_little32(val) (val)
|
||||
#elif RETRO_IS_LITTLE_ENDIAN
|
||||
#define swap_if_little32(val) (SWAP32(val))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Byte-swaps an unsigned 16-bit integer on big-endian systems.
|
||||
*
|
||||
* @param val The value to byteswap if necessary.
|
||||
* @return \c val byteswapped on big-endian systems,
|
||||
* \c val unchanged on little-endian systems.
|
||||
*/
|
||||
#if RETRO_IS_BIG_ENDIAN
|
||||
#define swap_if_big16(val) (SWAP16(val))
|
||||
#elif RETRO_IS_LITTLE_ENDIAN
|
||||
#define swap_if_big16(val) (val)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Byte-swaps an unsigned 16-bit integer on little-endian systems.
|
||||
*
|
||||
* @param val The value to byteswap if necessary.
|
||||
* @return \c val byteswapped on little-endian systems,
|
||||
* \c val unchanged on big-endian systems.
|
||||
*/
|
||||
#if RETRO_IS_BIG_ENDIAN
|
||||
#define swap_if_little16(val) (val)
|
||||
#elif RETRO_IS_LITTLE_ENDIAN
|
||||
#define swap_if_little16(val) (SWAP16(val))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Stores a 32-bit integer in at the given address, in big-endian order.
|
||||
*
|
||||
* @param addr The address to store the value at.
|
||||
* Behavior is undefined if \c NULL or not aligned to a 32-bit boundary.
|
||||
* @param data The value to store in \c addr.
|
||||
* Will be byteswapped if on a little-endian CPU.
|
||||
*/
|
||||
static INLINE void store32be(uint32_t *addr, uint32_t data)
|
||||
{
|
||||
*addr = swap_if_little32(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a 32-bit integer order from the given address, in big-endian order.
|
||||
*
|
||||
* @param addr The address to load the value from.
|
||||
* Behavior is undefined if \c NULL or not aligned to a 32-bit boundary.
|
||||
* @return The value at \c addr, byteswapped if on a little-endian CPU.
|
||||
*/
|
||||
static INLINE uint32_t load32be(const uint32_t *addr)
|
||||
{
|
||||
return swap_if_little32(*addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 16-bit integer to little-endian order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a big-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_cpu_to_le16(val) swap_if_big16(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 32-bit integer to little-endian order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a big-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_cpu_to_le32(val) swap_if_big32(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 64-bit integer to little-endian order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a big-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_cpu_to_le64(val) swap_if_big64(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 16-bit integer to host-native order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a big-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_le_to_cpu16(val) swap_if_big16(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 32-bit integer to host-native order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a big-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_le_to_cpu32(val) swap_if_big32(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 64-bit integer to host-native order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a big-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_le_to_cpu64(val) swap_if_big64(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 16-bit integer to big-endian order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a little-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_cpu_to_be16(val) swap_if_little16(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 32-bit integer to big-endian order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a little-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_cpu_to_be32(val) swap_if_little32(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 64-bit integer to big-endian order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a little-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_cpu_to_be64(val) swap_if_little64(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 16-bit integer from big-endian to host-native order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a little-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_be_to_cpu16(val) swap_if_little16(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 32-bit integer from big-endian to host-native order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a little-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_be_to_cpu32(val) swap_if_little32(val)
|
||||
|
||||
/**
|
||||
* Converts the given unsigned 64-bit integer from big-endian to host-native order if necessary.
|
||||
*
|
||||
* @param val The value to convert if necessary.
|
||||
* @return \c val byteswapped if on a little-endian CPU,
|
||||
* unchanged otherwise.
|
||||
*/
|
||||
#define retro_be_to_cpu64(val) swap_if_little64(val)
|
||||
|
||||
#ifdef __GNUC__
|
||||
/**
|
||||
* This attribute indicates that pointers to this type may alias
|
||||
* to pointers of any other type, similar to \c void* or \c char*.
|
||||
*/
|
||||
#define MAY_ALIAS __attribute__((__may_alias__))
|
||||
#else
|
||||
#define MAY_ALIAS
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct retro_unaligned_uint16_s
|
||||
{
|
||||
uint16_t val;
|
||||
} MAY_ALIAS;
|
||||
struct retro_unaligned_uint32_s
|
||||
{
|
||||
uint32_t val;
|
||||
} MAY_ALIAS;
|
||||
struct retro_unaligned_uint64_s
|
||||
{
|
||||
uint64_t val;
|
||||
} MAY_ALIAS;
|
||||
#pragma pack(pop)
|
||||
|
||||
/**
|
||||
* A wrapper around a \c uint16_t that allows unaligned access
|
||||
* where supported by the compiler.
|
||||
*/
|
||||
typedef struct retro_unaligned_uint16_s retro_unaligned_uint16_t;
|
||||
|
||||
/**
|
||||
* A wrapper around a \c uint32_t that allows unaligned access
|
||||
* where supported by the compiler.
|
||||
*/
|
||||
typedef struct retro_unaligned_uint32_s retro_unaligned_uint32_t;
|
||||
|
||||
/**
|
||||
* A wrapper around a \c uint64_t that allows unaligned access
|
||||
* where supported by the compiler.
|
||||
*/
|
||||
typedef struct retro_unaligned_uint64_s retro_unaligned_uint64_t;
|
||||
|
||||
/* L-value references to unaligned pointers. */
|
||||
#define retro_unaligned16(p) (((retro_unaligned_uint16_t *)(p))->val)
|
||||
#define retro_unaligned32(p) (((retro_unaligned_uint32_t *)(p))->val)
|
||||
#define retro_unaligned64(p) (((retro_unaligned_uint64_t *)(p))->val)
|
||||
|
||||
/**
|
||||
* Reads a 16-bit unsigned integer from the given address
|
||||
* and converts it from big-endian to host-native order (if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address of the integer to read.
|
||||
* Does not need to be divisible by 2
|
||||
* the way a \c uint16_t* usually would be.
|
||||
* @return The first two bytes of \c addr as a 16-bit unsigned integer,
|
||||
* byteswapped from big-endian to host-native order if necessary.
|
||||
*/
|
||||
static INLINE uint16_t retro_get_unaligned_16be(void *addr)
|
||||
{
|
||||
return retro_be_to_cpu16(retro_unaligned16(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 32-bit unsigned integer from the given address
|
||||
* and converts it from big-endian to host-native order (if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address of the integer to read.
|
||||
* Does not need to be divisible by 4
|
||||
* the way a \c uint32_t* usually would be.
|
||||
* @return The first four bytes of \c addr as a 32-bit unsigned integer,
|
||||
* byteswapped from big-endian to host-native order if necessary.
|
||||
*/
|
||||
static INLINE uint32_t retro_get_unaligned_32be(void *addr)
|
||||
{
|
||||
return retro_be_to_cpu32(retro_unaligned32(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 64-bit unsigned integer from the given address
|
||||
* and converts it from big-endian to host-native order (if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address of the integer to read.
|
||||
* Does not need to be divisible by 8
|
||||
* the way a \c uint64_t* usually would be.
|
||||
* @return The first eight bytes of \c addr as a 64-bit unsigned integer,
|
||||
* byteswapped from big-endian to host-native order if necessary.
|
||||
*/
|
||||
static INLINE uint64_t retro_get_unaligned_64be(void *addr)
|
||||
{
|
||||
return retro_be_to_cpu64(retro_unaligned64(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 16-bit unsigned integer from the given address
|
||||
* and converts it from little-endian to host-native order (if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address of the integer to read.
|
||||
* Does not need to be divisible by 2
|
||||
* the way a \c uint16_t* usually would be.
|
||||
* @return The first two bytes of \c addr as a 16-bit unsigned integer,
|
||||
* byteswapped from little-endian to host-native order if necessary.
|
||||
*/
|
||||
static INLINE uint16_t retro_get_unaligned_16le(void *addr)
|
||||
{
|
||||
return retro_le_to_cpu16(retro_unaligned16(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 32-bit unsigned integer from the given address
|
||||
* and converts it from little-endian to host-native order (if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address of the integer to read.
|
||||
* Does not need to be divisible by 4
|
||||
* the way a \c uint32_t* usually would be.
|
||||
* @return The first four bytes of \c addr as a 32-bit unsigned integer,
|
||||
* byteswapped from little-endian to host-native order if necessary.
|
||||
*/
|
||||
static INLINE uint32_t retro_get_unaligned_32le(void *addr)
|
||||
{
|
||||
return retro_le_to_cpu32(retro_unaligned32(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 64-bit unsigned integer from the given address
|
||||
* and converts it from little-endian to host-native order (if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address of the integer to read.
|
||||
* Does not need to be divisible by 8
|
||||
* the way a \c uint64_t* usually would be.
|
||||
* @return The first eight bytes of \c addr as a 64-bit unsigned integer,
|
||||
* byteswapped from little-endian to host-native order if necessary.
|
||||
*/
|
||||
static INLINE uint64_t retro_get_unaligned_64le(void *addr)
|
||||
{
|
||||
return retro_le_to_cpu64(retro_unaligned64(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 16-bit unsigned integer to the given address
|
||||
* (converted to little-endian order if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address to write the integer to.
|
||||
* Does not need to be divisible by 2
|
||||
* the way a \c uint16_t* usually would be.
|
||||
* @param v The value to write.
|
||||
*/
|
||||
static INLINE void retro_set_unaligned_16le(void *addr, uint16_t v)
|
||||
{
|
||||
retro_unaligned16(addr) = retro_cpu_to_le16(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 32-bit unsigned integer to the given address
|
||||
* (converted to little-endian order if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address to write the integer to.
|
||||
* Does not need to be divisible by 4
|
||||
* the way a \c uint32_t* usually would be.
|
||||
* @param v The value to write.
|
||||
*/
|
||||
static INLINE void retro_set_unaligned_32le(void *addr, uint32_t v)
|
||||
{
|
||||
retro_unaligned32(addr) = retro_cpu_to_le32(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 64-bit unsigned integer to the given address
|
||||
* (converted to little-endian order if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address to write the integer to.
|
||||
* Does not need to be divisible by 8
|
||||
* the way a \c uint64_t* usually would be.
|
||||
* @param v The value to write.
|
||||
*/
|
||||
static INLINE void retro_set_unaligned_64le(void *addr, uint64_t v)
|
||||
{
|
||||
retro_unaligned64(addr) = retro_cpu_to_le64(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 16-bit unsigned integer to the given address
|
||||
* (converted to big-endian order if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address to write the integer to.
|
||||
* Does not need to be divisible by 2
|
||||
* the way a \c uint16_t* usually would be.
|
||||
* @param v The value to write.
|
||||
*/
|
||||
static INLINE void retro_set_unaligned_16be(void *addr, uint16_t v)
|
||||
{
|
||||
retro_unaligned16(addr) = retro_cpu_to_be16(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 32-bit unsigned integer to the given address
|
||||
* (converted to big-endian order if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address to write the integer to.
|
||||
* Does not need to be divisible by 4
|
||||
* the way a \c uint32_t* usually would be.
|
||||
* @param v The value to write.
|
||||
*/
|
||||
static INLINE void retro_set_unaligned_32be(void *addr, uint32_t v)
|
||||
{
|
||||
retro_unaligned32(addr) = retro_cpu_to_be32(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 64-bit unsigned integer to the given address
|
||||
* (converted to big-endian order if necessary),
|
||||
* regardless of the CPU's alignment requirements.
|
||||
*
|
||||
* @param addr The address to write the integer to.
|
||||
* Does not need to be divisible by 8
|
||||
* the way a \c uint64_t* usually would be.
|
||||
* @param v The value to write.
|
||||
*/
|
||||
static INLINE void retro_set_unaligned_64be(void *addr, uint64_t v)
|
||||
{
|
||||
retro_unaligned64(addr) = retro_cpu_to_be64(v);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
+13
-4
@@ -98,13 +98,22 @@ need to be full of platform-specific workarounds.
|
||||
#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))
|
||||
/* GCC and clang define __SSE__/__SSE2__ themselves; MSVC reports the
|
||||
* same information through _M_IX86_FP and its architecture macros, so
|
||||
* translate those into the standard spellings. The inner guards let
|
||||
* clang-cl, which defines both sets, through without redefining. */
|
||||
#if defined(_MSC_VER)
|
||||
#if (defined(_M_IX86_FP) && _M_IX86_FP >= 1) || defined(_M_AMD64) || defined(_M_X64)
|
||||
#ifndef __SSE__
|
||||
#define __SSE__ 1
|
||||
#endif
|
||||
#endif
|
||||
#if (defined(_M_IX86_FP) && _M_IX86_FP >= 2) || defined(_M_AMD64) || defined(_M_X64)
|
||||
#ifndef __SSE2__
|
||||
#define __SSE2__ 1
|
||||
#endif
|
||||
#endif
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (retro_posix_source.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_POSIX_SOURCE_H
|
||||
#define __LIBRETRO_SDK_POSIX_SOURCE_H
|
||||
|
||||
/* The feature profile these sources are written against: strdup(),
|
||||
* strcasecmp(), fstatat(), O_CLOEXEC, M_PI, sigaltstack() and syscall()
|
||||
* all sit behind __USE_XOPEN2K8 or __USE_MISC, and a consumer is free to
|
||||
* build the library under a stricter profile than the default -- the
|
||||
* Debian/Launchpad core packaging passes -D_XOPEN_SOURCE=600.
|
||||
*
|
||||
* MUST BE THE FIRST INCLUDE: a libc settles what its headers expose when
|
||||
* the first of them is seen.
|
||||
*
|
||||
* The guards only raise the profile, and ask for what a build passing no
|
||||
* feature macros already gets, so nothing this library selects changes.
|
||||
* Linux only: elsewhere _POSIX_C_SOURCE would instead hide extensions
|
||||
* these sources use. */
|
||||
|
||||
/* Opt-in, because a unity build reaches its second file with system
|
||||
* headers already in scope, where these macros are inert rather than
|
||||
* wrong. __GLIBC__ arrives from <features.h>. */
|
||||
#if defined(RETRO_POSIX_SOURCE_CHECK_ORDER) && defined(__GLIBC__)
|
||||
#error "retro_posix_source.h must be included before any system header"
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(_GNU_SOURCE)
|
||||
|
||||
#if !defined(_POSIX_C_SOURCE) || (_POSIX_C_SOURCE - 0) < 200809L
|
||||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE - 0) < 700
|
||||
#undef _XOPEN_SOURCE
|
||||
#define _XOPEN_SOURCE 700
|
||||
#endif
|
||||
|
||||
#ifndef _DEFAULT_SOURCE
|
||||
#define _DEFAULT_SOURCE 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,709 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (file_stream.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_FILE_STREAM_H
|
||||
#define __LIBRETRO_SDK_FILE_STREAM_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <libretro.h>
|
||||
#include <retro_common_api.h>
|
||||
#include <retro_inline.h>
|
||||
#include <boolean.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
/** @defgroup file_stream File Streams
|
||||
*
|
||||
* All functions in this header will use the VFS interface set in \ref filestream_vfs_init if possible,
|
||||
* or else they will fall back to built-in equivalents.
|
||||
*
|
||||
* @note These functions are modeled after those in the C standard library
|
||||
* (and may even use them internally),
|
||||
* but identical behavior is not guaranteed.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The minimum version of the VFS interface required by the \c filestream functions.
|
||||
*/
|
||||
#define FILESTREAM_REQUIRED_VFS_VERSION 2
|
||||
#define FILESTREAM_COPY_REQUIRED_VFS_VERSION 5
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Opaque handle to a file stream.
|
||||
* @warning This is not interchangeable with \c FILE* or \c retro_vfs_file_handle.
|
||||
*/
|
||||
typedef struct RFILE RFILE;
|
||||
|
||||
/**
|
||||
* Initializes the \c filestream functions to use the VFS interface provided by the frontend.
|
||||
* Optional; if not called, all \c filestream functions
|
||||
* will use libretro-common's built-in implementations.
|
||||
*
|
||||
* @param vfs_info The VFS interface returned by the frontend.
|
||||
* If \c vfs_info::iface (but \em not \c vfs_info itself) is \c NULL,
|
||||
* then libretro-common's built-in VFS implementation will be used.
|
||||
*/
|
||||
void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info);
|
||||
|
||||
/**
|
||||
* Returns the size of the given file, in bytes.
|
||||
*
|
||||
* @param stream The open file to query.
|
||||
* @return The size of \c stream in bytes,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_size_t
|
||||
*/
|
||||
int64_t filestream_get_size(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Sets the size of the given file,
|
||||
* truncating or extending it as necessary.
|
||||
*
|
||||
* @param stream The file to resize.
|
||||
* @param length The new size of \c stream, in bytes.
|
||||
* @return 0 if the resize was successful,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_truncate_t
|
||||
*/
|
||||
int64_t filestream_truncate(RFILE *stream, int64_t length);
|
||||
|
||||
/**
|
||||
* Opens a file for reading or writing.
|
||||
*
|
||||
* @param path Path to the file to open.
|
||||
* Should be in the format described in \ref GET_VFS_INTERFACE.
|
||||
* @param mode The mode to open the file in.
|
||||
* Should be one or more of the flags in \refitem RETRO_VFS_FILE_ACCESS OR'd together,
|
||||
* and \c RETRO_VFS_FILE_ACCESS_READ or \c RETRO_VFS_FILE_ACCESS_WRITE
|
||||
* (or both) must be included.
|
||||
* @param hints Optional hints to pass to the frontend.
|
||||
*
|
||||
* @return The opened file, or \c NULL if there was an error.
|
||||
* Must be cleaned up with \c filestream_close when no longer needed.
|
||||
* @see retro_vfs_open_t
|
||||
*/
|
||||
RFILE* filestream_open(const char *path, unsigned mode, unsigned hints);
|
||||
|
||||
/**
|
||||
* Sets the current position of the file stream.
|
||||
* Use this to read specific sections of a file.
|
||||
*
|
||||
* @param stream The file to set the stream position of.
|
||||
* @param offset The new stream position, in bytes.
|
||||
* @param seek_position The position to seek from.
|
||||
* Should be one of the values in \refitem RETRO_VFS_SEEK_POSITION.
|
||||
* @return 0 if the stream position was set successfully,
|
||||
* or -1 if there was an error.
|
||||
* @see RETRO_VFS_SEEK_POSITION
|
||||
* @see retro_vfs_seek_t
|
||||
*/
|
||||
int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position);
|
||||
|
||||
/**
|
||||
* Reads data from the given file into a buffer.
|
||||
* If the read is successful,
|
||||
* the file's stream position will advance by the number of bytes read.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param data The buffer in which to store the read data.
|
||||
* @param len The size of \c data, in bytes.
|
||||
* @return The number of bytes read,
|
||||
* or -1 if there was an error.
|
||||
* May be less than \c len, but never more.
|
||||
* @see retro_vfs_read_t
|
||||
*/
|
||||
int64_t filestream_read(RFILE *stream, void *data, int64_t len);
|
||||
|
||||
/**
|
||||
* Writes data from a buffer to the given file.
|
||||
* If the write is successful,
|
||||
* the file's stream position will advance by the number of bytes written.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param data The buffer containing the data to write.
|
||||
* @param len The size of \c data, in bytes.
|
||||
* @return The number of bytes written,
|
||||
* or -1 if there was an error.
|
||||
* May be less than \c len, but never more.
|
||||
* @see retro_vfs_write_t
|
||||
*/
|
||||
int64_t filestream_write(RFILE *stream, const void *data, int64_t len);
|
||||
|
||||
/**
|
||||
* Returns the current position of the given file in bytes.
|
||||
*
|
||||
* @param stream The file to return the stream position for.
|
||||
* @return The current stream position in bytes relative to the beginning,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_tell_t
|
||||
*/
|
||||
int64_t filestream_tell(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Rewinds the given file to the beginning.
|
||||
* Equivalent to <tt>filestream_seek(stream, 0, RETRO_VFS_SEEK_POSITION_START)</tt>.
|
||||
|
||||
* @param stream The file to rewind.
|
||||
* May be \c NULL, in which case this function does nothing.
|
||||
*/
|
||||
void filestream_rewind(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Closes the given file.
|
||||
*
|
||||
* @param stream The file to close.
|
||||
* This should have been created with \c filestream_open.
|
||||
* Behavior is undefined if \c NULL.
|
||||
* @return 0 if the file was closed successfully,
|
||||
* or -1 if there was an error.
|
||||
* @post \c stream is no longer valid and should not be used,
|
||||
* even if this function fails.
|
||||
* @see retro_vfs_close_t
|
||||
*/
|
||||
int filestream_close(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Opens a file, reads its contents into a newly-allocated buffer,
|
||||
* then closes it.
|
||||
*
|
||||
* @param path[in] Path to the file to read.
|
||||
* Should be in the format described in \ref GET_VFS_INTERFACE.
|
||||
* @param buf[out] A pointer to the address of the newly-allocated buffer.
|
||||
* The buffer will contain the entirety of the file at \c path.
|
||||
* Will be allocated with \c malloc and must be freed with \c free.
|
||||
* @param len[out] Pointer to the size of the buffer in bytes.
|
||||
* May be \c NULL, in which case the length is not written.
|
||||
* Value is unspecified if this function fails.
|
||||
* @return 1 if the file was read successfully,
|
||||
* 0 if there was an error.
|
||||
* @see filestream_write_file
|
||||
*/
|
||||
int64_t filestream_read_file(const char *path, void **buf, int64_t *len);
|
||||
|
||||
/**
|
||||
* Reads a line of text from the given file,
|
||||
* up to a given length.
|
||||
*
|
||||
* Will read to the next newline or until the buffer is full,
|
||||
* whichever comes first.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param s The buffer to write the retrieved line to.
|
||||
* Will contain at most \c len - 1 characters
|
||||
* plus a null terminator.
|
||||
* The newline character (if any) will not be included.
|
||||
* The line delimiter must be Unix-style (\c '\n').
|
||||
* Carriage returns (\c '\r') will not be treated specially.
|
||||
* @param len The length of the buffer \c s, in bytes.
|
||||
* @return \s if successful, \c NULL if there was an error.
|
||||
*/
|
||||
char* filestream_gets(RFILE *stream, char *s, size_t len);
|
||||
|
||||
/**
|
||||
* Reads a single character from the given file.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @return The character read, or -1 upon reaching the end of the file.
|
||||
*/
|
||||
int filestream_getc(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Reads formatted text from the given file,
|
||||
* with arguments provided as a standard \c va_list.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param format The string to write, possibly including scanf-compatible format specifiers.
|
||||
* @param args Argument list with zero or more elements
|
||||
* whose values will be updated according to the semantics of \c format.
|
||||
* @return The number of arguments in \c args that were successfully assigned,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/fscanf
|
||||
* @see https://en.cppreference.com/w/c/variadic
|
||||
*/
|
||||
int filestream_vscanf(RFILE *stream, const char* format, va_list *args);
|
||||
|
||||
/**
|
||||
* Reads formatted text from the given file.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param format The string to write, possibly including scanf-compatible format specifiers.
|
||||
* @param ... Zero or more arguments that will be updated according to the semantics of \c format.
|
||||
* @return The number of arguments in \c ... that were successfully assigned,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/fscanf
|
||||
*/
|
||||
int filestream_scanf(RFILE *stream, const char* format, ...);
|
||||
|
||||
/**
|
||||
* Determines if there's any more data left to read from this file.
|
||||
*
|
||||
* @param stream The file to check the position of.
|
||||
* @return -1 if this stream has reached the end of the file,
|
||||
* 0 if not.
|
||||
*/
|
||||
int filestream_eof(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Writes the entirety of a given buffer to a file at a given path.
|
||||
* Any file that already exists will be overwritten.
|
||||
*
|
||||
* @param path Path to the file that will be written to.
|
||||
* @param data The buffer to write to \c path.
|
||||
* @param size The size of \c data, in bytes.
|
||||
* @return \c true if the file was written successfully,
|
||||
* \c false if there was an error.
|
||||
*/
|
||||
bool filestream_write_file(const char *path, const void *data, int64_t size);
|
||||
|
||||
/**
|
||||
* Writes the entirety of a given buffer to a file at a given path,
|
||||
* by way of a sibling temporary file that is renamed over the target
|
||||
* once it is complete. A process that dies partway through leaves
|
||||
* either the previous contents of \c path or the new ones, never a
|
||||
* partial file.
|
||||
*
|
||||
* Use this for anything a later run reads back and trusts without a
|
||||
* length or checksum of its own, where a truncated file is worse than
|
||||
* an absent one.
|
||||
*
|
||||
* Under a frontend VFS whose rename refuses an existing destination,
|
||||
* the target is removed first and the rename retried, so there the
|
||||
* replacement is not atomic.
|
||||
*
|
||||
* @param path Path to the file that will be written to.
|
||||
* @param data The buffer to write to \c path.
|
||||
* @param size The size of \c data, in bytes.
|
||||
* @return \c true if the file was written successfully,
|
||||
* \c false if there was an error, in which case \c path is left as it
|
||||
* was and no temporary file remains.
|
||||
*/
|
||||
bool filestream_write_file_atomic(const char *path, const void *data, int64_t size);
|
||||
|
||||
/**
|
||||
* Opens the sibling temporary file an atomic write of \c path goes
|
||||
* through (\c path with ".tmp" appended), for a writer that produces
|
||||
* the contents a piece at a time. Finish with
|
||||
* filestream_commit_atomic(), which closes it either way.
|
||||
*
|
||||
* @param path Path of the file that will be replaced.
|
||||
* @return The temporary file, open for writing, or \c NULL on error.
|
||||
*/
|
||||
RFILE *filestream_open_atomic(const char *path);
|
||||
|
||||
/**
|
||||
* Closes a file from filestream_open_atomic() and, if \c ok and the
|
||||
* close succeeded, renames it over \c path, exactly as
|
||||
* filestream_write_file_atomic() does.
|
||||
*
|
||||
* @param file The file returned by filestream_open_atomic(); always
|
||||
* closed and freed.
|
||||
* @param path The same path it was opened for.
|
||||
* @param ok Whether the caller wrote everything it meant to.
|
||||
* @return 0 if \c path now holds the new contents; -1 if the write or
|
||||
* close failed, in which case the temporary file is deleted and \c path
|
||||
* is left as it was; 1 if the contents are complete in the temporary
|
||||
* file but could not be renamed into place, in which case the temporary
|
||||
* file is left for the caller to copy or delete.
|
||||
*/
|
||||
int filestream_commit_atomic(RFILE *file, const char *path, bool ok);
|
||||
|
||||
/**
|
||||
* The name of the sibling temporary file an atomic write of \c path
|
||||
* goes through, for a writer that opens files by name.
|
||||
*
|
||||
* @param s Where the name is written.
|
||||
* @param len The size of \c s.
|
||||
* @param path Path of the file that will be replaced.
|
||||
* @return \c false if the name does not fit in \c s.
|
||||
*/
|
||||
bool filestream_atomic_temp_name(char *s, size_t len, const char *path);
|
||||
|
||||
/**
|
||||
* Finishes an atomic write of \c path whose temporary file, named by
|
||||
* filestream_atomic_temp_name(), was written and closed by other means:
|
||||
* as filestream_commit_atomic() does after its close.
|
||||
*
|
||||
* @param path Path of the file that will be replaced.
|
||||
* @param ok Whether the temporary file holds everything it should.
|
||||
* @return As filestream_commit_atomic().
|
||||
*/
|
||||
int filestream_finish_atomic(const char *path, bool ok);
|
||||
|
||||
/**
|
||||
* Writes a single character to the given file.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param c The character to write.
|
||||
* @return The character written,
|
||||
* or -1 if there was an error.
|
||||
* Will return -1 if \c stream is \c NULL.
|
||||
*/
|
||||
int filestream_putc(RFILE *stream, int c);
|
||||
|
||||
/**
|
||||
* Writes formatted text to the given file,
|
||||
* with arguments provided as a standard \c va_list.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param format The string to write, possibly including printf-compatible format specifiers.
|
||||
* @param args A list of arguments to be formatted and inserted in the resulting string.
|
||||
* @return The number of characters written,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/vfprintf
|
||||
* @see https://en.cppreference.com/w/c/variadic
|
||||
*/
|
||||
int filestream_vprintf(RFILE *stream, const char* format, va_list args);
|
||||
|
||||
/**
|
||||
* Writes formatted text to the given file.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param format The string to write, possibly including printf-compatible format specifiers.
|
||||
* @param ... Zero or more arguments to be formatted and inserted into the resulting string.
|
||||
* @return The number of characters written,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/printf
|
||||
*/
|
||||
int filestream_printf(RFILE *stream, const char* format, ...);
|
||||
|
||||
/**
|
||||
* Checks if there was an error in using the given file stream.
|
||||
*
|
||||
* @param stream The file stream to check for errors.
|
||||
* @return \c true if there was an error in using this stream,
|
||||
* \c false if not or if \c stream is \c NULL.
|
||||
*/
|
||||
int filestream_error(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Flushes pending writes to the operating system's file layer.
|
||||
* There is no guarantee that pending writes will be written to disk immediately.
|
||||
*
|
||||
* @param stream The file to flush.
|
||||
* @return 0 if the flush was successful,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_flush_t
|
||||
*/
|
||||
int filestream_flush(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Deletes the file at the given path.
|
||||
* If the file is open by any process,
|
||||
* the behavior is platform-specific.
|
||||
*
|
||||
* @note This function might or might not delete directories recursively,
|
||||
* depending on the platform and the underlying VFS implementation.
|
||||
*
|
||||
* @param path The file to delete.
|
||||
* @return 0 if the file was deleted successfully,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_remove_t
|
||||
*/
|
||||
int filestream_delete(const char *path);
|
||||
|
||||
/**
|
||||
* Moves a file to a new location, with a new name.
|
||||
*
|
||||
* @param old_path Path to the file to rename.
|
||||
* @param new_path The target name and location of the file.
|
||||
* @return 0 if the file was renamed successfully,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_rename_t
|
||||
*/
|
||||
int filestream_rename(const char *old_path, const char *new_path);
|
||||
|
||||
/**
|
||||
* Copies a regular file to a new location, replacing an existing one,
|
||||
* and does not return until it is done.
|
||||
*
|
||||
* Kept for callers that predate VFS API v5. It copies through
|
||||
* \c filestream_open/read/write and does not use the platform's copy
|
||||
* primitive; new code should use \c filestream_copy_begin, which does
|
||||
* and does not block.
|
||||
*
|
||||
* @param src_path Path to the file to copy.
|
||||
* @param dst_path The target name and location of the file.
|
||||
* @return 0 if the file was copied successfully,
|
||||
* or -1 if there was an error (no partial \c dst_path is left behind).
|
||||
* @see filestream_copy_begin
|
||||
*/
|
||||
int filestream_copy(const char *src_path, const char *dst_path);
|
||||
|
||||
/**
|
||||
* Starts copying a regular file without moving any of it yet.
|
||||
* Wraps \c retro_vfs_copy_begin_t; see it for the full contract. The
|
||||
* caller advances the copy with \c filestream_copy_step; no thread is
|
||||
* involved unless the caller supplies one.
|
||||
*
|
||||
* @param src_path Path to the file to copy. Must be a regular file.
|
||||
* @param dst_path Full path of the destination. Must differ from \c src_path.
|
||||
* @param flags Bitwise combination of \c RETRO_VFS_COPY flags, or 0.
|
||||
* @return A handle for \c filestream_copy_step and \c filestream_copy_close,
|
||||
* or \c NULL if the copy could not start (including when the frontend
|
||||
* does not offer VFS API v5).
|
||||
*/
|
||||
struct retro_vfs_copy_handle *filestream_copy_begin(const char *src_path, const char *dst_path, unsigned flags);
|
||||
|
||||
/**
|
||||
* Advances a copy started with \c filestream_copy_begin by at most
|
||||
* \c max_bytes and reports its state. See \c retro_vfs_copy_step_t for
|
||||
* how to use the budget as a latency/throughput dial.
|
||||
*
|
||||
* @param max_bytes Upper bound on bytes moved by this call; 0 selects a
|
||||
* default sized for a frame loop.
|
||||
* @return One of the \c RETRO_VFS_COPY_STATUS values.
|
||||
*/
|
||||
int filestream_copy_step(struct retro_vfs_copy_handle *handle, int64_t max_bytes, int64_t *bytes_done, int64_t *bytes_total);
|
||||
|
||||
/**
|
||||
* Releases a copy handle, cancelling the copy if it is still running.
|
||||
* See \c retro_vfs_copy_close_t.
|
||||
*
|
||||
* @return 0 if the copy had completed successfully, otherwise -1.
|
||||
*/
|
||||
int filestream_copy_close(struct retro_vfs_copy_handle *handle);
|
||||
|
||||
/**
|
||||
* Compares and verifies files.
|
||||
*
|
||||
* @param src_path Path to the file.
|
||||
* @param dst_path Path to the other file.
|
||||
* @return 0 if the files are equal,
|
||||
* or -1 if there was an error.
|
||||
*/
|
||||
int filestream_cmp(const char *src_path, const char *dst_path);
|
||||
|
||||
/**
|
||||
* Get the path that was used to open a file.
|
||||
*
|
||||
* @param stream The file to get the path of.
|
||||
* @return The path that was used to open \c stream,
|
||||
* or \c NULL if there was an error.
|
||||
* The string is owned by \c stream and must not be modified or freed by the caller.
|
||||
*/
|
||||
const char* filestream_get_path(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Borrowed read-only pointer to the entire file, when \c stream is
|
||||
* backed by a memory map.
|
||||
*
|
||||
* Open with \c RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS to ask for
|
||||
* a mapping. The hint has always produced one, but the map was
|
||||
* reachable only from inside the read path, which copies out of it -
|
||||
* so consumers of the hint paid for a copy of data that was already
|
||||
* addressable. Use this when the file is to be read rather than
|
||||
* owned: compared against a buffer, hashed, or handed to a parser
|
||||
* that takes a base pointer.
|
||||
*
|
||||
* \c NULL is a normal answer rather than an error - the platform may
|
||||
* have no mapping support, the hint may not have been given, or the
|
||||
* stream may come from a frontend-supplied VFS - so a caller must
|
||||
* keep its ordinary read path and treat this purely as a fast lane.
|
||||
*
|
||||
* @param stream The file to get the mapping of.
|
||||
* @param len Filled with the mapped length, or 0 when there is no
|
||||
* mapping. May be \c NULL.
|
||||
* @return Pointer to the mapped file, or \c NULL if it is not mapped.
|
||||
* The mapping is owned by \c stream, is valid only until \c stream is
|
||||
* closed, and must not be written or freed by the caller.
|
||||
*/
|
||||
const uint8_t *filestream_get_mapped_ptr(RFILE *stream, int64_t *len);
|
||||
|
||||
/**
|
||||
* filestream_read() without the copy: points \c ptr at up to \c len
|
||||
* octets at the stream position - in the lookahead, the file's
|
||||
* mapping or a network stream's read-ahead window - and moves past
|
||||
* them, as a read of that many would.
|
||||
*
|
||||
* @return The octets lent, 0 at the end of the file, or -1 when
|
||||
* nothing there can be lent; the caller then reads as before. Lent
|
||||
* octets stay valid until the next call on \c stream and must not be
|
||||
* written.
|
||||
*/
|
||||
int64_t filestream_view(RFILE *stream, int64_t len, const uint8_t **ptr);
|
||||
|
||||
typedef const uint8_t *(*filestream_mapped_ptr_cb_t)(void *hfile, int64_t *len);
|
||||
void filestream_set_mapped_ptr_cb(filestream_mapped_ptr_cb_t cb);
|
||||
|
||||
/**
|
||||
* Ask the OS to start reading [offset, offset + len) of \c stream into
|
||||
* memory ahead of a read or a touch of its mapping. A hint: it returns
|
||||
* at once and does nothing on a frontend-supplied VFS or where the
|
||||
* platform has no such request. See retro_vfs_file_prefetch_impl().
|
||||
*/
|
||||
void filestream_prefetch(RFILE *stream, uint64_t offset, uint64_t len);
|
||||
|
||||
/* An installed VFS that keeps local handles (vfs_hybrid) takes the hint
|
||||
* for them through this; without one the hint stops at filestream. */
|
||||
typedef void (*filestream_prefetch_cb_t)(void *hfile, uint64_t offset,
|
||||
uint64_t len);
|
||||
void filestream_set_prefetch_cb(filestream_prefetch_cb_t cb);
|
||||
|
||||
/**
|
||||
* Size of the window filestream_vscanf() reads and scans at a time,
|
||||
* i.e. the furthest a single conversion can reach.
|
||||
*
|
||||
* Named so a test can drive input either side of it rather than
|
||||
* assume a value, the way the compare window below is.
|
||||
*/
|
||||
#define FILESTREAM_SCANF_WINDOW 4096
|
||||
|
||||
/**
|
||||
* Size of the buffer filestream_matches_buf() compares through when
|
||||
* the file is not mapped.
|
||||
*
|
||||
* Exposed so a test can aim at the real boundary rather than a
|
||||
* remembered one - a boundary case pointed at the wrong offset is
|
||||
* just another mid-file check under a boundary's name.
|
||||
*
|
||||
* The ceiling is a stack budget, not a throughput one: this buffer is
|
||||
* a local, and the smallest thread stack in the tree is GEKKO's
|
||||
* 8 KiB (the GEKKO STACKSIZE in rthreads.c), with 32 KiB on 3DS and
|
||||
* 64 KiB on Vita. Larger reads are faster - at or above the VFS's
|
||||
* own 64 KiB stdio buffer they bypass it entirely - but the platforms
|
||||
* that take this path are the ones without memory mapping, which are
|
||||
* the same ones with those stacks.
|
||||
*/
|
||||
#define FILESTREAM_MATCHES_BUF_WINDOW 1024
|
||||
|
||||
/**
|
||||
* Does the file at \c path hold exactly \c len bytes equal to
|
||||
* \c data?
|
||||
*
|
||||
* For the common "has this changed since I last wrote it?" question,
|
||||
* where the answer decides whether to write at all. Callers used to
|
||||
* answer it by reading the whole file into a fresh allocation,
|
||||
* comparing, and freeing - paying a full-size malloc and a full-file
|
||||
* read even when a size mismatch made the answer free.
|
||||
*
|
||||
* This allocates nothing: it settles a size mismatch without reading,
|
||||
* compares in place against the memory map where the platform offers
|
||||
* one, falls back to a fixed window otherwise, and stops at the first
|
||||
* differing byte.
|
||||
*
|
||||
* @param path The file to compare against.
|
||||
* @param data The buffer to compare with. May be \c NULL only when
|
||||
* \c len is 0.
|
||||
* @param len The number of bytes in \c data.
|
||||
* @return \c true only if the file exists, is exactly \c len bytes,
|
||||
* and every byte matches. A missing or unreadable file is \c false,
|
||||
* not an error - the caller's next step is the same either way.
|
||||
*/
|
||||
bool filestream_matches_buf(const char *path, const void *data, size_t len);
|
||||
|
||||
/**
|
||||
* Determines if a file exists at the given path.
|
||||
*
|
||||
* @param path The path to check for existence.
|
||||
* @return \c true if a file exists at \c path,
|
||||
* \c false if not or if \c path is \c NULL or empty.
|
||||
*/
|
||||
bool filestream_exists(const char *path);
|
||||
|
||||
/**
|
||||
* Reads a line from the given file into a newly-allocated buffer.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @return Pointer to the line read from \c stream,
|
||||
* or \c NULL if there was an error.
|
||||
* Must be freed with \c free when no longer needed.
|
||||
*/
|
||||
char* filestream_getline(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Returns the open file handle
|
||||
* that was originally returned by the VFS interface.
|
||||
*
|
||||
* @param stream File handle returned by \c filestream_open.
|
||||
* @return The file handle returned by the underlying VFS implementation.
|
||||
*/
|
||||
libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream);
|
||||
|
||||
/**
|
||||
* filestream_punch_hole:
|
||||
* @stream: file handle.
|
||||
* @offset: byte offset to start deallocating at.
|
||||
* @len : number of bytes to deallocate.
|
||||
*
|
||||
* Deallocate a range within a file, leaving the file's length unchanged
|
||||
* and subsequent reads of the range returning zeroes. This is what makes a
|
||||
* sparse image sparse: a disk image that is mostly untouched occupies only
|
||||
* the blocks that were actually written.
|
||||
*
|
||||
* Not every backend can do this, and that is the normal case rather than
|
||||
* an error. It works when the stream came from libretro-common's own
|
||||
* implementation on a filesystem that supports it -- fallocate() with
|
||||
* FALLOC_FL_PUNCH_HOLE on Linux, FSCTL_SET_ZERO_DATA on Windows. It does
|
||||
* not work when the frontend supplied its own VFS, because a frontend
|
||||
* handle need not be a file at all and the interface exposes no
|
||||
* descriptor. Rather than extend the frontend ABI for one operation that
|
||||
* most backends cannot implement, this is a capability: callers ask, and
|
||||
* fall back to writing zeroes when the answer is no.
|
||||
*
|
||||
* Returns: 0 if the range was deallocated, -1 if the backend cannot do it
|
||||
* or the call failed. A caller that must guarantee zeroes still has to
|
||||
* write them when this returns -1; the only thing lost is the space
|
||||
* saving.
|
||||
*/
|
||||
int filestream_punch_hole(RFILE *stream, int64_t offset, int64_t len);
|
||||
|
||||
/**
|
||||
* filestream_get_sparse_granularity:
|
||||
* @stream: file handle.
|
||||
*
|
||||
* The allocation unit of the filesystem holding this file: the smallest
|
||||
* span that filestream_punch_hole can actually deallocate. Punching a
|
||||
* range shorter than this, or one not aligned to it, is accepted and
|
||||
* frees nothing, so a caller that wants sparse files has to work in
|
||||
* multiples of this value rather than assuming 4096.
|
||||
*
|
||||
* On Windows this is the cluster size on most filesystems, but on NTFS
|
||||
* it is the compression unit -- 16 clusters, capped at 64K -- because
|
||||
* that is the span NTFS actually deallocates. A 4K-cluster NTFS volume,
|
||||
* the common case, frees in 64K chunks, so punching a single cluster
|
||||
* frees nothing. On POSIX it is the block size the filesystem reports
|
||||
* for the file.
|
||||
*
|
||||
* Returns: the granularity in bytes, or 0 when it cannot be determined --
|
||||
* from a frontend-supplied VFS, for instance, which has no descriptor to
|
||||
* ask about. A caller that gets 0 should either skip hole punching or
|
||||
* pick a conservative unit; it must not treat 0 as "no alignment
|
||||
* needed".
|
||||
*/
|
||||
int64_t filestream_get_sparse_granularity(RFILE *stream);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rstrtod.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.
|
||||
*/
|
||||
|
||||
/* Decimal to binary floating point, correctly rounded.
|
||||
*
|
||||
* strtod() from the C library would do this, except that it reads the
|
||||
* decimal separator from the current locale: the same settings file
|
||||
* parses differently once someone's LC_NUMERIC says a comma. These
|
||||
* functions always take '.', never consult the locale, and never touch
|
||||
* errno, so a value written on one machine reads back the same on
|
||||
* another.
|
||||
*
|
||||
* The result is the nearest double (or float) to the exact decimal
|
||||
* value, ties resolved to even -- the same answer a correctly rounded
|
||||
* strtod() gives, for every input. Where the C library differs, the C
|
||||
* library is wrong.
|
||||
*
|
||||
* The caller passes a length rather than relying on a terminator, so a
|
||||
* number sitting inside a larger buffer needs no copy. On success the
|
||||
* end pointer reports how much was consumed, which lets a caller reject
|
||||
* trailing junk it did not expect.
|
||||
*
|
||||
* Accepted: an optional sign, decimal digits with an optional '.', an
|
||||
* optional 'e'/'E' exponent with optional sign, and the special forms
|
||||
* "inf", "infinity" and "nan" in any case. Values too large become
|
||||
* infinity and values too small become zero, both with the right sign;
|
||||
* neither is reported as an error, matching strtod().
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_STRING_RSTRTOD_H
|
||||
#define __LIBRETRO_SDK_STRING_RSTRTOD_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* rstrtod_len:
|
||||
* @str : bytes to read, need not be NUL-terminated
|
||||
* @len : length of @str
|
||||
* @end : filled with how many bytes were consumed
|
||||
*
|
||||
* Reads a decimal number, ignoring the locale. Leading whitespace is
|
||||
* skipped. @end may be NULL; when it is not, it receives 0 if nothing
|
||||
* could be read, in which case the return value is 0.0.
|
||||
*
|
||||
* Returns: the nearest double to the value read, ties to even.
|
||||
*/
|
||||
double rstrtod_len(const char *str, size_t len, size_t *end);
|
||||
|
||||
/**
|
||||
* rstrtof_len:
|
||||
* @str : bytes to read, need not be NUL-terminated
|
||||
* @len : length of @str
|
||||
* @end : filled with how many bytes were consumed
|
||||
*
|
||||
* As rstrtod_len(), rounding once to float rather than twice through
|
||||
* double -- a double rounding would be wrong for some inputs.
|
||||
*
|
||||
* Returns: the nearest float to the value read, ties to even.
|
||||
*/
|
||||
float rstrtof_len(const char *str, size_t len, size_t *end);
|
||||
|
||||
/**
|
||||
* rstrtod:
|
||||
* @str : NUL-terminated bytes to read
|
||||
* @end : filled with a pointer past what was consumed
|
||||
*
|
||||
* The strtod()-shaped spelling, for callers that have a C string.
|
||||
* @end may be NULL.
|
||||
*
|
||||
* Returns: the nearest double to the value read.
|
||||
*/
|
||||
double rstrtod(const char *str, char **end);
|
||||
|
||||
/**
|
||||
* rstrtof:
|
||||
* @str : NUL-terminated bytes to read
|
||||
* @end : filled with a pointer past what was consumed
|
||||
*
|
||||
* Returns: the nearest float to the value read.
|
||||
*/
|
||||
float rstrtof(const char *str, char **end);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,478 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (stdstring.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_STDSTRING_H
|
||||
#define __LIBRETRO_SDK_STDSTRING_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <boolean.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
#include <retro_inline.h>
|
||||
#include <compat/strl.h>
|
||||
#include <compat/posix_string.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
#define STRLEN_CONST(x) ((sizeof((x))-1))
|
||||
|
||||
#define string_is_not_equal(a, b) !string_is_equal((a), (b))
|
||||
|
||||
#define TOLOWER(c) ((c) | (lr_char_props[(unsigned char)(c)] & 0x20))
|
||||
#define TOUPPER(c) ((c) & ~(lr_char_props[(unsigned char)(c)] & 0x20))
|
||||
|
||||
/* C standard says \f \v are space, but this one disagrees */
|
||||
#define ISSPACE(c) (lr_char_props[(unsigned char)(c)] & 0x80)
|
||||
|
||||
#define ISDIGIT(c) (lr_char_props[(unsigned char)(c)] & 0x40)
|
||||
#define ISALPHA(c) (lr_char_props[(unsigned char)(c)] & 0x20)
|
||||
#define ISLOWER(c) (lr_char_props[(unsigned char)(c)] & 0x04)
|
||||
#define ISUPPER(c) (lr_char_props[(unsigned char)(c)] & 0x02)
|
||||
#define ISALNUM(c) (lr_char_props[(unsigned char)(c)] & 0x60)
|
||||
#define ISUALPHA(c) (lr_char_props[(unsigned char)(c)] & 0x28)
|
||||
#define ISUALNUM(c) (lr_char_props[(unsigned char)(c)] & 0x68)
|
||||
#define IS_XDIGIT(c) (lr_char_props[(unsigned char)(c)] & 0x01)
|
||||
|
||||
/* Deprecated alias, all callers should use string_is_equal_case_insensitive instead */
|
||||
#define string_is_equal_noncase string_is_equal_case_insensitive
|
||||
|
||||
static INLINE bool string_is_empty(const char *data)
|
||||
{
|
||||
return !data || !*data;
|
||||
}
|
||||
|
||||
static INLINE bool string_is_equal(const char *a, const char *b)
|
||||
{
|
||||
if (a == b)
|
||||
return true;
|
||||
if (!a || !b)
|
||||
return false;
|
||||
return !strcmp(a, b);
|
||||
}
|
||||
|
||||
static INLINE bool string_starts_with_size(const char *str, const char *prefix,
|
||||
size_t len)
|
||||
{
|
||||
return (str && prefix) ? !strncmp(prefix, str, len) : false;
|
||||
}
|
||||
|
||||
static INLINE bool string_starts_with(const char *str, const char *prefix)
|
||||
{
|
||||
return (str && prefix) ? !strncmp(prefix, str, strlen(prefix)) : false;
|
||||
}
|
||||
|
||||
static INLINE bool string_ends_with_size(const char *s, const char *suffix,
|
||||
size_t len, size_t suffix_len)
|
||||
{
|
||||
return (len < suffix_len) ? false :
|
||||
!memcmp(suffix, s + (len - suffix_len), suffix_len);
|
||||
}
|
||||
|
||||
static INLINE bool string_ends_with(const char *s, const char *suffix)
|
||||
{
|
||||
return s && suffix && string_ends_with_size(s, suffix, strlen(s), strlen(suffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* strlen_size:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* @return the length of 'str' (c.f. strlen()), but only
|
||||
* checks the first 'size' characters
|
||||
* - If 'str' is NULL, returns 0
|
||||
* - If 'str' is not NULL and no '\0' character is found
|
||||
* in the first 'size' characters, returns 'size'
|
||||
**/
|
||||
static INLINE size_t strlen_size(const char *str, size_t len)
|
||||
{
|
||||
size_t i = 0;
|
||||
if (str)
|
||||
while (i < len && str[i]) i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
static INLINE bool string_is_equal_case_insensitive(const char *a,
|
||||
const char *b)
|
||||
{
|
||||
int result = 0;
|
||||
const unsigned char *p1 = (const unsigned char*)a;
|
||||
const unsigned char *p2 = (const unsigned char*)b;
|
||||
|
||||
if (!a || !b)
|
||||
return false;
|
||||
if (p1 == p2)
|
||||
return true;
|
||||
|
||||
while ((result = tolower (*p1) - tolower (*p2++)) == 0)
|
||||
if (*p1++ == '\0')
|
||||
break;
|
||||
|
||||
return (result == 0);
|
||||
}
|
||||
|
||||
static INLINE bool string_starts_with_case_insensitive(const char *str,
|
||||
const char *prefix)
|
||||
{
|
||||
int result = 0;
|
||||
const unsigned char *p1 = (const unsigned char*)str;
|
||||
const unsigned char *p2 = (const unsigned char*)prefix;
|
||||
|
||||
if (!str || !prefix)
|
||||
return false;
|
||||
if (p1 == p2)
|
||||
return true;
|
||||
|
||||
while ((result = tolower (*p1++) - tolower (*p2)) == 0)
|
||||
if (*p2++ == '\0')
|
||||
break;
|
||||
|
||||
return (result == 0 || *p2 == '\0');
|
||||
}
|
||||
|
||||
char *string_to_upper(char *s);
|
||||
|
||||
char *string_to_lower(char *s);
|
||||
|
||||
char *string_ucwords(char *s);
|
||||
|
||||
char *string_replace_substring(
|
||||
const char *in, size_t in_len,
|
||||
const char *pattern, size_t pattern_len,
|
||||
const char *replacement, size_t replacement_len);
|
||||
|
||||
/**
|
||||
* string_trim_whitespace_left:
|
||||
*
|
||||
* Remove leading whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace_left(char *const s);
|
||||
|
||||
/**
|
||||
* string_trim_whitespace_right:
|
||||
*
|
||||
* Remove trailing whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace_right(char *const s);
|
||||
|
||||
/**
|
||||
* string_trim_whitespace:
|
||||
*
|
||||
* Remove leading and trailing whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace(char *const s);
|
||||
|
||||
/**
|
||||
* word_wrap:
|
||||
* @dst : pointer to destination buffer.
|
||||
* @dst_size : size of destination buffer.
|
||||
* @src : pointer to input string.
|
||||
* @src_len : length of @src
|
||||
* @line_width : max number of characters per line.
|
||||
* @wideglyph_width : not used, but is necessary to keep
|
||||
* compatibility with word_wrap_wideglyph().
|
||||
* @max_lines : max lines of destination string.
|
||||
* 0 means no limit.
|
||||
*
|
||||
* Wraps string specified by 'src' to destination buffer
|
||||
* specified by 'dst' and 'dst_size'.
|
||||
* This function assumes that all glyphs in the string
|
||||
* have an on-screen pixel width similar to that of
|
||||
* regular Latin characters - i.e. it will not wrap
|
||||
* correctly any text containing so-called 'wide' Unicode
|
||||
* characters (e.g. CJK languages, emojis, etc.).
|
||||
* @dst and @src must NOT overlap: the copy is performed
|
||||
* with strlcpy(), which has undefined behaviour for
|
||||
* overlapping buffers (and aborts via __chk_fail_overlap
|
||||
* with fortified libc on macOS).
|
||||
**/
|
||||
size_t word_wrap(char *dst, size_t dst_size, const char *src, size_t src_len,
|
||||
int line_width, int wideglyph_width, unsigned max_lines);
|
||||
|
||||
/**
|
||||
* word_wrap_wideglyph:
|
||||
* @dst : pointer to destination buffer.
|
||||
* @dst_size : size of destination buffer.
|
||||
* @src : pointer to input string.
|
||||
* @src_len : length of @src
|
||||
* @line_width : max number of characters per line.
|
||||
* @wideglyph_width : effective width of 'wide' Unicode glyphs.
|
||||
* the value here is normalised relative to the
|
||||
* typical on-screen pixel width of a regular
|
||||
* Latin character:
|
||||
* - a regular Latin character is defined to
|
||||
* have an effective width of 100
|
||||
* - wideglyph_width = 100 * (wide_character_pixel_width / latin_character_pixel_width)
|
||||
* - e.g. if 'wide' Unicode characters in 'src'
|
||||
* have an on-screen pixel width twice that of
|
||||
* regular Latin characters, wideglyph_width
|
||||
* would be 200
|
||||
* @max_lines : max lines of destination string.
|
||||
* 0 means no limit.
|
||||
*
|
||||
* Wraps string specified by @src to destination buffer
|
||||
* specified by @dst and @dst_size.
|
||||
* This function assumes that all glyphs in the string
|
||||
* are:
|
||||
* - EITHER 'non-wide' Unicode glyphs, with an on-screen
|
||||
* pixel width similar to that of regular Latin characters
|
||||
* - OR 'wide' Unicode glyphs (e.g. CJK languages, emojis, etc.)
|
||||
* with an on-screen pixel width defined by @wideglyph_width
|
||||
* Note that wrapping may occur in inappropriate locations
|
||||
* if @src string contains 'wide' Unicode characters whose
|
||||
* on-screen pixel width deviates greatly from the set
|
||||
* @wideglyph_width value.
|
||||
* @dst and @src must NOT overlap: the copy is performed
|
||||
* with strlcpy(), which has undefined behaviour for
|
||||
* overlapping buffers (and aborts via __chk_fail_overlap
|
||||
* with fortified libc on macOS).
|
||||
**/
|
||||
size_t word_wrap_wideglyph(
|
||||
char *dst, size_t dst_size,
|
||||
const char *src, size_t src_len,
|
||||
int line_width, int wideglyph_width,
|
||||
unsigned max_lines);
|
||||
|
||||
/**
|
||||
* string_tokenize:
|
||||
*
|
||||
* Splits string into tokens separated by @delim
|
||||
* > Returned token string must be free()'d
|
||||
* > Returns NULL if token is not found
|
||||
* > After each call, @str is set to the position after the
|
||||
* last found token
|
||||
* > Tokens *include* empty strings
|
||||
* Usage example:
|
||||
* char *str = "1,2,3,4,5,6,7,,,10,";
|
||||
* char **str_ptr = &str;
|
||||
* char *token = NULL;
|
||||
* while ((token = string_tokenize(str_ptr, ",")))
|
||||
* {
|
||||
* printf("%s\n", token);
|
||||
* free(token);
|
||||
* token = NULL;
|
||||
* }
|
||||
**/
|
||||
char *string_tokenize(char **str, const char *delim);
|
||||
|
||||
/**
|
||||
* string_remove_all_chars:
|
||||
* @s : input string (must be non-NULL, otherwise UB)
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Removes every instance of character @c from @s
|
||||
*
|
||||
* Returns the length of the resulting string.
|
||||
**/
|
||||
size_t string_remove_all_chars(char *s, char c);
|
||||
|
||||
/**
|
||||
* string_replace_all_chars:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
* @find : character to find
|
||||
* @replace : character to replace @find with
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strchr (in a loop)
|
||||
*
|
||||
* Replaces every instance of character @find in @str
|
||||
* with character @replace
|
||||
**/
|
||||
void string_replace_all_chars(char *str, char find, char replace);
|
||||
|
||||
/**
|
||||
* string_to_unsigned:
|
||||
* @str : input string
|
||||
*
|
||||
* Converts string to unsigned integer.
|
||||
*
|
||||
* @return 0 if string is invalid, otherwise > 0
|
||||
**/
|
||||
unsigned string_to_unsigned(const char *str);
|
||||
|
||||
/**
|
||||
* string_percent_decode:
|
||||
*
|
||||
* Decode %XX escapes from @src into @s. A '%' not followed by two
|
||||
* hex digits is copied as is; '+' is left alone. @s may equal @src.
|
||||
* Returns the decoded length, or -1 when @s is too small (@s holds
|
||||
* the truncated, terminated prefix).
|
||||
**/
|
||||
int string_percent_decode(char *s, size_t len, const char *src);
|
||||
|
||||
/**
|
||||
* string_hex_to_unsigned:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
*
|
||||
* Converts hexadecimal string to unsigned integer.
|
||||
* Handles optional leading '0x'.
|
||||
*
|
||||
* @return 0 if string is invalid, otherwise > 0
|
||||
**/
|
||||
unsigned string_hex_to_unsigned(const char *str);
|
||||
|
||||
/**
|
||||
* string_count_occurrences_single_character:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Get the total number of occurrences of character @c in @str.
|
||||
*
|
||||
* @return Total number of occurrences of character @c
|
||||
*/
|
||||
int string_count_occurrences_single_character(const char *str, char c);
|
||||
|
||||
/**
|
||||
* string_replace_whitespace_with_single_character:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Replaces all spaces with given character @c.
|
||||
**/
|
||||
void string_replace_whitespace_with_single_character(char *str, char c);
|
||||
|
||||
/**
|
||||
* string_replace_multi_space_with_single_space:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Replaces multiple spaces with a single space in a string.
|
||||
**/
|
||||
void string_replace_multi_space_with_single_space(char *str);
|
||||
|
||||
/**
|
||||
* string_remove_all_whitespace:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Remove all spaces from the given string.
|
||||
* Returns the length of the resulting string.
|
||||
**/
|
||||
size_t string_remove_all_whitespace(char *str_trimmed, const char *str);
|
||||
|
||||
/**
|
||||
* strlcpy_append:
|
||||
* @s : destination buffer
|
||||
* @len : total size of @s, including space for the NUL
|
||||
* @pos : in-out cursor. On entry, the offset within
|
||||
* @s where @src should be appended; on
|
||||
* successful return advanced past the appended
|
||||
* bytes. On truncation clamped to @len - 1 so
|
||||
* subsequent calls in a chain short-circuit.
|
||||
* @src : NUL-terminated source string to append
|
||||
*
|
||||
* Bound-checked replacement for the unsafe pattern
|
||||
*
|
||||
* *pos += strlcpy(@s + *pos, @src, @len - *pos);
|
||||
*
|
||||
* which is widely used to build strings piece-by-piece but is
|
||||
* NOT self-bounding -- strlcpy returns strlen(@src) regardless
|
||||
* of how much was actually written, so on truncation *pos
|
||||
* overshoots @len and the next @len - *pos subtraction underflows
|
||||
* size_t, corrupting subsequent appends.
|
||||
*
|
||||
* On success returns 0 and advances *pos by strlen(@src). On
|
||||
* truncation (the appended bytes plus a NUL would exceed @len)
|
||||
* returns -1, leaves @s NUL-terminated at @len - 1, and clamps
|
||||
* *pos to @len - 1 so a chain of strlcpy_append calls can
|
||||
* short-circuit cleanly -- the caller need only check the bitwise
|
||||
* OR of the chain's results once at the end.
|
||||
*
|
||||
* @return 0 on success, -1 on truncation or invalid arguments
|
||||
**/
|
||||
int strlcpy_append(char *s, size_t len, size_t *pos, const char *src);
|
||||
|
||||
/* Retrieve the last occurance of the given character in a string. */
|
||||
int string_index_last_occurance(const char *str, char c);
|
||||
|
||||
/**
|
||||
* string_find_index_substring_string:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
* @substr : substring to find in @str
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strstr
|
||||
*
|
||||
* Find the position of substring @substr in string @str.
|
||||
**/
|
||||
int string_find_index_substring_string(const char *str, const char *substr);
|
||||
|
||||
/**
|
||||
* string_copy_only_ascii:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Strips non-ASCII characters from a string.
|
||||
**/
|
||||
void string_copy_only_ascii(char *str_stripped, const char *str);
|
||||
|
||||
/**
|
||||
* string_ext_list_find:
|
||||
* @delim_str : '|'-delimited extension string (e.g. "bin|iso|cue")
|
||||
* @delim_len : length of @delim_str
|
||||
* @ext : single extension token to look for
|
||||
* @ext_len : length of @ext
|
||||
*
|
||||
* Checks whether @ext exists as an exact token inside @delim_str.
|
||||
* "bi" will not match "bin".
|
||||
*
|
||||
* Returns: true if found, false otherwise.
|
||||
**/
|
||||
bool string_ext_list_find(const char *delim_str, size_t delim_len,
|
||||
const char *ext, size_t ext_len);
|
||||
|
||||
/**
|
||||
* string_ext_list_append_dedup:
|
||||
* @dst : destination '|'-delimited string being built
|
||||
* @dst_len : pointer to current length of content in @dst (updated in place)
|
||||
* @dst_size : total buffer capacity of @dst
|
||||
* @ext : a single extension token (no '|') to append if not duplicate
|
||||
* @ext_len : length of @ext
|
||||
*
|
||||
* Appends a single extension to @dst with a '|' separator,
|
||||
* but only if @ext is not already present in @dst.
|
||||
**/
|
||||
void string_ext_list_append_dedup(char *dst, size_t *dst_len,
|
||||
size_t dst_size, const char *ext, size_t ext_len);
|
||||
|
||||
/**
|
||||
* string_ext_list_merge_dedup:
|
||||
* @dst : destination '|'-delimited string being built
|
||||
* @dst_len : pointer to current length of content in @dst (updated in place)
|
||||
* @dst_size : total buffer capacity of @dst
|
||||
* @src : source '|'-delimited extension string (may contain many tokens)
|
||||
*
|
||||
* Splits @src on '|' and appends each unique extension to @dst.
|
||||
* Extensions already present in @dst are skipped.
|
||||
**/
|
||||
void string_ext_list_merge_dedup(char *dst, size_t *dst_len,
|
||||
size_t dst_size, const char *src);
|
||||
|
||||
extern const unsigned char lr_char_props[256];
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
+111
-4
@@ -23,6 +23,7 @@
|
||||
#ifndef __LIBRETRO_SDK_VFS_H
|
||||
#define __LIBRETRO_SDK_VFS_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <retro_common_api.h>
|
||||
#include <boolean.h>
|
||||
#include <stdint.h>
|
||||
@@ -40,9 +41,12 @@ typedef void* HANDLE;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CDROM
|
||||
struct cdrom_toc;
|
||||
typedef struct
|
||||
{
|
||||
int64_t byte_pos;
|
||||
uint64_t disc_bytes; /* cooked 2048-byte image; zero for CD paths */
|
||||
struct cdrom_toc *toc;
|
||||
char *cue_buf;
|
||||
size_t cue_len;
|
||||
unsigned cur_lba;
|
||||
@@ -54,15 +58,89 @@ typedef struct
|
||||
unsigned char last_frame[2352];
|
||||
char drive;
|
||||
bool last_frame_valid;
|
||||
bool logical_blocks;
|
||||
bool device_only;
|
||||
bool media_changed; /* bound geometry is stale; reopen before reading */
|
||||
bool io_error;
|
||||
} vfs_cdrom_t;
|
||||
#endif
|
||||
|
||||
/* File mapping is available through POSIX mmap (HAVE_MMAP, chosen by
|
||||
* the build) or through Win32 file mappings (always available there).
|
||||
* Consumers gate on this umbrella so the two backends stay one code
|
||||
* path everywhere except the actual map/unmap calls. */
|
||||
#if defined(HAVE_MMAP) || (defined(_WIN32) && !defined(_XBOX))
|
||||
#define VFS_HAVE_FILE_MAPPING 1
|
||||
#endif
|
||||
|
||||
/* Descriptor-backed I/O: open()/read()/close() with no stdio layer in
|
||||
* between. The code for it is compiled on every target - it is the
|
||||
* path a file mapping falls back to - but until this existed it was
|
||||
* only ever *reached* where VFS_HAVE_FILE_MAPPING was defined, since
|
||||
* that was the sole gate on the hint that selects it. Everything
|
||||
* else was pinned to stdio no matter what the caller asked for.
|
||||
*
|
||||
* That matters most on the targets that were excluded. With newlib,
|
||||
* fread() on a buffered stream refills through the stream buffer and
|
||||
* memcpy()s each refill into the caller's buffer (see _fread_r), so a
|
||||
* whole-file read costs one read() per buffer-full plus a second copy
|
||||
* of every byte; glibc's fread() bypasses its buffer once the request
|
||||
* reaches a block, which is why this is invisible on desktop.
|
||||
*
|
||||
* Listed rather than assumed: the descriptor path compiles everywhere
|
||||
* but has only ever run on the mapping targets, so a platform joins
|
||||
* this list once its open()/read()/lseek() have been exercised. A
|
||||
* target that is not listed keeps the behaviour it has today. */
|
||||
#ifndef VFS_HAVE_DESCRIPTOR_IO
|
||||
#if defined(VFS_HAVE_FILE_MAPPING) \
|
||||
|| defined(VITA) \
|
||||
|| defined(_3DS) \
|
||||
|| (defined(WIIU) && defined(HAVE_WIIU_DESCRIPTOR_IO)) \
|
||||
|| defined(__SWITCH__) \
|
||||
|| defined(GEKKO) \
|
||||
|| defined(PSP) \
|
||||
|| defined(PS2)
|
||||
#define VFS_HAVE_DESCRIPTOR_IO 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Wii U takes the descriptor path only when built with
|
||||
* HAVE_WIIU_DESCRIPTOR_IO (Makefile.wiiu), until it has run on
|
||||
* hardware. Its SD reads land by DMA in whole cache lines, so a
|
||||
* whole-file read there allocates its buffer on a line and asks for
|
||||
* whole lines, which lets the read land in place in one FS call;
|
||||
* VFS_DESCRIPTOR_READ_ALIGN is that line. */
|
||||
#if defined(WIIU) && defined(HAVE_WIIU_DESCRIPTOR_IO) \
|
||||
&& !defined(VFS_DESCRIPTOR_READ_ALIGN)
|
||||
#define VFS_DESCRIPTOR_READ_ALIGN 64
|
||||
#endif
|
||||
|
||||
/* Descriptor-backed I/O for one-shot writers as well: a stream opened
|
||||
* for writing with RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK hands its
|
||||
* bytes straight to the descriptor, so a whole-file write is one open,
|
||||
* one write and one close. Same listing discipline as above: a
|
||||
* platform joins once its descriptor write path has been exercised.
|
||||
*
|
||||
* On Vita the descriptor path is sceIo* itself rather than newlib's
|
||||
* open()/write(), which sit on top of those calls and add a realpath
|
||||
* allocation, a directory stat, a strdup of the path into the fd
|
||||
* table and a mutex round-trip per call; stdio adds a FILE, a buffer
|
||||
* and an fstat on top of that. On an SD card every extra stat is a
|
||||
* directory lookup, so a bulk extraction pays for all of it per
|
||||
* member. */
|
||||
#ifndef VFS_HAVE_DESCRIPTOR_WRITE
|
||||
#if defined(VITA) || defined(GEKKO) || defined(PSP) || defined(PS2)
|
||||
#define VFS_HAVE_DESCRIPTOR_WRITE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
enum vfs_scheme
|
||||
{
|
||||
VFS_SCHEME_NONE = 0,
|
||||
VFS_SCHEME_CDROM,
|
||||
VFS_SCHEME_SAF,
|
||||
VFS_SCHEME_SMB
|
||||
VFS_SCHEME_SMB,
|
||||
VFS_SCHEME_NFS
|
||||
};
|
||||
|
||||
#if !(defined(__WINRT__) && defined(__cplusplus_winrt))
|
||||
@@ -72,15 +150,22 @@ struct retro_vfs_file_handle
|
||||
struct libretro_vfs_implementation_file
|
||||
#endif
|
||||
{
|
||||
#ifdef HAVE_CDROM
|
||||
vfs_cdrom_t cdrom; /* int64_t alignment */
|
||||
#endif
|
||||
int64_t size;
|
||||
uint64_t mappos;
|
||||
uint64_t mapsize;
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP)
|
||||
/* mapped read-ahead: where the next hint is due, the last read's end */
|
||||
uint64_t ra_next;
|
||||
uint64_t ra_last;
|
||||
#endif
|
||||
FILE *fp;
|
||||
#ifdef _WIN32
|
||||
HANDLE fh;
|
||||
/* CreateFileMapping handle backing `mapped`; NULL when unmapped */
|
||||
HANDLE map_handle;
|
||||
#if !defined(_XBOX) && !defined(HAVE_MMAP)
|
||||
void *warm; /* mapped read-ahead thread, NULL until used */
|
||||
#endif
|
||||
#endif
|
||||
char *buf;
|
||||
char* orig_path;
|
||||
@@ -88,9 +173,31 @@ struct libretro_vfs_implementation_file
|
||||
int fd;
|
||||
unsigned hints;
|
||||
enum vfs_scheme scheme;
|
||||
#ifdef HAVE_CDROM
|
||||
/* Allocated by retro_vfs_file_open_impl() only when the path
|
||||
* carries the cdrom:// scheme, and freed by
|
||||
* retro_vfs_file_close_impl(). It used to be an inline member at
|
||||
* the head of this struct, which is a poor trade: vfs_cdrom_t
|
||||
* embeds a 2352-byte sector cache, so every open file handle in a
|
||||
* build with HAVE_CDROM cost 2464 bytes instead of 72, and the
|
||||
* fields the read/seek paths actually touch (mappos, mapsize,
|
||||
* mapped, hints) were pushed past offset 2392 and split across two
|
||||
* cache lines instead of sharing one. Every consumer already gates
|
||||
* on 'scheme == VFS_SCHEME_CDROM' before reaching for it, so a
|
||||
* NULL here is unreachable on those paths. */
|
||||
vfs_cdrom_t *cdrom;
|
||||
#endif
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
intptr_t smb_fh;
|
||||
intptr_t smb_ctx;
|
||||
intptr_t smb_slot; /* pool slot held from open to close, 0 if private */
|
||||
intptr_t smb_prefetch; /* struct smb_prefetch *, read-only opens with threads */
|
||||
intptr_t smb_reopen; /* struct smb_reopen *: how to open it again */
|
||||
#endif
|
||||
#ifdef HAVE_NFSCLIENT
|
||||
intptr_t nfs_fh;
|
||||
intptr_t nfs_ctx;
|
||||
intptr_t nfs_prefetch; /* struct nfs_prefetch *, read-only opens with threads */
|
||||
#endif
|
||||
#if defined(HAVE_CDROM) && defined(__APPLE__)
|
||||
void *iokit_plugin; /* IOCFPlugInInterface ** */
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Copyright (C) 2026 The RetroArch team
|
||||
*
|
||||
* vfs_hybrid.h: local-first VFS dispatch with frontend fallback.
|
||||
*
|
||||
* Resolves the standing tension for need_fullpath cores: sandboxed
|
||||
* platforms (Android scoped storage) can only reach some content
|
||||
* through the frontend VFS, but frontend handles are opaque (no fd,
|
||||
* no mmap) and wholesale filestream_vfs_init() taxes every read on
|
||||
* platforms that never needed it and disables local mappings.
|
||||
*
|
||||
* The hybrid dispatches per FILE: local implementation first (real
|
||||
* fd, filestream_get_mapped_ptr keeps working), frontend only for
|
||||
* URI-shaped paths or, on sandboxed platforms, after a local
|
||||
* failure. Installed only when the frontend provides a VFS at all.
|
||||
*
|
||||
* Adoption is one call at retro_set_environment:
|
||||
* vfs_hybrid_init(environ_cb, log_cb);
|
||||
*/
|
||||
#ifndef __LIBRETRO_SDK_VFS_HYBRID_H
|
||||
#define __LIBRETRO_SDK_VFS_HYBRID_H
|
||||
|
||||
#include <libretro.h>
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
void vfs_hybrid_init(retro_environment_t env_cb, retro_log_printf_t log);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -33,6 +33,16 @@ RETRO_BEGIN_DECLS
|
||||
|
||||
libretro_vfs_implementation_file *retro_vfs_file_open_impl(const char *path, unsigned mode, unsigned hints);
|
||||
|
||||
/* Deallocate a range of a file without changing its length; see
|
||||
* filestream_punch_hole. Returns 0 on success, -1 when the platform or
|
||||
* filesystem cannot do it. */
|
||||
int retro_vfs_file_punch_hole_impl(libretro_vfs_implementation_file *stream, int64_t offset, int64_t len);
|
||||
|
||||
/* Allocation unit of the filesystem holding this file; see
|
||||
* filestream_get_sparse_granularity. 0 when unknown. */
|
||||
int64_t retro_vfs_file_get_sparse_granularity_impl(libretro_vfs_implementation_file *stream);
|
||||
|
||||
/* Consumes stream even when reporting final I/O failure; do not retry close. */
|
||||
int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream);
|
||||
|
||||
int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream);
|
||||
@@ -57,12 +67,86 @@ 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);
|
||||
|
||||
/* Borrowed pointer to the whole file, when this stream is backed by a
|
||||
* memory map, else NULL. Valid until the stream is closed; read-only;
|
||||
* never freed by the caller.
|
||||
*
|
||||
* RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS already maps the file,
|
||||
* but the mapping was reachable only from inside the read path, which
|
||||
* memcpy's out of it - so every consumer of the hint paid for a copy
|
||||
* of data that was already addressable, and the point of asking for
|
||||
* the map was lost. This hands the map itself to callers that want
|
||||
* to look rather than own: comparing a file against a buffer, hashing
|
||||
* it, or passing it to a parser that takes a base pointer.
|
||||
*
|
||||
* NULL is a normal answer, not an error - no HAVE_MMAP, hint not
|
||||
* given, a write mode, a scheme with no mapping, or a frontend-
|
||||
* supplied VFS - so callers need the copying path anyway and should
|
||||
* treat this as a fast lane, never as the only one. */
|
||||
const uint8_t *retro_vfs_file_get_mapped_ptr_impl(
|
||||
libretro_vfs_implementation_file *stream, int64_t *len);
|
||||
|
||||
/**
|
||||
* retro_vfs_file_view_impl:
|
||||
*
|
||||
* retro_vfs_file_read_impl() without the copy: points @ptr at up to
|
||||
* @len octets at the position, in the file's mapping or in a network
|
||||
* stream's read-ahead window, and moves past them. They stay valid
|
||||
* until the next call on @stream. Returns the octets lent, 0 at the
|
||||
* end of the file, or -1 when nothing there can be lent; the caller
|
||||
* then reads.
|
||||
**/
|
||||
int64_t retro_vfs_file_view_impl(libretro_vfs_implementation_file *stream,
|
||||
uint64_t len, const uint8_t **ptr);
|
||||
|
||||
/**
|
||||
* retro_vfs_file_prefetch_impl:
|
||||
* @offset : first byte of the range about to be read
|
||||
* @len : bytes of it
|
||||
*
|
||||
* Asks the OS to start bringing the range into memory, so a read or a
|
||||
* touch of the mapping that follows finds it resident instead of
|
||||
* stalling on the disk. Returns at once: madvise(MADV_WILLNEED) on a
|
||||
* mapped file, posix_fadvise(WILLNEED) on a descriptor; on Windows the
|
||||
* range is handed to the file's read-ahead thread, which calls
|
||||
* PrefetchVirtualMemory() (or touches the pages before Windows 8), so
|
||||
* the caller never waits on the disk. A no-op where none of these
|
||||
* exists, on a scheme that is not a local file, or when the OS
|
||||
* declines. A hint, never an error. */
|
||||
void retro_vfs_file_prefetch_impl(
|
||||
libretro_vfs_implementation_file *stream, uint64_t offset,
|
||||
uint64_t len);
|
||||
|
||||
int retro_vfs_stat_impl(const char *path, int32_t *size);
|
||||
|
||||
int retro_vfs_stat_64_impl(const char *path, int64_t *size);
|
||||
|
||||
int retro_vfs_mkdir_impl(const char *dir);
|
||||
|
||||
int retro_vfs_rmdir_impl(const char *dir);
|
||||
|
||||
/**
|
||||
* retro_vfs_restrict_permissions_impl:
|
||||
* @path : file to restrict
|
||||
*
|
||||
* Makes @path readable and writable by the owning user only, for
|
||||
* files that hold secrets. On platforms without per-user file
|
||||
* permissions (Windows profile directories, single-user consoles,
|
||||
* Android SAF) there is nothing to do and the call succeeds.
|
||||
*
|
||||
* Returns: 0 on success, -1 on failure.
|
||||
**/
|
||||
int retro_vfs_restrict_permissions_impl(const char *path);
|
||||
|
||||
/* VFS API v5 */
|
||||
int retro_vfs_set_readonly_impl(const char *path, int readonly);
|
||||
int retro_vfs_get_mtime_impl(const char *path, int64_t *mtime);
|
||||
int retro_vfs_set_mtime_impl(const char *path, int64_t mtime);
|
||||
struct retro_vfs_copy_handle *retro_vfs_copy_begin_impl(const char *src, const char *dst, unsigned flags);
|
||||
int retro_vfs_copy_step_impl(struct retro_vfs_copy_handle *handle, int64_t max_bytes, int64_t *bytes_done, int64_t *bytes_total);
|
||||
int retro_vfs_copy_close_impl(struct retro_vfs_copy_handle *handle);
|
||||
int retro_vfs_dirent_stat_impl(libretro_vfs_implementation_dir *rdir, int64_t *size, int64_t *mtime);
|
||||
|
||||
libretro_vfs_implementation_dir *retro_vfs_opendir_impl(const char *dir, bool include_hidden);
|
||||
|
||||
bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *dirstream);
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (vfs_implementation_saf.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_SAF_H
|
||||
#define __LIBRETRO_SDK_VFS_IMPLEMENTATION_SAF_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <jni.h>
|
||||
#include <libretro.h>
|
||||
#include <retro_environment.h>
|
||||
#include <vfs/vfs.h>
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct libretro_vfs_implementation_saf_dir
|
||||
{
|
||||
jobject directory_object;
|
||||
jstring dirent_name_object;
|
||||
const char *dirent_name;
|
||||
bool dirent_is_dir;
|
||||
} libretro_vfs_implementation_saf_dir;
|
||||
|
||||
struct libretro_vfs_implementation_saf_path_split_result
|
||||
{
|
||||
char *tree;
|
||||
char *path;
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize this VFS backend. This must be called prior to using any of the VFS operations provided by this backend.
|
||||
* get_jni_env should be a function that returns the JNIEnv for the current thread.
|
||||
* activity_object should be the current Android android.app.Activity.
|
||||
*/
|
||||
bool retro_vfs_init_saf(JNIEnv *(*get_jni_env)(void), jobject activity_object);
|
||||
|
||||
/*
|
||||
* Deinitialize this VFS backend.
|
||||
*/
|
||||
bool retro_vfs_deinit_saf(void);
|
||||
|
||||
/*
|
||||
* Split a serialized "saf://" path string into tree and path components for use with this backend.
|
||||
* Returns true if successful or false if not.
|
||||
* The results will be returned in `out` and must be freed by the caller.
|
||||
*/
|
||||
bool retro_vfs_path_split_saf(struct libretro_vfs_implementation_saf_path_split_result *out, const char *serialized_path);
|
||||
|
||||
/*
|
||||
* Join the tree and path components into a single serialized "saf://" path string.
|
||||
* Returns the serialized path if successful or NULL if not.
|
||||
* The returned path must be freed by the caller.
|
||||
*/
|
||||
char *retro_vfs_path_join_saf(const char *tree, const char *path);
|
||||
|
||||
/*
|
||||
* Split a "content://" document URI returned by the Android Storage Access Framework into tree and path components for use with this backend.
|
||||
* Returns true if successful or false if not.
|
||||
* The results will be returned in `out` and must be freed by the caller.
|
||||
*/
|
||||
bool retro_vfs_path_split_content_saf(struct libretro_vfs_implementation_saf_path_split_result *out, const char *content_uri);
|
||||
|
||||
/*
|
||||
* Open a file, returning its file descriptor if successful or -1 if not.
|
||||
* The file descriptor can be operated on using the POSIX file system API (`read()`, `write()`, `lseek()`, `close()`, etc).
|
||||
* You can also turn the file descriptor into a `FILE *` by calling `fdopen()` on it.
|
||||
*/
|
||||
int retro_vfs_file_open_saf(const char *tree, const char *path, unsigned mode);
|
||||
|
||||
int retro_vfs_file_remove_saf(const char *tree, const char *path);
|
||||
|
||||
int retro_vfs_file_rename_saf(const char *old_tree, const char *old_path, const char *new_tree, const char *new_path);
|
||||
|
||||
int retro_vfs_stat_saf(const char *tree, const char *path, int64_t *size);
|
||||
|
||||
int retro_vfs_mkdir_saf(const char *tree, const char *dir);
|
||||
|
||||
libretro_vfs_implementation_saf_dir *retro_vfs_opendir_saf(const char *tree, const char *dir, bool include_hidden);
|
||||
|
||||
bool retro_vfs_readdir_saf(libretro_vfs_implementation_saf_dir *dirstream);
|
||||
|
||||
const char *retro_vfs_dirent_get_name_saf(libretro_vfs_implementation_saf_dir *dirstream);
|
||||
|
||||
bool retro_vfs_dirent_is_dir_saf(libretro_vfs_implementation_saf_dir *dirstream);
|
||||
|
||||
int retro_vfs_closedir_saf(libretro_vfs_implementation_saf_dir *dirstream);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
+1550
File diff suppressed because it is too large.
Load diff
+2526
File diff suppressed because it is too large.
Load diff
+1238
File diff suppressed because it is too large.
Load diff
+654
@@ -0,0 +1,654 @@
|
||||
/* generated: 128-bit truncated powers of five, 5^-342 .. 5^308 */
|
||||
static const uint64_t rstrtod_pow5[651][2] = {
|
||||
{ RSTRTOD_U64(0xEEF453D6923BD65A), RSTRTOD_U64(0x113FAA2906A13B40) },
|
||||
{ RSTRTOD_U64(0x9558B4661B6565F8), RSTRTOD_U64(0x4AC7CA59A424C508) },
|
||||
{ RSTRTOD_U64(0xBAAEE17FA23EBF76), RSTRTOD_U64(0x5D79BCF00D2DF64A) },
|
||||
{ RSTRTOD_U64(0xE95A99DF8ACE6F53), RSTRTOD_U64(0xF4D82C2C107973DD) },
|
||||
{ RSTRTOD_U64(0x91D8A02BB6C10594), RSTRTOD_U64(0x79071B9B8A4BE86A) },
|
||||
{ RSTRTOD_U64(0xB64EC836A47146F9), RSTRTOD_U64(0x9748E2826CDEE285) },
|
||||
{ RSTRTOD_U64(0xE3E27A444D8D98B7), RSTRTOD_U64(0xFD1B1B2308169B26) },
|
||||
{ RSTRTOD_U64(0x8E6D8C6AB0787F72), RSTRTOD_U64(0xFE30F0F5E50E20F8) },
|
||||
{ RSTRTOD_U64(0xB208EF855C969F4F), RSTRTOD_U64(0xBDBD2D335E51A936) },
|
||||
{ RSTRTOD_U64(0xDE8B2B66B3BC4723), RSTRTOD_U64(0xAD2C788035E61383) },
|
||||
{ RSTRTOD_U64(0x8B16FB203055AC76), RSTRTOD_U64(0x4C3BCB5021AFCC32) },
|
||||
{ RSTRTOD_U64(0xADDCB9E83C6B1793), RSTRTOD_U64(0xDF4ABE242A1BBF3E) },
|
||||
{ RSTRTOD_U64(0xD953E8624B85DD78), RSTRTOD_U64(0xD71D6DAD34A2AF0E) },
|
||||
{ RSTRTOD_U64(0x87D4713D6F33AA6B), RSTRTOD_U64(0x8672648C40E5AD69) },
|
||||
{ RSTRTOD_U64(0xA9C98D8CCB009506), RSTRTOD_U64(0x680EFDAF511F18C3) },
|
||||
{ RSTRTOD_U64(0xD43BF0EFFDC0BA48), RSTRTOD_U64(0x0212BD1B2566DEF3) },
|
||||
{ RSTRTOD_U64(0x84A57695FE98746D), RSTRTOD_U64(0x014BB630F7604B58) },
|
||||
{ RSTRTOD_U64(0xA5CED43B7E3E9188), RSTRTOD_U64(0x419EA3BD35385E2E) },
|
||||
{ RSTRTOD_U64(0xCF42894A5DCE35EA), RSTRTOD_U64(0x52064CAC828675BA) },
|
||||
{ RSTRTOD_U64(0x818995CE7AA0E1B2), RSTRTOD_U64(0x7343EFEBD1940994) },
|
||||
{ RSTRTOD_U64(0xA1EBFB4219491A1F), RSTRTOD_U64(0x1014EBE6C5F90BF9) },
|
||||
{ RSTRTOD_U64(0xCA66FA129F9B60A6), RSTRTOD_U64(0xD41A26E077774EF7) },
|
||||
{ RSTRTOD_U64(0xFD00B897478238D0), RSTRTOD_U64(0x8920B098955522B5) },
|
||||
{ RSTRTOD_U64(0x9E20735E8CB16382), RSTRTOD_U64(0x55B46E5F5D5535B1) },
|
||||
{ RSTRTOD_U64(0xC5A890362FDDBC62), RSTRTOD_U64(0xEB2189F734AA831E) },
|
||||
{ RSTRTOD_U64(0xF712B443BBD52B7B), RSTRTOD_U64(0xA5E9EC7501D523E5) },
|
||||
{ RSTRTOD_U64(0x9A6BB0AA55653B2D), RSTRTOD_U64(0x47B233C92125366F) },
|
||||
{ RSTRTOD_U64(0xC1069CD4EABE89F8), RSTRTOD_U64(0x999EC0BB696E840B) },
|
||||
{ RSTRTOD_U64(0xF148440A256E2C76), RSTRTOD_U64(0xC00670EA43CA250E) },
|
||||
{ RSTRTOD_U64(0x96CD2A865764DBCA), RSTRTOD_U64(0x380406926A5E5729) },
|
||||
{ RSTRTOD_U64(0xBC807527ED3E12BC), RSTRTOD_U64(0xC605083704F5ECF3) },
|
||||
{ RSTRTOD_U64(0xEBA09271E88D976B), RSTRTOD_U64(0xF7864A44C633682F) },
|
||||
{ RSTRTOD_U64(0x93445B8731587EA3), RSTRTOD_U64(0x7AB3EE6AFBE0211E) },
|
||||
{ RSTRTOD_U64(0xB8157268FDAE9E4C), RSTRTOD_U64(0x5960EA05BAD82965) },
|
||||
{ RSTRTOD_U64(0xE61ACF033D1A45DF), RSTRTOD_U64(0x6FB92487298E33BE) },
|
||||
{ RSTRTOD_U64(0x8FD0C16206306BAB), RSTRTOD_U64(0xA5D3B6D479F8E057) },
|
||||
{ RSTRTOD_U64(0xB3C4F1BA87BC8696), RSTRTOD_U64(0x8F48A4899877186D) },
|
||||
{ RSTRTOD_U64(0xE0B62E2929ABA83C), RSTRTOD_U64(0x331ACDABFE94DE88) },
|
||||
{ RSTRTOD_U64(0x8C71DCD9BA0B4925), RSTRTOD_U64(0x9FF0C08B7F1D0B15) },
|
||||
{ RSTRTOD_U64(0xAF8E5410288E1B6F), RSTRTOD_U64(0x07ECF0AE5EE44DDA) },
|
||||
{ RSTRTOD_U64(0xDB71E91432B1A24A), RSTRTOD_U64(0xC9E82CD9F69D6151) },
|
||||
{ RSTRTOD_U64(0x892731AC9FAF056E), RSTRTOD_U64(0xBE311C083A225CD3) },
|
||||
{ RSTRTOD_U64(0xAB70FE17C79AC6CA), RSTRTOD_U64(0x6DBD630A48AAF407) },
|
||||
{ RSTRTOD_U64(0xD64D3D9DB981787D), RSTRTOD_U64(0x092CBBCCDAD5B109) },
|
||||
{ RSTRTOD_U64(0x85F0468293F0EB4E), RSTRTOD_U64(0x25BBF56008C58EA6) },
|
||||
{ RSTRTOD_U64(0xA76C582338ED2621), RSTRTOD_U64(0xAF2AF2B80AF6F24F) },
|
||||
{ RSTRTOD_U64(0xD1476E2C07286FAA), RSTRTOD_U64(0x1AF5AF660DB4AEE2) },
|
||||
{ RSTRTOD_U64(0x82CCA4DB847945CA), RSTRTOD_U64(0x50D98D9FC890ED4E) },
|
||||
{ RSTRTOD_U64(0xA37FCE126597973C), RSTRTOD_U64(0xE50FF107BAB528A1) },
|
||||
{ RSTRTOD_U64(0xCC5FC196FEFD7D0C), RSTRTOD_U64(0x1E53ED49A96272C9) },
|
||||
{ RSTRTOD_U64(0xFF77B1FCBEBCDC4F), RSTRTOD_U64(0x25E8E89C13BB0F7B) },
|
||||
{ RSTRTOD_U64(0x9FAACF3DF73609B1), RSTRTOD_U64(0x77B191618C54E9AD) },
|
||||
{ RSTRTOD_U64(0xC795830D75038C1D), RSTRTOD_U64(0xD59DF5B9EF6A2418) },
|
||||
{ RSTRTOD_U64(0xF97AE3D0D2446F25), RSTRTOD_U64(0x4B0573286B44AD1E) },
|
||||
{ RSTRTOD_U64(0x9BECCE62836AC577), RSTRTOD_U64(0x4EE367F9430AEC33) },
|
||||
{ RSTRTOD_U64(0xC2E801FB244576D5), RSTRTOD_U64(0x229C41F793CDA740) },
|
||||
{ RSTRTOD_U64(0xF3A20279ED56D48A), RSTRTOD_U64(0x6B43527578C11110) },
|
||||
{ RSTRTOD_U64(0x9845418C345644D6), RSTRTOD_U64(0x830A13896B78AAAA) },
|
||||
{ RSTRTOD_U64(0xBE5691EF416BD60C), RSTRTOD_U64(0x23CC986BC656D554) },
|
||||
{ RSTRTOD_U64(0xEDEC366B11C6CB8F), RSTRTOD_U64(0x2CBFBE86B7EC8AA9) },
|
||||
{ RSTRTOD_U64(0x94B3A202EB1C3F39), RSTRTOD_U64(0x7BF7D71432F3D6AA) },
|
||||
{ RSTRTOD_U64(0xB9E08A83A5E34F07), RSTRTOD_U64(0xDAF5CCD93FB0CC54) },
|
||||
{ RSTRTOD_U64(0xE858AD248F5C22C9), RSTRTOD_U64(0xD1B3400F8F9CFF69) },
|
||||
{ RSTRTOD_U64(0x91376C36D99995BE), RSTRTOD_U64(0x23100809B9C21FA2) },
|
||||
{ RSTRTOD_U64(0xB58547448FFFFB2D), RSTRTOD_U64(0xABD40A0C2832A78B) },
|
||||
{ RSTRTOD_U64(0xE2E69915B3FFF9F9), RSTRTOD_U64(0x16C90C8F323F516D) },
|
||||
{ RSTRTOD_U64(0x8DD01FAD907FFC3B), RSTRTOD_U64(0xAE3DA7D97F6792E4) },
|
||||
{ RSTRTOD_U64(0xB1442798F49FFB4A), RSTRTOD_U64(0x99CD11CFDF41779D) },
|
||||
{ RSTRTOD_U64(0xDD95317F31C7FA1D), RSTRTOD_U64(0x40405643D711D584) },
|
||||
{ RSTRTOD_U64(0x8A7D3EEF7F1CFC52), RSTRTOD_U64(0x482835EA666B2573) },
|
||||
{ RSTRTOD_U64(0xAD1C8EAB5EE43B66), RSTRTOD_U64(0xDA3243650005EED0) },
|
||||
{ RSTRTOD_U64(0xD863B256369D4A40), RSTRTOD_U64(0x90BED43E40076A83) },
|
||||
{ RSTRTOD_U64(0x873E4F75E2224E68), RSTRTOD_U64(0x5A7744A6E804A292) },
|
||||
{ RSTRTOD_U64(0xA90DE3535AAAE202), RSTRTOD_U64(0x711515D0A205CB37) },
|
||||
{ RSTRTOD_U64(0xD3515C2831559A83), RSTRTOD_U64(0x0D5A5B44CA873E04) },
|
||||
{ RSTRTOD_U64(0x8412D9991ED58091), RSTRTOD_U64(0xE858790AFE9486C3) },
|
||||
{ RSTRTOD_U64(0xA5178FFF668AE0B6), RSTRTOD_U64(0x626E974DBE39A873) },
|
||||
{ RSTRTOD_U64(0xCE5D73FF402D98E3), RSTRTOD_U64(0xFB0A3D212DC81290) },
|
||||
{ RSTRTOD_U64(0x80FA687F881C7F8E), RSTRTOD_U64(0x7CE66634BC9D0B9A) },
|
||||
{ RSTRTOD_U64(0xA139029F6A239F72), RSTRTOD_U64(0x1C1FFFC1EBC44E81) },
|
||||
{ RSTRTOD_U64(0xC987434744AC874E), RSTRTOD_U64(0xA327FFB266B56221) },
|
||||
{ RSTRTOD_U64(0xFBE9141915D7A922), RSTRTOD_U64(0x4BF1FF9F0062BAA9) },
|
||||
{ RSTRTOD_U64(0x9D71AC8FADA6C9B5), RSTRTOD_U64(0x6F773FC3603DB4AA) },
|
||||
{ RSTRTOD_U64(0xC4CE17B399107C22), RSTRTOD_U64(0xCB550FB4384D21D4) },
|
||||
{ RSTRTOD_U64(0xF6019DA07F549B2B), RSTRTOD_U64(0x7E2A53A146606A49) },
|
||||
{ RSTRTOD_U64(0x99C102844F94E0FB), RSTRTOD_U64(0x2EDA7444CBFC426E) },
|
||||
{ RSTRTOD_U64(0xC0314325637A1939), RSTRTOD_U64(0xFA911155FEFB5309) },
|
||||
{ RSTRTOD_U64(0xF03D93EEBC589F88), RSTRTOD_U64(0x793555AB7EBA27CB) },
|
||||
{ RSTRTOD_U64(0x96267C7535B763B5), RSTRTOD_U64(0x4BC1558B2F3458DF) },
|
||||
{ RSTRTOD_U64(0xBBB01B9283253CA2), RSTRTOD_U64(0x9EB1AAEDFB016F17) },
|
||||
{ RSTRTOD_U64(0xEA9C227723EE8BCB), RSTRTOD_U64(0x465E15A979C1CADD) },
|
||||
{ RSTRTOD_U64(0x92A1958A7675175F), RSTRTOD_U64(0x0BFACD89EC191ECA) },
|
||||
{ RSTRTOD_U64(0xB749FAED14125D36), RSTRTOD_U64(0xCEF980EC671F667C) },
|
||||
{ RSTRTOD_U64(0xE51C79A85916F484), RSTRTOD_U64(0x82B7E12780E7401B) },
|
||||
{ RSTRTOD_U64(0x8F31CC0937AE58D2), RSTRTOD_U64(0xD1B2ECB8B0908811) },
|
||||
{ RSTRTOD_U64(0xB2FE3F0B8599EF07), RSTRTOD_U64(0x861FA7E6DCB4AA16) },
|
||||
{ RSTRTOD_U64(0xDFBDCECE67006AC9), RSTRTOD_U64(0x67A791E093E1D49B) },
|
||||
{ RSTRTOD_U64(0x8BD6A141006042BD), RSTRTOD_U64(0xE0C8BB2C5C6D24E1) },
|
||||
{ RSTRTOD_U64(0xAECC49914078536D), RSTRTOD_U64(0x58FAE9F773886E19) },
|
||||
{ RSTRTOD_U64(0xDA7F5BF590966848), RSTRTOD_U64(0xAF39A475506A899F) },
|
||||
{ RSTRTOD_U64(0x888F99797A5E012D), RSTRTOD_U64(0x6D8406C952429604) },
|
||||
{ RSTRTOD_U64(0xAAB37FD7D8F58178), RSTRTOD_U64(0xC8E5087BA6D33B84) },
|
||||
{ RSTRTOD_U64(0xD5605FCDCF32E1D6), RSTRTOD_U64(0xFB1E4A9A90880A65) },
|
||||
{ RSTRTOD_U64(0x855C3BE0A17FCD26), RSTRTOD_U64(0x5CF2EEA09A550680) },
|
||||
{ RSTRTOD_U64(0xA6B34AD8C9DFC06F), RSTRTOD_U64(0xF42FAA48C0EA481F) },
|
||||
{ RSTRTOD_U64(0xD0601D8EFC57B08B), RSTRTOD_U64(0xF13B94DAF124DA27) },
|
||||
{ RSTRTOD_U64(0x823C12795DB6CE57), RSTRTOD_U64(0x76C53D08D6B70859) },
|
||||
{ RSTRTOD_U64(0xA2CB1717B52481ED), RSTRTOD_U64(0x54768C4B0C64CA6F) },
|
||||
{ RSTRTOD_U64(0xCB7DDCDDA26DA268), RSTRTOD_U64(0xA9942F5DCF7DFD0A) },
|
||||
{ RSTRTOD_U64(0xFE5D54150B090B02), RSTRTOD_U64(0xD3F93B35435D7C4D) },
|
||||
{ RSTRTOD_U64(0x9EFA548D26E5A6E1), RSTRTOD_U64(0xC47BC5014A1A6DB0) },
|
||||
{ RSTRTOD_U64(0xC6B8E9B0709F109A), RSTRTOD_U64(0x359AB6419CA1091C) },
|
||||
{ RSTRTOD_U64(0xF867241C8CC6D4C0), RSTRTOD_U64(0xC30163D203C94B63) },
|
||||
{ RSTRTOD_U64(0x9B407691D7FC44F8), RSTRTOD_U64(0x79E0DE63425DCF1E) },
|
||||
{ RSTRTOD_U64(0xC21094364DFB5636), RSTRTOD_U64(0x985915FC12F542E5) },
|
||||
{ RSTRTOD_U64(0xF294B943E17A2BC4), RSTRTOD_U64(0x3E6F5B7B17B2939E) },
|
||||
{ RSTRTOD_U64(0x979CF3CA6CEC5B5A), RSTRTOD_U64(0xA705992CEECF9C43) },
|
||||
{ RSTRTOD_U64(0xBD8430BD08277231), RSTRTOD_U64(0x50C6FF782A838354) },
|
||||
{ RSTRTOD_U64(0xECE53CEC4A314EBD), RSTRTOD_U64(0xA4F8BF5635246429) },
|
||||
{ RSTRTOD_U64(0x940F4613AE5ED136), RSTRTOD_U64(0x871B7795E136BE9A) },
|
||||
{ RSTRTOD_U64(0xB913179899F68584), RSTRTOD_U64(0x28E2557B59846E40) },
|
||||
{ RSTRTOD_U64(0xE757DD7EC07426E5), RSTRTOD_U64(0x331AEADA2FE589D0) },
|
||||
{ RSTRTOD_U64(0x9096EA6F3848984F), RSTRTOD_U64(0x3FF0D2C85DEF7622) },
|
||||
{ RSTRTOD_U64(0xB4BCA50B065ABE63), RSTRTOD_U64(0x0FED077A756B53AA) },
|
||||
{ RSTRTOD_U64(0xE1EBCE4DC7F16DFB), RSTRTOD_U64(0xD3E8495912C62895) },
|
||||
{ RSTRTOD_U64(0x8D3360F09CF6E4BD), RSTRTOD_U64(0x64712DD7ABBBD95D) },
|
||||
{ RSTRTOD_U64(0xB080392CC4349DEC), RSTRTOD_U64(0xBD8D794D96AACFB4) },
|
||||
{ RSTRTOD_U64(0xDCA04777F541C567), RSTRTOD_U64(0xECF0D7A0FC5583A1) },
|
||||
{ RSTRTOD_U64(0x89E42CAAF9491B60), RSTRTOD_U64(0xF41686C49DB57245) },
|
||||
{ RSTRTOD_U64(0xAC5D37D5B79B6239), RSTRTOD_U64(0x311C2875C522CED6) },
|
||||
{ RSTRTOD_U64(0xD77485CB25823AC7), RSTRTOD_U64(0x7D633293366B828C) },
|
||||
{ RSTRTOD_U64(0x86A8D39EF77164BC), RSTRTOD_U64(0xAE5DFF9C02033198) },
|
||||
{ RSTRTOD_U64(0xA8530886B54DBDEB), RSTRTOD_U64(0xD9F57F830283FDFD) },
|
||||
{ RSTRTOD_U64(0xD267CAA862A12D66), RSTRTOD_U64(0xD072DF63C324FD7C) },
|
||||
{ RSTRTOD_U64(0x8380DEA93DA4BC60), RSTRTOD_U64(0x4247CB9E59F71E6E) },
|
||||
{ RSTRTOD_U64(0xA46116538D0DEB78), RSTRTOD_U64(0x52D9BE85F074E609) },
|
||||
{ RSTRTOD_U64(0xCD795BE870516656), RSTRTOD_U64(0x67902E276C921F8C) },
|
||||
{ RSTRTOD_U64(0x806BD9714632DFF6), RSTRTOD_U64(0x00BA1CD8A3DB53B7) },
|
||||
{ RSTRTOD_U64(0xA086CFCD97BF97F3), RSTRTOD_U64(0x80E8A40ECCD228A5) },
|
||||
{ RSTRTOD_U64(0xC8A883C0FDAF7DF0), RSTRTOD_U64(0x6122CD128006B2CE) },
|
||||
{ RSTRTOD_U64(0xFAD2A4B13D1B5D6C), RSTRTOD_U64(0x796B805720085F82) },
|
||||
{ RSTRTOD_U64(0x9CC3A6EEC6311A63), RSTRTOD_U64(0xCBE3303674053BB1) },
|
||||
{ RSTRTOD_U64(0xC3F490AA77BD60FC), RSTRTOD_U64(0xBEDBFC4411068A9D) },
|
||||
{ RSTRTOD_U64(0xF4F1B4D515ACB93B), RSTRTOD_U64(0xEE92FB5515482D45) },
|
||||
{ RSTRTOD_U64(0x991711052D8BF3C5), RSTRTOD_U64(0x751BDD152D4D1C4B) },
|
||||
{ RSTRTOD_U64(0xBF5CD54678EEF0B6), RSTRTOD_U64(0xD262D45A78A0635E) },
|
||||
{ RSTRTOD_U64(0xEF340A98172AACE4), RSTRTOD_U64(0x86FB897116C87C35) },
|
||||
{ RSTRTOD_U64(0x9580869F0E7AAC0E), RSTRTOD_U64(0xD45D35E6AE3D4DA1) },
|
||||
{ RSTRTOD_U64(0xBAE0A846D2195712), RSTRTOD_U64(0x8974836059CCA10A) },
|
||||
{ RSTRTOD_U64(0xE998D258869FACD7), RSTRTOD_U64(0x2BD1A438703FC94C) },
|
||||
{ RSTRTOD_U64(0x91FF83775423CC06), RSTRTOD_U64(0x7B6306A34627DDD0) },
|
||||
{ RSTRTOD_U64(0xB67F6455292CBF08), RSTRTOD_U64(0x1A3BC84C17B1D543) },
|
||||
{ RSTRTOD_U64(0xE41F3D6A7377EECA), RSTRTOD_U64(0x20CABA5F1D9E4A94) },
|
||||
{ RSTRTOD_U64(0x8E938662882AF53E), RSTRTOD_U64(0x547EB47B7282EE9D) },
|
||||
{ RSTRTOD_U64(0xB23867FB2A35B28D), RSTRTOD_U64(0xE99E619A4F23AA44) },
|
||||
{ RSTRTOD_U64(0xDEC681F9F4C31F31), RSTRTOD_U64(0x6405FA00E2EC94D5) },
|
||||
{ RSTRTOD_U64(0x8B3C113C38F9F37E), RSTRTOD_U64(0xDE83BC408DD3DD05) },
|
||||
{ RSTRTOD_U64(0xAE0B158B4738705E), RSTRTOD_U64(0x9624AB50B148D446) },
|
||||
{ RSTRTOD_U64(0xD98DDAEE19068C76), RSTRTOD_U64(0x3BADD624DD9B0958) },
|
||||
{ RSTRTOD_U64(0x87F8A8D4CFA417C9), RSTRTOD_U64(0xE54CA5D70A80E5D7) },
|
||||
{ RSTRTOD_U64(0xA9F6D30A038D1DBC), RSTRTOD_U64(0x5E9FCF4CCD211F4D) },
|
||||
{ RSTRTOD_U64(0xD47487CC8470652B), RSTRTOD_U64(0x7647C32000696720) },
|
||||
{ RSTRTOD_U64(0x84C8D4DFD2C63F3B), RSTRTOD_U64(0x29ECD9F40041E074) },
|
||||
{ RSTRTOD_U64(0xA5FB0A17C777CF09), RSTRTOD_U64(0xF468107100525891) },
|
||||
{ RSTRTOD_U64(0xCF79CC9DB955C2CC), RSTRTOD_U64(0x7182148D4066EEB5) },
|
||||
{ RSTRTOD_U64(0x81AC1FE293D599BF), RSTRTOD_U64(0xC6F14CD848405531) },
|
||||
{ RSTRTOD_U64(0xA21727DB38CB002F), RSTRTOD_U64(0xB8ADA00E5A506A7D) },
|
||||
{ RSTRTOD_U64(0xCA9CF1D206FDC03B), RSTRTOD_U64(0xA6D90811F0E4851D) },
|
||||
{ RSTRTOD_U64(0xFD442E4688BD304A), RSTRTOD_U64(0x908F4A166D1DA664) },
|
||||
{ RSTRTOD_U64(0x9E4A9CEC15763E2E), RSTRTOD_U64(0x9A598E4E043287FF) },
|
||||
{ RSTRTOD_U64(0xC5DD44271AD3CDBA), RSTRTOD_U64(0x40EFF1E1853F29FE) },
|
||||
{ RSTRTOD_U64(0xF7549530E188C128), RSTRTOD_U64(0xD12BEE59E68EF47D) },
|
||||
{ RSTRTOD_U64(0x9A94DD3E8CF578B9), RSTRTOD_U64(0x82BB74F8301958CF) },
|
||||
{ RSTRTOD_U64(0xC13A148E3032D6E7), RSTRTOD_U64(0xE36A52363C1FAF02) },
|
||||
{ RSTRTOD_U64(0xF18899B1BC3F8CA1), RSTRTOD_U64(0xDC44E6C3CB279AC2) },
|
||||
{ RSTRTOD_U64(0x96F5600F15A7B7E5), RSTRTOD_U64(0x29AB103A5EF8C0BA) },
|
||||
{ RSTRTOD_U64(0xBCB2B812DB11A5DE), RSTRTOD_U64(0x7415D448F6B6F0E8) },
|
||||
{ RSTRTOD_U64(0xEBDF661791D60F56), RSTRTOD_U64(0x111B495B3464AD22) },
|
||||
{ RSTRTOD_U64(0x936B9FCEBB25C995), RSTRTOD_U64(0xCAB10DD900BEEC35) },
|
||||
{ RSTRTOD_U64(0xB84687C269EF3BFB), RSTRTOD_U64(0x3D5D514F40EEA743) },
|
||||
{ RSTRTOD_U64(0xE65829B3046B0AFA), RSTRTOD_U64(0x0CB4A5A3112A5113) },
|
||||
{ RSTRTOD_U64(0x8FF71A0FE2C2E6DC), RSTRTOD_U64(0x47F0E785EABA72AC) },
|
||||
{ RSTRTOD_U64(0xB3F4E093DB73A093), RSTRTOD_U64(0x59ED216765690F57) },
|
||||
{ RSTRTOD_U64(0xE0F218B8D25088B8), RSTRTOD_U64(0x306869C13EC3532D) },
|
||||
{ RSTRTOD_U64(0x8C974F7383725573), RSTRTOD_U64(0x1E414218C73A13FC) },
|
||||
{ RSTRTOD_U64(0xAFBD2350644EEACF), RSTRTOD_U64(0xE5D1929EF90898FB) },
|
||||
{ RSTRTOD_U64(0xDBAC6C247D62A583), RSTRTOD_U64(0xDF45F746B74ABF3A) },
|
||||
{ RSTRTOD_U64(0x894BC396CE5DA772), RSTRTOD_U64(0x6B8BBA8C328EB784) },
|
||||
{ RSTRTOD_U64(0xAB9EB47C81F5114F), RSTRTOD_U64(0x066EA92F3F326565) },
|
||||
{ RSTRTOD_U64(0xD686619BA27255A2), RSTRTOD_U64(0xC80A537B0EFEFEBE) },
|
||||
{ RSTRTOD_U64(0x8613FD0145877585), RSTRTOD_U64(0xBD06742CE95F5F37) },
|
||||
{ RSTRTOD_U64(0xA798FC4196E952E7), RSTRTOD_U64(0x2C48113823B73705) },
|
||||
{ RSTRTOD_U64(0xD17F3B51FCA3A7A0), RSTRTOD_U64(0xF75A15862CA504C6) },
|
||||
{ RSTRTOD_U64(0x82EF85133DE648C4), RSTRTOD_U64(0x9A984D73DBE722FC) },
|
||||
{ RSTRTOD_U64(0xA3AB66580D5FDAF5), RSTRTOD_U64(0xC13E60D0D2E0EBBB) },
|
||||
{ RSTRTOD_U64(0xCC963FEE10B7D1B3), RSTRTOD_U64(0x318DF905079926A9) },
|
||||
{ RSTRTOD_U64(0xFFBBCFE994E5C61F), RSTRTOD_U64(0xFDF17746497F7053) },
|
||||
{ RSTRTOD_U64(0x9FD561F1FD0F9BD3), RSTRTOD_U64(0xFEB6EA8BEDEFA634) },
|
||||
{ RSTRTOD_U64(0xC7CABA6E7C5382C8), RSTRTOD_U64(0xFE64A52EE96B8FC1) },
|
||||
{ RSTRTOD_U64(0xF9BD690A1B68637B), RSTRTOD_U64(0x3DFDCE7AA3C673B1) },
|
||||
{ RSTRTOD_U64(0x9C1661A651213E2D), RSTRTOD_U64(0x06BEA10CA65C084F) },
|
||||
{ RSTRTOD_U64(0xC31BFA0FE5698DB8), RSTRTOD_U64(0x486E494FCFF30A63) },
|
||||
{ RSTRTOD_U64(0xF3E2F893DEC3F126), RSTRTOD_U64(0x5A89DBA3C3EFCCFB) },
|
||||
{ RSTRTOD_U64(0x986DDB5C6B3A76B7), RSTRTOD_U64(0xF89629465A75E01D) },
|
||||
{ RSTRTOD_U64(0xBE89523386091465), RSTRTOD_U64(0xF6BBB397F1135824) },
|
||||
{ RSTRTOD_U64(0xEE2BA6C0678B597F), RSTRTOD_U64(0x746AA07DED582E2D) },
|
||||
{ RSTRTOD_U64(0x94DB483840B717EF), RSTRTOD_U64(0xA8C2A44EB4571CDD) },
|
||||
{ RSTRTOD_U64(0xBA121A4650E4DDEB), RSTRTOD_U64(0x92F34D62616CE414) },
|
||||
{ RSTRTOD_U64(0xE896A0D7E51E1566), RSTRTOD_U64(0x77B020BAF9C81D18) },
|
||||
{ RSTRTOD_U64(0x915E2486EF32CD60), RSTRTOD_U64(0x0ACE1474DC1D122F) },
|
||||
{ RSTRTOD_U64(0xB5B5ADA8AAFF80B8), RSTRTOD_U64(0x0D819992132456BB) },
|
||||
{ RSTRTOD_U64(0xE3231912D5BF60E6), RSTRTOD_U64(0x10E1FFF697ED6C6A) },
|
||||
{ RSTRTOD_U64(0x8DF5EFABC5979C8F), RSTRTOD_U64(0xCA8D3FFA1EF463C2) },
|
||||
{ RSTRTOD_U64(0xB1736B96B6FD83B3), RSTRTOD_U64(0xBD308FF8A6B17CB3) },
|
||||
{ RSTRTOD_U64(0xDDD0467C64BCE4A0), RSTRTOD_U64(0xAC7CB3F6D05DDBDF) },
|
||||
{ RSTRTOD_U64(0x8AA22C0DBEF60EE4), RSTRTOD_U64(0x6BCDF07A423AA96C) },
|
||||
{ RSTRTOD_U64(0xAD4AB7112EB3929D), RSTRTOD_U64(0x86C16C98D2C953C7) },
|
||||
{ RSTRTOD_U64(0xD89D64D57A607744), RSTRTOD_U64(0xE871C7BF077BA8B8) },
|
||||
{ RSTRTOD_U64(0x87625F056C7C4A8B), RSTRTOD_U64(0x11471CD764AD4973) },
|
||||
{ RSTRTOD_U64(0xA93AF6C6C79B5D2D), RSTRTOD_U64(0xD598E40D3DD89BD0) },
|
||||
{ RSTRTOD_U64(0xD389B47879823479), RSTRTOD_U64(0x4AFF1D108D4EC2C4) },
|
||||
{ RSTRTOD_U64(0x843610CB4BF160CB), RSTRTOD_U64(0xCEDF722A585139BB) },
|
||||
{ RSTRTOD_U64(0xA54394FE1EEDB8FE), RSTRTOD_U64(0xC2974EB4EE658829) },
|
||||
{ RSTRTOD_U64(0xCE947A3DA6A9273E), RSTRTOD_U64(0x733D226229FEEA33) },
|
||||
{ RSTRTOD_U64(0x811CCC668829B887), RSTRTOD_U64(0x0806357D5A3F5260) },
|
||||
{ RSTRTOD_U64(0xA163FF802A3426A8), RSTRTOD_U64(0xCA07C2DCB0CF26F8) },
|
||||
{ RSTRTOD_U64(0xC9BCFF6034C13052), RSTRTOD_U64(0xFC89B393DD02F0B6) },
|
||||
{ RSTRTOD_U64(0xFC2C3F3841F17C67), RSTRTOD_U64(0xBBAC2078D443ACE3) },
|
||||
{ RSTRTOD_U64(0x9D9BA7832936EDC0), RSTRTOD_U64(0xD54B944B84AA4C0E) },
|
||||
{ RSTRTOD_U64(0xC5029163F384A931), RSTRTOD_U64(0x0A9E795E65D4DF12) },
|
||||
{ RSTRTOD_U64(0xF64335BCF065D37D), RSTRTOD_U64(0x4D4617B5FF4A16D6) },
|
||||
{ RSTRTOD_U64(0x99EA0196163FA42E), RSTRTOD_U64(0x504BCED1BF8E4E46) },
|
||||
{ RSTRTOD_U64(0xC06481FB9BCF8D39), RSTRTOD_U64(0xE45EC2862F71E1D7) },
|
||||
{ RSTRTOD_U64(0xF07DA27A82C37088), RSTRTOD_U64(0x5D767327BB4E5A4D) },
|
||||
{ RSTRTOD_U64(0x964E858C91BA2655), RSTRTOD_U64(0x3A6A07F8D510F870) },
|
||||
{ RSTRTOD_U64(0xBBE226EFB628AFEA), RSTRTOD_U64(0x890489F70A55368C) },
|
||||
{ RSTRTOD_U64(0xEADAB0ABA3B2DBE5), RSTRTOD_U64(0x2B45AC74CCEA842F) },
|
||||
{ RSTRTOD_U64(0x92C8AE6B464FC96F), RSTRTOD_U64(0x3B0B8BC90012929E) },
|
||||
{ RSTRTOD_U64(0xB77ADA0617E3BBCB), RSTRTOD_U64(0x09CE6EBB40173745) },
|
||||
{ RSTRTOD_U64(0xE55990879DDCAABD), RSTRTOD_U64(0xCC420A6A101D0516) },
|
||||
{ RSTRTOD_U64(0x8F57FA54C2A9EAB6), RSTRTOD_U64(0x9FA946824A12232E) },
|
||||
{ RSTRTOD_U64(0xB32DF8E9F3546564), RSTRTOD_U64(0x47939822DC96ABFA) },
|
||||
{ RSTRTOD_U64(0xDFF9772470297EBD), RSTRTOD_U64(0x59787E2B93BC56F8) },
|
||||
{ RSTRTOD_U64(0x8BFBEA76C619EF36), RSTRTOD_U64(0x57EB4EDB3C55B65B) },
|
||||
{ RSTRTOD_U64(0xAEFAE51477A06B03), RSTRTOD_U64(0xEDE622920B6B23F2) },
|
||||
{ RSTRTOD_U64(0xDAB99E59958885C4), RSTRTOD_U64(0xE95FAB368E45ECEE) },
|
||||
{ RSTRTOD_U64(0x88B402F7FD75539B), RSTRTOD_U64(0x11DBCB0218EBB415) },
|
||||
{ RSTRTOD_U64(0xAAE103B5FCD2A881), RSTRTOD_U64(0xD652BDC29F26A11A) },
|
||||
{ RSTRTOD_U64(0xD59944A37C0752A2), RSTRTOD_U64(0x4BE76D3346F04960) },
|
||||
{ RSTRTOD_U64(0x857FCAE62D8493A5), RSTRTOD_U64(0x6F70A4400C562DDC) },
|
||||
{ RSTRTOD_U64(0xA6DFBD9FB8E5B88E), RSTRTOD_U64(0xCB4CCD500F6BB953) },
|
||||
{ RSTRTOD_U64(0xD097AD07A71F26B2), RSTRTOD_U64(0x7E2000A41346A7A8) },
|
||||
{ RSTRTOD_U64(0x825ECC24C873782F), RSTRTOD_U64(0x8ED400668C0C28C9) },
|
||||
{ RSTRTOD_U64(0xA2F67F2DFA90563B), RSTRTOD_U64(0x728900802F0F32FB) },
|
||||
{ RSTRTOD_U64(0xCBB41EF979346BCA), RSTRTOD_U64(0x4F2B40A03AD2FFBA) },
|
||||
{ RSTRTOD_U64(0xFEA126B7D78186BC), RSTRTOD_U64(0xE2F610C84987BFA9) },
|
||||
{ RSTRTOD_U64(0x9F24B832E6B0F436), RSTRTOD_U64(0x0DD9CA7D2DF4D7CA) },
|
||||
{ RSTRTOD_U64(0xC6EDE63FA05D3143), RSTRTOD_U64(0x91503D1C79720DBC) },
|
||||
{ RSTRTOD_U64(0xF8A95FCF88747D94), RSTRTOD_U64(0x75A44C6397CE912B) },
|
||||
{ RSTRTOD_U64(0x9B69DBE1B548CE7C), RSTRTOD_U64(0xC986AFBE3EE11ABB) },
|
||||
{ RSTRTOD_U64(0xC24452DA229B021B), RSTRTOD_U64(0xFBE85BADCE996169) },
|
||||
{ RSTRTOD_U64(0xF2D56790AB41C2A2), RSTRTOD_U64(0xFAE27299423FB9C4) },
|
||||
{ RSTRTOD_U64(0x97C560BA6B0919A5), RSTRTOD_U64(0xDCCD879FC967D41B) },
|
||||
{ RSTRTOD_U64(0xBDB6B8E905CB600F), RSTRTOD_U64(0x5400E987BBC1C921) },
|
||||
{ RSTRTOD_U64(0xED246723473E3813), RSTRTOD_U64(0x290123E9AAB23B69) },
|
||||
{ RSTRTOD_U64(0x9436C0760C86E30B), RSTRTOD_U64(0xF9A0B6720AAF6522) },
|
||||
{ RSTRTOD_U64(0xB94470938FA89BCE), RSTRTOD_U64(0xF808E40E8D5B3E6A) },
|
||||
{ RSTRTOD_U64(0xE7958CB87392C2C2), RSTRTOD_U64(0xB60B1D1230B20E05) },
|
||||
{ RSTRTOD_U64(0x90BD77F3483BB9B9), RSTRTOD_U64(0xB1C6F22B5E6F48C3) },
|
||||
{ RSTRTOD_U64(0xB4ECD5F01A4AA828), RSTRTOD_U64(0x1E38AEB6360B1AF4) },
|
||||
{ RSTRTOD_U64(0xE2280B6C20DD5232), RSTRTOD_U64(0x25C6DA63C38DE1B1) },
|
||||
{ RSTRTOD_U64(0x8D590723948A535F), RSTRTOD_U64(0x579C487E5A38AD0F) },
|
||||
{ RSTRTOD_U64(0xB0AF48EC79ACE837), RSTRTOD_U64(0x2D835A9DF0C6D852) },
|
||||
{ RSTRTOD_U64(0xDCDB1B2798182244), RSTRTOD_U64(0xF8E431456CF88E66) },
|
||||
{ RSTRTOD_U64(0x8A08F0F8BF0F156B), RSTRTOD_U64(0x1B8E9ECB641B5900) },
|
||||
{ RSTRTOD_U64(0xAC8B2D36EED2DAC5), RSTRTOD_U64(0xE272467E3D222F40) },
|
||||
{ RSTRTOD_U64(0xD7ADF884AA879177), RSTRTOD_U64(0x5B0ED81DCC6ABB10) },
|
||||
{ RSTRTOD_U64(0x86CCBB52EA94BAEA), RSTRTOD_U64(0x98E947129FC2B4EA) },
|
||||
{ RSTRTOD_U64(0xA87FEA27A539E9A5), RSTRTOD_U64(0x3F2398D747B36225) },
|
||||
{ RSTRTOD_U64(0xD29FE4B18E88640E), RSTRTOD_U64(0x8EEC7F0D19A03AAE) },
|
||||
{ RSTRTOD_U64(0x83A3EEEEF9153E89), RSTRTOD_U64(0x1953CF68300424AD) },
|
||||
{ RSTRTOD_U64(0xA48CEAAAB75A8E2B), RSTRTOD_U64(0x5FA8C3423C052DD8) },
|
||||
{ RSTRTOD_U64(0xCDB02555653131B6), RSTRTOD_U64(0x3792F412CB06794E) },
|
||||
{ RSTRTOD_U64(0x808E17555F3EBF11), RSTRTOD_U64(0xE2BBD88BBEE40BD1) },
|
||||
{ RSTRTOD_U64(0xA0B19D2AB70E6ED6), RSTRTOD_U64(0x5B6ACEAEAE9D0EC5) },
|
||||
{ RSTRTOD_U64(0xC8DE047564D20A8B), RSTRTOD_U64(0xF245825A5A445276) },
|
||||
{ RSTRTOD_U64(0xFB158592BE068D2E), RSTRTOD_U64(0xEED6E2F0F0D56713) },
|
||||
{ RSTRTOD_U64(0x9CED737BB6C4183D), RSTRTOD_U64(0x55464DD69685606C) },
|
||||
{ RSTRTOD_U64(0xC428D05AA4751E4C), RSTRTOD_U64(0xAA97E14C3C26B887) },
|
||||
{ RSTRTOD_U64(0xF53304714D9265DF), RSTRTOD_U64(0xD53DD99F4B3066A9) },
|
||||
{ RSTRTOD_U64(0x993FE2C6D07B7FAB), RSTRTOD_U64(0xE546A8038EFE402A) },
|
||||
{ RSTRTOD_U64(0xBF8FDB78849A5F96), RSTRTOD_U64(0xDE98520472BDD034) },
|
||||
{ RSTRTOD_U64(0xEF73D256A5C0F77C), RSTRTOD_U64(0x963E66858F6D4441) },
|
||||
{ RSTRTOD_U64(0x95A8637627989AAD), RSTRTOD_U64(0xDDE7001379A44AA9) },
|
||||
{ RSTRTOD_U64(0xBB127C53B17EC159), RSTRTOD_U64(0x5560C018580D5D53) },
|
||||
{ RSTRTOD_U64(0xE9D71B689DDE71AF), RSTRTOD_U64(0xAAB8F01E6E10B4A7) },
|
||||
{ RSTRTOD_U64(0x9226712162AB070D), RSTRTOD_U64(0xCAB3961304CA70E9) },
|
||||
{ RSTRTOD_U64(0xB6B00D69BB55C8D1), RSTRTOD_U64(0x3D607B97C5FD0D23) },
|
||||
{ RSTRTOD_U64(0xE45C10C42A2B3B05), RSTRTOD_U64(0x8CB89A7DB77C506B) },
|
||||
{ RSTRTOD_U64(0x8EB98A7A9A5B04E3), RSTRTOD_U64(0x77F3608E92ADB243) },
|
||||
{ RSTRTOD_U64(0xB267ED1940F1C61C), RSTRTOD_U64(0x55F038B237591ED4) },
|
||||
{ RSTRTOD_U64(0xDF01E85F912E37A3), RSTRTOD_U64(0x6B6C46DEC52F6689) },
|
||||
{ RSTRTOD_U64(0x8B61313BBABCE2C6), RSTRTOD_U64(0x2323AC4B3B3DA016) },
|
||||
{ RSTRTOD_U64(0xAE397D8AA96C1B77), RSTRTOD_U64(0xABEC975E0A0D081B) },
|
||||
{ RSTRTOD_U64(0xD9C7DCED53C72255), RSTRTOD_U64(0x96E7BD358C904A22) },
|
||||
{ RSTRTOD_U64(0x881CEA14545C7575), RSTRTOD_U64(0x7E50D64177DA2E55) },
|
||||
{ RSTRTOD_U64(0xAA242499697392D2), RSTRTOD_U64(0xDDE50BD1D5D0B9EA) },
|
||||
{ RSTRTOD_U64(0xD4AD2DBFC3D07787), RSTRTOD_U64(0x955E4EC64B44E865) },
|
||||
{ RSTRTOD_U64(0x84EC3C97DA624AB4), RSTRTOD_U64(0xBD5AF13BEF0B113F) },
|
||||
{ RSTRTOD_U64(0xA6274BBDD0FADD61), RSTRTOD_U64(0xECB1AD8AEACDD58F) },
|
||||
{ RSTRTOD_U64(0xCFB11EAD453994BA), RSTRTOD_U64(0x67DE18EDA5814AF3) },
|
||||
{ RSTRTOD_U64(0x81CEB32C4B43FCF4), RSTRTOD_U64(0x80EACF948770CED8) },
|
||||
{ RSTRTOD_U64(0xA2425FF75E14FC31), RSTRTOD_U64(0xA1258379A94D028E) },
|
||||
{ RSTRTOD_U64(0xCAD2F7F5359A3B3E), RSTRTOD_U64(0x096EE45813A04331) },
|
||||
{ RSTRTOD_U64(0xFD87B5F28300CA0D), RSTRTOD_U64(0x8BCA9D6E188853FD) },
|
||||
{ RSTRTOD_U64(0x9E74D1B791E07E48), RSTRTOD_U64(0x775EA264CF55347E) },
|
||||
{ RSTRTOD_U64(0xC612062576589DDA), RSTRTOD_U64(0x95364AFE032A819E) },
|
||||
{ RSTRTOD_U64(0xF79687AED3EEC551), RSTRTOD_U64(0x3A83DDBD83F52205) },
|
||||
{ RSTRTOD_U64(0x9ABE14CD44753B52), RSTRTOD_U64(0xC4926A9672793543) },
|
||||
{ RSTRTOD_U64(0xC16D9A0095928A27), RSTRTOD_U64(0x75B7053C0F178294) },
|
||||
{ RSTRTOD_U64(0xF1C90080BAF72CB1), RSTRTOD_U64(0x5324C68B12DD6339) },
|
||||
{ RSTRTOD_U64(0x971DA05074DA7BEE), RSTRTOD_U64(0xD3F6FC16EBCA5E04) },
|
||||
{ RSTRTOD_U64(0xBCE5086492111AEA), RSTRTOD_U64(0x88F4BB1CA6BCF585) },
|
||||
{ RSTRTOD_U64(0xEC1E4A7DB69561A5), RSTRTOD_U64(0x2B31E9E3D06C32E6) },
|
||||
{ RSTRTOD_U64(0x9392EE8E921D5D07), RSTRTOD_U64(0x3AFF322E62439FD0) },
|
||||
{ RSTRTOD_U64(0xB877AA3236A4B449), RSTRTOD_U64(0x09BEFEB9FAD487C3) },
|
||||
{ RSTRTOD_U64(0xE69594BEC44DE15B), RSTRTOD_U64(0x4C2EBE687989A9B4) },
|
||||
{ RSTRTOD_U64(0x901D7CF73AB0ACD9), RSTRTOD_U64(0x0F9D37014BF60A11) },
|
||||
{ RSTRTOD_U64(0xB424DC35095CD80F), RSTRTOD_U64(0x538484C19EF38C95) },
|
||||
{ RSTRTOD_U64(0xE12E13424BB40E13), RSTRTOD_U64(0x2865A5F206B06FBA) },
|
||||
{ RSTRTOD_U64(0x8CBCCC096F5088CB), RSTRTOD_U64(0xF93F87B7442E45D4) },
|
||||
{ RSTRTOD_U64(0xAFEBFF0BCB24AAFE), RSTRTOD_U64(0xF78F69A51539D749) },
|
||||
{ RSTRTOD_U64(0xDBE6FECEBDEDD5BE), RSTRTOD_U64(0xB573440E5A884D1C) },
|
||||
{ RSTRTOD_U64(0x89705F4136B4A597), RSTRTOD_U64(0x31680A88F8953031) },
|
||||
{ RSTRTOD_U64(0xABCC77118461CEFC), RSTRTOD_U64(0xFDC20D2B36BA7C3E) },
|
||||
{ RSTRTOD_U64(0xD6BF94D5E57A42BC), RSTRTOD_U64(0x3D32907604691B4D) },
|
||||
{ RSTRTOD_U64(0x8637BD05AF6C69B5), RSTRTOD_U64(0xA63F9A49C2C1B110) },
|
||||
{ RSTRTOD_U64(0xA7C5AC471B478423), RSTRTOD_U64(0x0FCF80DC33721D54) },
|
||||
{ RSTRTOD_U64(0xD1B71758E219652B), RSTRTOD_U64(0xD3C36113404EA4A9) },
|
||||
{ RSTRTOD_U64(0x83126E978D4FDF3B), RSTRTOD_U64(0x645A1CAC083126EA) },
|
||||
{ RSTRTOD_U64(0xA3D70A3D70A3D70A), RSTRTOD_U64(0x3D70A3D70A3D70A4) },
|
||||
{ RSTRTOD_U64(0xCCCCCCCCCCCCCCCC), RSTRTOD_U64(0xCCCCCCCCCCCCCCCD) },
|
||||
{ RSTRTOD_U64(0x8000000000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xA000000000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xC800000000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xFA00000000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x9C40000000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xC350000000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xF424000000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x9896800000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xBEBC200000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xEE6B280000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x9502F90000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xBA43B74000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xE8D4A51000000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x9184E72A00000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xB5E620F480000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xE35FA931A0000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x8E1BC9BF04000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xB1A2BC2EC5000000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xDE0B6B3A76400000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x8AC7230489E80000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xAD78EBC5AC620000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xD8D726B7177A8000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x878678326EAC9000), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xA968163F0A57B400), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xD3C21BCECCEDA100), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x84595161401484A0), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xA56FA5B99019A5C8), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0xCECB8F27F4200F3A), RSTRTOD_U64(0x0000000000000000) },
|
||||
{ RSTRTOD_U64(0x813F3978F8940984), RSTRTOD_U64(0x4000000000000000) },
|
||||
{ RSTRTOD_U64(0xA18F07D736B90BE5), RSTRTOD_U64(0x5000000000000000) },
|
||||
{ RSTRTOD_U64(0xC9F2C9CD04674EDE), RSTRTOD_U64(0xA400000000000000) },
|
||||
{ RSTRTOD_U64(0xFC6F7C4045812296), RSTRTOD_U64(0x4D00000000000000) },
|
||||
{ RSTRTOD_U64(0x9DC5ADA82B70B59D), RSTRTOD_U64(0xF020000000000000) },
|
||||
{ RSTRTOD_U64(0xC5371912364CE305), RSTRTOD_U64(0x6C28000000000000) },
|
||||
{ RSTRTOD_U64(0xF684DF56C3E01BC6), RSTRTOD_U64(0xC732000000000000) },
|
||||
{ RSTRTOD_U64(0x9A130B963A6C115C), RSTRTOD_U64(0x3C7F400000000000) },
|
||||
{ RSTRTOD_U64(0xC097CE7BC90715B3), RSTRTOD_U64(0x4B9F100000000000) },
|
||||
{ RSTRTOD_U64(0xF0BDC21ABB48DB20), RSTRTOD_U64(0x1E86D40000000000) },
|
||||
{ RSTRTOD_U64(0x96769950B50D88F4), RSTRTOD_U64(0x1314448000000000) },
|
||||
{ RSTRTOD_U64(0xBC143FA4E250EB31), RSTRTOD_U64(0x17D955A000000000) },
|
||||
{ RSTRTOD_U64(0xEB194F8E1AE525FD), RSTRTOD_U64(0x5DCFAB0800000000) },
|
||||
{ RSTRTOD_U64(0x92EFD1B8D0CF37BE), RSTRTOD_U64(0x5AA1CAE500000000) },
|
||||
{ RSTRTOD_U64(0xB7ABC627050305AD), RSTRTOD_U64(0xF14A3D9E40000000) },
|
||||
{ RSTRTOD_U64(0xE596B7B0C643C719), RSTRTOD_U64(0x6D9CCD05D0000000) },
|
||||
{ RSTRTOD_U64(0x8F7E32CE7BEA5C6F), RSTRTOD_U64(0xE4820023A2000000) },
|
||||
{ RSTRTOD_U64(0xB35DBF821AE4F38B), RSTRTOD_U64(0xDDA2802C8A800000) },
|
||||
{ RSTRTOD_U64(0xE0352F62A19E306E), RSTRTOD_U64(0xD50B2037AD200000) },
|
||||
{ RSTRTOD_U64(0x8C213D9DA502DE45), RSTRTOD_U64(0x4526F422CC340000) },
|
||||
{ RSTRTOD_U64(0xAF298D050E4395D6), RSTRTOD_U64(0x9670B12B7F410000) },
|
||||
{ RSTRTOD_U64(0xDAF3F04651D47B4C), RSTRTOD_U64(0x3C0CDD765F114000) },
|
||||
{ RSTRTOD_U64(0x88D8762BF324CD0F), RSTRTOD_U64(0xA5880A69FB6AC800) },
|
||||
{ RSTRTOD_U64(0xAB0E93B6EFEE0053), RSTRTOD_U64(0x8EEA0D047A457A00) },
|
||||
{ RSTRTOD_U64(0xD5D238A4ABE98068), RSTRTOD_U64(0x72A4904598D6D880) },
|
||||
{ RSTRTOD_U64(0x85A36366EB71F041), RSTRTOD_U64(0x47A6DA2B7F864750) },
|
||||
{ RSTRTOD_U64(0xA70C3C40A64E6C51), RSTRTOD_U64(0x999090B65F67D924) },
|
||||
{ RSTRTOD_U64(0xD0CF4B50CFE20765), RSTRTOD_U64(0xFFF4B4E3F741CF6D) },
|
||||
{ RSTRTOD_U64(0x82818F1281ED449F), RSTRTOD_U64(0xBFF8F10E7A8921A4) },
|
||||
{ RSTRTOD_U64(0xA321F2D7226895C7), RSTRTOD_U64(0xAFF72D52192B6A0D) },
|
||||
{ RSTRTOD_U64(0xCBEA6F8CEB02BB39), RSTRTOD_U64(0x9BF4F8A69F764490) },
|
||||
{ RSTRTOD_U64(0xFEE50B7025C36A08), RSTRTOD_U64(0x02F236D04753D5B4) },
|
||||
{ RSTRTOD_U64(0x9F4F2726179A2245), RSTRTOD_U64(0x01D762422C946590) },
|
||||
{ RSTRTOD_U64(0xC722F0EF9D80AAD6), RSTRTOD_U64(0x424D3AD2B7B97EF5) },
|
||||
{ RSTRTOD_U64(0xF8EBAD2B84E0D58B), RSTRTOD_U64(0xD2E0898765A7DEB2) },
|
||||
{ RSTRTOD_U64(0x9B934C3B330C8577), RSTRTOD_U64(0x63CC55F49F88EB2F) },
|
||||
{ RSTRTOD_U64(0xC2781F49FFCFA6D5), RSTRTOD_U64(0x3CBF6B71C76B25FB) },
|
||||
{ RSTRTOD_U64(0xF316271C7FC3908A), RSTRTOD_U64(0x8BEF464E3945EF7A) },
|
||||
{ RSTRTOD_U64(0x97EDD871CFDA3A56), RSTRTOD_U64(0x97758BF0E3CBB5AC) },
|
||||
{ RSTRTOD_U64(0xBDE94E8E43D0C8EC), RSTRTOD_U64(0x3D52EEED1CBEA317) },
|
||||
{ RSTRTOD_U64(0xED63A231D4C4FB27), RSTRTOD_U64(0x4CA7AAA863EE4BDD) },
|
||||
{ RSTRTOD_U64(0x945E455F24FB1CF8), RSTRTOD_U64(0x8FE8CAA93E74EF6A) },
|
||||
{ RSTRTOD_U64(0xB975D6B6EE39E436), RSTRTOD_U64(0xB3E2FD538E122B44) },
|
||||
{ RSTRTOD_U64(0xE7D34C64A9C85D44), RSTRTOD_U64(0x60DBBCA87196B616) },
|
||||
{ RSTRTOD_U64(0x90E40FBEEA1D3A4A), RSTRTOD_U64(0xBC8955E946FE31CD) },
|
||||
{ RSTRTOD_U64(0xB51D13AEA4A488DD), RSTRTOD_U64(0x6BABAB6398BDBE41) },
|
||||
{ RSTRTOD_U64(0xE264589A4DCDAB14), RSTRTOD_U64(0xC696963C7EED2DD1) },
|
||||
{ RSTRTOD_U64(0x8D7EB76070A08AEC), RSTRTOD_U64(0xFC1E1DE5CF543CA2) },
|
||||
{ RSTRTOD_U64(0xB0DE65388CC8ADA8), RSTRTOD_U64(0x3B25A55F43294BCB) },
|
||||
{ RSTRTOD_U64(0xDD15FE86AFFAD912), RSTRTOD_U64(0x49EF0EB713F39EBE) },
|
||||
{ RSTRTOD_U64(0x8A2DBF142DFCC7AB), RSTRTOD_U64(0x6E3569326C784337) },
|
||||
{ RSTRTOD_U64(0xACB92ED9397BF996), RSTRTOD_U64(0x49C2C37F07965404) },
|
||||
{ RSTRTOD_U64(0xD7E77A8F87DAF7FB), RSTRTOD_U64(0xDC33745EC97BE906) },
|
||||
{ RSTRTOD_U64(0x86F0AC99B4E8DAFD), RSTRTOD_U64(0x69A028BB3DED71A3) },
|
||||
{ RSTRTOD_U64(0xA8ACD7C0222311BC), RSTRTOD_U64(0xC40832EA0D68CE0C) },
|
||||
{ RSTRTOD_U64(0xD2D80DB02AABD62B), RSTRTOD_U64(0xF50A3FA490C30190) },
|
||||
{ RSTRTOD_U64(0x83C7088E1AAB65DB), RSTRTOD_U64(0x792667C6DA79E0FA) },
|
||||
{ RSTRTOD_U64(0xA4B8CAB1A1563F52), RSTRTOD_U64(0x577001B891185938) },
|
||||
{ RSTRTOD_U64(0xCDE6FD5E09ABCF26), RSTRTOD_U64(0xED4C0226B55E6F86) },
|
||||
{ RSTRTOD_U64(0x80B05E5AC60B6178), RSTRTOD_U64(0x544F8158315B05B4) },
|
||||
{ RSTRTOD_U64(0xA0DC75F1778E39D6), RSTRTOD_U64(0x696361AE3DB1C721) },
|
||||
{ RSTRTOD_U64(0xC913936DD571C84C), RSTRTOD_U64(0x03BC3A19CD1E38E9) },
|
||||
{ RSTRTOD_U64(0xFB5878494ACE3A5F), RSTRTOD_U64(0x04AB48A04065C723) },
|
||||
{ RSTRTOD_U64(0x9D174B2DCEC0E47B), RSTRTOD_U64(0x62EB0D64283F9C76) },
|
||||
{ RSTRTOD_U64(0xC45D1DF942711D9A), RSTRTOD_U64(0x3BA5D0BD324F8394) },
|
||||
{ RSTRTOD_U64(0xF5746577930D6500), RSTRTOD_U64(0xCA8F44EC7EE36479) },
|
||||
{ RSTRTOD_U64(0x9968BF6ABBE85F20), RSTRTOD_U64(0x7E998B13CF4E1ECB) },
|
||||
{ RSTRTOD_U64(0xBFC2EF456AE276E8), RSTRTOD_U64(0x9E3FEDD8C321A67E) },
|
||||
{ RSTRTOD_U64(0xEFB3AB16C59B14A2), RSTRTOD_U64(0xC5CFE94EF3EA101E) },
|
||||
{ RSTRTOD_U64(0x95D04AEE3B80ECE5), RSTRTOD_U64(0xBBA1F1D158724A12) },
|
||||
{ RSTRTOD_U64(0xBB445DA9CA61281F), RSTRTOD_U64(0x2A8A6E45AE8EDC97) },
|
||||
{ RSTRTOD_U64(0xEA1575143CF97226), RSTRTOD_U64(0xF52D09D71A3293BD) },
|
||||
{ RSTRTOD_U64(0x924D692CA61BE758), RSTRTOD_U64(0x593C2626705F9C56) },
|
||||
{ RSTRTOD_U64(0xB6E0C377CFA2E12E), RSTRTOD_U64(0x6F8B2FB00C77836C) },
|
||||
{ RSTRTOD_U64(0xE498F455C38B997A), RSTRTOD_U64(0x0B6DFB9C0F956447) },
|
||||
{ RSTRTOD_U64(0x8EDF98B59A373FEC), RSTRTOD_U64(0x4724BD4189BD5EAC) },
|
||||
{ RSTRTOD_U64(0xB2977EE300C50FE7), RSTRTOD_U64(0x58EDEC91EC2CB657) },
|
||||
{ RSTRTOD_U64(0xDF3D5E9BC0F653E1), RSTRTOD_U64(0x2F2967B66737E3ED) },
|
||||
{ RSTRTOD_U64(0x8B865B215899F46C), RSTRTOD_U64(0xBD79E0D20082EE74) },
|
||||
{ RSTRTOD_U64(0xAE67F1E9AEC07187), RSTRTOD_U64(0xECD8590680A3AA11) },
|
||||
{ RSTRTOD_U64(0xDA01EE641A708DE9), RSTRTOD_U64(0xE80E6F4820CC9495) },
|
||||
{ RSTRTOD_U64(0x884134FE908658B2), RSTRTOD_U64(0x3109058D147FDCDD) },
|
||||
{ RSTRTOD_U64(0xAA51823E34A7EEDE), RSTRTOD_U64(0xBD4B46F0599FD415) },
|
||||
{ RSTRTOD_U64(0xD4E5E2CDC1D1EA96), RSTRTOD_U64(0x6C9E18AC7007C91A) },
|
||||
{ RSTRTOD_U64(0x850FADC09923329E), RSTRTOD_U64(0x03E2CF6BC604DDB0) },
|
||||
{ RSTRTOD_U64(0xA6539930BF6BFF45), RSTRTOD_U64(0x84DB8346B786151C) },
|
||||
{ RSTRTOD_U64(0xCFE87F7CEF46FF16), RSTRTOD_U64(0xE612641865679A63) },
|
||||
{ RSTRTOD_U64(0x81F14FAE158C5F6E), RSTRTOD_U64(0x4FCB7E8F3F60C07E) },
|
||||
{ RSTRTOD_U64(0xA26DA3999AEF7749), RSTRTOD_U64(0xE3BE5E330F38F09D) },
|
||||
{ RSTRTOD_U64(0xCB090C8001AB551C), RSTRTOD_U64(0x5CADF5BFD3072CC5) },
|
||||
{ RSTRTOD_U64(0xFDCB4FA002162A63), RSTRTOD_U64(0x73D9732FC7C8F7F6) },
|
||||
{ RSTRTOD_U64(0x9E9F11C4014DDA7E), RSTRTOD_U64(0x2867E7FDDCDD9AFA) },
|
||||
{ RSTRTOD_U64(0xC646D63501A1511D), RSTRTOD_U64(0xB281E1FD541501B8) },
|
||||
{ RSTRTOD_U64(0xF7D88BC24209A565), RSTRTOD_U64(0x1F225A7CA91A4226) },
|
||||
{ RSTRTOD_U64(0x9AE757596946075F), RSTRTOD_U64(0x3375788DE9B06958) },
|
||||
{ RSTRTOD_U64(0xC1A12D2FC3978937), RSTRTOD_U64(0x0052D6B1641C83AE) },
|
||||
{ RSTRTOD_U64(0xF209787BB47D6B84), RSTRTOD_U64(0xC0678C5DBD23A49A) },
|
||||
{ RSTRTOD_U64(0x9745EB4D50CE6332), RSTRTOD_U64(0xF840B7BA963646E0) },
|
||||
{ RSTRTOD_U64(0xBD176620A501FBFF), RSTRTOD_U64(0xB650E5A93BC3D898) },
|
||||
{ RSTRTOD_U64(0xEC5D3FA8CE427AFF), RSTRTOD_U64(0xA3E51F138AB4CEBE) },
|
||||
{ RSTRTOD_U64(0x93BA47C980E98CDF), RSTRTOD_U64(0xC66F336C36B10137) },
|
||||
{ RSTRTOD_U64(0xB8A8D9BBE123F017), RSTRTOD_U64(0xB80B0047445D4184) },
|
||||
{ RSTRTOD_U64(0xE6D3102AD96CEC1D), RSTRTOD_U64(0xA60DC059157491E5) },
|
||||
{ RSTRTOD_U64(0x9043EA1AC7E41392), RSTRTOD_U64(0x87C89837AD68DB2F) },
|
||||
{ RSTRTOD_U64(0xB454E4A179DD1877), RSTRTOD_U64(0x29BABE4598C311FB) },
|
||||
{ RSTRTOD_U64(0xE16A1DC9D8545E94), RSTRTOD_U64(0xF4296DD6FEF3D67A) },
|
||||
{ RSTRTOD_U64(0x8CE2529E2734BB1D), RSTRTOD_U64(0x1899E4A65F58660C) },
|
||||
{ RSTRTOD_U64(0xB01AE745B101E9E4), RSTRTOD_U64(0x5EC05DCFF72E7F8F) },
|
||||
{ RSTRTOD_U64(0xDC21A1171D42645D), RSTRTOD_U64(0x76707543F4FA1F73) },
|
||||
{ RSTRTOD_U64(0x899504AE72497EBA), RSTRTOD_U64(0x6A06494A791C53A8) },
|
||||
{ RSTRTOD_U64(0xABFA45DA0EDBDE69), RSTRTOD_U64(0x0487DB9D17636892) },
|
||||
{ RSTRTOD_U64(0xD6F8D7509292D603), RSTRTOD_U64(0x45A9D2845D3C42B6) },
|
||||
{ RSTRTOD_U64(0x865B86925B9BC5C2), RSTRTOD_U64(0x0B8A2392BA45A9B2) },
|
||||
{ RSTRTOD_U64(0xA7F26836F282B732), RSTRTOD_U64(0x8E6CAC7768D7141E) },
|
||||
{ RSTRTOD_U64(0xD1EF0244AF2364FF), RSTRTOD_U64(0x3207D795430CD926) },
|
||||
{ RSTRTOD_U64(0x8335616AED761F1F), RSTRTOD_U64(0x7F44E6BD49E807B8) },
|
||||
{ RSTRTOD_U64(0xA402B9C5A8D3A6E7), RSTRTOD_U64(0x5F16206C9C6209A6) },
|
||||
{ RSTRTOD_U64(0xCD036837130890A1), RSTRTOD_U64(0x36DBA887C37A8C0F) },
|
||||
{ RSTRTOD_U64(0x802221226BE55A64), RSTRTOD_U64(0xC2494954DA2C9789) },
|
||||
{ RSTRTOD_U64(0xA02AA96B06DEB0FD), RSTRTOD_U64(0xF2DB9BAA10B7BD6C) },
|
||||
{ RSTRTOD_U64(0xC83553C5C8965D3D), RSTRTOD_U64(0x6F92829494E5ACC7) },
|
||||
{ RSTRTOD_U64(0xFA42A8B73ABBF48C), RSTRTOD_U64(0xCB772339BA1F17F9) },
|
||||
{ RSTRTOD_U64(0x9C69A97284B578D7), RSTRTOD_U64(0xFF2A760414536EFB) },
|
||||
{ RSTRTOD_U64(0xC38413CF25E2D70D), RSTRTOD_U64(0xFEF5138519684ABA) },
|
||||
{ RSTRTOD_U64(0xF46518C2EF5B8CD1), RSTRTOD_U64(0x7EB258665FC25D69) },
|
||||
{ RSTRTOD_U64(0x98BF2F79D5993802), RSTRTOD_U64(0xEF2F773FFBD97A61) },
|
||||
{ RSTRTOD_U64(0xBEEEFB584AFF8603), RSTRTOD_U64(0xAAFB550FFACFD8FA) },
|
||||
{ RSTRTOD_U64(0xEEAABA2E5DBF6784), RSTRTOD_U64(0x95BA2A53F983CF38) },
|
||||
{ RSTRTOD_U64(0x952AB45CFA97A0B2), RSTRTOD_U64(0xDD945A747BF26183) },
|
||||
{ RSTRTOD_U64(0xBA756174393D88DF), RSTRTOD_U64(0x94F971119AEEF9E4) },
|
||||
{ RSTRTOD_U64(0xE912B9D1478CEB17), RSTRTOD_U64(0x7A37CD5601AAB85D) },
|
||||
{ RSTRTOD_U64(0x91ABB422CCB812EE), RSTRTOD_U64(0xAC62E055C10AB33A) },
|
||||
{ RSTRTOD_U64(0xB616A12B7FE617AA), RSTRTOD_U64(0x577B986B314D6009) },
|
||||
{ RSTRTOD_U64(0xE39C49765FDF9D94), RSTRTOD_U64(0xED5A7E85FDA0B80B) },
|
||||
{ RSTRTOD_U64(0x8E41ADE9FBEBC27D), RSTRTOD_U64(0x14588F13BE847307) },
|
||||
{ RSTRTOD_U64(0xB1D219647AE6B31C), RSTRTOD_U64(0x596EB2D8AE258FC8) },
|
||||
{ RSTRTOD_U64(0xDE469FBD99A05FE3), RSTRTOD_U64(0x6FCA5F8ED9AEF3BB) },
|
||||
{ RSTRTOD_U64(0x8AEC23D680043BEE), RSTRTOD_U64(0x25DE7BB9480D5854) },
|
||||
{ RSTRTOD_U64(0xADA72CCC20054AE9), RSTRTOD_U64(0xAF561AA79A10AE6A) },
|
||||
{ RSTRTOD_U64(0xD910F7FF28069DA4), RSTRTOD_U64(0x1B2BA1518094DA04) },
|
||||
{ RSTRTOD_U64(0x87AA9AFF79042286), RSTRTOD_U64(0x90FB44D2F05D0842) },
|
||||
{ RSTRTOD_U64(0xA99541BF57452B28), RSTRTOD_U64(0x353A1607AC744A53) },
|
||||
{ RSTRTOD_U64(0xD3FA922F2D1675F2), RSTRTOD_U64(0x42889B8997915CE8) },
|
||||
{ RSTRTOD_U64(0x847C9B5D7C2E09B7), RSTRTOD_U64(0x69956135FEBADA11) },
|
||||
{ RSTRTOD_U64(0xA59BC234DB398C25), RSTRTOD_U64(0x43FAB9837E699095) },
|
||||
{ RSTRTOD_U64(0xCF02B2C21207EF2E), RSTRTOD_U64(0x94F967E45E03F4BB) },
|
||||
{ RSTRTOD_U64(0x8161AFB94B44F57D), RSTRTOD_U64(0x1D1BE0EEBAC278F5) },
|
||||
{ RSTRTOD_U64(0xA1BA1BA79E1632DC), RSTRTOD_U64(0x6462D92A69731732) },
|
||||
{ RSTRTOD_U64(0xCA28A291859BBF93), RSTRTOD_U64(0x7D7B8F7503CFDCFE) },
|
||||
{ RSTRTOD_U64(0xFCB2CB35E702AF78), RSTRTOD_U64(0x5CDA735244C3D43E) },
|
||||
{ RSTRTOD_U64(0x9DEFBF01B061ADAB), RSTRTOD_U64(0x3A0888136AFA64A7) },
|
||||
{ RSTRTOD_U64(0xC56BAEC21C7A1916), RSTRTOD_U64(0x088AAA1845B8FDD0) },
|
||||
{ RSTRTOD_U64(0xF6C69A72A3989F5B), RSTRTOD_U64(0x8AAD549E57273D45) },
|
||||
{ RSTRTOD_U64(0x9A3C2087A63F6399), RSTRTOD_U64(0x36AC54E2F678864B) },
|
||||
{ RSTRTOD_U64(0xC0CB28A98FCF3C7F), RSTRTOD_U64(0x84576A1BB416A7DD) },
|
||||
{ RSTRTOD_U64(0xF0FDF2D3F3C30B9F), RSTRTOD_U64(0x656D44A2A11C51D5) },
|
||||
{ RSTRTOD_U64(0x969EB7C47859E743), RSTRTOD_U64(0x9F644AE5A4B1B325) },
|
||||
{ RSTRTOD_U64(0xBC4665B596706114), RSTRTOD_U64(0x873D5D9F0DDE1FEE) },
|
||||
{ RSTRTOD_U64(0xEB57FF22FC0C7959), RSTRTOD_U64(0xA90CB506D155A7EA) },
|
||||
{ RSTRTOD_U64(0x9316FF75DD87CBD8), RSTRTOD_U64(0x09A7F12442D588F2) },
|
||||
{ RSTRTOD_U64(0xB7DCBF5354E9BECE), RSTRTOD_U64(0x0C11ED6D538AEB2F) },
|
||||
{ RSTRTOD_U64(0xE5D3EF282A242E81), RSTRTOD_U64(0x8F1668C8A86DA5FA) },
|
||||
{ RSTRTOD_U64(0x8FA475791A569D10), RSTRTOD_U64(0xF96E017D694487BC) },
|
||||
{ RSTRTOD_U64(0xB38D92D760EC4455), RSTRTOD_U64(0x37C981DCC395A9AC) },
|
||||
{ RSTRTOD_U64(0xE070F78D3927556A), RSTRTOD_U64(0x85BBE253F47B1417) },
|
||||
{ RSTRTOD_U64(0x8C469AB843B89562), RSTRTOD_U64(0x93956D7478CCEC8E) },
|
||||
{ RSTRTOD_U64(0xAF58416654A6BABB), RSTRTOD_U64(0x387AC8D1970027B2) },
|
||||
{ RSTRTOD_U64(0xDB2E51BFE9D0696A), RSTRTOD_U64(0x06997B05FCC0319E) },
|
||||
{ RSTRTOD_U64(0x88FCF317F22241E2), RSTRTOD_U64(0x441FECE3BDF81F03) },
|
||||
{ RSTRTOD_U64(0xAB3C2FDDEEAAD25A), RSTRTOD_U64(0xD527E81CAD7626C3) },
|
||||
{ RSTRTOD_U64(0xD60B3BD56A5586F1), RSTRTOD_U64(0x8A71E223D8D3B074) },
|
||||
{ RSTRTOD_U64(0x85C7056562757456), RSTRTOD_U64(0xF6872D5667844E49) },
|
||||
{ RSTRTOD_U64(0xA738C6BEBB12D16C), RSTRTOD_U64(0xB428F8AC016561DB) },
|
||||
{ RSTRTOD_U64(0xD106F86E69D785C7), RSTRTOD_U64(0xE13336D701BEBA52) },
|
||||
{ RSTRTOD_U64(0x82A45B450226B39C), RSTRTOD_U64(0xECC0024661173473) },
|
||||
{ RSTRTOD_U64(0xA34D721642B06084), RSTRTOD_U64(0x27F002D7F95D0190) },
|
||||
{ RSTRTOD_U64(0xCC20CE9BD35C78A5), RSTRTOD_U64(0x31EC038DF7B441F4) },
|
||||
{ RSTRTOD_U64(0xFF290242C83396CE), RSTRTOD_U64(0x7E67047175A15271) },
|
||||
{ RSTRTOD_U64(0x9F79A169BD203E41), RSTRTOD_U64(0x0F0062C6E984D386) },
|
||||
{ RSTRTOD_U64(0xC75809C42C684DD1), RSTRTOD_U64(0x52C07B78A3E60868) },
|
||||
{ RSTRTOD_U64(0xF92E0C3537826145), RSTRTOD_U64(0xA7709A56CCDF8A82) },
|
||||
{ RSTRTOD_U64(0x9BBCC7A142B17CCB), RSTRTOD_U64(0x88A66076400BB691) },
|
||||
{ RSTRTOD_U64(0xC2ABF989935DDBFE), RSTRTOD_U64(0x6ACFF893D00EA435) },
|
||||
{ RSTRTOD_U64(0xF356F7EBF83552FE), RSTRTOD_U64(0x0583F6B8C4124D43) },
|
||||
{ RSTRTOD_U64(0x98165AF37B2153DE), RSTRTOD_U64(0xC3727A337A8B704A) },
|
||||
{ RSTRTOD_U64(0xBE1BF1B059E9A8D6), RSTRTOD_U64(0x744F18C0592E4C5C) },
|
||||
{ RSTRTOD_U64(0xEDA2EE1C7064130C), RSTRTOD_U64(0x1162DEF06F79DF73) },
|
||||
{ RSTRTOD_U64(0x9485D4D1C63E8BE7), RSTRTOD_U64(0x8ADDCB5645AC2BA8) },
|
||||
{ RSTRTOD_U64(0xB9A74A0637CE2EE1), RSTRTOD_U64(0x6D953E2BD7173692) },
|
||||
{ RSTRTOD_U64(0xE8111C87C5C1BA99), RSTRTOD_U64(0xC8FA8DB6CCDD0437) },
|
||||
{ RSTRTOD_U64(0x910AB1D4DB9914A0), RSTRTOD_U64(0x1D9C9892400A22A2) },
|
||||
{ RSTRTOD_U64(0xB54D5E4A127F59C8), RSTRTOD_U64(0x2503BEB6D00CAB4B) },
|
||||
{ RSTRTOD_U64(0xE2A0B5DC971F303A), RSTRTOD_U64(0x2E44AE64840FD61D) },
|
||||
{ RSTRTOD_U64(0x8DA471A9DE737E24), RSTRTOD_U64(0x5CEAECFED289E5D2) },
|
||||
{ RSTRTOD_U64(0xB10D8E1456105DAD), RSTRTOD_U64(0x7425A83E872C5F47) },
|
||||
{ RSTRTOD_U64(0xDD50F1996B947518), RSTRTOD_U64(0xD12F124E28F77719) },
|
||||
{ RSTRTOD_U64(0x8A5296FFE33CC92F), RSTRTOD_U64(0x82BD6B70D99AAA6F) },
|
||||
{ RSTRTOD_U64(0xACE73CBFDC0BFB7B), RSTRTOD_U64(0x636CC64D1001550B) },
|
||||
{ RSTRTOD_U64(0xD8210BEFD30EFA5A), RSTRTOD_U64(0x3C47F7E05401AA4E) },
|
||||
{ RSTRTOD_U64(0x8714A775E3E95C78), RSTRTOD_U64(0x65ACFAEC34810A71) },
|
||||
{ RSTRTOD_U64(0xA8D9D1535CE3B396), RSTRTOD_U64(0x7F1839A741A14D0D) },
|
||||
{ RSTRTOD_U64(0xD31045A8341CA07C), RSTRTOD_U64(0x1EDE48111209A050) },
|
||||
{ RSTRTOD_U64(0x83EA2B892091E44D), RSTRTOD_U64(0x934AED0AAB460432) },
|
||||
{ RSTRTOD_U64(0xA4E4B66B68B65D60), RSTRTOD_U64(0xF81DA84D5617853F) },
|
||||
{ RSTRTOD_U64(0xCE1DE40642E3F4B9), RSTRTOD_U64(0x36251260AB9D668E) },
|
||||
{ RSTRTOD_U64(0x80D2AE83E9CE78F3), RSTRTOD_U64(0xC1D72B7C6B426019) },
|
||||
{ RSTRTOD_U64(0xA1075A24E4421730), RSTRTOD_U64(0xB24CF65B8612F81F) },
|
||||
{ RSTRTOD_U64(0xC94930AE1D529CFC), RSTRTOD_U64(0xDEE033F26797B627) },
|
||||
{ RSTRTOD_U64(0xFB9B7CD9A4A7443C), RSTRTOD_U64(0x169840EF017DA3B1) },
|
||||
{ RSTRTOD_U64(0x9D412E0806E88AA5), RSTRTOD_U64(0x8E1F289560EE864E) },
|
||||
{ RSTRTOD_U64(0xC491798A08A2AD4E), RSTRTOD_U64(0xF1A6F2BAB92A27E2) },
|
||||
{ RSTRTOD_U64(0xF5B5D7EC8ACB58A2), RSTRTOD_U64(0xAE10AF696774B1DB) },
|
||||
{ RSTRTOD_U64(0x9991A6F3D6BF1765), RSTRTOD_U64(0xACCA6DA1E0A8EF29) },
|
||||
{ RSTRTOD_U64(0xBFF610B0CC6EDD3F), RSTRTOD_U64(0x17FD090A58D32AF3) },
|
||||
{ RSTRTOD_U64(0xEFF394DCFF8A948E), RSTRTOD_U64(0xDDFC4B4CEF07F5B0) },
|
||||
{ RSTRTOD_U64(0x95F83D0A1FB69CD9), RSTRTOD_U64(0x4ABDAF101564F98E) },
|
||||
{ RSTRTOD_U64(0xBB764C4CA7A4440F), RSTRTOD_U64(0x9D6D1AD41ABE37F1) },
|
||||
{ RSTRTOD_U64(0xEA53DF5FD18D5513), RSTRTOD_U64(0x84C86189216DC5ED) },
|
||||
{ RSTRTOD_U64(0x92746B9BE2F8552C), RSTRTOD_U64(0x32FD3CF5B4E49BB4) },
|
||||
{ RSTRTOD_U64(0xB7118682DBB66A77), RSTRTOD_U64(0x3FBC8C33221DC2A1) },
|
||||
{ RSTRTOD_U64(0xE4D5E82392A40515), RSTRTOD_U64(0x0FABAF3FEAA5334A) },
|
||||
{ RSTRTOD_U64(0x8F05B1163BA6832D), RSTRTOD_U64(0x29CB4D87F2A7400E) },
|
||||
{ RSTRTOD_U64(0xB2C71D5BCA9023F8), RSTRTOD_U64(0x743E20E9EF511012) },
|
||||
{ RSTRTOD_U64(0xDF78E4B2BD342CF6), RSTRTOD_U64(0x914DA9246B255416) },
|
||||
{ RSTRTOD_U64(0x8BAB8EEFB6409C1A), RSTRTOD_U64(0x1AD089B6C2F7548E) },
|
||||
{ RSTRTOD_U64(0xAE9672ABA3D0C320), RSTRTOD_U64(0xA184AC2473B529B1) },
|
||||
{ RSTRTOD_U64(0xDA3C0F568CC4F3E8), RSTRTOD_U64(0xC9E5D72D90A2741E) },
|
||||
{ RSTRTOD_U64(0x8865899617FB1871), RSTRTOD_U64(0x7E2FA67C7A658892) },
|
||||
{ RSTRTOD_U64(0xAA7EEBFB9DF9DE8D), RSTRTOD_U64(0xDDBB901B98FEEAB7) },
|
||||
{ RSTRTOD_U64(0xD51EA6FA85785631), RSTRTOD_U64(0x552A74227F3EA565) },
|
||||
{ RSTRTOD_U64(0x8533285C936B35DE), RSTRTOD_U64(0xD53A88958F87275F) },
|
||||
{ RSTRTOD_U64(0xA67FF273B8460356), RSTRTOD_U64(0x8A892ABAF368F137) },
|
||||
{ RSTRTOD_U64(0xD01FEF10A657842C), RSTRTOD_U64(0x2D2B7569B0432D85) },
|
||||
{ RSTRTOD_U64(0x8213F56A67F6B29B), RSTRTOD_U64(0x9C3B29620E29FC73) },
|
||||
{ RSTRTOD_U64(0xA298F2C501F45F42), RSTRTOD_U64(0x8349F3BA91B47B8F) },
|
||||
{ RSTRTOD_U64(0xCB3F2F7642717713), RSTRTOD_U64(0x241C70A936219A73) },
|
||||
{ RSTRTOD_U64(0xFE0EFB53D30DD4D7), RSTRTOD_U64(0xED238CD383AA0110) },
|
||||
{ RSTRTOD_U64(0x9EC95D1463E8A506), RSTRTOD_U64(0xF4363804324A40AA) },
|
||||
{ RSTRTOD_U64(0xC67BB4597CE2CE48), RSTRTOD_U64(0xB143C6053EDCD0D5) },
|
||||
{ RSTRTOD_U64(0xF81AA16FDC1B81DA), RSTRTOD_U64(0xDD94B7868E94050A) },
|
||||
{ RSTRTOD_U64(0x9B10A4E5E9913128), RSTRTOD_U64(0xCA7CF2B4191C8326) },
|
||||
{ RSTRTOD_U64(0xC1D4CE1F63F57D72), RSTRTOD_U64(0xFD1C2F611F63A3F0) },
|
||||
{ RSTRTOD_U64(0xF24A01A73CF2DCCF), RSTRTOD_U64(0xBC633B39673C8CEC) },
|
||||
{ RSTRTOD_U64(0x976E41088617CA01), RSTRTOD_U64(0xD5BE0503E085D813) },
|
||||
{ RSTRTOD_U64(0xBD49D14AA79DBC82), RSTRTOD_U64(0x4B2D8644D8A74E18) },
|
||||
{ RSTRTOD_U64(0xEC9C459D51852BA2), RSTRTOD_U64(0xDDF8E7D60ED1219E) },
|
||||
{ RSTRTOD_U64(0x93E1AB8252F33B45), RSTRTOD_U64(0xCABB90E5C942B503) },
|
||||
{ RSTRTOD_U64(0xB8DA1662E7B00A17), RSTRTOD_U64(0x3D6A751F3B936243) },
|
||||
{ RSTRTOD_U64(0xE7109BFBA19C0C9D), RSTRTOD_U64(0x0CC512670A783AD4) },
|
||||
{ RSTRTOD_U64(0x906A617D450187E2), RSTRTOD_U64(0x27FB2B80668B24C5) },
|
||||
{ RSTRTOD_U64(0xB484F9DC9641E9DA), RSTRTOD_U64(0xB1F9F660802DEDF6) },
|
||||
{ RSTRTOD_U64(0xE1A63853BBD26451), RSTRTOD_U64(0x5E7873F8A0396973) },
|
||||
{ RSTRTOD_U64(0x8D07E33455637EB2), RSTRTOD_U64(0xDB0B487B6423E1E8) },
|
||||
{ RSTRTOD_U64(0xB049DC016ABC5E5F), RSTRTOD_U64(0x91CE1A9A3D2CDA62) },
|
||||
{ RSTRTOD_U64(0xDC5C5301C56B75F7), RSTRTOD_U64(0x7641A140CC7810FB) },
|
||||
{ RSTRTOD_U64(0x89B9B3E11B6329BA), RSTRTOD_U64(0xA9E904C87FCB0A9D) },
|
||||
{ RSTRTOD_U64(0xAC2820D9623BF429), RSTRTOD_U64(0x546345FA9FBDCD44) },
|
||||
{ RSTRTOD_U64(0xD732290FBACAF133), RSTRTOD_U64(0xA97C177947AD4095) },
|
||||
{ RSTRTOD_U64(0x867F59A9D4BED6C0), RSTRTOD_U64(0x49ED8EABCCCC485D) },
|
||||
{ RSTRTOD_U64(0xA81F301449EE8C70), RSTRTOD_U64(0x5C68F256BFFF5A74) },
|
||||
{ RSTRTOD_U64(0xD226FC195C6A2F8C), RSTRTOD_U64(0x73832EEC6FFF3111) },
|
||||
{ RSTRTOD_U64(0x83585D8FD9C25DB7), RSTRTOD_U64(0xC831FD53C5FF7EAB) },
|
||||
{ RSTRTOD_U64(0xA42E74F3D032F525), RSTRTOD_U64(0xBA3E7CA8B77F5E55) },
|
||||
{ RSTRTOD_U64(0xCD3A1230C43FB26F), RSTRTOD_U64(0x28CE1BD2E55F35EB) },
|
||||
{ RSTRTOD_U64(0x80444B5E7AA7CF85), RSTRTOD_U64(0x7980D163CF5B81B3) },
|
||||
{ RSTRTOD_U64(0xA0555E361951C366), RSTRTOD_U64(0xD7E105BCC332621F) },
|
||||
{ RSTRTOD_U64(0xC86AB5C39FA63440), RSTRTOD_U64(0x8DD9472BF3FEFAA7) },
|
||||
{ RSTRTOD_U64(0xFA856334878FC150), RSTRTOD_U64(0xB14F98F6F0FEB951) },
|
||||
{ RSTRTOD_U64(0x9C935E00D4B9D8D2), RSTRTOD_U64(0x6ED1BF9A569F33D3) },
|
||||
{ RSTRTOD_U64(0xC3B8358109E84F07), RSTRTOD_U64(0x0A862F80EC4700C8) },
|
||||
{ RSTRTOD_U64(0xF4A642E14C6262C8), RSTRTOD_U64(0xCD27BB612758C0FA) },
|
||||
{ RSTRTOD_U64(0x98E7E9CCCFBD7DBD), RSTRTOD_U64(0x8038D51CB897789C) },
|
||||
{ RSTRTOD_U64(0xBF21E44003ACDD2C), RSTRTOD_U64(0xE0470A63E6BD56C3) },
|
||||
{ RSTRTOD_U64(0xEEEA5D5004981478), RSTRTOD_U64(0x1858CCFCE06CAC74) },
|
||||
{ RSTRTOD_U64(0x95527A5202DF0CCB), RSTRTOD_U64(0x0F37801E0C43EBC8) },
|
||||
{ RSTRTOD_U64(0xBAA718E68396CFFD), RSTRTOD_U64(0xD30560258F54E6BA) },
|
||||
{ RSTRTOD_U64(0xE950DF20247C83FD), RSTRTOD_U64(0x47C6B82EF32A2069) },
|
||||
{ RSTRTOD_U64(0x91D28B7416CDD27E), RSTRTOD_U64(0x4CDC331D57FA5441) },
|
||||
{ RSTRTOD_U64(0xB6472E511C81471D), RSTRTOD_U64(0xE0133FE4ADF8E952) },
|
||||
{ RSTRTOD_U64(0xE3D8F9E563A198E5), RSTRTOD_U64(0x58180FDDD97723A6) },
|
||||
{ RSTRTOD_U64(0x8E679C2F5E44FF8F), RSTRTOD_U64(0x570F09EAA7EA7648) },
|
||||
};
|
||||
+1006
File diff suppressed because it is too large.
Load diff
+525
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Hybrid VFS: frontend coverage without giving up direct I/O.
|
||||
|
||||
Generic - intended for libretro-common. A core adopts it with one
|
||||
call at retro_set_environment:
|
||||
|
||||
vfs_hybrid_init(environ_cb, log_cb);
|
||||
|
||||
which replaces the wholesale filestream_vfs_init(frontend) pattern.
|
||||
|
||||
The tension this resolves: platforms with sandboxed storage (Android
|
||||
scoped storage / SAF content URIs) can only reach content through the
|
||||
frontend's VFS callbacks - but frontend VFS handles are opaque, with
|
||||
no file descriptor and therefore no mmap, and routing everything
|
||||
through them would kill the mapped-pak architecture (in-place central
|
||||
directories, stored-entry borrows, GetFileView, OGG borrows) plus add
|
||||
an indirect call to every read on platforms that never needed it.
|
||||
|
||||
Resolution: dispatch per FILE, local-first.
|
||||
|
||||
- Every open tries the LOCAL implementation first (libretro-common's
|
||||
own vfs_implementation, the same code the frontend compiles): a
|
||||
real fd, mmap intact, zero-copy intact, byte-identical to the
|
||||
pre-hybrid behavior.
|
||||
- The frontend interface is consulted only when the local open cannot
|
||||
possibly succeed or the platform is sandboxed: URI-shaped paths
|
||||
("scheme://...") always go frontend; on sandboxed platforms a local
|
||||
failure falls through to the frontend. On desktop a plain-path miss
|
||||
NEVER touches the frontend, so the loose-file probe cost is exactly
|
||||
what it was.
|
||||
- Files that arrive on the frontend branch cannot be mapped - true at
|
||||
the OS level, not a limitation of this layer - and every mapped-
|
||||
path consumer already has a tested unmapped fallback: the mapped-
|
||||
ptr accessor answers NULL for frontend-backed handles and rzip,
|
||||
GetFileView, and the borrows degrade to copies for those files
|
||||
only.
|
||||
|
||||
Installed only when the frontend actually provides a VFS interface;
|
||||
otherwise filestream keeps its direct local path with zero added
|
||||
indirection.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libretro.h>
|
||||
#include <vfs/vfs_implementation.h>
|
||||
#include <streams/file_stream.h>
|
||||
#include <file/file_path.h>
|
||||
#include <retro_dirent.h>
|
||||
|
||||
enum { HYB_LOCAL = 1, HYB_FRONT = 2 };
|
||||
|
||||
typedef struct {
|
||||
int be;
|
||||
void *h; /* libretro_vfs_implementation_file* or frontend handle */
|
||||
char *path;
|
||||
} hyb_file_t;
|
||||
|
||||
typedef struct {
|
||||
int be;
|
||||
void *h;
|
||||
} hyb_dir_t;
|
||||
|
||||
static struct retro_vfs_interface *hyb_front;
|
||||
static uint32_t hyb_front_version;
|
||||
|
||||
/* A sandboxed platform tries the frontend after ANY local failure;
|
||||
elsewhere only URI-shaped paths reach the frontend, so plain-path
|
||||
misses cost nothing extra. Overridable per platform/port. */
|
||||
#ifndef VFS_HYBRID_SANDBOXED
|
||||
#if defined(ANDROID) || defined(__ANDROID__)
|
||||
#define VFS_HYBRID_SANDBOXED 1
|
||||
#else
|
||||
#define VFS_HYBRID_SANDBOXED 0
|
||||
#endif
|
||||
#endif
|
||||
#define HYB_SANDBOXED VFS_HYBRID_SANDBOXED
|
||||
|
||||
static int hyb_is_uri( const char *path ) {
|
||||
return path && strstr( path, "://" ) != NULL;
|
||||
}
|
||||
|
||||
/* ---- file ops ---- */
|
||||
|
||||
static const char *hyb_get_path( struct retro_vfs_file_handle *fh ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
if ( !f )
|
||||
return NULL;
|
||||
if ( f->be == HYB_FRONT && hyb_front->get_path )
|
||||
return hyb_front->get_path( (struct retro_vfs_file_handle *)f->h );
|
||||
return f->path;
|
||||
}
|
||||
|
||||
static struct retro_vfs_file_handle *hyb_open( const char *path, unsigned mode, unsigned hints ) {
|
||||
hyb_file_t *f;
|
||||
void *h = NULL;
|
||||
int be = 0;
|
||||
|
||||
if ( !path )
|
||||
return NULL;
|
||||
if ( !hyb_is_uri( path ) ) {
|
||||
h = retro_vfs_file_open_impl( path, mode, hints );
|
||||
if ( h )
|
||||
be = HYB_LOCAL;
|
||||
}
|
||||
if ( !h && hyb_front && hyb_front->open && ( hyb_is_uri( path ) || HYB_SANDBOXED ) ) {
|
||||
h = hyb_front->open( path, mode, hints );
|
||||
if ( h )
|
||||
be = HYB_FRONT;
|
||||
}
|
||||
if ( !h )
|
||||
return NULL;
|
||||
f = (hyb_file_t *)calloc( 1, sizeof( *f ) );
|
||||
if ( !f ) {
|
||||
if ( be == HYB_LOCAL )
|
||||
retro_vfs_file_close_impl( (libretro_vfs_implementation_file *)h );
|
||||
else
|
||||
hyb_front->close( (struct retro_vfs_file_handle *)h );
|
||||
return NULL;
|
||||
}
|
||||
f->be = be;
|
||||
f->h = h;
|
||||
/* strdup is POSIX, not C89: under a strict-C89 toolchain the
|
||||
prototype is hidden and the implicit int return truncates the
|
||||
pointer on LP64. Copy manually with the headers we already
|
||||
include. */
|
||||
{
|
||||
size_t path_len = strlen( path ) + 1;
|
||||
f->path = (char *)malloc( path_len );
|
||||
if ( f->path )
|
||||
memcpy( f->path, path, path_len );
|
||||
}
|
||||
return (struct retro_vfs_file_handle *)f;
|
||||
}
|
||||
|
||||
static int hyb_close( struct retro_vfs_file_handle *fh ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
int r;
|
||||
if ( !f )
|
||||
return -1;
|
||||
if ( f->be == HYB_LOCAL )
|
||||
r = retro_vfs_file_close_impl( (libretro_vfs_implementation_file *)f->h );
|
||||
else
|
||||
r = hyb_front->close( (struct retro_vfs_file_handle *)f->h );
|
||||
free( f->path );
|
||||
free( f );
|
||||
return r;
|
||||
}
|
||||
|
||||
#define HYB_FWD1(name, ret) \
|
||||
static ret hyb_##name( struct retro_vfs_file_handle *fh ) { \
|
||||
hyb_file_t *f = (hyb_file_t *)fh; \
|
||||
if ( !f ) return (ret)-1; \
|
||||
if ( f->be == HYB_LOCAL ) \
|
||||
return retro_vfs_file_##name##_impl( (libretro_vfs_implementation_file *)f->h ); \
|
||||
return hyb_front->name( (struct retro_vfs_file_handle *)f->h ); \
|
||||
}
|
||||
|
||||
HYB_FWD1( size, int64_t )
|
||||
HYB_FWD1( tell, int64_t )
|
||||
HYB_FWD1( flush, int )
|
||||
|
||||
static int64_t hyb_truncate( struct retro_vfs_file_handle *fh, int64_t length ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
if ( !f )
|
||||
return -1;
|
||||
if ( f->be == HYB_LOCAL )
|
||||
return retro_vfs_file_truncate_impl( (libretro_vfs_implementation_file *)f->h, length );
|
||||
/* truncate is v2. A frontend that advertised v1 has not filled
|
||||
this member, so it must not be called - unlike the v3 members
|
||||
below, which are unreachable by construction because a
|
||||
frontend-backed dir handle can only be created when the
|
||||
negotiated version is already 3. */
|
||||
if ( hyb_front_version < 2 || !hyb_front->truncate )
|
||||
return -1;
|
||||
return hyb_front->truncate( (struct retro_vfs_file_handle *)f->h, length );
|
||||
}
|
||||
|
||||
static int64_t hyb_seek( struct retro_vfs_file_handle *fh, int64_t offset, int seek_position ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
if ( !f )
|
||||
return -1;
|
||||
if ( f->be == HYB_LOCAL )
|
||||
return retro_vfs_file_seek_impl( (libretro_vfs_implementation_file *)f->h, offset, seek_position );
|
||||
return hyb_front->seek( (struct retro_vfs_file_handle *)f->h, offset, seek_position );
|
||||
}
|
||||
|
||||
static int64_t hyb_read( struct retro_vfs_file_handle *fh, void *s, uint64_t len ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
if ( !f )
|
||||
return -1;
|
||||
if ( f->be == HYB_LOCAL )
|
||||
return retro_vfs_file_read_impl( (libretro_vfs_implementation_file *)f->h, s, len );
|
||||
return hyb_front->read( (struct retro_vfs_file_handle *)f->h, s, len );
|
||||
}
|
||||
|
||||
static int64_t hyb_write( struct retro_vfs_file_handle *fh, const void *s, uint64_t len ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
if ( !f )
|
||||
return -1;
|
||||
if ( f->be == HYB_LOCAL )
|
||||
return retro_vfs_file_write_impl( (libretro_vfs_implementation_file *)f->h, s, len );
|
||||
return hyb_front->write( (struct retro_vfs_file_handle *)f->h, s, len );
|
||||
}
|
||||
|
||||
static int hyb_remove( const char *path ) {
|
||||
if ( !hyb_is_uri( path ) ) {
|
||||
int r = retro_vfs_file_remove_impl( path );
|
||||
if ( r == 0 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
if ( hyb_front && hyb_front->remove )
|
||||
return hyb_front->remove( path );
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int hyb_rename( const char *old_path, const char *new_path ) {
|
||||
if ( !hyb_is_uri( old_path ) && !hyb_is_uri( new_path ) ) {
|
||||
int r = retro_vfs_file_rename_impl( old_path, new_path );
|
||||
if ( r == 0 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
if ( hyb_front && hyb_front->rename )
|
||||
return hyb_front->rename( old_path, new_path );
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---- v3: stat / mkdir / dirent ---- */
|
||||
|
||||
static int hyb_stat( const char *path, int32_t *size ) {
|
||||
if ( !hyb_is_uri( path ) ) {
|
||||
int r = retro_vfs_stat_impl( path, size );
|
||||
if ( r != 0 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
if ( hyb_front && hyb_front_version >= 3 && hyb_front->stat )
|
||||
return hyb_front->stat( path, size );
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- v4: 64-bit stat ---- */
|
||||
|
||||
static int hyb_stat_64( const char *path, int64_t *size ) {
|
||||
if ( !hyb_is_uri( path ) ) {
|
||||
int r = retro_vfs_stat_64_impl( path, size );
|
||||
if ( r != 0 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
/* stat_64 is v4. A frontend that advertised less has not filled
|
||||
this member, so it must not be called; sandboxed content on
|
||||
such a frontend falls back to the 32-bit stat above, which the
|
||||
callers of the 64-bit path already treat as best-effort. */
|
||||
if ( hyb_front && hyb_front_version >= 4 && hyb_front->stat_64 )
|
||||
return hyb_front->stat_64( path, size );
|
||||
if ( hyb_front && hyb_front_version >= 3 && hyb_front->stat ) {
|
||||
int32_t s32 = 0;
|
||||
int r = hyb_front->stat( path, size ? &s32 : NULL );
|
||||
if ( size )
|
||||
*size = (int64_t)s32;
|
||||
return r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hyb_mkdir( const char *dir ) {
|
||||
if ( !hyb_is_uri( dir ) ) {
|
||||
int r = retro_vfs_mkdir_impl( dir );
|
||||
if ( r != -1 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
if ( hyb_front && hyb_front_version >= 3 && hyb_front->mkdir )
|
||||
return hyb_front->mkdir( dir );
|
||||
return -1;
|
||||
}
|
||||
|
||||
static struct retro_vfs_dir_handle *hyb_opendir( const char *dir, bool include_hidden ) {
|
||||
hyb_dir_t *d;
|
||||
void *h = NULL;
|
||||
int be = 0;
|
||||
if ( !dir )
|
||||
return NULL;
|
||||
if ( !hyb_is_uri( dir ) ) {
|
||||
h = retro_vfs_opendir_impl( dir, include_hidden );
|
||||
if ( h )
|
||||
be = HYB_LOCAL;
|
||||
}
|
||||
if ( !h && hyb_front && hyb_front_version >= 3 && hyb_front->opendir
|
||||
&& ( hyb_is_uri( dir ) || HYB_SANDBOXED ) ) {
|
||||
h = hyb_front->opendir( dir, include_hidden );
|
||||
if ( h )
|
||||
be = HYB_FRONT;
|
||||
}
|
||||
if ( !h )
|
||||
return NULL;
|
||||
d = (hyb_dir_t *)calloc( 1, sizeof( *d ) );
|
||||
if ( !d ) {
|
||||
/* the backend handle is already open; hyb_open() releases its
|
||||
own on this path and so must this one */
|
||||
if ( be == HYB_LOCAL )
|
||||
retro_vfs_closedir_impl( (libretro_vfs_implementation_dir *)h );
|
||||
else
|
||||
hyb_front->closedir( (struct retro_vfs_dir_handle *)h );
|
||||
return NULL;
|
||||
}
|
||||
d->be = be;
|
||||
d->h = h;
|
||||
return (struct retro_vfs_dir_handle *)d;
|
||||
}
|
||||
|
||||
static bool hyb_readdir( struct retro_vfs_dir_handle *dh ) {
|
||||
hyb_dir_t *d = (hyb_dir_t *)dh;
|
||||
if ( !d )
|
||||
return false;
|
||||
if ( d->be == HYB_LOCAL )
|
||||
return retro_vfs_readdir_impl( (libretro_vfs_implementation_dir *)d->h );
|
||||
return hyb_front->readdir( (struct retro_vfs_dir_handle *)d->h );
|
||||
}
|
||||
|
||||
static const char *hyb_dirent_get_name( struct retro_vfs_dir_handle *dh ) {
|
||||
hyb_dir_t *d = (hyb_dir_t *)dh;
|
||||
if ( !d )
|
||||
return NULL;
|
||||
if ( d->be == HYB_LOCAL )
|
||||
return retro_vfs_dirent_get_name_impl( (libretro_vfs_implementation_dir *)d->h );
|
||||
return hyb_front->dirent_get_name( (struct retro_vfs_dir_handle *)d->h );
|
||||
}
|
||||
|
||||
static bool hyb_dirent_is_dir( struct retro_vfs_dir_handle *dh ) {
|
||||
hyb_dir_t *d = (hyb_dir_t *)dh;
|
||||
if ( !d )
|
||||
return false;
|
||||
if ( d->be == HYB_LOCAL )
|
||||
return retro_vfs_dirent_is_dir_impl( (libretro_vfs_implementation_dir *)d->h );
|
||||
return hyb_front->dirent_is_dir( (struct retro_vfs_dir_handle *)d->h );
|
||||
}
|
||||
|
||||
static int hyb_closedir( struct retro_vfs_dir_handle *dh ) {
|
||||
hyb_dir_t *d = (hyb_dir_t *)dh;
|
||||
int r;
|
||||
if ( !d )
|
||||
return -1;
|
||||
if ( d->be == HYB_LOCAL )
|
||||
r = retro_vfs_closedir_impl( (libretro_vfs_implementation_dir *)d->h );
|
||||
else
|
||||
r = hyb_front->closedir( (struct retro_vfs_dir_handle *)d->h );
|
||||
free( d );
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ---- v5: metadata, copy, dirent_stat ---- */
|
||||
|
||||
/* Same local-first rule as stat: a native path is answered locally,
|
||||
a URI (or anything on a sandboxed platform) goes to a frontend
|
||||
that advertised v5. A frontend older than v5 has not filled these
|
||||
members and must not be called. */
|
||||
|
||||
static int hyb_set_readonly( const char *path, int readonly ) {
|
||||
if ( !hyb_is_uri( path ) ) {
|
||||
int r = retro_vfs_set_readonly_impl( path, readonly );
|
||||
if ( r == 0 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
if ( hyb_front && hyb_front_version >= 5 && hyb_front->set_readonly )
|
||||
return hyb_front->set_readonly( path, readonly );
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int hyb_get_mtime( const char *path, int64_t *mtime ) {
|
||||
if ( !hyb_is_uri( path ) ) {
|
||||
int r = retro_vfs_get_mtime_impl( path, mtime );
|
||||
if ( r == 0 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
if ( hyb_front && hyb_front_version >= 5 && hyb_front->get_mtime )
|
||||
return hyb_front->get_mtime( path, mtime );
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int hyb_set_mtime( const char *path, int64_t mtime ) {
|
||||
if ( !hyb_is_uri( path ) ) {
|
||||
int r = retro_vfs_set_mtime_impl( path, mtime );
|
||||
if ( r == 0 || !( hyb_front && HYB_SANDBOXED ) )
|
||||
return r;
|
||||
}
|
||||
if ( hyb_front && hyb_front_version >= 5 && hyb_front->set_mtime )
|
||||
return hyb_front->set_mtime( path, mtime );
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A copy handle must be polled and closed by whichever side began it,
|
||||
so the wrapper records which one that was. */
|
||||
typedef struct { int be; void *h; } hyb_copy_t;
|
||||
|
||||
static struct retro_vfs_copy_handle *hyb_copy_begin( const char *src, const char *dst, unsigned flags ) {
|
||||
hyb_copy_t *c = (hyb_copy_t *)calloc( 1, sizeof( *c ) );
|
||||
if ( !c )
|
||||
return NULL;
|
||||
/* both native: local (fast paths live there). A URI on either
|
||||
side means at least one end only the frontend can reach. */
|
||||
if ( !hyb_is_uri( src ) && !hyb_is_uri( dst ) ) {
|
||||
c->h = retro_vfs_copy_begin_impl( src, dst, flags );
|
||||
if ( c->h || !( hyb_front && HYB_SANDBOXED ) ) {
|
||||
c->be = HYB_LOCAL;
|
||||
if ( !c->h ) { free( c ); return NULL; }
|
||||
return (struct retro_vfs_copy_handle *)c;
|
||||
}
|
||||
}
|
||||
if ( hyb_front && hyb_front_version >= 5 && hyb_front->copy_begin ) {
|
||||
c->h = hyb_front->copy_begin( src, dst, flags );
|
||||
if ( c->h ) { c->be = HYB_FRONT; return (struct retro_vfs_copy_handle *)c; }
|
||||
}
|
||||
free( c );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int hyb_copy_step( struct retro_vfs_copy_handle *ch, int64_t max_bytes, int64_t *done, int64_t *total ) {
|
||||
hyb_copy_t *c = (hyb_copy_t *)ch;
|
||||
if ( !c )
|
||||
return RETRO_VFS_COPY_FAILED;
|
||||
if ( c->be == HYB_LOCAL )
|
||||
return retro_vfs_copy_step_impl( (struct retro_vfs_copy_handle *)c->h, max_bytes, done, total );
|
||||
return hyb_front->copy_step( (struct retro_vfs_copy_handle *)c->h, max_bytes, done, total );
|
||||
}
|
||||
|
||||
static int hyb_copy_close( struct retro_vfs_copy_handle *ch ) {
|
||||
hyb_copy_t *c = (hyb_copy_t *)ch;
|
||||
int r;
|
||||
if ( !c )
|
||||
return -1;
|
||||
if ( c->be == HYB_LOCAL )
|
||||
r = retro_vfs_copy_close_impl( (struct retro_vfs_copy_handle *)c->h );
|
||||
else
|
||||
r = hyb_front->copy_close( (struct retro_vfs_copy_handle *)c->h );
|
||||
free( c );
|
||||
return r;
|
||||
}
|
||||
|
||||
static int hyb_dirent_stat( struct retro_vfs_dir_handle *dh, int64_t *size, int64_t *mtime ) {
|
||||
hyb_dir_t *d = (hyb_dir_t *)dh;
|
||||
if ( !d )
|
||||
return 0;
|
||||
if ( d->be == HYB_LOCAL )
|
||||
return retro_vfs_dirent_stat_impl( (libretro_vfs_implementation_dir *)d->h, size, mtime );
|
||||
if ( hyb_front_version >= 5 && hyb_front->dirent_stat )
|
||||
return hyb_front->dirent_stat( (struct retro_vfs_dir_handle *)d->h, size, mtime );
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* the zero-copy sideband: local-backed handles expose their mapping,
|
||||
frontend-backed ones honestly cannot */
|
||||
static const uint8_t *hyb_mapped_ptr( void *fh, int64_t *len ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
if ( len )
|
||||
*len = 0;
|
||||
if ( !f || f->be != HYB_LOCAL )
|
||||
return NULL;
|
||||
return retro_vfs_file_get_mapped_ptr_impl( (libretro_vfs_implementation_file *)f->h, len );
|
||||
}
|
||||
|
||||
static void hyb_prefetch( void *fh, uint64_t offset, uint64_t len ) {
|
||||
hyb_file_t *f = (hyb_file_t *)fh;
|
||||
if ( f && f->be == HYB_LOCAL )
|
||||
retro_vfs_file_prefetch_impl( (libretro_vfs_implementation_file *)f->h, offset, len );
|
||||
}
|
||||
|
||||
static struct retro_vfs_interface hyb_iface = {
|
||||
/* v1 */
|
||||
hyb_get_path, hyb_open, hyb_close, hyb_size, hyb_tell, hyb_seek,
|
||||
hyb_read, hyb_write, hyb_flush, hyb_remove, hyb_rename,
|
||||
/* v2 */
|
||||
hyb_truncate,
|
||||
/* v3 */
|
||||
hyb_stat, hyb_mkdir, hyb_opendir, hyb_readdir,
|
||||
hyb_dirent_get_name, hyb_dirent_is_dir, hyb_closedir,
|
||||
/* v4 */
|
||||
hyb_stat_64,
|
||||
/* v5 */
|
||||
hyb_set_readonly, hyb_get_mtime, hyb_set_mtime,
|
||||
hyb_copy_begin, hyb_copy_step, hyb_copy_close, hyb_dirent_stat
|
||||
};
|
||||
|
||||
void vfs_hybrid_init( retro_environment_t env_cb, retro_log_printf_t log ) {
|
||||
struct retro_vfs_interface_info info;
|
||||
|
||||
info.required_interface_version = 5;
|
||||
info.iface = NULL;
|
||||
if ( !env_cb( RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &info ) || !info.iface ) {
|
||||
info.required_interface_version = 4;
|
||||
info.iface = NULL;
|
||||
}
|
||||
if ( !info.iface && ( !env_cb( RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &info ) || !info.iface ) ) {
|
||||
info.required_interface_version = 3;
|
||||
info.iface = NULL;
|
||||
}
|
||||
if ( !info.iface && ( !env_cb( RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &info ) || !info.iface ) ) {
|
||||
info.required_interface_version = 1;
|
||||
info.iface = NULL;
|
||||
if ( !env_cb( RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &info ) || !info.iface ) {
|
||||
/* no frontend VFS: keep filestream's direct local path,
|
||||
zero added indirection - today's exact behavior */
|
||||
return;
|
||||
}
|
||||
}
|
||||
hyb_front = info.iface;
|
||||
hyb_front_version = info.required_interface_version;
|
||||
|
||||
{
|
||||
struct retro_vfs_interface_info ours;
|
||||
ours.required_interface_version = 5;
|
||||
ours.iface = &hyb_iface;
|
||||
filestream_vfs_init( &ours );
|
||||
path_vfs_init( &ours );
|
||||
dirent_vfs_init( &ours );
|
||||
}
|
||||
filestream_set_mapped_ptr_cb( hyb_mapped_ptr );
|
||||
filestream_set_prefetch_cb( hyb_prefetch );
|
||||
|
||||
if ( log )
|
||||
log( RETRO_LOG_INFO,
|
||||
"[vfs] hybrid: local-first with frontend v%u fallback%s\n",
|
||||
hyb_front_version, HYB_SANDBOXED ? " (sandboxed platform: full fallback)" : " (URI paths only)" );
|
||||
}
|
||||
+4458
File diff suppressed because it is too large.
Load diff
+17
-2
@@ -137,17 +137,30 @@ LIBRETRO_COMMON_SOURCES_CXX :=
|
||||
LIBRETRO_COMMON_SOURCES_ASM :=
|
||||
LIBRETRO_COMMON_SOURCES_OBJCXX :=
|
||||
LIBRETRO_COMMON_SOURCES_C += \
|
||||
$(CORE_DIR)/Externals/libretro-common/compat/compat_posix_string.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/compat/compat_strcasestr.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/compat/compat_strl.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/compat/fopen_utf8.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/encodings/encoding_utf.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/features/features_cpu.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/file/file_path.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/file/file_path_io.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/file/retro_dirent.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/gfx/shaders/rslc_dxbc.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/gfx/shaders/rslc_emit.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/gfx/shaders/rslc_front.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/gfx/shaders/rslc_ir.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/memmap/memmap.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/queues/retro_spsc.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/queues/retro_waitable_spsc.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/rthreads/retro_eventcount.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/rthreads/rthreads.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/time/rtime.c
|
||||
$(CORE_DIR)/Externals/libretro-common/streams/file_stream.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/string/rstrtod.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/string/stdstring.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/time/rtime.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/vfs/vfs_hybrid.c \
|
||||
$(CORE_DIR)/Externals/libretro-common/vfs/vfs_implementation.c
|
||||
|
||||
# FATFS
|
||||
FATFS_SOURCES_C :=
|
||||
@@ -1077,7 +1090,6 @@ DOLPHIN_SOURCES_CXX += \
|
||||
$(CORE_DIR)/Source/Core/Common/GL/GLContext.cpp \
|
||||
$(CORE_DIR)/Source/Core/Common/GL/GLUtil.cpp \
|
||||
$(CORE_DIR)/Source/Core/Common/GL/GLExtensions/GLExtensions.cpp \
|
||||
$(CORE_DIR)/Source/Core/DolphinLibretro/Common/VFile.cpp \
|
||||
$(CORE_DIR)/Source/Core/DolphinLibretro/Common/Globals.cpp \
|
||||
$(CORE_DIR)/Source/Core/DolphinLibretro/Common/Options.cpp \
|
||||
$(CORE_DIR)/Source/Core/DolphinLibretro/VideoContexts/GLContextLR.cpp \
|
||||
@@ -1807,6 +1819,9 @@ FATFS_FLAGS := -w -I$(EXT_DIR)/FatFs
|
||||
# RSLC_EXTENDED: rslc takes the stages and types the core's shaders use.
|
||||
LIBRETRO_COMMON_FLAGS := -DHAVE_THREADS -DHAVE_THREAD_STORAGE -DRSLC_EXTENDED \
|
||||
-I$(EXT_DIR)/libretro-common/include
|
||||
ifneq ($(OS_WIN),1)
|
||||
LIBRETRO_COMMON_FLAGS += -DHAVE_MMAP
|
||||
endif
|
||||
|
||||
|
||||
LZO_FLAGS := -w -I$(EXT_DIR)/LZO
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#include "Common/CPUDetect.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
@@ -106,26 +106,28 @@ static bool Read_ID_AA64MMFR1_EL1(u64* value)
|
||||
|
||||
static bool ReadDeviceTree(std::string* value, const std::string& name)
|
||||
{
|
||||
const std::string path = std::string("/proc/device-tree/") + name;
|
||||
std::ifstream file;
|
||||
File::OpenFStream(file, path.c_str(), std::ios_base::in);
|
||||
if (!file)
|
||||
File::LineReader file(std::string("/proc/device-tree/") + name);
|
||||
const char* text;
|
||||
size_t length;
|
||||
size_t end = 0;
|
||||
if (!file.Next(&text, &length))
|
||||
return false;
|
||||
|
||||
file >> *value;
|
||||
// the first word, as a stream extraction takes it
|
||||
while (end < length && text[end] && text[end] != ' ' && text[end] != '\t')
|
||||
end++;
|
||||
value->assign(text, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string ReadCpuinfoField(const std::string& field)
|
||||
{
|
||||
std::string line;
|
||||
std::ifstream file;
|
||||
File::OpenFStream(file, "/proc/cpuinfo", std::ios_base::in);
|
||||
if (!file)
|
||||
return {};
|
||||
|
||||
while (std::getline(file, line))
|
||||
File::LineReader file("/proc/cpuinfo");
|
||||
const char* text;
|
||||
size_t length;
|
||||
while (file.Next(&text, &length))
|
||||
{
|
||||
const std::string line(text, length);
|
||||
if (!line.starts_with(field))
|
||||
continue;
|
||||
auto non_tab = line.find_first_not_of("\t", field.length());
|
||||
@@ -143,13 +145,13 @@ static std::string ReadCpuinfoField(const std::string& field)
|
||||
|
||||
static bool Read_MIDR_EL1_Sysfs(u64* value)
|
||||
{
|
||||
std::ifstream file;
|
||||
File::OpenFStream(file, "/sys/devices/system/cpu/cpu0/regs/identification/midr_el1",
|
||||
std::ios_base::in);
|
||||
if (!file)
|
||||
File::LineReader file("/sys/devices/system/cpu/cpu0/regs/identification/midr_el1");
|
||||
const char* text;
|
||||
size_t length;
|
||||
if (!file.Next(&text, &length))
|
||||
return false;
|
||||
|
||||
file >> std::hex >> *value;
|
||||
*value = std::strtoull(std::string(text, length).c_str(), nullptr, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,6 @@
|
||||
#define unlink _unlink
|
||||
#define vscprintf _vscprintf
|
||||
|
||||
// 64 bit offsets for Windows
|
||||
#define fseeko _fseeki64
|
||||
#define ftello _ftelli64
|
||||
#define atoll _atoi64
|
||||
#ifndef __MINGW32__
|
||||
#define stat _stat64
|
||||
|
||||
@@ -3,38 +3,13 @@
|
||||
|
||||
#include "Common/DirectIOFile.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Buffer.h"
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#else
|
||||
#include <string>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef ANDROID
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
|
||||
#include "Common/Lazy.h"
|
||||
#endif
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#endif
|
||||
#include <streams/file_stream.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
#include "DolphinLibretro/Common/VFile.h"
|
||||
#endif
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
namespace File
|
||||
{
|
||||
@@ -74,6 +49,9 @@ DirectIOFile::DirectIOFile(const std::string& path, AccessMode access_mode, Open
|
||||
|
||||
bool DirectIOFile::Open(const std::string& path, AccessMode access_mode, OpenMode open_mode)
|
||||
{
|
||||
unsigned mode;
|
||||
int64_t map_len = 0;
|
||||
|
||||
ASSERT(!IsOpen());
|
||||
|
||||
if (open_mode == OpenMode::Default)
|
||||
@@ -83,224 +61,86 @@ bool DirectIOFile::Open(const std::string& path, AccessMode access_mode, OpenMod
|
||||
if (access_mode == AccessMode::Read && open_mode == OpenMode::Truncate)
|
||||
return false;
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
if (open_mode == OpenMode::Existing || open_mode == OpenMode::Create)
|
||||
{
|
||||
// used in Duplicate() later
|
||||
m_path = path;
|
||||
|
||||
return Libretro::VFile::Open(path, access_mode, open_mode,
|
||||
m_vfs_handle, m_mode, m_hints, m_current_offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
DWORD desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
if (access_mode == AccessMode::Read)
|
||||
desired_access = GENERIC_READ;
|
||||
else if (access_mode == AccessMode::Write)
|
||||
desired_access = GENERIC_WRITE;
|
||||
|
||||
if (access_mode != AccessMode::Read)
|
||||
{
|
||||
// Allow deleting and renaming through our handle.
|
||||
desired_access |= DELETE;
|
||||
}
|
||||
|
||||
// All sharing is allowed to more closely match default behavior on other OSes.
|
||||
constexpr DWORD share_mode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE;
|
||||
|
||||
DWORD creation_disposition = OPEN_ALWAYS;
|
||||
if (open_mode == OpenMode::Truncate)
|
||||
creation_disposition = CREATE_ALWAYS;
|
||||
else if (open_mode == OpenMode::Create)
|
||||
creation_disposition = CREATE_NEW;
|
||||
else if (open_mode == OpenMode::Existing)
|
||||
creation_disposition = OPEN_EXISTING;
|
||||
|
||||
m_handle = CreateFile(UTF8ToTStr(path).c_str(), desired_access, share_mode, nullptr,
|
||||
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (!IsOpen())
|
||||
WARN_LOG_FMT(COMMON, "CreateFile: {}", Common::GetLastErrorString());
|
||||
|
||||
#else
|
||||
#if defined(ANDROID)
|
||||
if (IsPathAndroidContent(path))
|
||||
{
|
||||
// Android documentation says that "w" may or may not truncate.
|
||||
// In case it does, we'll use "rw" when we don't want truncation.
|
||||
// This wrongly enables read access when we don't need it.
|
||||
if (access_mode == AccessMode::Write && open_mode != OpenMode::Truncate)
|
||||
access_mode = AccessMode::ReadAndWrite;
|
||||
|
||||
std::string open_mode_str = "rw";
|
||||
if (access_mode == AccessMode::Read)
|
||||
open_mode_str = "r";
|
||||
else if (access_mode == AccessMode::Write)
|
||||
open_mode_str = "w";
|
||||
|
||||
if (open_mode == OpenMode::Truncate)
|
||||
open_mode_str += 't';
|
||||
|
||||
if (open_mode == OpenMode::Create)
|
||||
{
|
||||
ASSERT_MSG(COMMON, false, "DirectIOFile doesn't support creating SAF files");
|
||||
const bool exists = filestream_exists(path.c_str());
|
||||
if (exists != (open_mode == OpenMode::Existing))
|
||||
return false;
|
||||
}
|
||||
|
||||
m_fd = OpenAndroidContent(path, open_mode_str);
|
||||
|
||||
if (!IsOpen() && (open_mode == OpenMode::Always || open_mode == OpenMode::Truncate))
|
||||
ASSERT_MSG(COMMON, Exists(path), "DirectIOFile doesn't support creating SAF files");
|
||||
|
||||
return IsOpen();
|
||||
}
|
||||
#endif
|
||||
int flags = O_RDWR;
|
||||
|
||||
if (access_mode == AccessMode::Read)
|
||||
flags = O_RDONLY;
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ;
|
||||
else if (access_mode == AccessMode::Write)
|
||||
flags = O_WRONLY;
|
||||
mode = RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
else
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ_WRITE;
|
||||
if (open_mode != OpenMode::Truncate && mode != RETRO_VFS_FILE_ACCESS_READ)
|
||||
mode |= RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
|
||||
|
||||
if (open_mode == OpenMode::Truncate)
|
||||
flags |= O_CREAT | O_TRUNC;
|
||||
else if (open_mode == OpenMode::Create)
|
||||
flags |= O_CREAT | O_EXCL;
|
||||
else if (open_mode != OpenMode::Existing)
|
||||
flags |= O_CREAT;
|
||||
m_file = filestream_open(path.c_str(), mode,
|
||||
mode == RETRO_VFS_FILE_ACCESS_READ ?
|
||||
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS :
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!m_file)
|
||||
return false;
|
||||
|
||||
m_fd = open(path.c_str(), flags, 0666);
|
||||
|
||||
#endif
|
||||
|
||||
return IsOpen();
|
||||
m_path = path;
|
||||
m_mode = mode;
|
||||
m_current_offset = 0;
|
||||
m_map = filestream_get_mapped_ptr(m_file, &map_len);
|
||||
m_map_size = m_map ? u64(map_len) : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectIOFile::Close()
|
||||
{
|
||||
if (!IsOpen())
|
||||
RFILE* file = m_file;
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
m_file = nullptr;
|
||||
m_map = nullptr;
|
||||
m_map_size = 0;
|
||||
m_current_offset = 0;
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
int result = Libretro::VFile::Close(m_vfs_handle);
|
||||
m_vfs_handle = nullptr;
|
||||
return result == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
return CloseHandle(std::exchange(m_handle, INVALID_HANDLE_VALUE)) != 0;
|
||||
#else
|
||||
return close(std::exchange(m_fd, -1)) == 0;
|
||||
#endif
|
||||
return filestream_close(file) == 0;
|
||||
}
|
||||
|
||||
bool DirectIOFile::IsOpen() const
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return m_vfs_handle != nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
return m_handle != INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
return m_fd != -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
template <auto* TransferFunc>
|
||||
static bool OverlappedTransfer(HANDLE handle, u64 offset, auto* data_ptr, u64 size)
|
||||
{
|
||||
// ReadFile/WriteFile take a 32bit size so we must loop to handle our 64bit size.
|
||||
while (true)
|
||||
{
|
||||
OVERLAPPED overlapped{};
|
||||
overlapped.Offset = DWORD(offset);
|
||||
overlapped.OffsetHigh = DWORD(offset >> 32);
|
||||
|
||||
DWORD bytes_transferred{};
|
||||
if (TransferFunc(handle, data_ptr, MathUtil::SaturatingCast<DWORD>(size), &bytes_transferred,
|
||||
&overlapped) == 0)
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "OverlappedTransfer: {}", Common::GetLastErrorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
size -= bytes_transferred;
|
||||
|
||||
if (size == 0)
|
||||
return true;
|
||||
|
||||
offset += bytes_transferred;
|
||||
data_ptr += bytes_transferred;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool DirectIOFile::OffsetRead(u64 offset, u8* out_ptr, u64 size)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
const u8* src = View(offset, size);
|
||||
if (src)
|
||||
{
|
||||
if (Libretro::VFile::Seek(m_vfs_handle, offset, SEEK_SET) < 0)
|
||||
return false;
|
||||
|
||||
int64_t bytes_read = Libretro::VFile::ReadBytes(m_vfs_handle, out_ptr, size);
|
||||
return bytes_read == static_cast<int64_t>(size);
|
||||
std::memcpy(out_ptr, src, size);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
return OverlappedTransfer<ReadFile>(m_handle, offset, out_ptr, size);
|
||||
#else
|
||||
return pread(m_fd, out_ptr, size, off_t(offset)) == ssize_t(size);
|
||||
#endif
|
||||
if (!m_file || m_map)
|
||||
return false;
|
||||
return filestream_seek(m_file, s64(offset), RETRO_VFS_SEEK_POSITION_START) >= 0 &&
|
||||
filestream_read(m_file, out_ptr, s64(size)) == s64(size);
|
||||
}
|
||||
|
||||
bool DirectIOFile::OffsetWrite(u64 offset, const u8* in_ptr, u64 size)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (Libretro::VFile::Seek(m_vfs_handle, offset, SEEK_SET) < 0)
|
||||
return false;
|
||||
if (!m_file)
|
||||
return false;
|
||||
return filestream_seek(m_file, s64(offset), RETRO_VFS_SEEK_POSITION_START) >= 0 &&
|
||||
filestream_write(m_file, in_ptr, s64(size)) == s64(size);
|
||||
}
|
||||
|
||||
int64_t bytes_written = Libretro::VFile::WriteBytes(m_vfs_handle, in_ptr, size);
|
||||
return bytes_written == static_cast<int64_t>(size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
return OverlappedTransfer<WriteFile>(m_handle, offset, in_ptr, size);
|
||||
#else
|
||||
return pwrite(m_fd, in_ptr, size, off_t(offset)) == ssize_t(size);
|
||||
#endif
|
||||
void DirectIOFile::Prefetch(u64 offset, u64 size) const
|
||||
{
|
||||
if (m_file)
|
||||
filestream_prefetch(m_file, offset, size);
|
||||
}
|
||||
|
||||
u64 DirectIOFile::GetSize() const
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return Libretro::VFile::GetSize(m_vfs_handle);
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
LARGE_INTEGER result{};
|
||||
if (GetFileSizeEx(m_handle, &result) != 0)
|
||||
return result.QuadPart;
|
||||
#else
|
||||
struct stat st{};
|
||||
if (fstat(m_fd, &st) == 0)
|
||||
return st.st_size;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
int64_t size;
|
||||
if (m_map)
|
||||
return m_map_size;
|
||||
if (!m_file)
|
||||
return 0;
|
||||
size = filestream_get_size(m_file);
|
||||
return size > 0 ? u64(size) : 0;
|
||||
}
|
||||
|
||||
bool DirectIOFile::Seek(s64 offset, SeekOrigin origin)
|
||||
@@ -331,146 +171,60 @@ bool DirectIOFile::Seek(s64 offset, SeekOrigin origin)
|
||||
|
||||
bool DirectIOFile::Flush()
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return Libretro::VFile::Flush(m_vfs_handle) == 0;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
return FlushFileBuffers(m_handle) != 0;
|
||||
#else
|
||||
return fsync(m_fd) == 0;
|
||||
#endif
|
||||
return m_file && filestream_flush(m_file) == 0;
|
||||
}
|
||||
|
||||
void DirectIOFile::Swap(DirectIOFile& other)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
std::swap(m_vfs_handle, other.m_vfs_handle);
|
||||
std::swap(m_path, other.m_path);
|
||||
std::swap(m_mode, other.m_mode);
|
||||
std::swap(m_hints, other.m_hints);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::swap(m_handle, other.m_handle);
|
||||
#else
|
||||
std::swap(m_fd, other.m_fd);
|
||||
#endif
|
||||
std::swap(m_path, other.m_path);
|
||||
std::swap(m_file, other.m_file);
|
||||
std::swap(m_map, other.m_map);
|
||||
std::swap(m_map_size, other.m_map_size);
|
||||
std::swap(m_current_offset, other.m_current_offset);
|
||||
std::swap(m_mode, other.m_mode);
|
||||
}
|
||||
|
||||
DirectIOFile DirectIOFile::Duplicate() const
|
||||
{
|
||||
DirectIOFile result;
|
||||
int64_t map_len = 0;
|
||||
|
||||
if (!IsOpen())
|
||||
return result;
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
// We need to reopen the file, since VFS handles cannot be duplicated.
|
||||
result.m_vfs_handle = Libretro::VFile::Open(
|
||||
m_path, m_mode, m_hints);
|
||||
|
||||
if (result.m_vfs_handle)
|
||||
{
|
||||
result.m_path = m_path;
|
||||
result.m_mode = m_mode;
|
||||
result.m_hints = m_hints;
|
||||
|
||||
// Restore offset if possible
|
||||
Libretro::VFile::Seek(
|
||||
result.m_vfs_handle,
|
||||
m_current_offset,
|
||||
SEEK_SET);
|
||||
result.m_current_offset = m_current_offset;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
const auto current_process = GetCurrentProcess();
|
||||
if (DuplicateHandle(current_process, m_handle, current_process, &result.m_handle, 0, FALSE,
|
||||
DUPLICATE_SAME_ACCESS) == 0)
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "DuplicateHandle: {}", Common::GetLastErrorString());
|
||||
}
|
||||
#else
|
||||
result.m_fd = dup(m_fd);
|
||||
#endif
|
||||
|
||||
// a handle of its own, never truncating what this one holds
|
||||
result.m_file = filestream_open(
|
||||
m_path.c_str(),
|
||||
m_mode == RETRO_VFS_FILE_ACCESS_READ ? m_mode :
|
||||
m_mode | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
|
||||
m_mode == RETRO_VFS_FILE_ACCESS_READ ? RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS :
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
ASSERT(result.IsOpen());
|
||||
if (!result.m_file)
|
||||
return result;
|
||||
|
||||
result.m_path = m_path;
|
||||
result.m_mode = m_mode;
|
||||
result.m_current_offset = m_current_offset;
|
||||
|
||||
result.m_map = filestream_get_mapped_ptr(result.m_file, &map_len);
|
||||
result.m_map_size = result.m_map ? u64(map_len) : 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Resize(DirectIOFile& file, u64 size)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return Libretro::VFile::Truncate(file.m_vfs_handle, size) == 0;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
// This operation is not "atomic", but it's the only thing we're using the file pointer for.
|
||||
// Concurrent `Resize` would need some external synchronization to prevent race regardless.
|
||||
const LARGE_INTEGER distance{.QuadPart = LONGLONG(size)};
|
||||
return (SetFilePointerEx(file.GetHandle(), distance, nullptr, FILE_BEGIN) != 0) &&
|
||||
(SetEndOfFile(file.GetHandle()) != 0);
|
||||
#else
|
||||
return ftruncate(file.GetHandle(), off_t(size)) == 0;
|
||||
#endif
|
||||
return file.m_file && filestream_truncate(file.m_file, s64(size)) == 0;
|
||||
}
|
||||
|
||||
bool Rename(DirectIOFile& file, const std::string& source_path [[maybe_unused]],
|
||||
bool Rename(DirectIOFile& file, const std::string& source_path,
|
||||
const std::string& destination_path)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return file.IsOpen() && Libretro::VFile::Rename(source_path, destination_path);
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
const auto dest_name = UTF8ToWString(destination_path);
|
||||
const auto dest_name_byte_size = DWORD(dest_name.size() * sizeof(WCHAR));
|
||||
FILE_RENAME_INFO info{
|
||||
.ReplaceIfExists = TRUE,
|
||||
.FileNameLength = dest_name_byte_size, // The size in bytes, not including null termination.
|
||||
};
|
||||
constexpr auto filename_struct_offset = offsetof(FILE_RENAME_INFO, FileName);
|
||||
Common::UniqueBuffer<u8> buffer(filename_struct_offset + dest_name_byte_size + sizeof(WCHAR));
|
||||
std::memcpy(buffer.data(), &info, filename_struct_offset);
|
||||
std::memcpy(buffer.data() + filename_struct_offset, dest_name.c_str(),
|
||||
dest_name_byte_size + sizeof(WCHAR));
|
||||
return SetFileInformationByHandle(file.GetHandle(), FileRenameInfo, buffer.data(),
|
||||
DWORD(buffer.size())) != 0;
|
||||
#else
|
||||
return file.IsOpen() && Rename(source_path, destination_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Delete(DirectIOFile& file, const std::string& filename)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return file.IsOpen() && Libretro::VFile::Delete(filename);
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
FILE_DISPOSITION_INFO info{.DeleteFile = TRUE};
|
||||
return SetFileInformationByHandle(file.GetHandle(), FileDispositionInfo, &info, sizeof(info)) !=
|
||||
0;
|
||||
#else
|
||||
return file.IsOpen() && Delete(filename, IfAbsentBehavior::NoConsoleWarning);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace File
|
||||
@@ -39,16 +39,18 @@ enum class OpenMode
|
||||
Create,
|
||||
};
|
||||
|
||||
// This file wrapper avoids use of the underlying system file position.
|
||||
// It keeps track of its own file position and read/write calls directly use it.
|
||||
// This makes copied handles entirely thread safe.
|
||||
// A file read and written at offsets of its own keeping, through
|
||||
// libretro-common's filestream. A file opened for reading alone is
|
||||
// memory-mapped where the platform can: OffsetRead() then copies straight
|
||||
// out of the mapping, View() lends from it without copying, and both are
|
||||
// thread safe on one object. Otherwise one object is one thread's; copies
|
||||
// open handles of their own.
|
||||
class DirectIOFile final
|
||||
{
|
||||
public:
|
||||
DirectIOFile();
|
||||
~DirectIOFile();
|
||||
|
||||
// Copies and moves are allowed.
|
||||
DirectIOFile(const DirectIOFile&);
|
||||
DirectIOFile& operator=(const DirectIOFile&);
|
||||
DirectIOFile(DirectIOFile&&);
|
||||
@@ -62,12 +64,9 @@ public:
|
||||
|
||||
bool Close();
|
||||
|
||||
bool IsOpen() const;
|
||||
bool IsOpen() const { return m_file != nullptr; }
|
||||
|
||||
// An offset from the start of the file may be specified directly.
|
||||
// These explicit offset versions entirely ignore the current file position.
|
||||
// They are thread safe, even when used on the same object.
|
||||
|
||||
bool OffsetRead(u64 offset, u8* out_ptr, u64 size);
|
||||
bool OffsetRead(u64 offset, std::span<u8> out_data)
|
||||
{
|
||||
@@ -79,6 +78,17 @@ public:
|
||||
return OffsetWrite(offset, in_data.data(), in_data.size());
|
||||
}
|
||||
|
||||
// size octets at offset, lent from the mapping: nullptr when the file is not
|
||||
// mapped or the range runs past its end. Valid until the file is closed.
|
||||
const u8* View(u64 offset, u64 size) const
|
||||
{
|
||||
return (m_map && offset <= m_map_size && size <= m_map_size - offset) ? m_map + offset :
|
||||
nullptr;
|
||||
}
|
||||
|
||||
// Asks the OS to start reading [offset, offset + size) in; returns at once.
|
||||
void Prefetch(u64 offset, u64 size) const;
|
||||
|
||||
// These Read/Write functions advance the current position on success.
|
||||
|
||||
bool Read(u8* out_ptr, u64 size)
|
||||
@@ -109,56 +119,25 @@ public:
|
||||
|
||||
bool Flush();
|
||||
|
||||
auto GetHandle() const
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
ERROR_LOG_FMT(COMMON, "VFS: Attempt to use std::FILE in DirectIO which should not happen in VFS mode");
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
return m_handle;
|
||||
#else
|
||||
return m_fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void Swap(DirectIOFile& other);
|
||||
DirectIOFile Duplicate() const;
|
||||
|
||||
#if defined(_WIN32)
|
||||
// A workaround to avoid including <windows.h> in this header.
|
||||
// HANDLE is just void* and INVALID_HANDLE_VALUE is -1.
|
||||
using HandleType = void*;
|
||||
HandleType m_handle{HandleType(-1)};
|
||||
#else
|
||||
using HandleType = int;
|
||||
HandleType m_fd{-1};
|
||||
#endif
|
||||
|
||||
u64 m_current_offset{};
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
retro_vfs_file_handle* m_vfs_handle = nullptr;
|
||||
std::string m_path;
|
||||
unsigned m_mode{};
|
||||
unsigned m_hints{};
|
||||
RFILE* m_file = nullptr;
|
||||
const u8* m_map = nullptr;
|
||||
u64 m_map_size = 0;
|
||||
u64 m_current_offset = 0;
|
||||
unsigned m_mode = 0;
|
||||
|
||||
friend bool Resize(DirectIOFile& file, u64 size);
|
||||
#endif
|
||||
};
|
||||
|
||||
// These take an open file handle to avoid failures from other processes trying to open our files.
|
||||
// This is mainly an issue on Windows.
|
||||
|
||||
bool Resize(DirectIOFile& file, u64 size);
|
||||
|
||||
// Attempts to replace a destination file if one already exists.
|
||||
// Note: Windows uses `file`. Elsewhere uses `source_path`, but `file` is checked for openness.
|
||||
bool Rename(DirectIOFile& file, const std::string& source_path,
|
||||
const std::string& destination_path);
|
||||
// Note: Ditto, only Windows actually uses the file handle. Provide both.
|
||||
bool Delete(DirectIOFile& file, const std::string& filename);
|
||||
|
||||
} // namespace File
|
||||
@@ -4,104 +4,94 @@
|
||||
#include "Common/FileSearch.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
#include <cstring>
|
||||
|
||||
#include <retro_dirent.h>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elifdef ANDROID
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
static bool EndsWithNoCase(const char* name, size_t name_len, std::string_view ext)
|
||||
{
|
||||
size_t i;
|
||||
if (name_len < ext.size())
|
||||
return false;
|
||||
name += name_len - ext.size();
|
||||
for (i = 0; i < ext.size(); i++)
|
||||
{
|
||||
char a = name[i];
|
||||
char b = ext[i];
|
||||
if (a >= 'A' && a <= 'Z')
|
||||
a += 'a' - 'A';
|
||||
if (b >= 'A' && b <= 'Z')
|
||||
b += 'a' - 'A';
|
||||
if (a != b)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool MatchesAny(const char* name, size_t name_len, std::span<const std::string_view> exts)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < exts.size(); i++)
|
||||
if (EndsWithNoCase(name, name_len, exts[i]))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void Search(const std::string& directory, std::span<const std::string_view> exts,
|
||||
bool recursive, std::vector<std::string>* result)
|
||||
{
|
||||
struct RDIR* dir = retro_opendir_include_hidden(directory.c_str(), true);
|
||||
if (!dir)
|
||||
return;
|
||||
while (retro_readdir(dir))
|
||||
{
|
||||
const char* name = retro_dirent_get_name(dir);
|
||||
size_t name_len;
|
||||
bool is_dir;
|
||||
if (!name || (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))))
|
||||
continue;
|
||||
name_len = std::strlen(name);
|
||||
is_dir = retro_dirent_is_dir(dir, nullptr);
|
||||
|
||||
std::string path = directory;
|
||||
if (!path.empty() && path.back() != DIR_SEP_CHR && path.back() != '\\')
|
||||
path += DIR_SEP_CHR;
|
||||
path.append(name, name_len);
|
||||
|
||||
if (exts.empty() || (!is_dir && MatchesAny(name, name_len, exts)))
|
||||
result->push_back(path);
|
||||
if (is_dir && recursive)
|
||||
Search(path, exts, true, result);
|
||||
}
|
||||
retro_closedir(dir);
|
||||
}
|
||||
|
||||
std::vector<std::string> DoFileSearch(std::span<const std::string_view> directories,
|
||||
std::span<const std::string_view> exts, bool recursive)
|
||||
{
|
||||
const bool accept_all = exts.empty();
|
||||
|
||||
std::vector<fs::path> native_exts;
|
||||
for (const auto& ext : exts)
|
||||
native_exts.push_back(StringToPath(ext));
|
||||
|
||||
// N.B. This avoids doing any copies
|
||||
auto ext_matches = [&native_exts](const fs::path& path) {
|
||||
const std::basic_string_view<fs::path::value_type> native_path = path.native();
|
||||
return std::ranges::any_of(native_exts, [&native_path](const auto& ext) {
|
||||
const auto compare_len = ext.native().length();
|
||||
if (native_path.length() < compare_len)
|
||||
return false;
|
||||
const auto substr_to_compare = native_path.substr(native_path.length() - compare_len);
|
||||
#ifdef _WIN32
|
||||
return CompareStringOrdinal(substr_to_compare.data(), static_cast<int>(compare_len),
|
||||
ext.c_str(), static_cast<int>(compare_len), TRUE) == CSTR_EQUAL;
|
||||
#else
|
||||
return strncasecmp(substr_to_compare.data(), ext.c_str(), compare_len) == 0;
|
||||
#endif
|
||||
});
|
||||
};
|
||||
|
||||
std::vector<std::string> result;
|
||||
auto add_filtered = [&](const fs::directory_entry& entry) {
|
||||
auto& path = entry.path();
|
||||
if (accept_all || (!entry.is_directory() && ext_matches(path)))
|
||||
result.emplace_back(PathToString(path));
|
||||
};
|
||||
for (const auto& directory : directories)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
if (IsPathAndroidContent(directory))
|
||||
{
|
||||
const std::vector<std::string> partial_result =
|
||||
DoFileSearchAndroidContent(directory, exts, recursive);
|
||||
|
||||
result.insert(result.end(), std::make_move_iterator(partial_result.begin()),
|
||||
std::make_move_iterator(partial_result.end()));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
fs::path directory_path = StringToPath(directory);
|
||||
std::error_code error;
|
||||
if (recursive)
|
||||
{
|
||||
for (auto it = fs::recursive_directory_iterator(directory_path, error);
|
||||
it != fs::recursive_directory_iterator(); it.increment(error))
|
||||
add_filtered(*it);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto it = fs::directory_iterator(directory_path, error);
|
||||
it != fs::directory_iterator(); it.increment(error))
|
||||
add_filtered(*it);
|
||||
}
|
||||
if (error)
|
||||
ERROR_LOG_FMT(COMMON, "{} error on {}: {}", __func__, directory, error.message());
|
||||
}
|
||||
std::string dir(directory);
|
||||
while (dir.size() > 1 && (dir.back() == DIR_SEP_CHR || dir.back() == '\\'))
|
||||
dir.pop_back();
|
||||
Search(dir, exts, recursive, &result);
|
||||
}
|
||||
|
||||
// Remove duplicates (occurring because caller gave e.g. duplicate or overlapping directories -
|
||||
// not because std::filesystem returns duplicates). Also note that this pathname-based uniqueness
|
||||
// isn't as thorough as std::filesystem::equivalent.
|
||||
// Remove duplicates (the caller gave e.g. duplicate or overlapping directories).
|
||||
std::ranges::sort(result);
|
||||
const auto unique_result = std::ranges::unique(result);
|
||||
result.erase(unique_result.begin(), unique_result.end());
|
||||
|
||||
// Dolphin expects to be able to use "/" (DIR_SEP) everywhere.
|
||||
// std::filesystem uses the OS separator.
|
||||
constexpr fs::path::value_type os_separator = fs::path::preferred_separator;
|
||||
static_assert(os_separator == DIR_SEP_CHR || os_separator == '\\', "Unsupported path separator");
|
||||
if constexpr (os_separator != DIR_SEP_CHR)
|
||||
{
|
||||
for (auto& path : result)
|
||||
std::ranges::replace(path, '\\', DIR_SEP_CHR);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
for (auto& path : result)
|
||||
std::ranges::replace(path, '\\', DIR_SEP_CHR);
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+261
-746
File diff suppressed because it is too large.
Load diff
@@ -3,23 +3,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Common/StringUtil.h"
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include "Common/StringUtil.h"
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#endif
|
||||
#include "Common/IOFile.h"
|
||||
|
||||
// User directory indices for GetUserPath
|
||||
enum
|
||||
@@ -112,10 +101,7 @@ struct FSTEntry
|
||||
std::vector<FSTEntry> children;
|
||||
};
|
||||
|
||||
// The functions in this class are functionally identical to the standalone functions
|
||||
// below, but if you are going to be calling more than one of the functions using the
|
||||
// same path, creating a single FileInfo object and calling its functions multiple
|
||||
// times is faster than calling standalone functions multiple times.
|
||||
// What a path is, found once for the queries a caller makes of it.
|
||||
class FileInfo final
|
||||
{
|
||||
public:
|
||||
@@ -123,18 +109,18 @@ public:
|
||||
explicit FileInfo(const char* path);
|
||||
|
||||
// Returns true if the path exists
|
||||
bool Exists() const;
|
||||
bool Exists() const { return m_exists; }
|
||||
// Returns true if the path exists and is a directory
|
||||
bool IsDirectory() const;
|
||||
bool IsDirectory() const { return m_is_directory; }
|
||||
// Returns true if the path exists and is a file
|
||||
bool IsFile() const;
|
||||
bool IsFile() const { return m_exists && !m_is_directory; }
|
||||
// Returns the size of a file (or returns 0 if the path doesn't refer to a file)
|
||||
u64 GetSize() const;
|
||||
u64 GetSize() const { return m_size; }
|
||||
|
||||
private:
|
||||
std::filesystem::file_status m_status;
|
||||
std::uintmax_t m_size;
|
||||
bool m_exists;
|
||||
u64 m_size = 0;
|
||||
bool m_exists = false;
|
||||
bool m_is_directory = false;
|
||||
};
|
||||
|
||||
// Returns true if the path exists
|
||||
@@ -149,9 +135,6 @@ bool IsFile(const std::string& path);
|
||||
// Returns the size of a file (or returns 0 if the path isn't a file that exists)
|
||||
u64 GetSize(const std::string& path);
|
||||
|
||||
// Overloaded GetSize, accepts FILE*
|
||||
u64 GetSize(FILE* f);
|
||||
|
||||
// Creates a single directory. Returns true if successful or if the path already exists.
|
||||
bool CreateDir(const std::string& filename);
|
||||
|
||||
@@ -181,10 +164,7 @@ bool DeleteDir(const std::string& filename,
|
||||
// renames file srcFilename to destFilename, returns true on success
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename);
|
||||
|
||||
// ditto, but syncs the source file and, on Unix, syncs the directories after rename
|
||||
bool RenameSync(const std::string& srcFilename, const std::string& destFilename);
|
||||
|
||||
// Copies a file at source_path to destination_path, as if by std::filesystem::copy_file().
|
||||
// Copies a file at source_path to destination_path.
|
||||
// If a file already exists at destination_path it is overwritten. Returns true on success.
|
||||
bool CopyRegularFile(std::string_view source_path, std::string_view destination_path);
|
||||
|
||||
@@ -197,11 +177,8 @@ FSTEntry ScanDirectoryTree(const std::string& directory, bool recursive);
|
||||
// deletes the given directory and anything under it. Returns true on success.
|
||||
bool DeleteDirRecursively(const std::string& directory);
|
||||
|
||||
// Returns the current directory
|
||||
std::string GetCurrentDir();
|
||||
|
||||
// Copies source_path to dest_path, as if by std::filesystem::copy(). Returns true on success or if
|
||||
// the source and destination are already the same (as determined by std::filesystem::equivalent()).
|
||||
// Copies source_path to dest_path, directories recursively. Returns true on success or if the
|
||||
// source and destination are the same path.
|
||||
bool Copy(std::string_view source_path, std::string_view dest_path,
|
||||
bool overwrite_existing = false);
|
||||
|
||||
@@ -212,12 +189,6 @@ bool Copy(std::string_view source_path, std::string_view dest_path,
|
||||
// completed.
|
||||
bool MoveWithOverwrite(std::string_view source_path, std::string_view dest_path);
|
||||
|
||||
// Set the current directory to given directory
|
||||
bool SetCurrentDir(const std::string& directory);
|
||||
|
||||
// Creates and returns the path to a new temporary directory.
|
||||
std::string CreateTempDir();
|
||||
|
||||
// Get a filename that can hopefully be atomically renamed to the given path.
|
||||
std::string GetTempFilenameForAtomicWrite(std::string path);
|
||||
|
||||
@@ -229,47 +200,45 @@ const std::string& GetUserPath(unsigned int dir_index);
|
||||
// Rebuilds internal directory structure to compensate for the new directory
|
||||
void SetUserPath(unsigned int dir_index, std::string path);
|
||||
|
||||
// probably doesn't belong here
|
||||
std::string GetThemeDir(const std::string& theme_name);
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
const std::string& GetSysDirectory();
|
||||
|
||||
#if defined(__LIBRETRO__) && !defined(ANDROID)
|
||||
void SetSysDirectory(const std::string& path);
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
void SetSysDirectory(const std::string& path);
|
||||
#ifdef ANDROID
|
||||
void SetGpuDriverDirectories(const std::string& path, const std::string& lib_path);
|
||||
const std::string GetGpuDriverDirectory(unsigned int dir_index);
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
std::string GetBundleDirectory();
|
||||
#endif
|
||||
// The lines of a file, one Next() at a time, without their line endings ("\n" or "\r\n").
|
||||
// Lines are lent from the file's mapping where it has one, else from a window read a block
|
||||
// at a time; each is valid until the next call.
|
||||
class LineReader final
|
||||
{
|
||||
public:
|
||||
explicit LineReader(const std::string& filename);
|
||||
bool IsOpen() const { return m_file.IsOpen(); }
|
||||
bool Next(const char** text, size_t* length);
|
||||
|
||||
std::string GetExePath();
|
||||
std::string GetExeDirectory();
|
||||
private:
|
||||
IOFile m_file;
|
||||
std::vector<char> m_window;
|
||||
const char* m_map = nullptr;
|
||||
size_t m_size = 0; // mapped length, or octets held in the window
|
||||
size_t m_pos = 0; // the next line's start
|
||||
bool m_eof = false;
|
||||
};
|
||||
|
||||
// path taken relative to directory, unless path is absolute.
|
||||
std::string JoinPath(const std::string& directory, const std::string& path);
|
||||
|
||||
// Calls line(ud, text, length) for each line of the file, without its line ending ("\n" or
|
||||
// "\r\n"). Lines are lent from the file's mapping where it has one, else from a window read
|
||||
// a block at a time; valid only during the call. False if the file cannot be opened.
|
||||
bool ForEachLine(const std::string& filename, void (*line)(void* ud, const char* text, size_t length),
|
||||
void* ud);
|
||||
|
||||
bool WriteStringToFile(const std::string& filename, std::string_view str);
|
||||
bool ReadFileToString(const std::string& filename, std::string& str);
|
||||
|
||||
// To deal with Windows not fully supporting UTF-8 and Android not fully supporting paths.
|
||||
template <typename T>
|
||||
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
fstream.open(UTF8ToTStr(filename).c_str(), openmode);
|
||||
#else
|
||||
#ifdef ANDROID
|
||||
// Unfortunately it seems like the non-standard __open is the only way to use a file descriptor
|
||||
if (IsPathAndroidContent(filename))
|
||||
fstream.__open(OpenAndroidContent(filename, OpenModeToAndroid(openmode)), openmode);
|
||||
else
|
||||
#endif
|
||||
fstream.open(filename.c_str(), openmode);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace File
|
||||
+129
-162
@@ -3,55 +3,58 @@
|
||||
|
||||
#include "Common/IOFile.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <share.h>
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <algorithm>
|
||||
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#endif
|
||||
#include <streams/file_stream.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
#include "DolphinLibretro/Common/VFile.h"
|
||||
#endif
|
||||
|
||||
namespace File
|
||||
{
|
||||
IOFile::IOFile() : m_file(nullptr), m_good(true)
|
||||
#ifdef __LIBRETRO__
|
||||
, m_vfs_handle{nullptr}
|
||||
#endif
|
||||
/* fopen() modes in filestream's terms. "a" positions at the end once,
|
||||
* at open: nothing in the tree mixes seeks with appends. */
|
||||
static unsigned ModeFromString(const char* openmode, bool* append)
|
||||
{
|
||||
bool read = false, write = false, plus = false;
|
||||
const char* p;
|
||||
*append = false;
|
||||
for (p = openmode; *p; p++)
|
||||
{
|
||||
switch (*p)
|
||||
{
|
||||
case 'r':
|
||||
read = true;
|
||||
break;
|
||||
case 'w':
|
||||
write = true;
|
||||
break;
|
||||
case 'a':
|
||||
*append = true;
|
||||
break;
|
||||
case '+':
|
||||
plus = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*append)
|
||||
return RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING |
|
||||
(plus ? RETRO_VFS_FILE_ACCESS_READ : 0);
|
||||
if (read && plus)
|
||||
return RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
|
||||
if (write)
|
||||
return plus ? RETRO_VFS_FILE_ACCESS_READ_WRITE : RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
return RETRO_VFS_FILE_ACCESS_READ;
|
||||
}
|
||||
|
||||
IOFile::IOFile(std::FILE* file) : m_file(file), m_good(true)
|
||||
IOFile::IOFile() : m_file(nullptr), m_good(true)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
m_vfs_handle = nullptr;
|
||||
|
||||
if (Libretro::VFile::HasVFS())
|
||||
ERROR_LOG_FMT(COMMON, "VFS: Attempt to use IOFile::IOFile with a std::FILE which should not happen");
|
||||
#endif
|
||||
}
|
||||
|
||||
IOFile::IOFile(const std::string& filename, const char openmode[], SharedAccess sh)
|
||||
: m_file(nullptr), m_good(true)
|
||||
#ifdef __LIBRETRO__
|
||||
, m_vfs_handle{nullptr}
|
||||
#endif
|
||||
{
|
||||
Open(filename, openmode, sh);
|
||||
}
|
||||
@@ -62,9 +65,6 @@ IOFile::~IOFile()
|
||||
}
|
||||
|
||||
IOFile::IOFile(IOFile&& other) noexcept : m_file(nullptr), m_good(true)
|
||||
#ifdef __LIBRETRO__
|
||||
, m_vfs_handle{nullptr}
|
||||
#endif
|
||||
{
|
||||
Swap(other);
|
||||
}
|
||||
@@ -77,185 +77,152 @@ IOFile& IOFile::operator=(IOFile&& other) noexcept
|
||||
|
||||
void IOFile::Swap(IOFile& other) noexcept
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
std::swap(m_vfs_handle, other.m_vfs_handle);
|
||||
#endif
|
||||
std::swap(m_file, other.m_file);
|
||||
std::swap(m_good, other.m_good);
|
||||
RFILE* file = m_file;
|
||||
bool good = m_good;
|
||||
m_file = other.m_file;
|
||||
m_good = other.m_good;
|
||||
other.m_file = file;
|
||||
other.m_good = good;
|
||||
}
|
||||
|
||||
bool IOFile::Open(const std::string& filename, const char openmode[],
|
||||
[[maybe_unused]] SharedAccess sh)
|
||||
bool IOFile::Open(const std::string& filename, const char openmode[], SharedAccess)
|
||||
{
|
||||
bool append;
|
||||
unsigned mode;
|
||||
|
||||
Close();
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
m_vfs_handle = Libretro::VFile::Open(filename, openmode);
|
||||
m_good = m_vfs_handle != nullptr;
|
||||
return m_good;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (sh == SharedAccess::Default)
|
||||
{
|
||||
m_good = _tfopen_s(&m_file, UTF8ToTStr(filename).c_str(), UTF8ToTStr(openmode).c_str()) == 0;
|
||||
}
|
||||
else if (sh == SharedAccess::Read)
|
||||
{
|
||||
m_file = _tfsopen(UTF8ToTStr(filename).c_str(), UTF8ToTStr(openmode).c_str(), SH_DENYWR);
|
||||
m_good = m_file != nullptr;
|
||||
}
|
||||
#else
|
||||
#ifdef ANDROID
|
||||
if (IsPathAndroidContent(filename))
|
||||
m_file = fdopen(OpenAndroidContent(filename, OpenModeToAndroid(openmode)), openmode);
|
||||
else
|
||||
#endif
|
||||
m_file = std::fopen(filename.c_str(), openmode);
|
||||
|
||||
mode = ModeFromString(openmode, &append);
|
||||
/* read-only files are mapped where the platform can: reads copy straight
|
||||
* out of the page cache, and GetMappedPtr()/View() lend without copying */
|
||||
m_file = filestream_open(filename.c_str(), mode,
|
||||
mode == RETRO_VFS_FILE_ACCESS_READ ?
|
||||
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS :
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
m_good = m_file != nullptr;
|
||||
#endif
|
||||
|
||||
if (m_good && append && filestream_seek(m_file, 0, RETRO_VFS_SEEK_POSITION_END) < 0)
|
||||
m_good = false;
|
||||
return m_good;
|
||||
}
|
||||
|
||||
bool IOFile::Close()
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (!IsOpen() || 0 != Libretro::VFile::Close(m_vfs_handle))
|
||||
m_good = false;
|
||||
|
||||
m_vfs_handle = nullptr;
|
||||
return m_good;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!IsOpen() || 0 != std::fclose(m_file))
|
||||
if (!m_file || filestream_close(m_file) != 0)
|
||||
m_good = false;
|
||||
|
||||
m_file = nullptr;
|
||||
return m_good;
|
||||
}
|
||||
|
||||
void IOFile::SetHandle(std::FILE* file)
|
||||
size_t IOFile::RawRead(void* data, size_t length)
|
||||
{
|
||||
Close();
|
||||
ClearError();
|
||||
m_file = file;
|
||||
int64_t got;
|
||||
if (!m_file)
|
||||
return 0;
|
||||
if (!length)
|
||||
return 0;
|
||||
got = filestream_read(m_file, data, (int64_t)length);
|
||||
return got > 0 ? (size_t)got : 0;
|
||||
}
|
||||
|
||||
bool IOFile::RawWrite(const void* data, size_t length)
|
||||
{
|
||||
if (!m_file)
|
||||
return false;
|
||||
if (!length)
|
||||
return true;
|
||||
return filestream_write(m_file, data, (int64_t)length) == (int64_t)length;
|
||||
}
|
||||
|
||||
bool IOFile::WriteZeros(size_t length)
|
||||
{
|
||||
static const u8 zeros[4096] = {};
|
||||
while (length && m_good)
|
||||
{
|
||||
const size_t n = length < sizeof(zeros) ? length : sizeof(zeros);
|
||||
WriteBytes(zeros, n);
|
||||
length -= n;
|
||||
}
|
||||
return m_good;
|
||||
}
|
||||
|
||||
const u8* IOFile::GetMappedPtr(u64* size)
|
||||
{
|
||||
int64_t len = 0;
|
||||
const u8* ptr = m_file ? filestream_get_mapped_ptr(m_file, &len) : nullptr;
|
||||
if (size)
|
||||
*size = ptr ? (u64)len : 0;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
const u8* IOFile::View(size_t length, size_t* lent)
|
||||
{
|
||||
const uint8_t* ptr = nullptr;
|
||||
const int64_t n = m_file ? filestream_view(m_file, (int64_t)length, &ptr) : -1;
|
||||
if (n <= 0)
|
||||
{
|
||||
*lent = 0;
|
||||
return nullptr;
|
||||
}
|
||||
*lent = (size_t)n;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void IOFile::Prefetch(u64 offset, u64 length)
|
||||
{
|
||||
if (m_file)
|
||||
filestream_prefetch(m_file, offset, length);
|
||||
}
|
||||
|
||||
u64 IOFile::GetSize() const
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (IsOpen())
|
||||
return Libretro::VFile::GetSize(m_vfs_handle);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (IsOpen())
|
||||
return File::GetSize(m_file);
|
||||
else
|
||||
int64_t size;
|
||||
if (!m_file)
|
||||
return 0;
|
||||
size = filestream_get_size(m_file);
|
||||
return size > 0 ? (u64)size : 0;
|
||||
}
|
||||
|
||||
bool IOFile::Seek(s64 offset, SeekOrigin origin)
|
||||
{
|
||||
int fseek_origin;
|
||||
int whence;
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin::Begin:
|
||||
fseek_origin = SEEK_SET;
|
||||
whence = RETRO_VFS_SEEK_POSITION_START;
|
||||
break;
|
||||
case SeekOrigin::Current:
|
||||
fseek_origin = SEEK_CUR;
|
||||
whence = RETRO_VFS_SEEK_POSITION_CURRENT;
|
||||
break;
|
||||
case SeekOrigin::End:
|
||||
fseek_origin = SEEK_END;
|
||||
whence = RETRO_VFS_SEEK_POSITION_END;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (!IsOpen() || 0 != Libretro::VFile::Seek(m_vfs_handle, offset, fseek_origin))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!IsOpen() || 0 != fseeko(m_file, offset, fseek_origin))
|
||||
if (!m_file || filestream_seek(m_file, offset, whence) < 0)
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
u64 IOFile::Tell() const
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (IsOpen())
|
||||
return Libretro::VFile::Tell(m_vfs_handle);
|
||||
else
|
||||
return UINT64_MAX;
|
||||
}
|
||||
#endif
|
||||
if (IsOpen())
|
||||
return ftello(m_file);
|
||||
else
|
||||
int64_t pos;
|
||||
if (!m_file)
|
||||
return UINT64_MAX;
|
||||
pos = filestream_tell(m_file);
|
||||
return pos < 0 ? UINT64_MAX : (u64)pos;
|
||||
}
|
||||
|
||||
bool IOFile::Flush()
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (!IsOpen() || 0 != Libretro::VFile::Flush(m_vfs_handle))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
#endif
|
||||
if (!IsOpen() || 0 != std::fflush(m_file))
|
||||
if (!m_file || filestream_flush(m_file) != 0)
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
bool IOFile::Resize(u64 size)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (!IsOpen() || 0 != Libretro::VFile::Resize(m_vfs_handle, size))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!IsOpen() || 0 != _chsize_s(_fileno(m_file), size))
|
||||
#else
|
||||
// TODO: handle 64bit and growing
|
||||
if (!IsOpen() || 0 != ftruncate(fileno(m_file), size))
|
||||
#endif
|
||||
if (!m_file || filestream_truncate(m_file, (int64_t)size) != 0)
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
|
||||
+29
-103
@@ -5,16 +5,13 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "DolphinLibretro/Common/VFile.h"
|
||||
#endif
|
||||
struct RFILE;
|
||||
|
||||
namespace File
|
||||
{
|
||||
@@ -31,14 +28,12 @@ enum class SharedAccess
|
||||
Read,
|
||||
};
|
||||
|
||||
// simple wrapper for cstdlib file functions to
|
||||
// hopefully will make error checking easier
|
||||
// and make forgetting an fclose() harder
|
||||
// A file through libretro-common's filestream: the frontend's VFS where it
|
||||
// has one, libretro-common's own implementation otherwise.
|
||||
class IOFile
|
||||
{
|
||||
public:
|
||||
IOFile();
|
||||
IOFile(std::FILE* file);
|
||||
IOFile(const std::string& filename, const char openmode[],
|
||||
SharedAccess sh = SharedAccess::Default);
|
||||
|
||||
@@ -60,37 +55,11 @@ public:
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
bool ReadArray(T* elements, size_t count, size_t* num_read = nullptr)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
m_good = false;
|
||||
return m_good;
|
||||
}
|
||||
|
||||
int64_t bytes_read = Libretro::VFile::ReadBytes(m_vfs_handle,
|
||||
elements,
|
||||
static_cast<uint64_t>(count * sizeof(T)));
|
||||
|
||||
size_t read_count = (bytes_read >= 0) ? static_cast<size_t>(bytes_read) / sizeof(T) : 0;
|
||||
|
||||
if (read_count != count)
|
||||
m_good = false;
|
||||
|
||||
if (num_read)
|
||||
*num_read = read_count;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
#endif
|
||||
size_t read_count = 0;
|
||||
if (!IsOpen() || count != (read_count = std::fread(elements, sizeof(T), count, m_file)))
|
||||
const size_t read_count = RawRead(elements, count * sizeof(T)) / sizeof(T);
|
||||
if (read_count != count)
|
||||
m_good = false;
|
||||
|
||||
if (num_read)
|
||||
*num_read = read_count;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
@@ -98,38 +67,8 @@ public:
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
bool WriteArray(const T* elements, size_t count)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
m_good = false;
|
||||
return m_good;
|
||||
}
|
||||
|
||||
const uint64_t bytes_to_write = static_cast<uint64_t>(count * sizeof(T));
|
||||
int64_t bytes_written = Libretro::VFile::WriteBytes(m_vfs_handle, elements, bytes_to_write);
|
||||
|
||||
if (bytes_written < 0)
|
||||
{
|
||||
m_good = false;
|
||||
return m_good;
|
||||
}
|
||||
|
||||
size_t write_count = static_cast<size_t>(bytes_written) / sizeof(T);
|
||||
if (write_count != count)
|
||||
{
|
||||
m_good = false;
|
||||
return m_good;
|
||||
}
|
||||
|
||||
return m_good;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!IsOpen() || count != std::fwrite(elements, sizeof(T), count, m_file))
|
||||
if (!RawWrite(elements, count * sizeof(T)))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
@@ -154,29 +93,26 @@ public:
|
||||
|
||||
bool WriteString(std::string_view str) { return WriteBytes(str.data(), str.size()); }
|
||||
|
||||
bool IsOpen() const
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return nullptr != m_vfs_handle;
|
||||
#endif
|
||||
return nullptr != m_file;
|
||||
}
|
||||
// Writes length zero octets.
|
||||
bool WriteZeros(size_t length);
|
||||
|
||||
bool IsOpen() const { return m_file != nullptr; }
|
||||
// m_good is set to false when a read, write or other function fails
|
||||
bool IsGood() const { return m_good; }
|
||||
explicit operator bool() const { return IsGood() && IsOpen(); }
|
||||
std::FILE* GetHandle()
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
ERROR_LOG_FMT(COMMON, "VFS: Attempt to use std::FILE which should not happen in VFS mode");
|
||||
#endif
|
||||
return m_file;
|
||||
}
|
||||
#ifdef __LIBRETRO__
|
||||
retro_vfs_file_handle* GetVFSHandle() { return m_vfs_handle; }
|
||||
#endif
|
||||
void SetHandle(std::FILE* file);
|
||||
RFILE* GetHandle() { return m_file; }
|
||||
|
||||
// The whole file, read-only, where it is memory-mapped (a file opened for
|
||||
// reading by the local implementation); nullptr otherwise. Valid until close.
|
||||
const u8* GetMappedPtr(u64* size = nullptr);
|
||||
|
||||
// Up to length octets at the position, lent rather than copied, the position
|
||||
// moving past them; nullptr when nothing there can be lent. Valid until the
|
||||
// next call on this file.
|
||||
const u8* View(size_t length, size_t* lent);
|
||||
|
||||
// Asks the OS to start reading [offset, offset + length) in; returns at once.
|
||||
void Prefetch(u64 offset, u64 length);
|
||||
|
||||
bool Seek(s64 offset, SeekOrigin origin);
|
||||
u64 Tell() const;
|
||||
@@ -185,24 +121,14 @@ public:
|
||||
bool Flush();
|
||||
|
||||
// clear error state
|
||||
void ClearError()
|
||||
{
|
||||
m_good = true;
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
return;
|
||||
#endif
|
||||
if (IsOpen())
|
||||
std::clearerr(m_file);
|
||||
}
|
||||
void ClearError() { m_good = true; }
|
||||
|
||||
private:
|
||||
std::FILE* m_file;
|
||||
bool m_good;
|
||||
size_t RawRead(void* data, size_t length);
|
||||
bool RawWrite(const void* data, size_t length);
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
retro_vfs_file_handle* m_vfs_handle = nullptr;
|
||||
#endif
|
||||
RFILE* m_file;
|
||||
bool m_good;
|
||||
};
|
||||
|
||||
} // namespace File
|
||||
@@ -55,6 +55,11 @@ bool LoadPNG(std::span<const u8> input, Common::UniqueBuffer<u8>* data_out, u32*
|
||||
return true;
|
||||
}
|
||||
|
||||
static int WritePNGStream(spng_ctx*, void* user, void* data, size_t length)
|
||||
{
|
||||
return static_cast<File::IOFile*>(user)->WriteBytes(data, length) ? 0 : SPNG_IO_ERROR;
|
||||
}
|
||||
|
||||
bool SavePNG(const std::string& path, const u8* input, ImageByteFormat format, u32 width,
|
||||
u32 height, u32 stride, int level)
|
||||
{
|
||||
@@ -80,7 +85,7 @@ bool SavePNG(const std::string& path, const u8* input, ImageByteFormat format, u
|
||||
return false;
|
||||
|
||||
auto outfile = File::IOFile(path, "wb");
|
||||
if (spng_set_png_file(ctx.get(), outfile.GetHandle()))
|
||||
if (!outfile || spng_set_png_stream(ctx.get(), WritePNGStream, &outfile))
|
||||
return false;
|
||||
|
||||
if (spng_set_option(ctx.get(), SPNG_IMG_COMPRESSION_LEVEL, level))
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -13,6 +12,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include <streams/file_stream.h>
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace Common
|
||||
@@ -242,39 +243,23 @@ bool IniFile::Load(const std::string& filename, bool keep_current_data)
|
||||
sections.clear();
|
||||
// first section consists of the comments before the first real section
|
||||
|
||||
// Open file
|
||||
std::ifstream in;
|
||||
File::OpenFStream(in, filename, std::ios::in);
|
||||
|
||||
if (in.fail())
|
||||
File::LineReader in(filename);
|
||||
if (!in.IsOpen())
|
||||
return false;
|
||||
|
||||
Section* current_section = nullptr;
|
||||
bool first_line = true;
|
||||
while (!in.eof())
|
||||
const char* text;
|
||||
size_t length;
|
||||
while (in.Next(&text, &length))
|
||||
{
|
||||
std::string line_str;
|
||||
if (!std::getline(in, line_str))
|
||||
{
|
||||
if (in.eof())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view line = line_str;
|
||||
std::string_view line(text, length);
|
||||
|
||||
// Skips the UTF-8 BOM at the start of files. Notepad likes to add this.
|
||||
if (first_line && line.substr(0, 3) == "\xEF\xBB\xBF")
|
||||
line.remove_prefix(3);
|
||||
first_line = false;
|
||||
|
||||
#ifndef _WIN32
|
||||
// Check for CRLF eol and convert it to LF
|
||||
if (!line.empty() && line.back() == '\r')
|
||||
line.remove_suffix(1);
|
||||
#endif
|
||||
|
||||
if (!line.empty())
|
||||
{
|
||||
if (line[0] == '[')
|
||||
@@ -312,44 +297,47 @@ bool IniFile::Load(const std::string& filename, bool keep_current_data)
|
||||
}
|
||||
}
|
||||
|
||||
in.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IniFile::Save(const std::string& filename)
|
||||
{
|
||||
std::ofstream out;
|
||||
std::string temp = File::GetTempFilenameForAtomicWrite(filename);
|
||||
File::OpenFStream(out, temp, std::ios::out);
|
||||
|
||||
if (out.fail())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::string out;
|
||||
|
||||
for (const Section& section : sections)
|
||||
{
|
||||
if (!section.keys_order.empty() || !section.m_lines.empty())
|
||||
out << '[' << section.name << ']' << std::endl;
|
||||
{
|
||||
out += '[';
|
||||
out += section.name;
|
||||
out += "]\n";
|
||||
}
|
||||
|
||||
if (section.keys_order.empty())
|
||||
{
|
||||
for (const std::string& s : section.m_lines)
|
||||
out << s << std::endl;
|
||||
{
|
||||
out += s;
|
||||
out += '\n';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const std::string& kvit : section.keys_order)
|
||||
{
|
||||
auto pair = section.values.find(kvit);
|
||||
out << pair->first << " = " << pair->second << std::endl;
|
||||
out += pair->first;
|
||||
out += " = ";
|
||||
out += pair->second;
|
||||
out += '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
return File::RenameSync(temp, filename);
|
||||
// an unchanged file is left alone; a changed one is replaced whole or not at all
|
||||
if (filestream_matches_buf(filename.c_str(), out.data(), out.size()))
|
||||
return true;
|
||||
return filestream_write_file_atomic(filename.c_str(), out.data(), s64(out.size()));
|
||||
}
|
||||
|
||||
// Unit test. TODO: Move to the real unit test framework.
|
||||
|
||||
@@ -40,12 +40,7 @@ void Init(const std::string& perf_dir)
|
||||
const std::string dir = perf_dir.empty() ? "/tmp" : perf_dir;
|
||||
const std::string filename = fmt::format("{}/perf-{}.map", dir, getpid());
|
||||
if (s_perf_map_file.Open(filename, "w"))
|
||||
{
|
||||
// Disable buffering in order to avoid missing some mappings
|
||||
// in the event of a crash:
|
||||
std::setvbuf(s_perf_map_file.GetHandle(), nullptr, _IONBF, 0);
|
||||
s_is_enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
#include "Common/JsonUtil.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
|
||||
picojson::object ToJsonObject(const Common::Vec3& vec)
|
||||
{
|
||||
@@ -45,24 +45,32 @@ std::optional<bool> ReadBoolFromJson(const picojson::object& obj, const std::str
|
||||
|
||||
bool JsonToFile(const std::string& filename, const picojson::value& root, bool prettify)
|
||||
{
|
||||
std::ofstream json_stream;
|
||||
File::OpenFStream(json_stream, filename, std::ios_base::out);
|
||||
if (!json_stream.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
json_stream << root.serialize(prettify);
|
||||
return true;
|
||||
const std::string json = root.serialize(prettify);
|
||||
return File::WriteStringToFile(filename, json);
|
||||
}
|
||||
|
||||
bool JsonFromFile(const std::string& filename, picojson::value* root, std::string* error)
|
||||
{
|
||||
File::IOFile file(filename, "rb");
|
||||
std::string json_data;
|
||||
if (!File::ReadFileToString(filename, json_data))
|
||||
{
|
||||
const char* begin;
|
||||
u64 size;
|
||||
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
// parsed straight from the mapping where the file has one
|
||||
begin = reinterpret_cast<const char*>(file.GetMappedPtr(&size));
|
||||
if (!begin)
|
||||
{
|
||||
json_data.resize(file.GetSize());
|
||||
if (!file.ReadArray(json_data.data(), json_data.size()))
|
||||
return false;
|
||||
begin = json_data.data();
|
||||
size = json_data.size();
|
||||
}
|
||||
|
||||
*error = picojson::parse(*root, json_data);
|
||||
error->clear();
|
||||
picojson::parse(*root, begin, begin + size, error);
|
||||
return error->empty();
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
@@ -60,42 +59,24 @@
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
#include "DiscIO/VolumeWad.h"
|
||||
|
||||
#ifndef __LIBRETRO__
|
||||
static
|
||||
#endif
|
||||
std::vector<std::string> ReadM3UFile(const std::string& m3u_path,
|
||||
const std::string& folder_path)
|
||||
static void PushLine(void* ud, const char* text, size_t length)
|
||||
{
|
||||
static_cast<std::vector<std::string>*>(ud)->emplace_back(text, length);
|
||||
}
|
||||
|
||||
std::vector<std::string> ReadM3UFile(const std::string& m3u_path, const std::string& folder_path)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::vector<std::string> nonexistent;
|
||||
std::vector<std::string> lines;
|
||||
std::string newline;
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
lines = Libretro::VFile::ReadLines(m3u_path);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
std::ifstream s;
|
||||
File::OpenFStream(s, m3u_path, std::ios_base::in);
|
||||
|
||||
while (std::getline(s, newline))
|
||||
lines.push_back(newline);
|
||||
}
|
||||
File::ForEachLine(m3u_path, PushLine, &lines);
|
||||
|
||||
for (std::string& line : lines)
|
||||
{
|
||||
// This is the UTF-8 representation of U+FEFF.
|
||||
constexpr std::string_view utf8_bom = "\xEF\xBB\xBF";
|
||||
|
||||
#ifndef _WIN32
|
||||
if (line.ends_with('\r'))
|
||||
line.pop_back();
|
||||
#endif
|
||||
|
||||
if (line.starts_with(utf8_bom))
|
||||
{
|
||||
WARN_LOG_FMT(BOOT, "UTF-8 BOM in file: {}", m3u_path);
|
||||
@@ -104,7 +85,7 @@ std::vector<std::string> ReadM3UFile(const std::string& m3u_path,
|
||||
|
||||
if (!line.empty() && line.front() != '#') // Comments start with #
|
||||
{
|
||||
const std::string path_to_add = PathToString(StringToPath(folder_path) / StringToPath(line));
|
||||
const std::string path_to_add = File::JoinPath(folder_path, line);
|
||||
(File::Exists(path_to_add) ? result : nonexistent).push_back(path_to_add);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +70,6 @@
|
||||
#include "Core/System.h"
|
||||
#include "Core/WiiRoot.h"
|
||||
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
#include "Core/MemoryWatcher.h"
|
||||
#endif
|
||||
|
||||
#include "DiscIO/RiivolutionPatcher.h"
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
@@ -119,10 +115,6 @@ static void StartingToRunning(void)
|
||||
static_cast<int>(State::Running));
|
||||
}
|
||||
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
static std::unique_ptr<MemoryWatcher> s_memory_watcher;
|
||||
#endif
|
||||
|
||||
struct HostJob
|
||||
{
|
||||
mpsc_stack_node_t link; // first: the drained chain is cast back to jobs
|
||||
@@ -139,19 +131,6 @@ static thread_local bool tls_is_gpu_thread = false;
|
||||
void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot,
|
||||
WindowSystemInfo wsi);
|
||||
|
||||
void OnFrameEnd(Core::System& system)
|
||||
{
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
if (s_memory_watcher)
|
||||
{
|
||||
ASSERT(IsCPUThread());
|
||||
const CPUThreadGuard guard(system);
|
||||
|
||||
s_memory_watcher->Step(guard);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Display messages and return values
|
||||
|
||||
// Formatted stop message
|
||||
@@ -313,10 +292,6 @@ static void CpuThread(Core::System& system, const std::optional<std::string> sav
|
||||
if (exception_handler)
|
||||
EMM::InstallExceptionHandler();
|
||||
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
s_memory_watcher = std::make_unique<MemoryWatcher>();
|
||||
#endif
|
||||
|
||||
if (savestate_path)
|
||||
{
|
||||
::State::LoadAs(system, *savestate_path);
|
||||
@@ -356,10 +331,6 @@ static void CpuThread(Core::System& system, const std::optional<std::string> sav
|
||||
// Enter CPU run loop. When we leave it - we are done.
|
||||
system.GetCPU().Run();
|
||||
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
s_memory_watcher.reset();
|
||||
#endif
|
||||
|
||||
if (exception_handler)
|
||||
EMM::UninstallExceptionHandler();
|
||||
|
||||
@@ -1066,10 +1037,6 @@ CPUThreadGuard::~CPUThreadGuard()
|
||||
|
||||
void SingleCorePostRunShutdown()
|
||||
{
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
s_memory_watcher.reset();
|
||||
#endif
|
||||
|
||||
if (EMM::IsExceptionHandlerSupported())
|
||||
EMM::UninstallExceptionHandler();
|
||||
|
||||
|
||||
@@ -154,7 +154,6 @@ void SaveScreenShot(std::string_view name);
|
||||
// Logs a message meant for the user; the core draws nothing over the game.
|
||||
void DisplayMessage(std::string message, int time_in_ms = 0);
|
||||
|
||||
void OnFrameEnd(Core::System& system);
|
||||
|
||||
// Run a function on the CPU thread, asynchronously.
|
||||
void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction<void()> function
|
||||
|
||||
@@ -373,8 +373,7 @@ std::unique_ptr<FifoDataFile> FifoDataFile::Load(const std::string& filename, bo
|
||||
|
||||
void FifoDataFile::PadFile(size_t numBytes, File::IOFile& file)
|
||||
{
|
||||
for (size_t i = 0; i < numBytes; ++i)
|
||||
fputc(0, file.GetHandle());
|
||||
file.WriteZeros(numBytes);
|
||||
}
|
||||
|
||||
void FifoDataFile::SetFlag(u32 flag, bool set)
|
||||
|
||||
@@ -885,7 +885,6 @@ void VideoInterfaceManager::EndField(FieldType field, u64 ticks)
|
||||
OutputField(field, ticks);
|
||||
|
||||
m_system.GetVideoEvents().vi_end_field_event.Trigger();
|
||||
Core::OnFrameEnd(m_system);
|
||||
}
|
||||
|
||||
// Purpose: Send VI interrupt when triggered
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
@@ -16,6 +15,7 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
||||
@@ -86,10 +86,7 @@ void Wiimote::Reset()
|
||||
{
|
||||
// Write out existing EEPROM
|
||||
INFO_LOG_FMT(WIIMOTE, "Wrote EEPROM for {}", GetName());
|
||||
std::ofstream file;
|
||||
File::OpenFStream(file, eeprom_file, std::ios::binary | std::ios::out);
|
||||
file.write(reinterpret_cast<char*>(m_eeprom.data.data()), EEPROM_FREE_SIZE);
|
||||
file.close();
|
||||
File::IOFile(eeprom_file, "wb").WriteBytes(m_eeprom.data.data(), EEPROM_FREE_SIZE);
|
||||
|
||||
m_eeprom_dirty = false;
|
||||
}
|
||||
@@ -98,10 +95,7 @@ void Wiimote::Reset()
|
||||
if (!want_determinism && File::Exists(eeprom_file))
|
||||
{
|
||||
// Read existing EEPROM
|
||||
std::ifstream file;
|
||||
File::OpenFStream(file, eeprom_file, std::ios::binary | std::ios::in);
|
||||
file.read(reinterpret_cast<char*>(m_eeprom.data.data()), EEPROM_FREE_SIZE);
|
||||
file.close();
|
||||
File::IOFile(eeprom_file, "rb").ReadBytes(m_eeprom.data.data(), EEPROM_FREE_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -154,11 +148,9 @@ void Wiimote::Reset()
|
||||
if (File::Exists(mii_file))
|
||||
{
|
||||
// Import from the existing mii.bin file, if present
|
||||
std::ifstream file;
|
||||
File::OpenFStream(file, mii_file, std::ios::binary | std::ios::in);
|
||||
file.read(reinterpret_cast<char*>(m_eeprom.mii_data_1.data()), m_eeprom.mii_data_1.size());
|
||||
File::IOFile(mii_file, "rb").ReadBytes(m_eeprom.mii_data_1.data(),
|
||||
m_eeprom.mii_data_1.size());
|
||||
m_eeprom.mii_data_2 = m_eeprom.mii_data_1;
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,50 +16,6 @@
|
||||
using namespace WiimoteCommon;
|
||||
using namespace WiimoteReal;
|
||||
|
||||
#if defined(__linux__)
|
||||
// Here we check the currently attached Linux driver just for logging purposes.
|
||||
// Maybe in the future we'll be able to bind the desired HID driver somehow.
|
||||
static void LogLinuxDriverName(const char* device_path)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const auto sys_device = "/sys/class/hidraw" / fs::path(device_path).filename() / "device";
|
||||
|
||||
// "driver" is a symlink to a path that contains the driver name.
|
||||
// usually /sys/bus/hid/drivers/hid-generic (what we want)
|
||||
// or this /sys/bus/hid/drivers/wiimote (what we don't want)
|
||||
std::error_code err;
|
||||
const auto sys_driver = fs::canonical(sys_device / "driver", err);
|
||||
|
||||
if (err)
|
||||
{
|
||||
WARN_LOG_FMT(WIIMOTE, "Could not determine current driver of {}. error: {}", device_path,
|
||||
err.message());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto driver_name = sys_driver.filename();
|
||||
if (driver_name == "wiimote")
|
||||
{
|
||||
// If Linux's 'wiimote' driver is attached, we will be forever fighting with it.
|
||||
// It's not going to work very well.
|
||||
|
||||
// One could write fs::canonical(sys_device).filename() to (sys_driver / "unbind")
|
||||
// And then also write it to "/sys/bus/hid/drivers/hid-generic/bind"
|
||||
// This should create a new hidraw device with the 'hid-generic' driver.
|
||||
|
||||
// But that requires elevated permissions.. Linux is annoying.
|
||||
|
||||
ERROR_LOG_FMT(WIIMOTE, "Wii remote at {} has the Linux 'wiimote' driver attached.",
|
||||
device_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG_FMT(WIIMOTE, "Wii remote at {} has driver: {}", device_path, driver_name.string());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool IsDeviceUsable(const std::string& device_path)
|
||||
{
|
||||
hid_device* handle = hid_open_path(device_path.c_str());
|
||||
@@ -118,10 +74,6 @@ auto WiimoteScannerHidapi::FindAttachedWiimotes() -> FindResults
|
||||
if (!is_wiimote || !IsNewWiimote(device->path) || !IsDeviceUsable(device->path))
|
||||
continue;
|
||||
|
||||
#if defined(__linux__)
|
||||
LogLinuxDriverName(device->path);
|
||||
#endif
|
||||
|
||||
auto wiimote = std::make_unique<WiimoteHidapi>(device->path);
|
||||
const bool is_balance_board = IsBalanceBoardName(name) || wiimote->IsBalanceBoard();
|
||||
if (is_balance_board)
|
||||
|
||||
@@ -136,27 +136,17 @@ Result<u32> HostFileSystem::ReadBytesFromFile(Fd fd, u8* ptr, u32 count)
|
||||
// File might be opened twice, need to seek before we read
|
||||
handle->host_file->Seek(handle->file_offset, File::SeekOrigin::Begin);
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (Libretro::VFile::HasVFS())
|
||||
{
|
||||
const u32 actually_read_vfs = Libretro::VFile::ReadBytes(handle->host_file->GetVFSHandle(), ptr, count);
|
||||
|
||||
if (actually_read_vfs != count)
|
||||
return std::unexpected{ResultCode::AccessDenied};
|
||||
|
||||
handle->file_offset += actually_read_vfs;
|
||||
return actually_read_vfs;
|
||||
}
|
||||
#endif
|
||||
const u32 actually_read = static_cast<u32>(fread(ptr, 1, count, handle->host_file->GetHandle()));
|
||||
|
||||
if (actually_read != count && ferror(handle->host_file->GetHandle()))
|
||||
// count stops at the end of the file: a short read is an error
|
||||
size_t actually_read = 0;
|
||||
handle->host_file->ReadArray(ptr, count, &actually_read);
|
||||
handle->host_file->ClearError();
|
||||
if (actually_read != count)
|
||||
return std::unexpected{ResultCode::AccessDenied};
|
||||
|
||||
// IOS returns the number of bytes read and adds that value to the seek position,
|
||||
// instead of adding the *requested* read length.
|
||||
handle->file_offset += actually_read;
|
||||
return actually_read;
|
||||
handle->file_offset += u32(actually_read);
|
||||
return u32(actually_read);
|
||||
}
|
||||
|
||||
Result<u32> HostFileSystem::WriteBytesToFile(Fd fd, const u8* ptr, u32 count)
|
||||
|
||||
@@ -294,8 +294,7 @@ s32 SDIOSlot0Device::ExecuteCommand(const Request& request, u32 buffer_in, u32 b
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_SD, "Read Failed - error: {}, eof: {}", std::ferror(m_card.GetHandle()),
|
||||
std::feof(m_card.GetHandle()));
|
||||
ERROR_LOG_FMT(IOS_SD, "Read Failed");
|
||||
ret = RET_FAIL;
|
||||
}
|
||||
}
|
||||
@@ -324,8 +323,7 @@ s32 SDIOSlot0Device::ExecuteCommand(const Request& request, u32 buffer_in, u32 b
|
||||
|
||||
if (!m_card.WriteBytes(memory.GetPointerForRange(req.addr, size), size))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_SD, "Write Failed - error: {}, eof: {}", std::ferror(m_card.GetHandle()),
|
||||
std::feof(m_card.GetHandle()));
|
||||
ERROR_LOG_FMT(IOS_SD, "Write Failed");
|
||||
ret = RET_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/MemoryWatcher.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
|
||||
MemoryWatcher::MemoryWatcher()
|
||||
{
|
||||
m_running = false;
|
||||
if (!LoadAddresses(File::GetUserPath(F_MEMORYWATCHERLOCATIONS_IDX)))
|
||||
return;
|
||||
if (!OpenSocket(File::GetUserPath(F_MEMORYWATCHERSOCKET_IDX)))
|
||||
return;
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
MemoryWatcher::~MemoryWatcher()
|
||||
{
|
||||
if (!m_running)
|
||||
return;
|
||||
|
||||
m_running = false;
|
||||
close(m_fd);
|
||||
}
|
||||
|
||||
bool MemoryWatcher::LoadAddresses(const std::string& path)
|
||||
{
|
||||
std::ifstream locations;
|
||||
File::OpenFStream(locations, path, std::ios_base::in);
|
||||
if (!locations)
|
||||
return false;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(locations, line))
|
||||
ParseLine(line);
|
||||
|
||||
return !m_values.empty();
|
||||
}
|
||||
|
||||
void MemoryWatcher::ParseLine(const std::string& line)
|
||||
{
|
||||
m_values[line] = 0;
|
||||
m_addresses[line] = std::vector<u32>();
|
||||
|
||||
std::istringstream offsets(line);
|
||||
offsets >> std::hex;
|
||||
u32 offset;
|
||||
while (offsets >> offset)
|
||||
m_addresses[line].push_back(offset);
|
||||
}
|
||||
|
||||
bool MemoryWatcher::OpenSocket(const std::string& path)
|
||||
{
|
||||
m_addr.sun_family = AF_UNIX;
|
||||
strncpy(m_addr.sun_path, path.c_str(), sizeof(m_addr.sun_path) - 1);
|
||||
|
||||
m_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||
return m_fd >= 0;
|
||||
}
|
||||
|
||||
u32 MemoryWatcher::ChasePointer(const Core::CPUThreadGuard& guard, const std::string& line)
|
||||
{
|
||||
u32 value = 0;
|
||||
for (u32 offset : m_addresses[line])
|
||||
{
|
||||
value = PowerPC::MMU::HostRead<u32>(guard, value + offset);
|
||||
if (!PowerPC::MMU::HostIsRAMAddress(guard, value))
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string MemoryWatcher::ComposeMessages(const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
std::ostringstream message_stream;
|
||||
message_stream << std::hex;
|
||||
|
||||
for (auto& entry : m_values)
|
||||
{
|
||||
std::string address = entry.first;
|
||||
u32& current_value = entry.second;
|
||||
|
||||
u32 new_value = ChasePointer(guard, address);
|
||||
if (new_value != current_value)
|
||||
{
|
||||
// Update the value
|
||||
current_value = new_value;
|
||||
message_stream << address << '\n' << new_value << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return message_stream.str();
|
||||
}
|
||||
|
||||
void MemoryWatcher::Step(const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
if (!m_running)
|
||||
return;
|
||||
|
||||
std::string message = ComposeMessages(guard);
|
||||
sendto(m_fd, message.c_str(), message.size() + 1, 0, reinterpret_cast<sockaddr*>(&m_addr),
|
||||
sizeof(m_addr));
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
}
|
||||
|
||||
// MemoryWatcher reads a file containing in-game memory addresses and outputs
|
||||
// changes to those memory addresses to a unix domain socket as the game runs.
|
||||
//
|
||||
// The input file is a newline-separated list of hex memory addresses, without
|
||||
// the "0x". To follow pointers, separate addresses with a space. For example,
|
||||
// "ABCD EF" will watch the address at (*0xABCD) + 0xEF.
|
||||
// The output to the socket is two lines. The first is the address from the
|
||||
// input file, and the second is the new value in hex.
|
||||
class MemoryWatcher final
|
||||
{
|
||||
public:
|
||||
MemoryWatcher();
|
||||
~MemoryWatcher();
|
||||
void Step(const Core::CPUThreadGuard& guard);
|
||||
|
||||
private:
|
||||
bool LoadAddresses(const std::string& path);
|
||||
bool OpenSocket(const std::string& path);
|
||||
|
||||
void ParseLine(const std::string& line);
|
||||
u32 ChasePointer(const Core::CPUThreadGuard& guard, const std::string& line);
|
||||
std::string ComposeMessages(const Core::CPUThreadGuard& guard);
|
||||
|
||||
bool m_running = false;
|
||||
|
||||
int m_fd;
|
||||
sockaddr_un m_addr{};
|
||||
|
||||
// Address as stored in the file -> list of offsets to follow
|
||||
std::map<std::string, std::vector<u32>> m_addresses;
|
||||
// Address as stored in the file -> current value
|
||||
std::map<std::string, u32> m_values;
|
||||
};
|
||||
@@ -363,8 +363,8 @@ bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard)
|
||||
// bad=true means carefully load map files that might not be from exactly the right version
|
||||
bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, std::string filename, bool bad)
|
||||
{
|
||||
File::IOFile f(filename, "r");
|
||||
if (!f)
|
||||
File::LineReader f(filename);
|
||||
if (!f.IsOpen())
|
||||
return false;
|
||||
|
||||
XFuncMap new_functions;
|
||||
@@ -380,8 +380,16 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, std::string filenam
|
||||
|
||||
char line[512];
|
||||
std::string section_name;
|
||||
while (fgets(line, 512, f.GetHandle()))
|
||||
const char* text;
|
||||
size_t text_length;
|
||||
while (f.Next(&text, &text_length))
|
||||
{
|
||||
// as the line a 512-octet fgets() gives
|
||||
if (text_length > sizeof(line) - 2)
|
||||
text_length = sizeof(line) - 2;
|
||||
std::memcpy(line, text, text_length);
|
||||
line[text_length] = '\n';
|
||||
line[text_length + 1] = '\0';
|
||||
size_t length = strlen(line);
|
||||
if (length < 4)
|
||||
continue;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "Core/PowerPC/SignatureDB/CSVSignatureDB.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
@@ -17,15 +16,15 @@
|
||||
// Checksum | Size | Symbol | [Object Location |] Object Name
|
||||
bool CSVSignatureDB::Load(const std::string& file_path)
|
||||
{
|
||||
std::string line;
|
||||
std::ifstream ifs;
|
||||
File::OpenFStream(ifs, file_path, std::ios_base::in);
|
||||
|
||||
if (!ifs)
|
||||
File::LineReader reader(file_path);
|
||||
if (!reader.IsOpen())
|
||||
return false;
|
||||
for (size_t i = 1; std::getline(ifs, line); i += 1)
|
||||
|
||||
const char* text;
|
||||
size_t length;
|
||||
for (size_t i = 1; reader.Next(&text, &length); i += 1)
|
||||
{
|
||||
std::istringstream iss(line);
|
||||
std::istringstream iss(std::string(text, length));
|
||||
u32 checksum, size;
|
||||
std::string tab, symbol, object_location, object_name;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -128,16 +127,15 @@ void MEGASignatureDB::Clear()
|
||||
|
||||
bool MEGASignatureDB::Load(const std::string& file_path)
|
||||
{
|
||||
std::ifstream ifs;
|
||||
File::OpenFStream(ifs, file_path, std::ios_base::in);
|
||||
|
||||
if (!ifs)
|
||||
File::LineReader reader(file_path);
|
||||
if (!reader.IsOpen())
|
||||
return false;
|
||||
|
||||
std::string line;
|
||||
for (size_t i = 1; std::getline(ifs, line); ++i)
|
||||
const char* text;
|
||||
size_t length;
|
||||
for (size_t i = 1; reader.Next(&text, &length); ++i)
|
||||
{
|
||||
std::istringstream iss(line);
|
||||
std::istringstream iss(std::string(text, length));
|
||||
MEGASignature sig;
|
||||
|
||||
if (GetCode(&sig, &iss) && GetName(&sig, &iss) && GetRefs(&sig, &iss))
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "Core/State.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -502,9 +501,7 @@ static void CompressAndDumpState(Core::System& system, const CompressAndDumpStat
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::filesystem::path temp_path(StringToPath(filename));
|
||||
Core::DisplayMessage(fmt::format("Saved State to {}", PathToString(temp_path.filename())),
|
||||
2000);
|
||||
Core::DisplayMessage(fmt::format("Saved State to {}", filename), 2000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -881,9 +878,7 @@ static void LoadAsFromCore(Core::System& system, std::string filename)
|
||||
{
|
||||
if (loaded_successfully)
|
||||
{
|
||||
const std::filesystem::path temp_filename(StringToPath(filename));
|
||||
Core::DisplayMessage(
|
||||
fmt::format("Loaded State from {}", PathToString(temp_filename.filename())), 2000);
|
||||
Core::DisplayMessage(fmt::format("Loaded State from {}", filename), 2000);
|
||||
if (File::Exists(filename + ".dtm"))
|
||||
{
|
||||
movie.LoadInput(filename + ".dtm");
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "Core/TitleDatabase.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
@@ -27,19 +26,18 @@ static Map LoadMap(const std::string& file_path)
|
||||
{
|
||||
Map map;
|
||||
|
||||
std::ifstream txt;
|
||||
File::OpenFStream(txt, file_path, std::ios::in);
|
||||
|
||||
std::string line;
|
||||
while (std::getline(txt, line))
|
||||
File::LineReader txt(file_path);
|
||||
const char* text;
|
||||
size_t length;
|
||||
while (txt.Next(&text, &length))
|
||||
{
|
||||
if (line.empty())
|
||||
const std::string_view line_view(text, length);
|
||||
if (line_view.empty())
|
||||
continue;
|
||||
|
||||
const size_t equals_index = line.find('=');
|
||||
const size_t equals_index = line_view.find('=');
|
||||
if (equals_index != std::string::npos)
|
||||
{
|
||||
const std::string_view line_view(line);
|
||||
const std::string_view game_id = StripWhitespace(line_view.substr(0, equals_index));
|
||||
if (game_id.length() >= 4)
|
||||
map.emplace(game_id, StripWhitespace(line_view.substr(equals_index + 1)));
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
static std::string MakeAbsolute(const std::string& directory, const std::string& path)
|
||||
{
|
||||
return PathToString(StringToPath(directory) / StringToPath(path));
|
||||
return File::JoinPath(directory, path);
|
||||
}
|
||||
|
||||
std::optional<GameModDescriptor> ParseGameModDescriptorFile(const std::string& filename)
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
#include <libretro.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/Thread.h"
|
||||
@@ -29,7 +28,7 @@
|
||||
#include "DolphinLibretro/Audio.h"
|
||||
#include "DolphinLibretro/Input.h"
|
||||
#include "DolphinLibretro/Log.h"
|
||||
#include "DolphinLibretro/Common/VFile.h"
|
||||
#include <vfs/vfs_hybrid.h>
|
||||
#include "DolphinLibretro/Common/Globals.h"
|
||||
#include "DolphinLibretro/Common/Options.h"
|
||||
#include "DolphinLibretro/Video.h"
|
||||
@@ -44,7 +43,6 @@
|
||||
#include "VideoCommon/Resources/CustomResourceManager.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Libretro
|
||||
{
|
||||
@@ -117,62 +115,43 @@ void generate_cht_from_ini(std::string fileName)
|
||||
if (!File::CreateFullPath(cheatFile))
|
||||
return;
|
||||
|
||||
std::ofstream out(cheatFile);
|
||||
if (!out.is_open())
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Cheats: unable to create a new cht file at: {}", cheatFile);
|
||||
return;
|
||||
}
|
||||
|
||||
out << "cheats = " << total << "\n\n";
|
||||
|
||||
// built whole, written in one go
|
||||
std::string out = fmt::format("cheats = {}\n\n", total);
|
||||
size_t cheat_index = 0;
|
||||
|
||||
// -------------------------
|
||||
// Action Replay
|
||||
// -------------------------
|
||||
for (const auto& c : Libretro::g_ar_codes)
|
||||
{
|
||||
std::string code;
|
||||
|
||||
for (const auto& op : c.ops)
|
||||
{
|
||||
if (!code.empty())
|
||||
code += "+";
|
||||
|
||||
code += fmt::format("{:08X} {:08X}", op.cmd_addr, op.value);
|
||||
fmt::format_to(std::back_inserter(code), "{:08X} {:08X}", op.cmd_addr, op.value);
|
||||
}
|
||||
|
||||
out << "cheat" << cheat_index << "_desc = \"" << c.name << "\"\n";
|
||||
out << "cheat" << cheat_index << "_code = \"" << code << "\"\n";
|
||||
out << "cheat" << cheat_index << "_enable = " << (c.enabled ? "true" : "false") << "\n\n";
|
||||
|
||||
cheat_index++;
|
||||
fmt::format_to(std::back_inserter(out),
|
||||
"cheat{0}_desc = \"{1}\"\ncheat{0}_code = \"{2}\"\ncheat{0}_enable = {3}\n\n",
|
||||
cheat_index++, c.name, code, c.enabled ? "true" : "false");
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Gecko
|
||||
// -------------------------
|
||||
for (const auto& c : Libretro::g_gecko_codes)
|
||||
{
|
||||
std::string code;
|
||||
|
||||
for (const auto& line : c.codes)
|
||||
{
|
||||
if (!code.empty())
|
||||
code += "+";
|
||||
|
||||
code += "+";
|
||||
code += line.original_line;
|
||||
}
|
||||
|
||||
out << "cheat" << cheat_index << "_desc = \"" << c.name << "\"\n";
|
||||
out << "cheat" << cheat_index << "_code = \"" << code << "\"\n";
|
||||
out << "cheat" << cheat_index << "_enable = " << (c.enabled ? "true" : "false") << "\n\n";
|
||||
|
||||
cheat_index++;
|
||||
fmt::format_to(std::back_inserter(out),
|
||||
"cheat{0}_desc = \"{1}\"\ncheat{0}_code = \"{2}\"\ncheat{0}_enable = {3}\n\n",
|
||||
cheat_index++, c.name, code, c.enabled ? "true" : "false");
|
||||
}
|
||||
|
||||
out.close();
|
||||
if (!File::WriteStringToFile(cheatFile, out))
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Cheats: unable to create a new cht file at: {}", cheatFile);
|
||||
return;
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(BOOT, "Cheats: cht file created successfully at: {}", cheatFile);
|
||||
}
|
||||
@@ -207,6 +186,29 @@ static void SetRegionFreeFallback(const BootParameters& boot_params)
|
||||
|
||||
} // namespace Libretro
|
||||
|
||||
/* The path without its last component, and its last component, either
|
||||
* separator taken and trailing ones ignored. */
|
||||
static std::string PathParent(const std::string& path)
|
||||
{
|
||||
size_t end = path.find_last_not_of("/\\");
|
||||
size_t slash;
|
||||
if (end == std::string::npos)
|
||||
return std::string();
|
||||
slash = path.find_last_of("/\\", end);
|
||||
return slash == std::string::npos ? std::string() : path.substr(0, slash);
|
||||
}
|
||||
|
||||
static std::string PathLast(const std::string& path)
|
||||
{
|
||||
size_t end = path.find_last_not_of("/\\");
|
||||
size_t slash;
|
||||
if (end == std::string::npos)
|
||||
return std::string();
|
||||
slash = path.find_last_of("/\\", end);
|
||||
return path.substr(slash == std::string::npos ? 0 : slash + 1,
|
||||
end - (slash == std::string::npos ? 0 : slash + 1) + 1);
|
||||
}
|
||||
|
||||
bool retro_load_game(const struct retro_game_info* game)
|
||||
{
|
||||
Libretro::ResetDiskControlState();
|
||||
@@ -229,12 +231,12 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
*/
|
||||
if (Libretro::GBPlayer_active)
|
||||
{
|
||||
std::filesystem::path gba_content_dir = std::filesystem::path("");
|
||||
std::string gba_content_dir;
|
||||
if (!Libretro::GBPlayer_rom_path.empty())
|
||||
gba_content_dir = std::filesystem::path(Libretro::GBPlayer_rom_path).parent_path().filename().string();
|
||||
gba_content_dir = PathLast(PathParent(Libretro::GBPlayer_rom_path));
|
||||
|
||||
Libretro::environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &gba_save_dir);
|
||||
std::filesystem::path base = std::filesystem::path(gba_save_dir);
|
||||
std::string base = gba_save_dir ? gba_save_dir : "";
|
||||
|
||||
bool core_sort = false;
|
||||
retro_system_info sys_info{};
|
||||
@@ -242,21 +244,21 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
const std::string library_name = sys_info.library_name;
|
||||
|
||||
if (!base.empty() && !library_name.empty())
|
||||
core_sort = Common::CaseInsensitiveEquals(base.filename().string(), library_name);
|
||||
core_sort = Common::CaseInsensitiveEquals(PathLast(base), library_name);
|
||||
if (core_sort)
|
||||
base = base.parent_path();
|
||||
base = PathParent(base);
|
||||
|
||||
bool content_sort = false;
|
||||
std::string gc_content_dir;
|
||||
if (!gba_content_dir.empty() && !base.empty())
|
||||
content_sort = Common::CaseInsensitiveEquals(base.filename().string(), gba_content_dir.string());
|
||||
content_sort = Common::CaseInsensitiveEquals(PathLast(base), gba_content_dir);
|
||||
if (content_sort)
|
||||
{
|
||||
gc_content_dir = std::filesystem::path(game->path).parent_path().filename().string();
|
||||
base = base.parent_path();
|
||||
gc_content_dir = PathLast(PathParent(game->path));
|
||||
base = PathParent(base);
|
||||
}
|
||||
|
||||
rebuild_save_dir = base.string();
|
||||
rebuild_save_dir = base;
|
||||
if (content_sort)
|
||||
rebuild_save_dir = rebuild_save_dir + DIR_SEP + gc_content_dir;
|
||||
if (core_sort)
|
||||
@@ -290,7 +292,7 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
|
||||
using namespace Libretro::Options;
|
||||
Libretro::Options::Init();
|
||||
Libretro::VFile::Init();
|
||||
vfs_hybrid_init(Libretro::environ_cb, nullptr);
|
||||
|
||||
#ifdef ANDROID
|
||||
static bool sysdir_set = false;
|
||||
@@ -704,13 +706,11 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
NOTICE_LOG_FMT(VIDEO, "Using GFX backend: {}", Config::Get(Config::MAIN_GFX_BACKEND));
|
||||
|
||||
std::vector<std::string> normalized_game_paths;
|
||||
normalized_game_paths.push_back(Libretro::VFile::NormalizePath(game->path));
|
||||
normalized_game_paths.push_back(game->path);
|
||||
std::string folder_path_str;
|
||||
std::string filename_str;
|
||||
std::string extension;
|
||||
SplitPath(normalized_game_paths.front(), &folder_path_str, &filename_str, &extension);
|
||||
fs::path folder_path(folder_path_str);
|
||||
fs::path filename(filename_str);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(),
|
||||
[](unsigned char c){ return std::tolower(c); });
|
||||
|
||||
@@ -722,7 +722,6 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
size_t last_slash = normalized_game_paths.front().find_last_of("\\/");
|
||||
if (last_slash != std::string::npos)
|
||||
folder_path_str = normalized_game_paths.front().substr(0, last_slash + 1);
|
||||
folder_path = fs::path(folder_path_str);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -737,7 +736,7 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
}
|
||||
|
||||
for (auto& normalized_game_path : normalized_game_paths)
|
||||
Libretro::disk_paths.push_back(Libretro::VFile::DenormalizePath(normalized_game_path));
|
||||
Libretro::disk_paths.push_back(normalized_game_path);
|
||||
|
||||
Libretro::Input::Init(wsi);
|
||||
|
||||
@@ -914,7 +913,7 @@ static bool retro_set_eject_state(bool ejected)
|
||||
system.GetDVDInterface().EjectDisc(guard, DVD::EjectCause::User);
|
||||
else if (disk_index < disk_paths.size())
|
||||
{
|
||||
const std::string path = Libretro::VFile::NormalizePath(disk_paths[disk_index]);
|
||||
const std::string& path = disk_paths[disk_index];
|
||||
system.GetDVDInterface().ChangeDisc(guard, path);
|
||||
}
|
||||
}, true); // wait_for_completion = true
|
||||
|
||||
@@ -1791,24 +1791,6 @@ static struct retro_core_option_v2_definition option_defs[] = {
|
||||
"disabled"
|
||||
},
|
||||
#endif // HAS_OPENGL/__WEBOS__
|
||||
{
|
||||
Libretro::Options::retroarch_core::ENABLE_LIBRETRO_VFS,
|
||||
"RetroArch core > Enable Virtual File System (VFS)",
|
||||
"Enable Virtual File System (VFS)",
|
||||
"Enable VFS for SAF/SMB etc. Requires core RESTART. This feature is NEW and EXPERIMENTAL. Backup your saves regularly.",
|
||||
nullptr,
|
||||
CATEGORY_RETROARCH_CORE,
|
||||
{
|
||||
{ "disabled", nullptr },
|
||||
{ "enabled", nullptr },
|
||||
{ nullptr, nullptr }
|
||||
},
|
||||
#if defined(ANDROID) || defined(__WEBOS__)
|
||||
"enabled" // enable by default because of SAF (android play version) / storage (webOS)
|
||||
#else
|
||||
"disabled"
|
||||
#endif
|
||||
},
|
||||
{
|
||||
Libretro::Options::retroarch_core::ENABLE_DEFAULT_MOUSE_BINDINGS,
|
||||
"RetroArch core > Enable Default Mouse Bindings",
|
||||
|
||||
@@ -346,7 +346,6 @@ namespace retroarch_core {
|
||||
constexpr const char SUPPORTS_GL_BASE_VERTEX[] = "dolphin_supports_gl_base_vertex";
|
||||
#endif
|
||||
|
||||
constexpr const char ENABLE_LIBRETRO_VFS[] = "dolphin_libretro_vfs_enabled";
|
||||
constexpr const char ENABLE_DEFAULT_MOUSE_BINDINGS[] = "dolphin_default_mouse_bindings_enabled";
|
||||
constexpr const char ENABLE_LOG_POPUPS[] = "dolphin_log_popups_enabled";
|
||||
} // namespace retroarch_core
|
||||
|
||||
@@ -1,944 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <cstdio>
|
||||
#include <sys/stat.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
|
||||
#include "VFile.h"
|
||||
#include "Globals.h"
|
||||
#include "Common/DirectIOFile.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
|
||||
#include <libretro.h>
|
||||
#include "DolphinLibretro/Common/Options.h"
|
||||
#include "DolphinLibretro/Log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef stat
|
||||
#endif
|
||||
|
||||
#define LIBRETRO_VFS_INTERFACE_VERSION 4
|
||||
|
||||
namespace Libretro
|
||||
{
|
||||
namespace VFile
|
||||
{
|
||||
retro_vfs_interface* vfs_interface = nullptr;
|
||||
static bool initialized = false;
|
||||
static bool is_enabled = true;
|
||||
|
||||
void Init()
|
||||
{
|
||||
is_enabled = Libretro::GetOption<bool>(Options::retroarch_core::ENABLE_LIBRETRO_VFS, /*def=*/true);
|
||||
|
||||
if (!is_enabled)
|
||||
return;
|
||||
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
retro_vfs_interface_info vfs_info{};
|
||||
vfs_info.required_interface_version = LIBRETRO_VFS_INTERFACE_VERSION;
|
||||
vfs_info.iface = nullptr;
|
||||
|
||||
if (Libretro::environ_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_info))
|
||||
{
|
||||
if (vfs_info.iface != nullptr)
|
||||
{
|
||||
vfs_interface = vfs_info.iface;
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vfs_interface)
|
||||
{
|
||||
WARN_LOG_FMT(BOOT, "LibRetro VFS V4 not available, using standard file I/O");
|
||||
}
|
||||
}
|
||||
|
||||
bool HasVFS()
|
||||
{
|
||||
return is_enabled && initialized && vfs_interface;
|
||||
}
|
||||
|
||||
// Dolphin expects to be able to use "/" (DIR_SEP) everywhere.
|
||||
// RetroArch uses the OS separator.
|
||||
// Convert between them when switching between systems.
|
||||
std::string NormalizePath(const std::string& path)
|
||||
{
|
||||
std::string newPath = path;
|
||||
#ifdef _MSC_VER
|
||||
namespace fs = std::filesystem;
|
||||
constexpr fs::path::value_type os_separator = fs::path::preferred_separator;
|
||||
static_assert(os_separator == DIR_SEP_CHR || os_separator == '\\',
|
||||
"Unsupported path separator");
|
||||
if (os_separator != DIR_SEP_CHR)
|
||||
std::replace(newPath.begin(), newPath.end(), '\\', DIR_SEP_CHR);
|
||||
#endif
|
||||
return newPath;
|
||||
}
|
||||
|
||||
std::string DenormalizePath(const std::string& path)
|
||||
{
|
||||
std::string newPath = path;
|
||||
#ifdef _MSC_VER
|
||||
namespace fs = std::filesystem;
|
||||
constexpr fs::path::value_type os_separator = fs::path::preferred_separator;
|
||||
static_assert(os_separator == DIR_SEP_CHR || os_separator == '\\',
|
||||
"Unsupported path separator");
|
||||
if (os_separator != DIR_SEP_CHR)
|
||||
std::replace(newPath.begin(), newPath.end(), DIR_SEP_CHR, '\\');
|
||||
#endif
|
||||
return newPath;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// File / DirectIOFile VFS Implementation
|
||||
// ============================================================================
|
||||
bool Exists(const std::string& path)
|
||||
{
|
||||
int64_t size = -1;
|
||||
int flags = vfs_interface->stat_64(path.c_str(), &size);
|
||||
|
||||
bool valid = (flags & RETRO_VFS_STAT_IS_VALID) != 0;
|
||||
|
||||
DEBUG_LOG_FMT(COMMON, "Exists {}: {}", path, valid);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
void FileInfo(std::filesystem::file_status& status, uintmax_t& size, bool& exists,
|
||||
const char* path)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "FileInfo {}", path);
|
||||
|
||||
using namespace std;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Defaults: not found
|
||||
exists = false;
|
||||
size = 0;
|
||||
status.type(fs::file_type::none);
|
||||
|
||||
if (!vfs_interface)
|
||||
return;
|
||||
|
||||
int64_t tmp_size = 0;
|
||||
int flags = vfs_interface->stat_64(path, &tmp_size);
|
||||
|
||||
if (!(flags & RETRO_VFS_STAT_IS_VALID))
|
||||
return;
|
||||
|
||||
exists = true;
|
||||
|
||||
if (flags & RETRO_VFS_STAT_IS_DIRECTORY)
|
||||
status.type(fs::file_type::directory);
|
||||
else
|
||||
{
|
||||
status.type(fs::file_type::regular);
|
||||
size = static_cast<uintmax_t>(tmp_size);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDirectory(const std::string& path)
|
||||
{
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
int64_t size = -1;
|
||||
int flags = vfs_interface->stat_64(path.c_str(), &size);
|
||||
|
||||
return (flags & RETRO_VFS_STAT_IS_VALID) && (flags & RETRO_VFS_STAT_IS_DIRECTORY);
|
||||
}
|
||||
|
||||
bool IsFile(const std::string& path)
|
||||
{
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
int64_t size = -1;
|
||||
int flags = vfs_interface->stat_64(path.c_str(), &size);
|
||||
|
||||
return (flags & RETRO_VFS_STAT_IS_VALID) && !(flags & RETRO_VFS_STAT_IS_DIRECTORY);
|
||||
}
|
||||
|
||||
u64 GetSize(const std::string& path)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "GetSize {}", path);
|
||||
|
||||
if (!vfs_interface)
|
||||
return 0;
|
||||
|
||||
int64_t size = 0;
|
||||
|
||||
int flags = vfs_interface->stat_64(path.c_str(), &size);
|
||||
|
||||
if (flags & RETRO_VFS_STAT_IS_VALID)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "GetSize returning {}", static_cast<u64>(size));
|
||||
return static_cast<u64>(size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 GetSize(::retro_vfs_file_handle* handle)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "GetSize (VFS) handle");
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return 0;
|
||||
|
||||
int64_t sz = vfs_interface->size(handle);
|
||||
return sz >= 0 ? static_cast<u64>(sz) : 0;
|
||||
}
|
||||
|
||||
bool Delete(const std::string& path)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "Delete {}", path);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
return vfs_interface->remove(path.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool CreateDir(const std::string& path)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "CreateDir {}", path);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
return vfs_interface->mkdir(path.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool DeleteDir(const std::string& path, bool recursive)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "DeleteDir {} recursive={}", path, recursive);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
if (!recursive)
|
||||
{
|
||||
retro_vfs_dir_handle* dir = vfs_interface->opendir(path.c_str(), false);
|
||||
if (dir)
|
||||
{
|
||||
if (vfs_interface->closedir)
|
||||
vfs_interface->closedir(dir);
|
||||
if (vfs_interface->remove)
|
||||
return vfs_interface->remove(path.c_str()) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursive delete
|
||||
retro_vfs_dir_handle* dir = vfs_interface->opendir(path.c_str(), false);
|
||||
if (!dir)
|
||||
return false;
|
||||
|
||||
bool ok = true;
|
||||
|
||||
while (vfs_interface->readdir(dir))
|
||||
{
|
||||
const char* name = vfs_interface->dirent_get_name(dir);
|
||||
if (!name || strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
|
||||
continue;
|
||||
|
||||
std::string child_path = path + "/" + name;
|
||||
if (vfs_interface->dirent_is_dir(dir))
|
||||
{
|
||||
if (!DeleteDir(child_path, true))
|
||||
ok = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vfs_interface->remove(child_path.c_str()) != 0)
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
vfs_interface->closedir(dir);
|
||||
|
||||
if (vfs_interface->remove(path.c_str()) != 0)
|
||||
ok = false;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool Rename(const std::string& src, const std::string& dst)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "Rename {} {}", src, dst);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
// IOS_FS: Failed to rename temporary FST file otherwise
|
||||
if (Exists(dst))
|
||||
{
|
||||
if(!Delete(dst))
|
||||
WARN_LOG_FMT(COMMON, "Rename failed src: {} dest: {}", src, dst);
|
||||
}
|
||||
|
||||
return vfs_interface->rename(src.c_str(), dst.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool CopyRegularFile(std::string_view src,
|
||||
std::string_view dst,
|
||||
bool overwrite_existing)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "CopyRegularFile {} {} overwrite={}", src, dst, overwrite_existing);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
std::string src_str(src);
|
||||
std::string dst_str(dst);
|
||||
|
||||
// Open source for reading
|
||||
retro_vfs_file_handle* in = vfs_interface->open(src_str.c_str(),
|
||||
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!in)
|
||||
return false;
|
||||
|
||||
// If not overwriting, check existence first
|
||||
if (!overwrite_existing && Exists(dst_str))
|
||||
{
|
||||
vfs_interface->close(in);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open destination for writing (will truncate if exists)
|
||||
retro_vfs_file_handle* out = vfs_interface->open(dst_str.c_str(),
|
||||
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!out)
|
||||
{
|
||||
vfs_interface->close(in);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy in chunks
|
||||
static constexpr size_t CHUNK = 64 * 1024;
|
||||
std::vector<uint8_t> buf(CHUNK);
|
||||
bool ok = true;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int64_t r = vfs_interface->read(in, buf.data(), buf.size());
|
||||
if (r < 0) { ok = false; break; }
|
||||
if (r == 0) break; // EOF
|
||||
int64_t w = vfs_interface->write(out, buf.data(), static_cast<uint64_t>(r));
|
||||
if (w != r) { ok = false; break; }
|
||||
}
|
||||
|
||||
vfs_interface->close(out);
|
||||
vfs_interface->close(in);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool CreateEmptyFile(const std::string& path)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "CreateEmptyFile {}", path);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
retro_vfs_file_handle* f = vfs_interface->open(path.c_str(),
|
||||
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!f) return false;
|
||||
|
||||
return vfs_interface->close(f) == 0;
|
||||
}
|
||||
|
||||
bool WriteStringToFile(const std::string& filename, std::string_view str)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "WriteStringToFile {}: {}", filename, str);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
retro_vfs_file_handle* f = vfs_interface->open(filename.c_str(),
|
||||
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
bool ok = (vfs_interface->write(f, str.data(), str.size()) == (int64_t)str.size());
|
||||
vfs_interface->close(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool ReadFileToString(const std::string& filename, std::string& str)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "ReadFileToString {}: {}", filename, str);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
retro_vfs_file_handle* f = vfs_interface->open(filename.c_str(),
|
||||
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
int64_t size = vfs_interface->size(f);
|
||||
if (size <= 0)
|
||||
{
|
||||
vfs_interface->close(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
str.resize(size);
|
||||
int64_t read = vfs_interface->read(f, str.data(), size);
|
||||
vfs_interface->close(f);
|
||||
|
||||
return read == size;
|
||||
}
|
||||
|
||||
std::vector<std::string> ReadLines(const std::string& filename)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "ReadLines {}", filename);
|
||||
|
||||
std::vector<std::string> lines;
|
||||
|
||||
if (!vfs_interface)
|
||||
return lines;
|
||||
|
||||
retro_vfs_file_handle* f =
|
||||
vfs_interface->open(filename.c_str(),
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!f)
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "ReadLines {} failed to open", filename.c_str());
|
||||
return lines;
|
||||
}
|
||||
|
||||
int64_t size = vfs_interface->size(f);
|
||||
if (size <= 0)
|
||||
{
|
||||
vfs_interface->close(f);
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::string buffer(size, '\0');
|
||||
int64_t read = vfs_interface->read(f, buffer.data(), size);
|
||||
vfs_interface->close(f);
|
||||
|
||||
if (read != size)
|
||||
return lines;
|
||||
|
||||
std::stringstream ss(buffer);
|
||||
std::string line;
|
||||
|
||||
while (std::getline(ss, line))
|
||||
lines.push_back(line);
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
bool CreateDirs(std::string_view path)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "CreateDirs {}", path);
|
||||
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
if (path.empty())
|
||||
return false;
|
||||
|
||||
std::string accum;
|
||||
size_t i = 0;
|
||||
|
||||
if (path[0] == '/')
|
||||
accum = "/";
|
||||
|
||||
while (i < path.size())
|
||||
{
|
||||
size_t slash = path.find('/', i);
|
||||
std::string token = (slash == std::string::npos) ?
|
||||
std::string(path.substr(i)) :
|
||||
std::string(path.substr(i, slash - i));
|
||||
|
||||
if (!token.empty() && token != ".")
|
||||
{
|
||||
if (!accum.empty() && accum.back() != '/')
|
||||
accum.push_back('/');
|
||||
accum += token;
|
||||
|
||||
int rc = vfs_interface->mkdir(accum.c_str());
|
||||
if (rc != 0 && rc != -2) // -2 means already exists
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slash == std::string::npos)
|
||||
break;
|
||||
|
||||
i = slash + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateFullPath(std::string_view fullPath)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "CreateFullPath {}", fullPath);
|
||||
|
||||
if (fullPath.empty())
|
||||
return false;
|
||||
|
||||
size_t last = fullPath.rfind('/');
|
||||
if (last == std::string::npos)
|
||||
return CreateDirs(".");
|
||||
|
||||
return CreateDirs(fullPath.substr(0, last));
|
||||
}
|
||||
|
||||
::File::FSTEntry ScanDirectoryTree(const std::string& directory, bool recursive)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "ScanDirectoryTree {} {}", directory, recursive);
|
||||
|
||||
::File::FSTEntry parent_entry;
|
||||
parent_entry.physicalName = directory;
|
||||
parent_entry.isDirectory = IsDirectory(directory);
|
||||
parent_entry.size = 0;
|
||||
|
||||
if (!vfs_interface)
|
||||
return parent_entry;
|
||||
|
||||
retro_vfs_dir_handle* dir = vfs_interface->opendir(directory.c_str(), false);
|
||||
if (!dir)
|
||||
return parent_entry;
|
||||
|
||||
auto calc_dir_size = [](::File::FSTEntry& entry) {
|
||||
entry.size += entry.children.size();
|
||||
for (auto& child : entry.children)
|
||||
if (child.isDirectory)
|
||||
entry.size += child.size;
|
||||
};
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
std::stack<std::string> dir_stack;
|
||||
dir_stack.push(directory);
|
||||
|
||||
while (!dir_stack.empty())
|
||||
{
|
||||
std::string current_path = dir_stack.top();
|
||||
dir_stack.pop();
|
||||
|
||||
DEBUG_LOG_FMT(COMMON, "ScanDirectoryTree (recursive) {}", current_path);
|
||||
|
||||
if (current_path.empty())
|
||||
continue;
|
||||
|
||||
retro_vfs_dir_handle* d = vfs_interface->opendir(current_path.c_str(), false);
|
||||
if (!d)
|
||||
continue;
|
||||
|
||||
// Find the entry object corresponding to current_path
|
||||
std::function<::File::FSTEntry*(::File::FSTEntry&)> findEntry =
|
||||
[&](::File::FSTEntry& e) -> ::File::FSTEntry* {
|
||||
if (e.physicalName == current_path)
|
||||
return &e;
|
||||
for (auto& child : e.children)
|
||||
{
|
||||
if (auto* found = findEntry(child))
|
||||
return found;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
::File::FSTEntry* parent = findEntry(parent_entry);
|
||||
if (!parent)
|
||||
{
|
||||
vfs_interface->closedir(d);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (vfs_interface->readdir(d))
|
||||
{
|
||||
const char* name = vfs_interface->dirent_get_name(d);
|
||||
if (!name || strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
|
||||
continue;
|
||||
|
||||
::File::FSTEntry entry;
|
||||
entry.virtualName = name;
|
||||
entry.physicalName = current_path + "/" + name;
|
||||
entry.isDirectory = vfs_interface->dirent_is_dir(d);
|
||||
entry.size = 0;
|
||||
|
||||
parent->children.push_back(entry);
|
||||
|
||||
if (entry.isDirectory)
|
||||
dir_stack.push(entry.physicalName);
|
||||
}
|
||||
|
||||
vfs_interface->closedir(d);
|
||||
calc_dir_size(*parent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (vfs_interface->readdir(dir))
|
||||
{
|
||||
const char* name = vfs_interface->dirent_get_name(dir);
|
||||
if (!name || strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
|
||||
continue;
|
||||
|
||||
::File::FSTEntry entry;
|
||||
entry.virtualName = name;
|
||||
entry.physicalName = directory + "/" + name;
|
||||
entry.isDirectory = vfs_interface->dirent_is_dir(dir);
|
||||
entry.size = 0;
|
||||
|
||||
parent_entry.children.push_back(entry);
|
||||
}
|
||||
calc_dir_size(parent_entry);
|
||||
}
|
||||
|
||||
vfs_interface->closedir(dir);
|
||||
|
||||
return parent_entry;
|
||||
}
|
||||
|
||||
// Move a file or directory to a new location, overwriting if it exists.
|
||||
bool MoveWithOverwrite(const std::string& src, const std::string& dst)
|
||||
{
|
||||
if (!vfs_interface)
|
||||
return false;
|
||||
|
||||
// Try direct rename first
|
||||
if (vfs_interface->rename(src.c_str(), dst.c_str()) == 0)
|
||||
return true;
|
||||
|
||||
// rename failed, try fallbacks
|
||||
|
||||
// Check if src is a directory
|
||||
int64_t size = -1;
|
||||
int flags = vfs_interface->stat_64(src.c_str(), &size);
|
||||
bool is_dir = (flags & RETRO_VFS_STAT_IS_VALID) && (flags & RETRO_VFS_STAT_IS_DIRECTORY);
|
||||
|
||||
if (!is_dir)
|
||||
{
|
||||
// src is a file: copy + delete
|
||||
if (!CopyRegularFile(src, dst, true)) // overwrite_existing = true
|
||||
return false;
|
||||
if (vfs_interface->remove(src.c_str()) != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// src is a directory: recurse into it
|
||||
retro_vfs_dir_handle* dir = vfs_interface->opendir(src.c_str(), false);
|
||||
if (!dir)
|
||||
return false;
|
||||
|
||||
bool ok = true;
|
||||
|
||||
while (vfs_interface->readdir(dir))
|
||||
{
|
||||
const char* name = vfs_interface->dirent_get_name(dir);
|
||||
if (!name || strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
|
||||
continue;
|
||||
|
||||
std::string child_src = src + "/" + name;
|
||||
std::string child_dst = dst + "/" + name;
|
||||
|
||||
if (!MoveWithOverwrite(child_src, child_dst))
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vfs_interface->closedir(dir);
|
||||
|
||||
// Remove the source directory itself
|
||||
if (ok)
|
||||
{
|
||||
if (vfs_interface->remove(src.c_str()) != 0)
|
||||
ok = false;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IOFile VFS Implementation
|
||||
// ============================================================================
|
||||
::retro_vfs_file_handle* Open(const std::string& filename, const char openmode[])
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFile::Open {} {}", filename, openmode);
|
||||
|
||||
if (!vfs_interface)
|
||||
return nullptr;
|
||||
|
||||
unsigned mode = 0;
|
||||
unsigned hints = RETRO_VFS_FILE_ACCESS_HINT_NONE;
|
||||
|
||||
bool read = false;
|
||||
bool write = false;
|
||||
bool append = false;
|
||||
bool plus = false;
|
||||
|
||||
for (const char* p = openmode; *p; ++p)
|
||||
{
|
||||
switch (*p)
|
||||
{
|
||||
case 'r': read = true; break;
|
||||
case 'w': write = true; break;
|
||||
case 'a': append = true; break;
|
||||
case '+': plus = true; break;
|
||||
case 'b': break;
|
||||
}
|
||||
}
|
||||
if (read && !write && !plus)
|
||||
{
|
||||
// "r" or "rb" - read only
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ;
|
||||
}
|
||||
else if (write && !read && !plus && !append)
|
||||
{
|
||||
// "w" or "wb" - write only, truncate
|
||||
mode = RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
}
|
||||
else if (append && !plus)
|
||||
{
|
||||
// "a" or "ab" - append only
|
||||
mode = RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
}
|
||||
else if (read && plus)
|
||||
{
|
||||
// "r+" or "r+b" - read and write, file must exist
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
|
||||
}
|
||||
else if (write && plus)
|
||||
{
|
||||
// "w+" or "w+b" - read and write, truncate
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
}
|
||||
else if (append && plus)
|
||||
{
|
||||
// "a+" or "a+b" - read and append
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to read
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ;
|
||||
}
|
||||
|
||||
::retro_vfs_file_handle* handle = vfs_interface->open(filename.c_str(), mode, hints);
|
||||
|
||||
if (handle)
|
||||
{
|
||||
// Handle write mode - truncate to 0
|
||||
if (write && !plus && !append)
|
||||
{
|
||||
vfs_interface->truncate(handle, 0);
|
||||
}
|
||||
|
||||
// Handle append mode - seek to end
|
||||
if (append)
|
||||
{
|
||||
vfs_interface->seek(handle, 0, RETRO_VFS_SEEK_POSITION_END);
|
||||
}
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
// helper: return int status, 0 = success, nonzero = error
|
||||
int Close(::retro_vfs_file_handle* handle)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFileClose");
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return -1;
|
||||
|
||||
return vfs_interface->close(handle);
|
||||
}
|
||||
|
||||
s64 Seek(::retro_vfs_file_handle* handle, s64 offset, ::File::SeekOrigin origin)
|
||||
{
|
||||
int whence = RETRO_VFS_SEEK_POSITION_START;
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case ::File::SeekOrigin::Current:
|
||||
whence = static_cast<int>(::File::SeekOrigin::Current);
|
||||
break;
|
||||
case ::File::SeekOrigin::End:
|
||||
whence = static_cast<int>(::File::SeekOrigin::End);
|
||||
break;
|
||||
case ::File::SeekOrigin::Begin:
|
||||
default:
|
||||
whence = static_cast<int>(::File::SeekOrigin::Begin);
|
||||
break;
|
||||
}
|
||||
|
||||
return Libretro::VFile::Seek(handle, offset, whence);
|
||||
}
|
||||
|
||||
s64 Seek(::retro_vfs_file_handle* handle, s64 offset, int origin)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFileSeek {} {}", offset, origin);
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return -1;
|
||||
|
||||
int whence = RETRO_VFS_SEEK_POSITION_START;
|
||||
|
||||
if (origin == SEEK_CUR)
|
||||
whence = RETRO_VFS_SEEK_POSITION_CURRENT;
|
||||
else if (origin == SEEK_END)
|
||||
whence = RETRO_VFS_SEEK_POSITION_END;
|
||||
|
||||
return vfs_interface->seek(handle, offset, whence);
|
||||
}
|
||||
|
||||
s64 Tell(::retro_vfs_file_handle* handle)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFileTell");
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return UINT64_MAX;
|
||||
|
||||
int64_t pos = vfs_interface->tell(handle);
|
||||
return pos >= 0 ? static_cast<u64>(pos) : UINT64_MAX;
|
||||
}
|
||||
|
||||
int Flush(::retro_vfs_file_handle* handle)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFileFlush");
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return -1;
|
||||
|
||||
return vfs_interface->flush(handle);
|
||||
}
|
||||
|
||||
s64 Resize(::retro_vfs_file_handle* handle, u64 size)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFileResize {}", size);
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return -1;
|
||||
|
||||
return vfs_interface->truncate(handle, size);
|
||||
}
|
||||
|
||||
u64 ReadBytes(::retro_vfs_file_handle* handle, void* data, size_t length)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFileReadBytes {}", length);
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return 0;
|
||||
|
||||
int64_t result = vfs_interface->read(handle, data, length);
|
||||
return result >= 0 ? static_cast<u64>(result) : 0;
|
||||
}
|
||||
|
||||
u64 WriteBytes(::retro_vfs_file_handle* handle, const void* data, size_t length)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFileWriteBytes {}", length);
|
||||
|
||||
if (!vfs_interface || !handle)
|
||||
return 0;
|
||||
|
||||
int64_t result = vfs_interface->write(handle, data, length);
|
||||
return result >= 0 ? static_cast<u64>(result) : 0;
|
||||
}
|
||||
|
||||
// Helper for DirectIOFile::Open
|
||||
bool Open(const std::string& path,
|
||||
::File::AccessMode access_mode,
|
||||
::File::OpenMode open_mode,
|
||||
retro_vfs_file_handle*& out_handle,
|
||||
unsigned& out_mode,
|
||||
unsigned& out_hints,
|
||||
u64& out_offset)
|
||||
{
|
||||
unsigned mode = 0;
|
||||
|
||||
if (access_mode == ::File::AccessMode::Read)
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ;
|
||||
else if (access_mode == ::File::AccessMode::Write)
|
||||
mode = RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
else // ReadAndWrite
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
|
||||
|
||||
unsigned hints = RETRO_VFS_FILE_ACCESS_HINT_NONE;
|
||||
|
||||
if (open_mode == ::File::OpenMode::Truncate)
|
||||
{
|
||||
// VFS doesn't have explicit truncate - we'll handle it after opening
|
||||
mode |= RETRO_VFS_FILE_ACCESS_WRITE;
|
||||
}
|
||||
else if (open_mode == ::File::OpenMode::Create)
|
||||
{
|
||||
if (Exists(path))
|
||||
return false;
|
||||
}
|
||||
|
||||
retro_vfs_file_handle* handle = vfs_interface->open(path.c_str(), mode, hints);
|
||||
if (!handle)
|
||||
return false;
|
||||
|
||||
if (open_mode == ::File::OpenMode::Truncate)
|
||||
{
|
||||
if (vfs_interface->truncate(handle, 0) != 0)
|
||||
{
|
||||
vfs_interface->close(handle);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
out_handle = handle;
|
||||
out_mode = mode;
|
||||
out_hints = hints;
|
||||
out_offset = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
::retro_vfs_file_handle* Open(const std::string& filename, unsigned mode, unsigned hints)
|
||||
{
|
||||
DEBUG_LOG_FMT(COMMON, "IOFile::Open {} {} {}", filename, mode, hints);
|
||||
|
||||
if (!vfs_interface)
|
||||
return nullptr;
|
||||
|
||||
::retro_vfs_file_handle* handle = vfs_interface->open(filename.c_str(), mode, hints);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
s64 Truncate(retro_vfs_file_handle* vfs_handle, const u64 size)
|
||||
{
|
||||
if (!vfs_interface || !vfs_handle)
|
||||
return 0;
|
||||
|
||||
return vfs_interface->truncate(vfs_handle, size);
|
||||
}
|
||||
|
||||
std::string GetPath(::retro_vfs_file_handle* handle)
|
||||
{
|
||||
if (!vfs_interface || !handle)
|
||||
return std::string();
|
||||
|
||||
return Libretro::VFile::vfs_interface->get_path(handle);
|
||||
}
|
||||
|
||||
} // namespace File
|
||||
} // namespace Libretro
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include <libretro.h>
|
||||
#include <vfs/vfs.h>
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
namespace File
|
||||
{
|
||||
enum class SeekOrigin;
|
||||
enum class AccessMode;
|
||||
enum class OpenMode;
|
||||
}
|
||||
|
||||
namespace Libretro
|
||||
{
|
||||
namespace VFile
|
||||
{
|
||||
extern retro_vfs_interface* vfs_interface;
|
||||
|
||||
// Generic methods
|
||||
void Init();
|
||||
bool HasVFS();
|
||||
std::string NormalizePath(const std::string& path);
|
||||
std::string DenormalizePath(const std::string& path);
|
||||
|
||||
// FileUtil
|
||||
bool Exists(const std::string& path);
|
||||
void FileInfo(std::filesystem::file_status& status, uintmax_t& size, bool& exists,
|
||||
const char* path);
|
||||
bool IsDirectory(const std::string& path);
|
||||
bool IsFile(const std::string& path);
|
||||
u64 GetSize(const std::string& path);
|
||||
u64 GetSize(::retro_vfs_file_handle* handle);
|
||||
|
||||
bool Delete(const std::string& path);
|
||||
bool CreateDir(const std::string& path);
|
||||
bool DeleteDir(const std::string& path, bool recursive = false);
|
||||
bool Rename(const std::string& src, const std::string& dst);
|
||||
bool CopyRegularFile(std::string_view src, std::string_view dst, bool overwrite_existing = true);
|
||||
bool CreateEmptyFile(const std::string& path);
|
||||
bool WriteStringToFile(const std::string& filename, std::string_view str);
|
||||
bool ReadFileToString(const std::string& filename, std::string& str);
|
||||
bool CreateDirs(std::string_view path);
|
||||
bool CreateFullPath(std::string_view fullPath);
|
||||
::File::FSTEntry ScanDirectoryTree(const std::string& directory, bool recursive);
|
||||
bool MoveWithOverwrite(const std::string& src, const std::string& dst);
|
||||
|
||||
// IOFile
|
||||
::retro_vfs_file_handle* Open(const std::string& filename, const char openmode[]);
|
||||
int Close(::retro_vfs_file_handle* handle);
|
||||
s64 Seek(::retro_vfs_file_handle* handle, s64 offset, ::File::SeekOrigin origin);
|
||||
s64 Seek(::retro_vfs_file_handle* handle, s64 offset, int origin);
|
||||
s64 Tell(::retro_vfs_file_handle* handle);
|
||||
s64 Resize(::retro_vfs_file_handle* handle, u64 size);
|
||||
u64 ReadBytes(::retro_vfs_file_handle* handle, void* data, size_t length);
|
||||
u64 WriteBytes(::retro_vfs_file_handle* handle, const void* data, size_t length);
|
||||
int Flush(::retro_vfs_file_handle* handle);
|
||||
|
||||
// DirectIOFile
|
||||
bool Open(const std::string& path, ::File::AccessMode access_mode, ::File::OpenMode open_mode,
|
||||
retro_vfs_file_handle*& out_handle,
|
||||
unsigned& out_mode,
|
||||
unsigned& out_hints,
|
||||
u64& out_offset);
|
||||
::retro_vfs_file_handle* Open(const std::string& filename, unsigned mode, unsigned hints);
|
||||
|
||||
s64 Truncate(retro_vfs_file_handle* vfs_handle, const u64 size);
|
||||
|
||||
// IOS::HLE::FS
|
||||
std::string GetPath(::retro_vfs_file_handle* handle);
|
||||
|
||||
std::vector<std::string> ReadLines(const std::string& filename);
|
||||
|
||||
} // namespace File
|
||||
} // namespace Libretro
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <libretro.h>
|
||||
#include <string>
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
#include <cmath>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h> // for SHGetFolderPath
|
||||
#ifdef __MINGW32__
|
||||
#include "Common/WILShim.h"
|
||||
#endif
|
||||
#include <wil/resource.h>
|
||||
#endif
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -298,183 +291,8 @@ void CreateDirectories()
|
||||
|
||||
void SetUserDirectory(std::string custom_path)
|
||||
{
|
||||
if (!custom_path.empty())
|
||||
{
|
||||
File::CreateFullPath(custom_path + DIR_SEP);
|
||||
File::SetUserPath(D_USER_IDX, std::move(custom_path));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string user_path;
|
||||
#ifdef _WIN32
|
||||
// Detect where the User directory is. There are five different cases
|
||||
// (on top of the command line flag, which overrides all this):
|
||||
// 1. GetExeDirectory()\portable.txt exists
|
||||
// -> Use GetExeDirectory()\User
|
||||
// 2. HKCU\Software\Dolphin Emulator\LocalUserConfig exists and is true
|
||||
// -> Use GetExeDirectory()\User
|
||||
// 3. HKCU\Software\Dolphin Emulator\UserConfigPath exists
|
||||
// -> Use this as the user directory path
|
||||
// 4. My Documents\Dolphin Emulator exists (default user folder before PR 10708)
|
||||
// -> Use this as the user directory path
|
||||
// 5. AppData\Roaming exists
|
||||
// -> Use AppData\Roaming\Dolphin Emulator as the User directory path
|
||||
// 6. Default
|
||||
// -> Use GetExeDirectory()\User
|
||||
|
||||
// Get AppData path in case we need it.
|
||||
wil::unique_cotaskmem_string appdata;
|
||||
bool appdata_found = SUCCEEDED(
|
||||
SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, appdata.put()));
|
||||
|
||||
// Check our registry keys
|
||||
wil::unique_hkey hkey;
|
||||
DWORD local = 0;
|
||||
std::unique_ptr<TCHAR[]> configPath;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Dolphin Emulator"), 0, KEY_QUERY_VALUE,
|
||||
hkey.put()) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD size = sizeof(local);
|
||||
if (RegQueryValueEx(hkey.get(), TEXT("LocalUserConfig"), nullptr, nullptr,
|
||||
reinterpret_cast<LPBYTE>(&local), &size) != ERROR_SUCCESS)
|
||||
{
|
||||
local = 0;
|
||||
}
|
||||
|
||||
size = 0;
|
||||
RegQueryValueEx(hkey.get(), TEXT("UserConfigPath"), nullptr, nullptr, nullptr, &size);
|
||||
configPath = std::make_unique<TCHAR[]>(size / sizeof(TCHAR));
|
||||
if (RegQueryValueEx(hkey.get(), TEXT("UserConfigPath"), nullptr, nullptr,
|
||||
reinterpret_cast<LPBYTE>(configPath.get()), &size) != ERROR_SUCCESS)
|
||||
{
|
||||
configPath.reset();
|
||||
}
|
||||
}
|
||||
|
||||
local = local != 0 || File::Exists(File::GetExeDirectory() + DIR_SEP "portable.txt");
|
||||
|
||||
// Attempt to check if the old User directory exists in Documents.
|
||||
wil::unique_cotaskmem_string documents;
|
||||
bool documents_found = SUCCEEDED(
|
||||
SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, documents.put()));
|
||||
|
||||
std::optional<std::string> old_user_folder;
|
||||
if (documents_found)
|
||||
{
|
||||
old_user_folder = TStrToUTF8(documents.get()) + DIR_SEP NORMAL_USER_DIR DIR_SEP;
|
||||
}
|
||||
|
||||
if (local) // Case 1-2
|
||||
{
|
||||
user_path = File::GetExeDirectory() + DIR_SEP PORTABLE_USER_DIR DIR_SEP;
|
||||
}
|
||||
else if (configPath) // Case 3
|
||||
{
|
||||
user_path = TStrToUTF8(configPath.get());
|
||||
}
|
||||
else if (old_user_folder && File::Exists(old_user_folder.value())) // Case 4
|
||||
{
|
||||
user_path = old_user_folder.value();
|
||||
}
|
||||
else if (appdata_found) // Case 5
|
||||
{
|
||||
user_path = TStrToUTF8(appdata.get()) + DIR_SEP NORMAL_USER_DIR DIR_SEP;
|
||||
|
||||
// Set the UserConfigPath value in the registry for backwards compatibility with older Dolphin
|
||||
// builds, which will look for the default User directory in Documents. If we set this key,
|
||||
// they will use this as the User directory instead.
|
||||
// (If we're in this case, then this key doesn't exist, so it's OK to set it.)
|
||||
std::wstring wstr_path = UTF8ToWString(user_path);
|
||||
RegSetKeyValueW(HKEY_CURRENT_USER, TEXT("Software\\Dolphin Emulator"), TEXT("UserConfigPath"),
|
||||
REG_SZ, wstr_path.c_str(),
|
||||
static_cast<DWORD>((wstr_path.size() + 1) * sizeof(wchar_t)));
|
||||
}
|
||||
else // Case 6
|
||||
{
|
||||
user_path = File::GetExeDirectory() + DIR_SEP PORTABLE_USER_DIR DIR_SEP;
|
||||
}
|
||||
#else
|
||||
if (File::IsDirectory(ROOT_DIR DIR_SEP EMBEDDED_USER_DIR))
|
||||
{
|
||||
user_path = ROOT_DIR DIR_SEP EMBEDDED_USER_DIR DIR_SEP;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* env_path = getenv("DOLPHIN_EMU_USERPATH");
|
||||
const char* home = getenv("HOME");
|
||||
if (!home)
|
||||
home = getenv("PWD");
|
||||
if (!home)
|
||||
home = "";
|
||||
std::string home_path = std::string(home) + DIR_SEP;
|
||||
|
||||
// On a non-Apple and non-Android POSIX system, there are 4 cases:
|
||||
// 1. GetExeDirectory()/portable.txt exists
|
||||
// -> Use GetExeDirectory()/User
|
||||
// 2. $DOLPHIN_EMU_USERPATH is set
|
||||
// -> Use $DOLPHIN_EMU_USERPATH
|
||||
// 3. ~/.dolphin-emu directory exists, and we're not in flatpak
|
||||
// -> Use ~/.dolphin-emu
|
||||
// 4. Default
|
||||
// -> Use XDG basedir, see
|
||||
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
//
|
||||
// On macOS:
|
||||
// 1. GetExeDirectory()/portable.txt exists
|
||||
// -> Use GetExeDirectory()/User
|
||||
// 2. $DOLPHIN_EMU_USERPATH is set
|
||||
// -> Use $DOLPHIN_EMU_USERPATH
|
||||
// 3. Default
|
||||
//
|
||||
// On Android, custom_path is set, so this code path is never reached.
|
||||
std::string exe_path = File::GetExeDirectory();
|
||||
if (File::Exists(exe_path + DIR_SEP "portable.txt"))
|
||||
{
|
||||
user_path = exe_path + DIR_SEP PORTABLE_USER_DIR DIR_SEP;
|
||||
}
|
||||
else if (env_path)
|
||||
{
|
||||
user_path = env_path;
|
||||
}
|
||||
#if defined(__APPLE__) || defined(ANDROID)
|
||||
else
|
||||
{
|
||||
user_path = home_path + NORMAL_USER_DIR DIR_SEP;
|
||||
}
|
||||
#else
|
||||
else
|
||||
{
|
||||
user_path = home_path + "." NORMAL_USER_DIR DIR_SEP;
|
||||
|
||||
if (File::Exists("/.flatpak-info") || !File::Exists(user_path))
|
||||
{
|
||||
const char* data_home = getenv("XDG_DATA_HOME");
|
||||
std::string data_path =
|
||||
std::string(data_home && data_home[0] == '/' ? data_home :
|
||||
(home_path + ".local" DIR_SEP "share")) +
|
||||
DIR_SEP NORMAL_USER_DIR DIR_SEP;
|
||||
|
||||
const char* config_home = getenv("XDG_CONFIG_HOME");
|
||||
std::string config_path =
|
||||
std::string(config_home && config_home[0] == '/' ? config_home :
|
||||
(home_path + ".config")) +
|
||||
DIR_SEP NORMAL_USER_DIR DIR_SEP;
|
||||
|
||||
const char* cache_home = getenv("XDG_CACHE_HOME");
|
||||
std::string cache_path =
|
||||
std::string(cache_home && cache_home[0] == '/' ? cache_home : (home_path + ".cache")) +
|
||||
DIR_SEP NORMAL_USER_DIR DIR_SEP;
|
||||
|
||||
File::SetUserPath(D_USER_IDX, data_path);
|
||||
File::SetUserPath(D_CONFIG_IDX, config_path);
|
||||
File::SetUserPath(D_CACHE_IDX, cache_path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
File::SetUserPath(D_USER_IDX, std::move(user_path));
|
||||
File::CreateFullPath(custom_path + DIR_SEP);
|
||||
File::SetUserPath(D_USER_IDX, std::move(custom_path));
|
||||
}
|
||||
|
||||
bool TriggerSTMPowerEvent()
|
||||
|
||||
@@ -99,7 +99,6 @@ std::optional<AbstractShader::BinaryData> CompileWithRslc(D3D_FEATURE_LEVEL feat
|
||||
enum rslc_stage rstage;
|
||||
std::string full;
|
||||
std::optional<AbstractShader::BinaryData> code;
|
||||
FILE* f;
|
||||
|
||||
rstage = stage == ShaderStage::Vertex ? RSLC_STAGE_VERTEX :
|
||||
stage == ShaderStage::Pixel ? RSLC_STAGE_FRAGMENT :
|
||||
@@ -130,13 +129,7 @@ std::optional<AbstractShader::BinaryData> CompileWithRslc(D3D_FEATURE_LEVEL feat
|
||||
{
|
||||
std::string path = VideoBackendBase::BadShaderFilename("rslc", num_failures++);
|
||||
File::CreateFullPath(path);
|
||||
f = fopen(path.c_str(), "wb");
|
||||
if (f)
|
||||
{
|
||||
fwrite(full.data(), 1, full.size(), f);
|
||||
fprintf(f, "\n/* rslc: %s */\n", err);
|
||||
fclose(f);
|
||||
}
|
||||
File::WriteStringToFile(path, fmt::format("{}\n/* rslc: {} */\n", full, err));
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "VideoBackends/OGL/ProgramShaderCache.h"
|
||||
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -391,13 +390,10 @@ bool ProgramShaderCache::CheckShaderCompileResult(GLuint id, GLenum type, std::s
|
||||
ERROR_LOG_FMT(VIDEO, "{} failed compilation:\n{}", prefix, info_log);
|
||||
|
||||
std::string filename = VideoBackendBase::BadShaderFilename(prefix, num_failures++);
|
||||
std::ofstream file;
|
||||
File::OpenFStream(file, filename, std::ios_base::out);
|
||||
file << s_glsl_header << code << info_log;
|
||||
file << "\n";
|
||||
file << "Dolphin Version: " + Common::GetScmRevStr() + "\n";
|
||||
file << "Video Backend: " + g_video_backend->GetDisplayName();
|
||||
file.close();
|
||||
File::WriteStringToFile(filename,
|
||||
fmt::format("{}{}{}\nDolphin Version: {}\nVideo Backend: {}",
|
||||
s_glsl_header, code, info_log, Common::GetScmRevStr(),
|
||||
g_video_backend->GetDisplayName()));
|
||||
|
||||
PanicAlertFmt("Failed to compile {} shader: {}\n"
|
||||
"Debug info ({}, {}, {}):\n{}",
|
||||
@@ -429,20 +425,16 @@ bool ProgramShaderCache::CheckProgramLinkResult(GLuint id, std::string_view vcod
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Program failed linking:\n{}", info_log);
|
||||
std::string filename = VideoBackendBase::BadShaderFilename("p", num_failures++);
|
||||
std::ofstream file;
|
||||
File::OpenFStream(file, filename, std::ios_base::out);
|
||||
std::string dump;
|
||||
if (!vcode.empty())
|
||||
file << s_glsl_header << vcode << '\n';
|
||||
dump += fmt::format("{}{}\n", s_glsl_header, vcode);
|
||||
if (!gcode.empty())
|
||||
file << s_glsl_header << gcode << '\n';
|
||||
dump += fmt::format("{}{}\n", s_glsl_header, gcode);
|
||||
if (!pcode.empty())
|
||||
file << s_glsl_header << pcode << '\n';
|
||||
|
||||
file << info_log;
|
||||
file << "\n";
|
||||
file << "Dolphin Version: " + Common::GetScmRevStr() + "\n";
|
||||
file << "Video Backend: " + g_video_backend->GetDisplayName();
|
||||
file.close();
|
||||
dump += fmt::format("{}{}\n", s_glsl_header, pcode);
|
||||
dump += fmt::format("{}\nDolphin Version: {}\nVideo Backend: {}", info_log,
|
||||
Common::GetScmRevStr(), g_video_backend->GetDisplayName());
|
||||
File::WriteStringToFile(filename, dump);
|
||||
|
||||
PanicAlertFmt("Failed to link shaders: {}\n"
|
||||
"Debug info ({}, {}, {}):\n{}",
|
||||
|
||||
@@ -121,16 +121,10 @@ static void ReportFailure(const char* stage_name, const std::string& source, con
|
||||
{
|
||||
static int counter;
|
||||
std::string path = VideoBackendBase::BadShaderFilename(stage_name, counter++);
|
||||
FILE* f;
|
||||
|
||||
ERROR_LOG_FMT(VIDEO, "rslc could not compile a {} shader: {}", stage_name, err);
|
||||
File::CreateFullPath(path);
|
||||
f = fopen(path.c_str(), "wb");
|
||||
if (!f)
|
||||
return;
|
||||
fwrite(source.data(), 1, source.size(), f);
|
||||
fprintf(f, "\n/* rslc: %s */\n", err);
|
||||
fclose(f);
|
||||
File::WriteStringToFile(path, fmt::format("{}\n/* rslc: {} */\n", source, err));
|
||||
}
|
||||
|
||||
static std::optional<SPIRVCodeVector> Compile(std::string_view source_code, enum rslc_stage stage,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
@@ -131,14 +130,6 @@ void GraphicsModGroupConfig::Load()
|
||||
void GraphicsModGroupConfig::Save() const
|
||||
{
|
||||
const std::string file_path = GetPath();
|
||||
std::ofstream json_stream;
|
||||
File::OpenFStream(json_stream, file_path, std::ios_base::out);
|
||||
if (!json_stream.is_open())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path);
|
||||
return;
|
||||
}
|
||||
|
||||
picojson::object serialized_root;
|
||||
picojson::array serialized_mods;
|
||||
for (const auto& mod : m_graphics_mods)
|
||||
@@ -150,7 +141,8 @@ void GraphicsModGroupConfig::Save() const
|
||||
serialized_root.emplace("mods", std::move(serialized_mods));
|
||||
|
||||
const auto output = picojson::value{serialized_root}.serialize(true);
|
||||
json_stream << output;
|
||||
if (!File::WriteStringToFile(file_path, output))
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to write graphics mod group json file '{}'", file_path);
|
||||
}
|
||||
|
||||
void GraphicsModGroupConfig::SetChangeCount(u32 change_count)
|
||||
|
||||
Reference in new issue
Block a user