diff --git a/.gitignore b/.gitignore index 88062784..426e43db 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ /tests/debugging/archive/ /tests/*.c /tests/*.cpp -/tools/_build/ /website/docs/ *.vcxproj.user .vs/ @@ -47,8 +46,4 @@ /research/ma_atomic.c /research/miniaudio_engine.c /tests/stress/ -/tools/codegen/miniaudio_amalgamator.c -/tools/codegen/miniaudio_codegen_utils.c -/tools/codegen/miniaudio_docgen.c -/tools/codegen/miniaudio_hrtfgen.c -/tools/codegen/miniaudio_splitter.c \ No newline at end of file +/tools/hrtfgen/ \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 3c52adcd..523efb63 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,16 +1,27 @@ -v0.11.23 - TBD +v0.11.23 - 2025-09-11 ===================== * Fixed an error in `ma_channel_map_to_string()` where the output string is not null terminated correctly. * Fixed an error with logging due to mishandling of va_list. * Fixed some errors when compiling with `MA_NO_RUNTIME_LINKING`. +* Fixed an error with `ma_sound` initialization where the initial loop points are not set correctly. * Fixed an alignment error with the ring buffer. * Fixed a memory leak in the resource manager when opening a file fails. * Fixed an assertion failure in the resource manager when opening a file fails. +* Fixed a compilation warning relating to `MA_FALLTHROUGH` +* Fixed an undefined behavior error in the s16 to s32 conversion routine. +* Fixed an undefined behavior error relating to MurmurHash3. +* Fixed an undefined behavior error with the LCG random number generator. +* Fixed a compilation error with `MA_NO_SSE2`. +* Fixed some unused function warnings. +* Fixed a rare, but technically possible division by zero error. +* Some const correctness fixes for `ma_sound`. * Improved compatibility with old versions of GCC. +* Miscellaneous documentation fixes. * WAV, FLAC and MP3 decoders have been brought up to date with dr_libs. Of particular note, this should fix some long outstanding bugs with MP3 due to metadata not being handled correctly. * POSIX: Added a fallback for when creation of a real-time thread fails. This fallback can be disabled with `MA_NO_PTHREAD_REALTIME_PRIORITY_FALLBACK` if you need an explicit failure. * POSIX: pthread.h is no longer included when `MA_NO_THREADING` is defined. * WASAPI: Improved handling of COM initialization and shutdown to make it a bit more robust. +* WASAPI: Fix an error due to a missing struct member. * PulseAudio: Fixed a crash when requesting a channel count greater than 32. * AAudio: Fixed a crash when uninitializing the device while in the middle of rerouting. diff --git a/CMakeLists.txt b/CMakeLists.txt index a373ac18..0d251f17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ project(miniaudio VERSION ${MINIAUDIO_VERSION}) # Options option(MINIAUDIO_BUILD_EXAMPLES "Build miniaudio examples" OFF) option(MINIAUDIO_BUILD_TESTS "Build miniaudio tests" OFF) +option(MINIAUDIO_BUILD_TOOLS "Build miniaudio development tools. Leave this disabled unless you know what you're doing. If you enable this and you get build errors, you clearly do not know what you're doing and yet you still enabled this option. Why would you do that?" OFF) option(MINIAUDIO_FORCE_CXX "Force compilation as C++" OFF) option(MINIAUDIO_FORCE_C89 "Force compilation as C89" OFF) option(MINIAUDIO_NO_EXTRA_NODES "Do not build extra node graph nodes" OFF) @@ -869,6 +870,15 @@ if (MINIAUDIO_BUILD_EXAMPLES) add_miniaudio_example(miniaudio_simple_spatialization simple_spatialization.c) endif() + +# Tools +if (MINIAUDIO_BUILD_TOOLS) + set(TOOLS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools) + + add_executable(madoc ${TOOLS_DIR}/madoc/madoc.c) +endif() + + if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") set(MINIAUDIO_PC_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") else() diff --git a/README.md b/README.md index aaf738db..ee7f46e0 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@

discord + x

@@ -32,7 +33,7 @@ Features - High-level API for sound management, mixing, effects and optional 3D spatialization. - Flexible node graph system for advanced mixing and effect processing. - Resource management for loading sound files. -- Decoding, with built-in support for WAV, FLAC and MP3, in addition to being able to plug in custom decoders. +- Decoding, with built-in support for WAV, FLAC, and MP3, in addition to being able to plug in custom decoders. - Encoding (WAV only). - Data conversion. - Resampling, including custom resamplers. @@ -206,7 +207,7 @@ Security ======== I deal with all security related issues publicly and transparently, and it can sometimes take a while before I get a chance to address it. If this is an issue for you, you need to use another library. The fastest way to get -a bug fixed is to submit a pull request, but if this is unpractical for you please post a ticket to the public +a bug fixed is to submit a pull request, but if this is impractical for you please post a ticket to the public GitHub issue tracker. diff --git a/camal/split.camal b/camal/split.camal new file mode 100644 index 00000000..dda87ec9 --- /dev/null +++ b/camal/split.camal @@ -0,0 +1,26 @@ +miniaudio_h :: <../miniaudio.h>; +miniaudio_split_h := <../extras/miniaudio_split/miniaudio.h>; +miniaudio_split_c := <../extras/miniaudio_split/miniaudio.c>; + +header := @(miniaudio_h["/\*" : "\*/"]); +footer := @(miniaudio_h["/\*\RThis software" : "\*/"]); + +content_h : string; +content_h["$"] = header; +content_h["$"] = "\n"; +content_h["$"] = @(miniaudio_h["#ifndef miniaudio_h" : "#endif /\* miniaudio_h \*/"]); +content_h["$"] = "\n\n"; +content_h["$"] = footer; +content_h["$"] = "\n"; + +content_c : string; +content_c["$"] = header; +content_c["$"] = "\n"; +content_c["$"] = '#include "miniaudio.h"\n\n'; +content_c["$"] = @(miniaudio_h["#ifndef miniaudio_c" : "#endif /\* miniaudio_c \*/"]); +content_c["$"] = "\n\n"; +content_c["$"] = footer; +content_c["$"] = "\n"; + +miniaudio_split_h = content_h; +miniaudio_split_c = content_c; \ No newline at end of file diff --git a/external/fs/fs.c b/external/fs/fs.c index f6ba0de9..d35c134d 100644 --- a/external/fs/fs.c +++ b/external/fs/fs.c @@ -3,22 +3,13 @@ #include "fs.h" -/* TODO: Remove this. To replicate errors, Just comment out this _XOPEN_SOURCE section and compile with `-std=c89` on GCC. */ -/* -This is for `-std=c89` compatibility. Without this there will be a few pthread related issues as well as some stdio -functions being unavailable. They will need workarounds. - -Note that this causes errors on Apple platforms, so we exclude Apple from this. -*/ -#ifndef __APPLE__ - #ifndef _XOPEN_SOURCE - #define _XOPEN_SOURCE 700 - #else - #if _XOPEN_SOURCE < 500 - #error _XOPEN_SOURCE must be >= 500. fs is not usable. - #endif - #endif +/* BEG fs_platform_detection.c */ +#if defined(_WIN32) + #define FS_WIN32 +#else + #define FS_POSIX #endif +/* END fs_platform_detection.c */ #include @@ -378,24 +369,352 @@ FS_API int fs_strnicmp(const char* str1, const char* str2, size_t count) } -#if defined(_WIN32) -#include /* <-- Just can't get away from this darn thing... Needed for mutexes and file iteration. */ - -static fs_result fs_result_from_GetLastError(DWORD error) +/* BEG fs_result.c */ +FS_API const char* fs_result_to_string(fs_result result) { - switch (error) + switch (result) { - case ERROR_SUCCESS: return FS_SUCCESS; - case ERROR_NOT_ENOUGH_MEMORY: return FS_OUT_OF_MEMORY; - case ERROR_BUSY: return FS_BUSY; - case ERROR_SEM_TIMEOUT: return FS_TIMEOUT; - default: break; + case FS_SUCCESS: return "No error"; + case FS_ERROR: return "Unknown error"; + case FS_INVALID_ARGS: return "Invalid argument"; + case FS_INVALID_OPERATION: return "Invalid operation"; + case FS_OUT_OF_MEMORY: return "Out of memory"; + case FS_OUT_OF_RANGE: return "Out of range"; + case FS_ACCESS_DENIED: return "Permission denied"; + case FS_DOES_NOT_EXIST: return "Resource does not exist"; + case FS_ALREADY_EXISTS: return "Resource already exists"; + case FS_TOO_MANY_OPEN_FILES: return "Too many open files"; + case FS_INVALID_FILE: return "Invalid file"; + case FS_TOO_BIG: return "Too large"; + case FS_PATH_TOO_LONG: return "Path too long"; + case FS_NAME_TOO_LONG: return "Name too long"; + case FS_NOT_DIRECTORY: return "Not a directory"; + case FS_IS_DIRECTORY: return "Is a directory"; + case FS_DIRECTORY_NOT_EMPTY: return "Directory not empty"; + case FS_AT_END: return "At end"; + case FS_NO_SPACE: return "No space available"; + case FS_BUSY: return "Device or resource busy"; + case FS_IO_ERROR: return "Input/output error"; + case FS_INTERRUPT: return "Interrupted"; + case FS_UNAVAILABLE: return "Resource unavailable"; + case FS_ALREADY_IN_USE: return "Resource already in use"; + case FS_BAD_ADDRESS: return "Bad address"; + case FS_BAD_SEEK: return "Illegal seek"; + case FS_BAD_PIPE: return "Broken pipe"; + case FS_DEADLOCK: return "Deadlock"; + case FS_TOO_MANY_LINKS: return "Too many links"; + case FS_NOT_IMPLEMENTED: return "Not implemented"; + case FS_NO_MESSAGE: return "No message of desired type"; + case FS_BAD_MESSAGE: return "Invalid message"; + case FS_NO_DATA_AVAILABLE: return "No data available"; + case FS_INVALID_DATA: return "Invalid data"; + case FS_TIMEOUT: return "Timeout"; + case FS_NO_NETWORK: return "Network unavailable"; + case FS_NOT_UNIQUE: return "Not unique"; + case FS_NOT_SOCKET: return "Socket operation on non-socket"; + case FS_NO_ADDRESS: return "Destination address required"; + case FS_BAD_PROTOCOL: return "Protocol wrong type for socket"; + case FS_PROTOCOL_UNAVAILABLE: return "Protocol not available"; + case FS_PROTOCOL_NOT_SUPPORTED: return "Protocol not supported"; + case FS_PROTOCOL_FAMILY_NOT_SUPPORTED: return "Protocol family not supported"; + case FS_ADDRESS_FAMILY_NOT_SUPPORTED: return "Address family not supported"; + case FS_SOCKET_NOT_SUPPORTED: return "Socket type not supported"; + case FS_CONNECTION_RESET: return "Connection reset"; + case FS_ALREADY_CONNECTED: return "Already connected"; + case FS_NOT_CONNECTED: return "Not connected"; + case FS_CONNECTION_REFUSED: return "Connection refused"; + case FS_NO_HOST: return "No host"; + case FS_IN_PROGRESS: return "Operation in progress"; + case FS_CANCELLED: return "Operation cancelled"; + case FS_MEMORY_ALREADY_MAPPED: return "Memory already mapped"; + case FS_DIFFERENT_DEVICE: return "Different device"; + default: return "Unknown error"; } +} +#include + +FS_API fs_result fs_result_from_errno(int error) +{ + if (error == 0) { + return FS_SUCCESS; + } +#ifdef EPERM + else if (error == EPERM) { return FS_INVALID_OPERATION; } +#endif +#ifdef ENOENT + else if (error == ENOENT) { return FS_DOES_NOT_EXIST; } +#endif +#ifdef ESRCH + else if (error == ESRCH) { return FS_DOES_NOT_EXIST; } +#endif +#ifdef EINTR + else if (error == EINTR) { return FS_INTERRUPT; } +#endif +#ifdef EIO + else if (error == EIO) { return FS_IO_ERROR; } +#endif +#ifdef ENXIO + else if (error == ENXIO) { return FS_DOES_NOT_EXIST; } +#endif +#ifdef E2BIG + else if (error == E2BIG) { return FS_INVALID_ARGS; } +#endif +#ifdef ENOEXEC + else if (error == ENOEXEC) { return FS_INVALID_FILE; } +#endif +#ifdef EBADF + else if (error == EBADF) { return FS_INVALID_FILE; } +#endif +#ifdef EAGAIN + else if (error == EAGAIN) { return FS_UNAVAILABLE; } +#endif +#ifdef ENOMEM + else if (error == ENOMEM) { return FS_OUT_OF_MEMORY; } +#endif +#ifdef EACCES + else if (error == EACCES) { return FS_ACCESS_DENIED; } +#endif +#ifdef EFAULT + else if (error == EFAULT) { return FS_BAD_ADDRESS; } +#endif +#ifdef EBUSY + else if (error == EBUSY) { return FS_BUSY; } +#endif +#ifdef EEXIST + else if (error == EEXIST) { return FS_ALREADY_EXISTS; } +#endif +#ifdef EXDEV + else if (error == EXDEV) { return FS_DIFFERENT_DEVICE; } +#endif +#ifdef ENODEV + else if (error == ENODEV) { return FS_DOES_NOT_EXIST; } +#endif +#ifdef ENOTDIR + else if (error == ENOTDIR) { return FS_NOT_DIRECTORY; } +#endif +#ifdef EISDIR + else if (error == EISDIR) { return FS_IS_DIRECTORY; } +#endif +#ifdef EINVAL + else if (error == EINVAL) { return FS_INVALID_ARGS; } +#endif +#ifdef ENFILE + else if (error == ENFILE) { return FS_TOO_MANY_OPEN_FILES; } +#endif +#ifdef EMFILE + else if (error == EMFILE) { return FS_TOO_MANY_OPEN_FILES; } +#endif +#ifdef ENOTTY + else if (error == ENOTTY) { return FS_INVALID_OPERATION; } +#endif +#ifdef ETXTBSY + else if (error == ETXTBSY) { return FS_BUSY; } +#endif +#ifdef EFBIG + else if (error == EFBIG) { return FS_TOO_BIG; } +#endif +#ifdef ENOSPC + else if (error == ENOSPC) { return FS_NO_SPACE; } +#endif +#ifdef ESPIPE + else if (error == ESPIPE) { return FS_BAD_SEEK; } +#endif +#ifdef EROFS + else if (error == EROFS) { return FS_ACCESS_DENIED; } +#endif +#ifdef EPIPE + else if (error == EPIPE) { return FS_BAD_PIPE; } +#endif +#ifdef EDOM + else if (error == EDOM) { return FS_OUT_OF_RANGE; } +#endif +#ifdef ERANGE + else if (error == ERANGE) { return FS_OUT_OF_RANGE; } +#endif +#ifdef EDEADLK + else if (error == EDEADLK) { return FS_DEADLOCK; } +#endif +#ifdef ENAMETOOLONG + else if (error == ENAMETOOLONG) { return FS_PATH_TOO_LONG; } +#endif +#ifdef ENOSYS + else if (error == ENOSYS) { return FS_NOT_IMPLEMENTED; } +#endif +#ifdef ENOTEMPTY + else if (error == ENOTEMPTY) { return FS_DIRECTORY_NOT_EMPTY; } +#endif +#ifdef ELNRNG + else if (error == ELNRNG) { return FS_OUT_OF_RANGE; } +#endif +#ifdef EBFONT + else if (error == EBFONT) { return FS_INVALID_FILE; } +#endif +#ifdef ENODATA + else if (error == ENODATA) { return FS_NO_DATA_AVAILABLE; } +#endif +#ifdef ETIME + else if (error == ETIME) { return FS_TIMEOUT; } +#endif +#ifdef ENOSR + else if (error == ENOSR) { return FS_NO_DATA_AVAILABLE; } +#endif +#ifdef ENONET + else if (error == ENONET) { return FS_NO_NETWORK; } +#endif +#ifdef EOVERFLOW + else if (error == EOVERFLOW) { return FS_TOO_BIG; } +#endif +#ifdef ELIBACC + else if (error == ELIBACC) { return FS_ACCESS_DENIED; } +#endif +#ifdef ELIBBAD + else if (error == ELIBBAD) { return FS_INVALID_FILE; } +#endif +#ifdef ELIBSCN + else if (error == ELIBSCN) { return FS_INVALID_FILE; } +#endif +#ifdef EILSEQ + else if (error == EILSEQ) { return FS_INVALID_DATA; } +#endif +#ifdef ENOTSOCK + else if (error == ENOTSOCK) { return FS_NOT_SOCKET; } +#endif +#ifdef EDESTADDRREQ + else if (error == EDESTADDRREQ) { return FS_NO_ADDRESS; } +#endif +#ifdef EMSGSIZE + else if (error == EMSGSIZE) { return FS_TOO_BIG; } +#endif +#ifdef EPROTOTYPE + else if (error == EPROTOTYPE) { return FS_BAD_PROTOCOL; } +#endif +#ifdef ENOPROTOOPT + else if (error == ENOPROTOOPT) { return FS_PROTOCOL_UNAVAILABLE; } +#endif +#ifdef EPROTONOSUPPORT + else if (error == EPROTONOSUPPORT) { return FS_PROTOCOL_NOT_SUPPORTED; } +#endif +#ifdef ESOCKTNOSUPPORT + else if (error == ESOCKTNOSUPPORT) { return FS_SOCKET_NOT_SUPPORTED; } +#endif +#ifdef EOPNOTSUPP + else if (error == EOPNOTSUPP) { return FS_INVALID_OPERATION; } +#endif +#ifdef EPFNOSUPPORT + else if (error == EPFNOSUPPORT) { return FS_PROTOCOL_FAMILY_NOT_SUPPORTED; } +#endif +#ifdef EAFNOSUPPORT + else if (error == EAFNOSUPPORT) { return FS_ADDRESS_FAMILY_NOT_SUPPORTED; } +#endif +#ifdef EADDRINUSE + else if (error == EADDRINUSE) { return FS_ALREADY_IN_USE; } +#endif +#ifdef ENETDOWN + else if (error == ENETDOWN) { return FS_NO_NETWORK; } +#endif +#ifdef ENETUNREACH + else if (error == ENETUNREACH) { return FS_NO_NETWORK; } +#endif +#ifdef ENETRESET + else if (error == ENETRESET) { return FS_NO_NETWORK; } +#endif +#ifdef ECONNABORTED + else if (error == ECONNABORTED) { return FS_NO_NETWORK; } +#endif +#ifdef ECONNRESET + else if (error == ECONNRESET) { return FS_CONNECTION_RESET; } +#endif +#ifdef ENOBUFS + else if (error == ENOBUFS) { return FS_NO_SPACE; } +#endif +#ifdef EISCONN + else if (error == EISCONN) { return FS_ALREADY_CONNECTED; } +#endif +#ifdef ENOTCONN + else if (error == ENOTCONN) { return FS_NOT_CONNECTED; } +#endif +#ifdef ETIMEDOUT + else if (error == ETIMEDOUT) { return FS_TIMEOUT; } +#endif +#ifdef ECONNREFUSED + else if (error == ECONNREFUSED) { return FS_CONNECTION_REFUSED; } +#endif +#ifdef EHOSTDOWN + else if (error == EHOSTDOWN) { return FS_NO_HOST; } +#endif +#ifdef EHOSTUNREACH + else if (error == EHOSTUNREACH) { return FS_NO_HOST; } +#endif +#ifdef EALREADY + else if (error == EALREADY) { return FS_IN_PROGRESS; } +#endif +#ifdef EINPROGRESS + else if (error == EINPROGRESS) { return FS_IN_PROGRESS; } +#endif +#ifdef ESTALE + else if (error == ESTALE) { return FS_INVALID_FILE; } +#endif +#ifdef EREMOTEIO + else if (error == EREMOTEIO) { return FS_IO_ERROR; } +#endif +#ifdef EDQUOT + else if (error == EDQUOT) { return FS_NO_SPACE; } +#endif +#ifdef ENOMEDIUM + else if (error == ENOMEDIUM) { return FS_DOES_NOT_EXIST; } +#endif +#ifdef ECANCELED + else if (error == ECANCELED) { return FS_CANCELLED; } +#endif + return FS_ERROR; } -#endif +#if defined(_WIN32) +#include /* For GetLastError, ERROR_* constants. */ + +FS_API fs_result fs_result_from_GetLastError(void) +{ + switch (GetLastError()) + { + case ERROR_SUCCESS: return FS_SUCCESS; + case ERROR_NOT_ENOUGH_MEMORY: return FS_OUT_OF_MEMORY; + case ERROR_OUTOFMEMORY: return FS_OUT_OF_MEMORY; + case ERROR_BUSY: return FS_BUSY; + case ERROR_SEM_TIMEOUT: return FS_TIMEOUT; + case ERROR_ALREADY_EXISTS: return FS_ALREADY_EXISTS; + case ERROR_FILE_EXISTS: return FS_ALREADY_EXISTS; + case ERROR_ACCESS_DENIED: return FS_ACCESS_DENIED; + case ERROR_WRITE_PROTECT: return FS_ACCESS_DENIED; + case ERROR_PRIVILEGE_NOT_HELD: return FS_ACCESS_DENIED; + case ERROR_SHARING_VIOLATION: return FS_ACCESS_DENIED; + case ERROR_LOCK_VIOLATION: return FS_ACCESS_DENIED; + case ERROR_FILE_NOT_FOUND: return FS_DOES_NOT_EXIST; + case ERROR_PATH_NOT_FOUND: return FS_DOES_NOT_EXIST; + case ERROR_INVALID_NAME: return FS_INVALID_ARGS; + case ERROR_BAD_PATHNAME: return FS_INVALID_ARGS; + case ERROR_INVALID_PARAMETER: return FS_INVALID_ARGS; + case ERROR_INVALID_HANDLE: return FS_INVALID_ARGS; + case ERROR_INVALID_FUNCTION: return FS_INVALID_OPERATION; + case ERROR_FILENAME_EXCED_RANGE: return FS_PATH_TOO_LONG; + case ERROR_DIRECTORY: return FS_NOT_DIRECTORY; + case ERROR_DIR_NOT_EMPTY: return FS_DIRECTORY_NOT_EMPTY; + case ERROR_FILE_TOO_LARGE: return FS_TOO_BIG; + case ERROR_DISK_FULL: return FS_OUT_OF_RANGE; + case ERROR_HANDLE_EOF: return FS_AT_END; + case ERROR_SEEK: return FS_BAD_SEEK; + case ERROR_OPERATION_ABORTED: return FS_CANCELLED; + case ERROR_CANCELLED: return FS_INTERRUPT; + case ERROR_TOO_MANY_OPEN_FILES: return FS_TOO_MANY_OPEN_FILES; + case ERROR_INVALID_DATA: return FS_INVALID_DATA; + case ERROR_NO_DATA: return FS_NO_DATA_AVAILABLE; + case ERROR_NOT_SAME_DEVICE: return FS_DIFFERENT_DEVICE; + default: return FS_ERROR; /* Generic error. */ + } +} +#endif /* _WIN32 */ +/* END fs_result.c */ /* BEG fs_allocation_callbacks.c */ @@ -503,38 +822,69 @@ if you wanted to amalgamate this into another project which uses c89thread and w code. These are the differences: * The c89 namespace is replaced with "fs_". - * There is no c89mtx_timedlock() equivalent. - * `fs_mtx_plain`, etc. have been capitalized and taken out of the enum. - * c89thread_success is FS_SUCCESS - * c89thrd_error is EINVAL - * c89thrd_busy is EBUSY + * There is no c89mtx_trylock() or c89mtx_timedlock() equivalent. + * c89thrd_success is FS_SUCCESS + * c89thrd_error is FS_ERROR + * c89thrd_busy is FS_BUSY * c89thrd_pthread_* is fs_pthread_* Parameter ordering is the same as c89thread to make amalgamation easier. */ -#if defined(_WIN32) && !defined(FS_USE_PTHREAD) - /* Win32. Don't include windows.h here. */ -#else - #include - typedef pthread_t fs_pthread; - typedef pthread_mutex_t fs_pthread_mutex; - typedef pthread_cond_t fs_pthread_cond; -#endif +/* BEG fs_thread_basic_types.c */ +#if defined(FS_POSIX) + #ifndef FS_USE_PTHREAD + #define FS_USE_PTHREAD + #endif -#if defined(_WIN32) -typedef struct -{ - void* handle; /* HANDLE, CreateMutex(), CreateEvent() */ - int type; -} fs_mtx; -#else -typedef fs_pthread_mutex fs_mtx; + #ifndef FS_NO_PTHREAD_IN_HEADER + #include + typedef pthread_t fs_pthread_t; + typedef pthread_mutex_t fs_pthread_mutex_t; + #else + typedef fs_uintptr fs_pthread_t; + typedef union fs_pthread_mutex_t { char __data[40]; fs_uint64 __alignment; } fs_pthread_mutex_t; + #endif #endif +/* END fs_thread_basic_types.c */ -#define FS_MTX_PLAIN 0 -#define FS_MTX_TIMED 1 -#define FS_MTX_RECURSIVE 2 + +/* BEG fs_thread_mtx.h */ +#if defined(FS_WIN32) + typedef struct + { + void* handle; /* HANDLE, CreateMutex(), CreateEvent() */ + int type; + } fs_mtx; +#else + /* + We may need to force the use of a manual recursive mutex which will happen when compiling + on very old compilers, or with `-std=c89`. + */ + + /* If __STDC_VERSION__ is not defined it means we're compiling in C89 mode. */ + #if !defined(FS_USE_MANUAL_RECURSIVE_MUTEX) && !defined(__STDC_VERSION__) + #define FS_USE_MANUAL_RECURSIVE_MUTEX + #endif + + /* This is for checking if PTHREAD_MUTEX_RECURSIVE is available. */ + #if !defined(FS_USE_MANUAL_RECURSIVE_MUTEX) && (!defined(__USE_UNIX98) && !defined(__USE_XOPEN2K8)) + #define FS_USE_MANUAL_RECURSIVE_MUTEX + #endif + + #ifdef FS_USE_MANUAL_RECURSIVE_MUTEX + typedef struct + { + fs_pthread_mutex_t mutex; /* The underlying pthread mutex. */ + fs_pthread_mutex_t guard; /* Guard for metadata (owner and recursionCount). */ + fs_pthread_t owner; + int recursionCount; + int type; + } fs_mtx; + #else + typedef fs_pthread_mutex_t fs_mtx; + #endif +#endif enum { @@ -542,14 +892,16 @@ enum fs_mtx_timed = 0x00000001, fs_mtx_recursive = 0x00000002 }; +/* END fs_thread_mtx.h */ -#if defined(_WIN32) -FS_API int fs_mtx_init(fs_mtx* mutex, int type) +/* BEG fs_thread_mtx.c */ +#if defined(FS_WIN32) && !defined(FS_USE_PTHREAD) +static int fs_mtx_init(fs_mtx* mutex, int type) { HANDLE hMutex; if (mutex == NULL) { - return EINVAL; + return FS_ERROR; } /* Initialize the object to zero for safety. */ @@ -561,14 +913,14 @@ FS_API int fs_mtx_init(fs_mtx* mutex, int type) event (CreateEvent()) is not thread-aware and will deadlock (will not allow recursiveness). In Win32 I'm making all mutex's timeable. */ - if ((type & FS_MTX_RECURSIVE) != 0) { + if ((type & fs_mtx_recursive) != 0) { hMutex = CreateMutexA(NULL, FALSE, NULL); } else { hMutex = CreateEventA(NULL, FALSE, TRUE, NULL); } if (hMutex == NULL) { - return fs_result_from_GetLastError(GetLastError()); + return fs_result_from_GetLastError(); } mutex->handle = (void*)hMutex; @@ -577,7 +929,7 @@ FS_API int fs_mtx_init(fs_mtx* mutex, int type) return FS_SUCCESS; } -FS_API void fs_mtx_destroy(fs_mtx* mutex) +static void fs_mtx_destroy(fs_mtx* mutex) { if (mutex == NULL) { return; @@ -586,146 +938,260 @@ FS_API void fs_mtx_destroy(fs_mtx* mutex) CloseHandle((HANDLE)mutex->handle); } -FS_API int fs_mtx_lock(fs_mtx* mutex) +static int fs_mtx_lock(fs_mtx* mutex) { DWORD result; if (mutex == NULL) { - return EINVAL; + return FS_ERROR; } result = WaitForSingleObject((HANDLE)mutex->handle, INFINITE); if (result != WAIT_OBJECT_0) { - return EINVAL; + return FS_ERROR; } return FS_SUCCESS; } -FS_API int fs_mtx_trylock(fs_mtx* mutex) -{ - DWORD result; - - if (mutex == NULL) { - return EINVAL; - } - - result = WaitForSingleObject((HANDLE)mutex->handle, 0); - if (result != WAIT_OBJECT_0) { - return EBUSY; - } - - return FS_SUCCESS; -} - -FS_API int fs_mtx_unlock(fs_mtx* mutex) +static int fs_mtx_unlock(fs_mtx* mutex) { BOOL result; if (mutex == NULL) { - return EINVAL; + return FS_ERROR; } - if ((mutex->type & FS_MTX_RECURSIVE) != 0) { + if ((mutex->type & fs_mtx_recursive) != 0) { result = ReleaseMutex((HANDLE)mutex->handle); } else { result = SetEvent((HANDLE)mutex->handle); } if (!result) { - return EINVAL; + return FS_ERROR; } return FS_SUCCESS; } #else -FS_API int fs_mtx_init(fs_mtx* mutex, int type) +static int fs_mtx_init(fs_mtx* mutex, int type) { int result; - pthread_mutexattr_t attr; /* For specifying whether or not the mutex is recursive. */ if (mutex == NULL) { - return EINVAL; + return FS_ERROR; } - pthread_mutexattr_init(&attr); - if ((type & FS_MTX_RECURSIVE) != 0) { - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - } else { - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); /* Will deadlock. Consistent with Win32. */ + #ifdef FS_USE_MANUAL_RECURSIVE_MUTEX + { + /* Initialize the main mutex */ + result = pthread_mutex_init(&mutex->mutex, NULL); + if (result != 0) { + return FS_ERROR; + } + + /* For recursive mutexes, we need the guard mutex and metadata */ + if ((type & fs_mtx_recursive) != 0) { + if (pthread_mutex_init(&mutex->guard, NULL) != 0) { + pthread_mutex_destroy(&mutex->mutex); + return FS_ERROR; + } + + mutex->owner = 0; /* No owner initially. */ + mutex->recursionCount = 0; + } + + mutex->type = type; + + return FS_SUCCESS; } + #else + { + pthread_mutexattr_t attr; /* For specifying whether or not the mutex is recursive. */ - result = pthread_mutex_init((pthread_mutex_t*)mutex, &attr); - pthread_mutexattr_destroy(&attr); + pthread_mutexattr_init(&attr); + if ((type & fs_mtx_recursive) != 0) { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } else { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); /* Will deadlock. Consistent with Win32. */ + } - if (result != 0) { - return EINVAL; + result = pthread_mutex_init((pthread_mutex_t*)mutex, &attr); + pthread_mutexattr_destroy(&attr); + + if (result != 0) { + return FS_ERROR; + } + + return FS_SUCCESS; } - - return FS_SUCCESS; + #endif } -FS_API void fs_mtx_destroy(fs_mtx* mutex) +static void fs_mtx_destroy(fs_mtx* mutex) { if (mutex == NULL) { return; } - pthread_mutex_destroy((pthread_mutex_t*)mutex); -} - -FS_API int fs_mtx_lock(fs_mtx* mutex) -{ - int result; - - if (mutex == NULL) { - return EINVAL; - } - - result = pthread_mutex_lock((pthread_mutex_t*)mutex); - if (result != 0) { - return EINVAL; - } - - return FS_SUCCESS; -} - -FS_API int fs_mtx_trylock(fs_mtx* mutex) -{ - int result; - - if (mutex == NULL) { - return EINVAL; - } - - result = pthread_mutex_trylock((pthread_mutex_t*)mutex); - if (result != 0) { - if (result == EBUSY) { - return EBUSY; + #ifdef FS_USE_MANUAL_RECURSIVE_MUTEX + { + /* Only destroy the guard mutex if it was initialized (for recursive mutexes) */ + if ((mutex->type & fs_mtx_recursive) != 0) { + pthread_mutex_destroy(&mutex->guard); } - return EINVAL; + pthread_mutex_destroy(&mutex->mutex); } - - return FS_SUCCESS; + #else + { + pthread_mutex_destroy((pthread_mutex_t*)mutex); + } + #endif } -FS_API int fs_mtx_unlock(fs_mtx* mutex) +static int fs_mtx_lock(fs_mtx* mutex) { int result; if (mutex == NULL) { - return EINVAL; + return FS_ERROR; } - result = pthread_mutex_unlock((pthread_mutex_t*)mutex); - if (result != 0) { - return EINVAL; + #ifdef FS_USE_MANUAL_RECURSIVE_MUTEX + { + pthread_t currentThread; + + /* Optimized path for plain mutexes. */ + if ((mutex->type & fs_mtx_recursive) == 0) { + result = pthread_mutex_lock(&mutex->mutex); + if (result != 0) { + return FS_ERROR; + } + + return FS_SUCCESS; + } + + /* Getting here means it's a recursive mutex. */ + currentThread = pthread_self(); + + /* First, lock the guard mutex to safely access the metadata. */ + result = pthread_mutex_lock(&mutex->guard); + if (result != 0) { + return FS_ERROR; + } + + /* We can bomb out early if the current thread already owns this mutex. */ + if (mutex->recursionCount > 0 && pthread_equal(mutex->owner, currentThread)) { + mutex->recursionCount += 1; + pthread_mutex_unlock(&mutex->guard); + return FS_SUCCESS; + } + + /* The guard mutex needs to be unlocked before locking the main mutex or else we'll deadlock. */ + pthread_mutex_unlock(&mutex->guard); + + result = pthread_mutex_lock(&mutex->mutex); + if (result != 0) { + return FS_ERROR; + } + + /* Update metadata. */ + result = pthread_mutex_lock(&mutex->guard); + if (result != 0) { + pthread_mutex_unlock(&mutex->mutex); + return FS_ERROR; + } + + mutex->owner = currentThread; + mutex->recursionCount = 1; + + pthread_mutex_unlock(&mutex->guard); + + return FS_SUCCESS; + } + #else + { + result = pthread_mutex_lock((pthread_mutex_t*)mutex); + if (result != 0) { + return FS_ERROR; + } + + return FS_SUCCESS; + } + #endif +} + +static int fs_mtx_unlock(fs_mtx* mutex) +{ + int result; + + if (mutex == NULL) { + return FS_ERROR; } - return FS_SUCCESS; + #ifdef FS_USE_MANUAL_RECURSIVE_MUTEX + { + pthread_t currentThread; + + /* Optimized path for plain mutexes. */ + if ((mutex->type & fs_mtx_recursive) == 0) { + result = pthread_mutex_unlock(&mutex->mutex); + if (result != 0) { + return FS_ERROR; + } + + return FS_SUCCESS; + } + + /* Getting here means it's a recursive mutex. */ + currentThread = pthread_self(); + + /* Lock the guard mutex to safely access the metadata */ + result = pthread_mutex_lock(&mutex->guard); + if (result != 0) { + return FS_ERROR; + } + + /* Check if the current thread owns the mutex */ + if (mutex->recursionCount == 0 || !pthread_equal(mutex->owner, currentThread)) { + /* Getting here means we are trying to unlock a mutex that is not owned by this thread. Bomb out. */ + pthread_mutex_unlock(&mutex->guard); + return FS_ERROR; + } + + mutex->recursionCount -= 1; + + if (mutex->recursionCount == 0) { + /* Last unlock. Clear ownership and unlock the main mutex. */ + mutex->owner = 0; + pthread_mutex_unlock(&mutex->guard); + + result = pthread_mutex_unlock(&mutex->mutex); + if (result != 0) { + return FS_ERROR; + } + } else { + /* Still recursively locked, just unlock the guard mutex. */ + pthread_mutex_unlock(&mutex->guard); + } + + return FS_SUCCESS; + } + #else + { + result = pthread_mutex_unlock((pthread_mutex_t*)mutex); + if (result != 0) { + return FS_ERROR; + } + + return FS_SUCCESS; + } + #endif } #endif +/* END fs_thread_mtx.c */ /* END fs_thread.c */ @@ -1048,6 +1514,10 @@ FS_API fs_result fs_stream_read_to_end(fs_stream* pStream, fs_format format, con /* BEG fs.c */ +const char* FS_STDIN = ""; +const char* FS_STDOUT = ""; +const char* FS_STDERR = ""; + static size_t fs_backend_alloc_size(const fs_backend* pBackend, const void* pBackendConfig) { FS_ASSERT(pBackend != NULL); @@ -1158,17 +1628,6 @@ static fs_result fs_backend_file_open(const fs_backend* pBackend, fs* pFS, fs_st } } -static fs_result fs_backend_file_open_handle(const fs_backend* pBackend, fs* pFS, void* hBackendFile, fs_file* pFile) -{ - FS_ASSERT(pBackend != NULL); - - if (pBackend->file_open_handle == NULL) { - return FS_NOT_IMPLEMENTED; - } else { - return pBackend->file_open_handle(pFS, hBackendFile, pFile); - } -} - static void fs_backend_file_close(const fs_backend* pBackend, fs_file* pFile) { FS_ASSERT(pBackend != NULL); @@ -1235,6 +1694,17 @@ static fs_result fs_backend_file_flush(const fs_backend* pBackend, fs_file* pFil } } +static fs_result fs_backend_file_truncate(const fs_backend* pBackend, fs_file* pFile) +{ + FS_ASSERT(pBackend != NULL); + + if (pBackend->file_truncate == NULL) { + return FS_NOT_IMPLEMENTED; + } else { + return pBackend->file_truncate(pFile); + } +} + static fs_result fs_backend_file_info(const fs_backend* pBackend, fs_file* pFile, fs_file_info* pInfo) { FS_ASSERT(pBackend != NULL); @@ -1300,6 +1770,17 @@ static void fs_backend_free_iterator(const fs_backend* pBackend, fs_iterator* pI } +FS_API fs_archive_type fs_archive_type_init(const fs_backend* pBackend, const char* pExtension) +{ + fs_archive_type archiveType; + + archiveType.pBackend = pBackend; + archiveType.pExtension = pExtension; + + return archiveType; +} + + /* This is the maximum number of ureferenced opened archive files that will be kept in memory before garbage collection of those archives is triggered. @@ -1321,7 +1802,7 @@ FS_API fs_config fs_config_init_default(void) return config; } -FS_API fs_config fs_config_init(const fs_backend* pBackend, void* pBackendConfig, fs_stream* pStream) +FS_API fs_config fs_config_init(const fs_backend* pBackend, const void* pBackendConfig, fs_stream* pStream) { fs_config config = fs_config_init_default(); config.pBackend = pBackend; @@ -1387,9 +1868,37 @@ typedef enum fs_mount_priority } fs_mount_priority; -static void fs_gc_archives_nolock(fs* pFS, int policy); /* Defined further down in the file. */ +static void fs_gc(fs* pFS, int policy, fs* pSpecificArchive); /* Generic internal GC function. */ +static const char* fs_mount_point_real_path(const fs_mount_point* pMountPoint) +{ + FS_ASSERT(pMountPoint != NULL); + + return (const char*)FS_OFFSET_PTR(pMountPoint, sizeof(fs_mount_point) + pMountPoint->pathOff); +} + +static size_t fs_mount_point_real_path_len(const fs_mount_point* pMountPoint) +{ + FS_ASSERT(pMountPoint != NULL); + + return pMountPoint->pathLen; +} + +static const char* fs_mount_point_virtual_path(const fs_mount_point* pMountPoint) +{ + FS_ASSERT(pMountPoint != NULL); + + return (const char*)FS_OFFSET_PTR(pMountPoint, sizeof(fs_mount_point) + pMountPoint->mountPointOff); +} + +static size_t fs_mount_point_virtual_path_len(const fs_mount_point* pMountPoint) +{ + FS_ASSERT(pMountPoint != NULL); + + return pMountPoint->mountPointLen; +} + static size_t fs_mount_point_size(size_t pathLen, size_t mountPointLen) { return FS_ALIGN(sizeof(fs_mount_point) + pathLen + 1 + mountPointLen + 1, FS_SIZEOF_PTR); @@ -1592,10 +2101,21 @@ static fs_result fs_mount_list_remove(fs_mount_list* pList, fs_mount_point* pMou +static const fs_backend* fs_get_default_backend(void) +{ + /* */ if (FS_BACKEND_POSIX != NULL) { + return FS_BACKEND_POSIX; + } else if (FS_BACKEND_WIN32 != NULL) { + return FS_BACKEND_WIN32; + } else { + return NULL; + } +} + static const fs_backend* fs_get_backend_or_default(const fs* pFS) { if (pFS == NULL) { - return FS_STDIO; + return fs_get_default_backend(); } else { return pFS->pBackend; } @@ -1611,7 +2131,7 @@ typedef struct fs_registered_backend_iterator size_t extensionLen; } fs_registered_backend_iterator; -FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMode, fs_file** ppFile, fs_file_info* pInfo); +static fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMode, fs_file** ppFile, fs_file_info* pInfo); static fs_result fs_next_registered_backend(fs_registered_backend_iterator* pIterator); static fs_result fs_first_registered_backend(fs* pFS, fs_registered_backend_iterator* pIterator) @@ -1768,7 +2288,224 @@ static size_t fs_archive_type_sizeof(const fs_archive_type* pArchiveType) } -static fs_mount_point* fs_find_best_write_mount_point(fs* pFS, const char* pPath, const char** ppMountPointPath, const char** ppSubPath) +typedef struct +{ + size_t len; + char stack[1024]; + char* heap; + const char* ref; +} fs_string; + +static fs_string fs_string_new(void) +{ + fs_string str; + + str.len = 0; + str.stack[0] = '\0'; + str.heap = NULL; + str.ref = NULL; + + return str; +} + +static fs_string fs_string_new_ref(const char* ref, size_t len) +{ + fs_string str = fs_string_new(); + str.ref = ref; + str.len = len; + + if (str.len == FS_NULL_TERMINATED) { + str.len = strlen(ref); + } + + return str; +} + +static fs_result fs_string_alloc(size_t len, const fs_allocation_callbacks* pAllocationCallbacks, fs_string* pString) +{ + *pString = fs_string_new(); + + if (len < sizeof(pString->stack)) { + pString->stack[0] = '\0'; + } else { + pString->heap = (char*)fs_malloc(len + 1, pAllocationCallbacks); + if (pString->heap == NULL) { + return FS_OUT_OF_MEMORY; + } + + pString->heap[0] = '\0'; + } + + return FS_SUCCESS; +} + +static void fs_string_free(fs_string* pString, const fs_allocation_callbacks* pAllocationCallbacks) +{ + if (pString == NULL) { + return; + } + + if (pString->heap != NULL) { + fs_free(pString->heap, pAllocationCallbacks); + pString->heap = NULL; + } +} + +static const char* fs_string_cstr(const fs_string* pString) +{ + FS_ASSERT(pString != NULL); + + if (pString->ref != NULL) { + return pString->ref; + } + + if (pString->heap != NULL) { + return pString->heap; + } + + return pString->stack; +} + +static size_t fs_string_len(const fs_string* pString) +{ + return pString->len; +} + +static void fs_string_append_preallocated(fs_string* pString, const char* pSrc, size_t srcLen) +{ + char* pDst; + + FS_ASSERT(pSrc != NULL); + FS_ASSERT(pString != NULL); + FS_ASSERT(pString->ref == NULL); /* Can't concatenate to a reference string. */ + + if (srcLen == FS_NULL_TERMINATED) { + srcLen = strlen(pSrc); + } + + if (pString->heap != NULL) { + pDst = pString->heap; + } else { + pDst = pString->stack; + } + + FS_COPY_MEMORY(pDst + pString->len, pSrc, srcLen); + pString->len += srcLen; + pDst[pString->len] = '\0'; +} + + +static const char* fs_path_trim_mount_point_base(const char* pPath, size_t pathLen, const char* pMountPoint, size_t mountPointLen) +{ + FS_ASSERT(pPath != NULL && pMountPoint != NULL); + + /* + Special case here, and why we need to use this function instead of fs_path_trim_base() directly. For mount + points, the "" mount is *not* considered to match with a path that starts with "/". + */ + if (pathLen > 0 && pPath[0] == '/' && pMountPoint[0] == '\0') { + return NULL; /* Path starts with "/", but the mount point is "". This is not a match. */ + } + + return fs_path_trim_base(pPath, pathLen, pMountPoint, mountPointLen); +} + +/* +This will return an error if the path is not prefixed with the mount point, or if it attempts to +navigate above the mount point when disallowed. +*/ +static fs_result fs_resolve_sub_path_from_mount_point(fs* pFS, fs_mount_point* pMountPoint, const char* pPath, int openMode, fs_string* pResolvedSubPath) +{ + fs_result result; + int stringLen; + int normalizeOptions = (openMode & FS_NO_ABOVE_ROOT_NAVIGATION); + const char* pSubPath; + + FS_ASSERT(pPath != NULL); + FS_ASSERT(pResolvedSubPath != NULL); + + *pResolvedSubPath = fs_string_new(); + + if (pFS == NULL || pMountPoint == NULL) { + return FS_INVALID_ARGS; + } + + pSubPath = fs_path_trim_mount_point_base(pPath, FS_NULL_TERMINATED, fs_mount_point_virtual_path(pMountPoint), fs_mount_point_virtual_path_len(pMountPoint)); + if (pSubPath == NULL) { + return FS_DOES_NOT_EXIST; /* The file path does not start with this mount point. */ + } + + /* A path starting with a slash does not allow for navigating above the root. */ + if (pPath[0] == '/' || pPath[0] == '\\') { + normalizeOptions |= FS_NO_ABOVE_ROOT_NAVIGATION; + } + + /* + The sub-path needs to be cleaned. This is where FS_NO_ABOVE_ROOT_NAVIGATION is validated. We can skip this + process if special directories have been disabled since a clean path will be implied and therefore there + should be no possibility of navigating above the root. + */ + if ((openMode & FS_NO_SPECIAL_DIRS) == 0) { + stringLen = fs_path_normalize(pResolvedSubPath->stack, sizeof(pResolvedSubPath->stack), pSubPath, FS_NULL_TERMINATED, normalizeOptions); + if (stringLen < 0) { + return FS_DOES_NOT_EXIST; /* Most likely violating FS_NO_ABOVE_ROOT_NAVIGATION. */ + } + + pResolvedSubPath->len = (size_t)stringLen; + + if ((size_t)stringLen >= sizeof(pResolvedSubPath->stack)) { + result = fs_string_alloc((size_t)stringLen, fs_get_allocation_callbacks(pFS), pResolvedSubPath); + if (result != FS_SUCCESS) { + return result; + } + + fs_path_normalize(pResolvedSubPath->heap, pResolvedSubPath->len + 1, pSubPath, FS_NULL_TERMINATED, normalizeOptions); /* <-- This should never fail. */ + } + } else { + *pResolvedSubPath = fs_string_new_ref(pSubPath, FS_NULL_TERMINATED); + } + + return FS_SUCCESS; +} + +/* This is similar to fs_resolve_sub_path_from_mount_point() but returns the real path instead of the sub-path. */ +static fs_result fs_resolve_real_path_from_mount_point(fs* pFS, fs_mount_point* pMountPoint, const char* pPath, int openMode, fs_string* pResolvedRealPath) +{ + fs_result result; + fs_string subPath; + int stringLen; + + *pResolvedRealPath = fs_string_new(); + + result = fs_resolve_sub_path_from_mount_point(pFS, pMountPoint, pPath, openMode, &subPath); + if (result != FS_SUCCESS) { + return result; + } + + stringLen = fs_path_append(pResolvedRealPath->stack, sizeof(pResolvedRealPath->stack), fs_mount_point_real_path(pMountPoint), fs_mount_point_real_path_len(pMountPoint), fs_string_cstr(&subPath), fs_string_len(&subPath)); + if (stringLen < 0) { + fs_string_free(&subPath, fs_get_allocation_callbacks(pFS)); + return FS_PATH_TOO_LONG; /* The only error we would get here is if the path is too long. */ + } + + pResolvedRealPath->len = (size_t)stringLen; + + if ((size_t)stringLen >= sizeof(pResolvedRealPath->stack)) { + result = fs_string_alloc((size_t)stringLen, fs_get_allocation_callbacks(pFS), pResolvedRealPath); + if (result != FS_SUCCESS) { + return result; + } + + fs_path_append(pResolvedRealPath->heap, pResolvedRealPath->len + 1, fs_mount_point_real_path(pMountPoint), fs_mount_point_real_path_len(pMountPoint), fs_string_cstr(&subPath), fs_string_len(&subPath)); /* <-- This should never fail. */ + } + + /* Now that actual path has been constructed we can discard of our sub-path. */ + fs_string_free(&subPath, fs_get_allocation_callbacks(pFS)); + + return FS_SUCCESS; +} + +static fs_mount_point* fs_find_best_write_mount_point(fs* pFS, const char* pPath, int openMode, fs_string* pResolvedRealPath) { /* This is a bit different from read mounts because we want to use the mount point that most closely @@ -1787,27 +2524,28 @@ static fs_mount_point* fs_find_best_write_mount_point(fs* pFS, const char* pPath fs_result result; fs_mount_list_iterator iMountPoint; fs_mount_point* pBestMountPoint = NULL; - const char* pBestMountPointPath = NULL; const char* pBestMountPointFileSubPath = NULL; - + for (result = fs_mount_list_first(pFS->pWriteMountPoints, &iMountPoint); result == FS_SUCCESS; result = fs_mount_list_next(&iMountPoint)) { - const char* pFileSubPath = fs_path_trim_base(pPath, FS_NULL_TERMINATED, iMountPoint.pMountPointPath, FS_NULL_TERMINATED); + const char* pFileSubPath = fs_path_trim_mount_point_base(pPath, FS_NULL_TERMINATED, iMountPoint.pMountPointPath, FS_NULL_TERMINATED); if (pFileSubPath == NULL) { continue; /* The file path doesn't start with this mount point so skip. */ } if (pBestMountPointFileSubPath == NULL || strlen(pFileSubPath) < strlen(pBestMountPointFileSubPath)) { pBestMountPoint = iMountPoint.internal.pMountPoint; - pBestMountPointPath = iMountPoint.pPath; pBestMountPointFileSubPath = pFileSubPath; } } - if (ppMountPointPath != NULL) { - *ppMountPointPath = pBestMountPointPath; + if (pBestMountPoint == NULL) { + return NULL; } - if (ppSubPath != NULL) { - *ppSubPath = pBestMountPointFileSubPath; + + /* At this point we have identified the best mount point. We now need to resolve the absolute path. */ + result = fs_resolve_real_path_from_mount_point(pFS, pBestMountPoint, pPath, openMode, pResolvedRealPath); + if (result != FS_SUCCESS) { + return NULL; /* This probably failed because the path was trying to navigate above the mount point when not allowed to do so. */ } return pBestMountPoint; @@ -1838,7 +2576,7 @@ FS_API fs_result fs_init(const fs_config* pConfig, fs** ppFS) pBackend = pConfig->pBackend; if (pBackend == NULL) { - pBackend = FS_STDIO; + pBackend = fs_get_default_backend(); } /* If the backend is still null at this point it means the default backend has been disabled. */ @@ -1859,7 +2597,7 @@ FS_API fs_result fs_init(const fs_config* pConfig, fs** ppFS) } pFS->pBackend = pBackend; - pFS->pStream = pConfig->pStream; /* <-- This is allowed to be null, which will be the case for standard OS file system APIs like stdio. Streams are used for things like archives like Zip files, or in-memory file systems. */ + pFS->pStream = pConfig->pStream; /* <-- This is allowed to be null, which will be the case for standard OS file system APIs. Streams are used for things like archives like Zip files, or in-memory file systems. */ pFS->refCount = 1; pFS->allocationCallbacks = fs_allocation_callbacks_init_copy(pConfig->pAllocationCallbacks); pFS->backendDataSize = backendDataSizeInBytes; @@ -1903,13 +2641,13 @@ FS_API fs_result fs_init(const fs_config* pConfig, fs** ppFS) We need a mutex for fs_open_archive() and fs_close_archive(). This needs to be recursive because during garbage collection we may end up closing archives in archives. */ - fs_mtx_init(&pFS->archiveLock, FS_MTX_RECURSIVE); + fs_mtx_init(&pFS->archiveLock, fs_mtx_recursive); /* We need a mutex for the reference counting. This is needed because we may have multiple threads opening and closing files at the same time. */ - fs_mtx_init(&pFS->refLock, FS_MTX_RECURSIVE); + fs_mtx_init(&pFS->refLock, fs_mtx_recursive); /* We're now ready to initialize the backend. */ result = fs_backend_init(pBackend, pFS, pConfig->pBackendConfig, pConfig->pStream); @@ -1990,26 +2728,93 @@ FS_API fs_result fs_ioctl(fs* pFS, int request, void* pArg) return fs_backend_ioctl(pFS->pBackend, pFS, request, pArg); } -FS_API fs_result fs_remove(fs* pFS, const char* pFilePath) +FS_API fs_result fs_remove(fs* pFS, const char* pFilePath, int options) { - if (pFS == NULL || pFilePath == NULL) { + fs_result result; + const fs_backend* pBackend; + + if (pFilePath == NULL) { return FS_INVALID_ARGS; } - return fs_backend_remove(pFS->pBackend, pFS, pFilePath); + pBackend = fs_get_backend_or_default(pFS); + if (pBackend == NULL) { + return FS_INVALID_ARGS; + } + + /* If we're using the default file system, ignore mount points since there's no real notion of them. */ + if (pFS == NULL) { + options |= FS_IGNORE_MOUNTS; + } + + if ((options & FS_IGNORE_MOUNTS) != 0) { + result = fs_backend_remove(pBackend, pFS, pFilePath); + } else { + fs_string realFilePath; + fs_mount_point* pMountPoint; + + pMountPoint = fs_find_best_write_mount_point(pFS, pFilePath, options, &realFilePath); + if (pMountPoint == NULL) { + return FS_DOES_NOT_EXIST; /* Couldn't find a mount point. */ + } + + result = fs_backend_remove(pBackend, pFS, fs_string_cstr(&realFilePath)); + fs_string_free(&realFilePath, fs_get_allocation_callbacks(pFS)); + } + + return result; } -FS_API fs_result fs_rename(fs* pFS, const char* pOldName, const char* pNewName) +FS_API fs_result fs_rename(fs* pFS, const char* pOldPath, const char* pNewPath, int options) { - if (pFS == NULL || pOldName == NULL || pNewName == NULL) { + fs_result result; + const fs_backend* pBackend; + + if (pOldPath == NULL || pNewPath == NULL) { return FS_INVALID_ARGS; } - return fs_backend_rename(pFS->pBackend, pFS, pOldName, pNewName); + pBackend = fs_get_backend_or_default(pFS); + if (pBackend == NULL) { + return FS_INVALID_ARGS; + } + + /* If we're using the default file system, ignore mount points since there's no real notion of them. */ + if (pFS == NULL) { + options |= FS_IGNORE_MOUNTS; + } + + /* If we're ignoring mounts we can just call straight into the backend. */ + if ((options & FS_IGNORE_MOUNTS) != 0) { + result = fs_backend_rename(pBackend, pFS, pOldPath, pNewPath); + } else { + fs_string realOldPath; + fs_string realNewPath; + fs_mount_point* pMountPointOld; + fs_mount_point* pMountPointNew; + + pMountPointOld = fs_find_best_write_mount_point(pFS, pOldPath, options, &realOldPath); + if (pMountPointOld == NULL) { + return FS_DOES_NOT_EXIST; /* Couldn't find a mount point. */ + } + + pMountPointNew = fs_find_best_write_mount_point(pFS, pNewPath, options, &realNewPath); + if (pMountPointNew == NULL) { + fs_string_free(&realNewPath, fs_get_allocation_callbacks(pFS)); + return FS_DOES_NOT_EXIST; /* Couldn't find a mount point. */ + } + + result = fs_backend_rename(pBackend, pFS, fs_string_cstr(&realOldPath), fs_string_cstr(&realNewPath)); + fs_string_free(&realOldPath, fs_get_allocation_callbacks(pFS)); + fs_string_free(&realNewPath, fs_get_allocation_callbacks(pFS)); + } + + return result; } FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options) { + fs_result result; char pRunningPathStack[1024]; char* pRunningPathHeap = NULL; char* pRunningPath = pRunningPathStack; @@ -2017,8 +2822,7 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options) fs_path_iterator iSegment; const fs_backend* pBackend; fs_mount_point* pMountPoint = NULL; - const char* pMountPointPath = NULL; - const char* pMountPointSubPath = NULL; + fs_string realPath; pBackend = fs_get_backend_or_default(pFS); @@ -2038,51 +2842,57 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options) /* If we're using mount points we'll want to find the best one from our input path. */ if ((options & FS_IGNORE_MOUNTS) != 0) { pMountPoint = NULL; - pMountPointPath = ""; - pMountPointSubPath = pPath; + realPath = fs_string_new_ref(pPath, FS_NULL_TERMINATED); } else { - pMountPoint = fs_find_best_write_mount_point(pFS, pPath, &pMountPointPath, &pMountPointSubPath); + pMountPoint = fs_find_best_write_mount_point(pFS, pPath, options, &realPath); if (pMountPoint == NULL) { - return FS_INVALID_FILE; /* Couldn't find a mount point. */ + return FS_DOES_NOT_EXIST; /* Couldn't find a mount point. */ } } + /* + Before trying to create the directory structure, just try creating it directly on the backend. If this + fails with FS_DOES_NOT_EXIST, it means one of the parent directories does not exist. In this case we + need to create the parent directories first, but only if FS_NO_CREATE_DIRS is not set. + */ + result = fs_backend_mkdir(pBackend, pFS, fs_string_cstr(&realPath)); + if (result != FS_DOES_NOT_EXIST) { + fs_string_free(&realPath, fs_get_allocation_callbacks(pFS)); + return result; /* Either success or some other error. */ + } + + /* + Getting here means there is a missing parent directory somewhere. We'll need to try creating it by + iterating over each segment and creating each directory. We only do this if FS_NO_CREATE_DIRS is not + set in which case we just return FS_DOES_NOT_EXIST. + */ + if ((options & FS_NO_CREATE_DIRS) != 0) { + fs_string_free(&realPath, fs_get_allocation_callbacks(pFS)); + + FS_ASSERT(result == FS_DOES_NOT_EXIST); + return result; + } /* We need to iterate over each segment and create the directory. If any of these fail we'll need to abort. */ - if (fs_path_first(pMountPointSubPath, FS_NULL_TERMINATED, &iSegment) != FS_SUCCESS) { - return FS_SUCCESS; /* It's an empty path. */ - } - - - /* We need to pre-fill our running path with the mount point. */ - runningPathLen = strlen(pMountPointPath); - if (runningPathLen + 1 >= sizeof(pRunningPathStack)) { - pRunningPathHeap = (char*)fs_malloc(runningPathLen + 1 + 1, fs_get_allocation_callbacks(pFS)); - if (pRunningPathHeap == NULL) { - return FS_OUT_OF_MEMORY; - } - - pRunningPath = pRunningPathHeap; - } - - FS_COPY_MEMORY(pRunningPath, pMountPointPath, runningPathLen); - pRunningPath[runningPathLen] = '\0'; - - /* We need to make sure we have a trailing slash. */ - if (runningPathLen > 0 && pRunningPath[runningPathLen - 1] != '/') { - pRunningPath[runningPathLen] = '/'; - runningPathLen += 1; - pRunningPath[runningPathLen] = '\0'; + if (fs_path_first(fs_string_cstr(&realPath), FS_NULL_TERMINATED, &iSegment) != FS_SUCCESS) { + /* + If we get here it means the path is empty. We should actually never get here because the backend + should have already handled this in our initial attempt at fs_backend_mkdir(). If this assert is + getting triggered it means there's a bug in the backend. + */ + FS_ASSERT(FS_FALSE); + fs_string_free(&realPath, fs_get_allocation_callbacks(pFS)); + return FS_ALREADY_EXISTS; /* It's an empty path. */ } + pRunningPath[0] = '\0'; for (;;) { - fs_result result; - if (runningPathLen + iSegment.segmentLength + 1 + 1 >= sizeof(pRunningPathStack)) { if (pRunningPath == pRunningPathStack) { pRunningPathHeap = (char*)fs_malloc(runningPathLen + iSegment.segmentLength + 1 + 1, fs_get_allocation_callbacks(pFS)); if (pRunningPathHeap == NULL) { + fs_string_free(&realPath, fs_get_allocation_callbacks(pFS)); return FS_OUT_OF_MEMORY; } @@ -2094,6 +2904,7 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options) pNewRunningPathHeap = (char*)fs_realloc(pRunningPathHeap, runningPathLen + iSegment.segmentLength + 1 + 1, fs_get_allocation_callbacks(pFS)); if (pNewRunningPathHeap == NULL) { fs_free(pRunningPathHeap, fs_get_allocation_callbacks(pFS)); + fs_string_free(&realPath, fs_get_allocation_callbacks(pFS)); return FS_OUT_OF_MEMORY; } @@ -2105,7 +2916,16 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options) runningPathLen += iSegment.segmentLength; pRunningPath[runningPathLen] = '\0'; - result = fs_backend_mkdir(pBackend, pFS, pRunningPath); + /* + The running path might be an empty string due to the way we parse our path. For example, a path + such as `/foo/bar` will have an empty segment before the first slash. In this case we want to + treat the empty segment as a valid already-existing directory. + */ + if (runningPathLen > 0) { + result = fs_backend_mkdir(pBackend, pFS, pRunningPath); + } else { + result = FS_ALREADY_EXISTS; + } /* We just pretend to be successful if the directory already exists. */ if (result == FS_ALREADY_EXISTS) { @@ -2113,10 +2933,8 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options) } if (result != FS_SUCCESS) { - if (pRunningPathHeap != NULL) { - fs_free(pRunningPathHeap, fs_get_allocation_callbacks(pFS)); - } - + fs_free(pRunningPathHeap, fs_get_allocation_callbacks(pFS)); + fs_string_free(&realPath, fs_get_allocation_callbacks(pFS)); return result; } @@ -2129,9 +2947,8 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options) } } - if (pRunningPathHeap != NULL) { - fs_free(pRunningPathHeap, fs_get_allocation_callbacks(pFS)); - } + fs_free(pRunningPathHeap, fs_get_allocation_callbacks(pFS)); + fs_string_free(&realPath, fs_get_allocation_callbacks(pFS)); return FS_SUCCESS; } @@ -2270,314 +3087,49 @@ FS_API fs_uint32 fs_refcount(fs* pFS) } -static void fs_on_refcount_changed_internal(void* pUserData, fs* pFS, fs_uint32 newRefCount, fs_uint32 oldRefCount) -{ - fs* pOwnerFS; - (void)pUserData; - (void)pFS; - (void)newRefCount; - (void)oldRefCount; - - pOwnerFS = (fs*)pUserData; - FS_ASSERT(pOwnerFS != NULL); - - if (newRefCount == 1) { - /* In this case there are no more files referencing this archive. We'll want to do some garbage collection. */ - fs_gc_archives(pOwnerFS, FS_GC_POLICY_THRESHOLD); - } -} - -static fs_result fs_open_archive_nolock(fs* pFS, const fs_backend* pBackend, void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive) +static fs_result fs_find_registered_archive_type_by_path(fs* pFS, const char* pPath, size_t pathLen, const fs_backend** ppBackend, const void** ppBackendConfig) { fs_result result; - fs* pArchive; - fs_config archiveConfig; - fs_file* pArchiveFile; - char pArchivePathNTStack[1024]; - char* pArchivePathNTHeap = NULL; /* <-- Must be initialized to null. */ - char* pArchivePathNT; - fs_opened_archive* pOpenedArchive; - - /* - The first thing to do is check if the archive has already been opened. If so, we just increment - the reference count and return the already-loaded fs object. - */ - pOpenedArchive = fs_find_opened_archive(pFS, pArchivePath, archivePathLen); - if (pOpenedArchive != NULL) { - pArchive = pOpenedArchive->pArchive; - } else { - /* - Getting here means the archive is not cached. We'll need to open it. Unfortunately our path is - not null terminated so we'll need to do that now. We'll try to avoid a heap allocation if we - can. - */ - if (archivePathLen == FS_NULL_TERMINATED) { - pArchivePathNT = (char*)pArchivePath; /* <-- Safe cast. We won't be modifying this. */ - } else { - if (archivePathLen >= sizeof(pArchivePathNTStack)) { - pArchivePathNTHeap = (char*)fs_malloc(archivePathLen + 1, fs_get_allocation_callbacks(pFS)); - if (pArchivePathNTHeap == NULL) { - return FS_OUT_OF_MEMORY; - } - - pArchivePathNT = pArchivePathNTHeap; - } else { - pArchivePathNT = pArchivePathNTStack; - } - - FS_COPY_MEMORY(pArchivePathNT, pArchivePath, archivePathLen); - pArchivePathNT[archivePathLen] = '\0'; - } - - result = fs_file_open(pFS, pArchivePathNT, openMode, &pArchiveFile); - if (result != FS_SUCCESS) { - fs_free(pArchivePathNTHeap, fs_get_allocation_callbacks(pFS)); - return result; - } - - archiveConfig = fs_config_init(pBackend, pBackendConfig, fs_file_get_stream(pArchiveFile)); - archiveConfig.pAllocationCallbacks = fs_get_allocation_callbacks(pFS); - archiveConfig.onRefCountChanged = fs_on_refcount_changed_internal; - archiveConfig.pRefCountChangedUserData = pFS; /* The user data is always the fs object that owns this archive. */ - - result = fs_init(&archiveConfig, &pArchive); - fs_free(pArchivePathNTHeap, fs_get_allocation_callbacks(pFS)); - - if (result != FS_SUCCESS) { /* <-- This is the result of fs_init().*/ - fs_file_close(pArchiveFile); - return result; - } - - /* - We need to support the ability to open archives within archives. To do this, the archive fs - object needs to inherit the registered archive types. Fortunately this is easy because we do - this as one single allocation which means we can just reference it directly. The API has a - restriction that archive type registration cannot be modified after a file has been opened. - */ - pArchive->pArchiveTypes = pFS->pArchiveTypes; - pArchive->archiveTypesAllocSize = pFS->archiveTypesAllocSize; - pArchive->isOwnerOfArchiveTypes = FS_FALSE; - - /* Add the new archive to the cache. */ - result = fs_add_opened_archive(pFS, pArchive, pArchivePath, archivePathLen); - if (result != FS_SUCCESS) { - fs_uninit(pArchive); - fs_file_close(pArchiveFile); - return result; - } - } - - FS_ASSERT(pArchive != NULL); - - *ppArchive = ((openMode & FS_NO_INCREMENT_REFCOUNT) == 0) ? fs_ref(pArchive) : pArchive; - return FS_SUCCESS; -} - -FS_API fs_result fs_open_archive_ex(fs* pFS, const fs_backend* pBackend, void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive) -{ - fs_result result; - - if (ppArchive == NULL) { - return FS_INVALID_ARGS; - } - - *ppArchive = NULL; - - if (pFS == NULL || pBackend == NULL || pArchivePath == NULL || archivePathLen == 0) { - return FS_INVALID_ARGS; - } - - /* - It'd be nice to be able to resolve the path here to eliminate any "." and ".." segments thereby - making the path always consistent for a given archive. However, I cannot think of a way to do - this robustly without having a backend-specific function like a `resolve()` or whatnot. The - problem is that the path might be absolute, or it might be relative, and to get it right, - parcticularly when dealing with different operating systems' ways of specifying an absolute - path, you really need to have the support of the backend. I might add support for this later. - */ - - fs_mtx_lock(&pFS->archiveLock); - { - result = fs_open_archive_nolock(pFS, pBackend, pBackendConfig, pArchivePath, archivePathLen, openMode, ppArchive); - } - fs_mtx_unlock(&pFS->archiveLock); - - return result; -} - -FS_API fs_result fs_open_archive(fs* pFS, const char* pArchivePath, int openMode, fs** ppArchive) -{ - fs_result backendIteratorResult; fs_registered_backend_iterator iBackend; + + if (ppBackend != NULL) { + *ppBackend = NULL; + } + if (ppBackendConfig != NULL) { + *ppBackendConfig = NULL; + } + + for (result = fs_first_registered_backend(pFS, &iBackend); result == FS_SUCCESS; result = fs_next_registered_backend(&iBackend)) { + if (fs_path_extension_equal(pPath, pathLen, iBackend.pExtension, iBackend.extensionLen)) { + if (ppBackend != NULL) { + *ppBackend = iBackend.pBackend; + } + if (ppBackendConfig != NULL) { + *ppBackendConfig = iBackend.pBackendConfig; + } + + return FS_SUCCESS; + } + } + + return FS_DOES_NOT_EXIST; +} + +FS_API fs_bool32 fs_path_looks_like_archive(fs* pFS, const char* pPath, size_t pathLen) +{ fs_result result; - if (ppArchive == NULL) { - return FS_INVALID_ARGS; + if (pFS == NULL || pPath == NULL || pathLen == 0 || pPath[0] == '\0') { + return FS_FALSE; } - *ppArchive = NULL; /* Safety. */ - - if (pFS == NULL || pArchivePath == NULL) { - return FS_INVALID_ARGS; + result = fs_find_registered_archive_type_by_path(pFS, pPath, pathLen, NULL, NULL); + if (result == FS_SUCCESS) { + return FS_TRUE; } - /* - There can be multiple backends registered to the same extension. We just iterate over each one in order - and use the first that works. - */ - result = FS_NO_BACKEND; - for (backendIteratorResult = fs_first_registered_backend(pFS, &iBackend); backendIteratorResult == FS_SUCCESS; backendIteratorResult = fs_next_registered_backend(&iBackend)) { - if (fs_path_extension_equal(pArchivePath, FS_NULL_TERMINATED, iBackend.pExtension, iBackend.extensionLen)) { - result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, pArchivePath, FS_NULL_TERMINATED, openMode, ppArchive); - if (result == FS_SUCCESS) { - return FS_SUCCESS; - } - } - } - - /* Failed to open from any archive backend. */ - return result; -} - -FS_API void fs_close_archive(fs* pArchive) -{ - fs_uint32 newRefCount; - - if (pArchive == NULL) { - return; - } - - /* In fs_open_archive() we incremented the reference count. Now we need to decrement it. */ - newRefCount = fs_unref(pArchive); - - /* - If the reference count of the archive is 1 it means we don't currently have any files opened. We should - look at garbage collecting. - */ - if (newRefCount == 1) { - /* - This is a bit hacky and should probably change. When we initialized the archive in fs_open_archive() we set the user - data of the onRefCountChanged callback to be the fs object that owns this archive. We'll just use that to fire the - garbage collection process. - */ - fs* pArchiveOwnerFS = (fs*)pArchive->pRefCountChangedUserData; - FS_ASSERT(pArchiveOwnerFS != NULL); - - fs_gc_archives(pArchiveOwnerFS, FS_GC_POLICY_THRESHOLD); - } -} - -static void fs_gc_archives_nolock(fs* pFS, int policy) -{ - size_t unreferencedCount = 0; - size_t collectionCount = 0; - size_t cursor = 0; - - FS_ASSERT(pFS != NULL); - - /* - If we're doing a full garbage collection we need to recursively run the garbage collection process - on opened archives. - */ - if ((policy & FS_GC_POLICY_FULL) != 0) { - cursor = 0; - while (cursor < pFS->openedArchivesSize) { - fs_opened_archive* pOpenedArchive = (fs_opened_archive*)FS_OFFSET_PTR(pFS->pOpenedArchives, cursor); - FS_ASSERT(pOpenedArchive != NULL); - - fs_gc_archives(pOpenedArchive->pArchive, policy); - cursor += FS_ALIGN(sizeof(fs*) + sizeof(size_t) + strlen(pOpenedArchive->pPath) + 1, FS_SIZEOF_PTR); - } - } - - - /* The first thing to do is count how many unreferenced archives there are. */ - cursor = 0; - while (cursor < pFS->openedArchivesSize) { - fs_opened_archive* pOpenedArchive = (fs_opened_archive*)FS_OFFSET_PTR(pFS->pOpenedArchives, cursor); - - if (fs_refcount(pOpenedArchive->pArchive) == 1) { - unreferencedCount += 1; - } - - /* Getting here means this archive is not the one we're looking for. */ - cursor += FS_ALIGN(sizeof(fs*) + sizeof(size_t) + strlen(pOpenedArchive->pPath) + 1, FS_SIZEOF_PTR); - } - - /* Now we need to determine how many archives we should unload. */ - if ((policy & FS_GC_POLICY_THRESHOLD) != 0) { - if (unreferencedCount > fs_get_archive_gc_threshold(pFS)) { - collectionCount = unreferencedCount - fs_get_archive_gc_threshold(pFS); - } else { - collectionCount = 0; /* We're below the threshold. Don't collect anything. */ - } - } else if ((policy & FS_GC_POLICY_FULL) != 0) { - collectionCount = unreferencedCount; - } else { - FS_ASSERT(!"Invalid GC policy."); - } - - /* Now we need to unload the archives. */ - cursor = 0; - while (collectionCount > 0 && cursor < pFS->openedArchivesSize) { - fs_opened_archive* pOpenedArchive = (fs_opened_archive*)FS_OFFSET_PTR(pFS->pOpenedArchives, cursor); - - if (fs_refcount(pOpenedArchive->pArchive) == 1) { - fs_file* pArchiveFile; - - /* For our cached archives, the stream should always be a file. */ - pArchiveFile = (fs_file*)pOpenedArchive->pArchive->pStream; - FS_ASSERT(pArchiveFile != NULL); - - fs_uninit(pOpenedArchive->pArchive); - fs_file_close(pArchiveFile); - - /* We can remove the archive from the list only after it's been closed. */ - fs_remove_opened_archive(pFS, pOpenedArchive); - - collectionCount -= 1; - - /* Note that we're not advancing the cursor here because we just removed this entry. */ - } else { - cursor += FS_ALIGN(sizeof(fs*) + sizeof(size_t) + strlen(pOpenedArchive->pPath) + 1, FS_SIZEOF_PTR); - } - } -} - -FS_API void fs_gc_archives(fs* pFS, int policy) -{ - if (pFS == NULL) { - return; - } - - if (policy == 0 || ((policy & FS_GC_POLICY_THRESHOLD) != 0 && (policy & FS_GC_POLICY_FULL) != 0)) { - return; /* Invalid policy. Must specify FS_GC_POLICY_THRESHOLD or FS_GC_POLICY_FULL, but not both. */ - } - - fs_mtx_lock(&pFS->archiveLock); - { - fs_gc_archives_nolock(pFS, policy); - } - fs_mtx_unlock(&pFS->archiveLock); -} - -FS_API void fs_set_archive_gc_threshold(fs* pFS, size_t threshold) -{ - if (pFS == NULL) { - return; - } - - pFS->archiveGCThreshold = threshold; -} - -FS_API size_t fs_get_archive_gc_threshold(fs* pFS) -{ - if (pFS == NULL) { - return 0; - } - - return pFS->archiveGCThreshold; + return FS_FALSE; } @@ -2707,7 +3259,6 @@ static fs_result fs_open_or_info_from_archive(fs* pFS, const char* pFilePath, in do { - fs_result backendIteratorResult; fs_registered_backend_iterator iBackend; fs_bool32 isArchive = FS_FALSE; @@ -2720,8 +3271,11 @@ static fs_result fs_open_or_info_from_archive(fs* pFS, const char* pFilePath, in } /* If an archive has been explicitly listed in the path, we must try loading from that. */ - for (backendIteratorResult = fs_first_registered_backend(pFS, &iBackend); backendIteratorResult == FS_SUCCESS; backendIteratorResult = fs_next_registered_backend(&iBackend)) { + for (result = fs_first_registered_backend(pFS, &iBackend); result == FS_SUCCESS; result = fs_next_registered_backend(&iBackend)) { if (fs_path_extension_equal(iFilePathSeg.pFullPath + iFilePathSeg.segmentOffset, iFilePathSeg.segmentLength, iBackend.pExtension, iBackend.extensionLen)) { + const fs_backend* pBackend = iBackend.pBackend; + const void* pBackendConfig = iBackend.pBackendConfig; + isArchive = FS_TRUE; /* This path points to an explicit archive. If this is the file we're trying to actually load, we'll want to handle that too. */ @@ -2735,7 +3289,7 @@ static fs_result fs_open_or_info_from_archive(fs* pFS, const char* pFilePath, in } else { fs* pArchive; - result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, iFilePathSeg.pFullPath, iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength, FS_NO_INCREMENT_REFCOUNT | FS_OPAQUE | openMode, &pArchive); + result = fs_open_archive_ex(pFS, pBackend, pBackendConfig, iFilePathSeg.pFullPath, iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength, FS_NO_INCREMENT_REFCOUNT | FS_OPAQUE | openMode, &pArchive); if (result != FS_SUCCESS) { /* We failed to open the archive. If it's due to the archive not existing we just continue searching. Otherwise @@ -2792,36 +3346,27 @@ static fs_result fs_open_or_info_from_archive(fs* pFS, const char* pFilePath, in fs_iterator* pIterator; for (pIterator = fs_backend_first(fs_get_backend_or_default(pFS), pFS, iFilePathSeg.pFullPath, iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength); pIterator != NULL; pIterator = fs_backend_next(fs_get_backend_or_default(pFS), pIterator)) { - for (backendIteratorResult = fs_first_registered_backend(pFS, &iBackend); backendIteratorResult == FS_SUCCESS; backendIteratorResult = fs_next_registered_backend(&iBackend)) { + for (result = fs_first_registered_backend(pFS, &iBackend); result == FS_SUCCESS; result = fs_next_registered_backend(&iBackend)) { if (fs_path_extension_equal(pIterator->pName, pIterator->nameLen, iBackend.pExtension, iBackend.extensionLen)) { /* Looks like an archive. We can load this one up and try opening from it. */ + const fs_backend* pBackend = iBackend.pBackend; + const void* pBackendConfig = iBackend.pBackendConfig; fs* pArchive; - char pArchivePathNTStack[1024]; - char* pArchivePathNTHeap = NULL; /* <-- Must be initialized to null. */ - char* pArchivePathNT; - size_t archivePathLen; + fs_string archivePath; - archivePathLen = iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength + 1 + pIterator->nameLen; - if (archivePathLen >= sizeof(pArchivePathNTStack)) { - pArchivePathNTHeap = (char*)fs_malloc(archivePathLen + 1, fs_get_allocation_callbacks(pFS)); - if (pArchivePathNTHeap == NULL) { - fs_backend_free_iterator(fs_get_backend_or_default(pFS), pIterator); - return FS_OUT_OF_MEMORY; - } - - pArchivePathNT = pArchivePathNTHeap; - } else { - pArchivePathNT = pArchivePathNTStack; + result = fs_string_alloc(iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength + 1 + pIterator->nameLen, fs_get_allocation_callbacks(pFS), &archivePath); + if (result != FS_SUCCESS) { + fs_backend_free_iterator(fs_get_backend_or_default(pFS), pIterator); + return result; } - FS_COPY_MEMORY(pArchivePathNT, iFilePathSeg.pFullPath, iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength); - pArchivePathNT[iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength] = '/'; - FS_COPY_MEMORY(pArchivePathNT + iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength + 1, pIterator->pName, pIterator->nameLen); - pArchivePathNT[archivePathLen] = '\0'; + fs_string_append_preallocated(&archivePath, iFilePathSeg.pFullPath, iFilePathSeg.segmentOffset + iFilePathSeg.segmentLength); + fs_string_append_preallocated(&archivePath, "/", FS_NULL_TERMINATED); + fs_string_append_preallocated(&archivePath, pIterator->pName, pIterator->nameLen); /* At this point we've constructed the archive name and we can now open it. */ - result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, pArchivePathNT, FS_NULL_TERMINATED, FS_NO_INCREMENT_REFCOUNT | FS_OPAQUE | openMode, &pArchive); - fs_free(pArchivePathNTHeap, fs_get_allocation_callbacks(pFS)); + result = fs_open_archive_ex(pFS, pBackend, pBackendConfig, fs_string_cstr(&archivePath), FS_NULL_TERMINATED, FS_NO_INCREMENT_REFCOUNT | FS_OPAQUE | openMode, &pArchive); + fs_string_free(&archivePath, fs_get_allocation_callbacks(pFS)); if (result != FS_SUCCESS) { /* <-- This is checking the result of fs_open_archive_ex(). */ continue; /* Failed to open this archive. Keep looking. */ @@ -2850,7 +3395,7 @@ static fs_result fs_open_or_info_from_archive(fs* pFS, const char* pFilePath, in return FS_SUCCESS; } } - + /* Getting here means this file could not be loaded from any registered archive types. Just move on to the next file. @@ -2924,21 +3469,11 @@ static fs_result fs_file_alloc(fs* pFS, fs_file** ppFile) return FS_SUCCESS; } -static fs_result fs_file_alloc_if_necessary(fs* pFS, fs_file** ppFile) -{ - FS_ASSERT(ppFile != NULL); - - if (*ppFile == NULL) { - return fs_file_alloc(pFS, ppFile); - } else { - return FS_SUCCESS; - } -} - -static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char* pFilePath, int openMode, fs_file** ppFile, fs_file_info* pInfo) +static fs_result fs_file_alloc_and_open_or_info(fs* pFS, const char* pFilePath, int openMode, fs_file** ppFile, fs_file_info* pInfo) { fs_result result; const fs_backend* pBackend; + fs_bool32 isStandardIOFile; pBackend = fs_get_backend_or_default(pFS); if (pBackend == NULL) { @@ -2946,9 +3481,10 @@ static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char } if (ppFile != NULL) { - result = fs_file_alloc_if_necessary(pFS, ppFile); + FS_ASSERT(*ppFile == NULL); + + result = fs_file_alloc(pFS, ppFile); if (result != FS_SUCCESS) { - *ppFile = NULL; return result; } } @@ -2957,17 +3493,16 @@ static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char Take a copy of the file system's stream if necessary. We only need to do this if we're opening the file, and if the owner `fs` object `pFS` itself has a stream. */ - if (pFS != NULL && ppFile != NULL) { - fs_stream* pFSStream = pFS->pStream; - if (pFSStream != NULL) { - result = fs_stream_duplicate(pFSStream, fs_get_allocation_callbacks(pFS), &(*ppFile)->pStreamForBackend); - if (result != FS_SUCCESS) { - fs_file_free(ppFile); - return result; - } + if (pFS != NULL && ppFile != NULL && pFS->pStream != NULL) { + result = fs_stream_duplicate(pFS->pStream, fs_get_allocation_callbacks(pFS), &(*ppFile)->pStreamForBackend); + if (result != FS_SUCCESS) { + fs_file_free(ppFile); + return result; } } + isStandardIOFile = (pFilePath == FS_STDIN || pFilePath == FS_STDOUT || pFilePath == FS_STDERR); + /* This is the lowest level opening function. We never want to look at mounts when opening from here. The input file path should already be prefixed with the mount point. @@ -2980,7 +3515,7 @@ static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char if (ppFile != NULL) { /* Create the directory structure if necessary. */ - if ((openMode & FS_WRITE) != 0 && (openMode & FS_NO_CREATE_DIRS) == 0) { + if ((openMode & FS_WRITE) != 0 && (openMode & FS_NO_CREATE_DIRS) == 0 && !isStandardIOFile) { char pDirPathStack[1024]; char* pDirPathHeap = NULL; char* pDirPath; @@ -3000,7 +3535,7 @@ static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char fs_stream_delete_duplicate((*ppFile)->pStreamForBackend, fs_get_allocation_callbacks(pFS)); fs_file_free(ppFile); fs_free(pDirPathHeap, fs_get_allocation_callbacks(pFS)); - return FS_ERROR; /* Should never hit this. */ + return FS_ERROR; } pDirPath = pDirPathHeap; @@ -3009,7 +3544,7 @@ static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char } result = fs_mkdir(pFS, pDirPath, FS_IGNORE_MOUNTS); - if (result != FS_SUCCESS) { + if (result != FS_SUCCESS && result != FS_ALREADY_EXISTS) { fs_stream_delete_duplicate((*ppFile)->pStreamForBackend, fs_get_allocation_callbacks(pFS)); fs_file_free(ppFile); return result; @@ -3020,6 +3555,8 @@ static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char if (result != FS_SUCCESS) { fs_stream_delete_duplicate((*ppFile)->pStreamForBackend, fs_get_allocation_callbacks(pFS)); + fs_file_free(ppFile); + goto try_opening_from_archive; } /* Grab the info from the opened file if we're also grabbing that. */ @@ -3034,17 +3571,15 @@ static fs_result fs_file_alloc_if_necessary_and_open_or_info(fs* pFS, const char } } - if (!FS_IS_OPAQUE(openMode) && (openMode & FS_WRITE) == 0) { +try_opening_from_archive: + if (!FS_IS_OPAQUE(openMode) && (openMode & FS_WRITE) == 0 && !isStandardIOFile) { /* If we failed to open the file because it doesn't exist we need to try loading it from an archive. We can only do this if the file is being loaded by an explicitly initialized fs object. */ if (pFS != NULL && (result == FS_DOES_NOT_EXIST || result == FS_NOT_DIRECTORY)) { - if (ppFile != NULL) { - fs_file_free(ppFile); - } - + fs_file_free(ppFile); result = fs_open_or_info_from_archive(pFS, pFilePath, openMode, ppFile, pInfo); } } @@ -3072,7 +3607,7 @@ static fs_result fs_validate_path(const char* pPath, size_t pathLen, int mode) return FS_SUCCESS; } -FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMode, fs_file** ppFile, fs_file_info* pInfo) +static fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMode, fs_file** ppFile, fs_file_info* pInfo) { fs_result result; fs_result mountPointIerationResult; @@ -3090,6 +3625,11 @@ FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMo return FS_INVALID_ARGS; } + /* Special case for standard IO files. */ + if (pFilePath == FS_STDIN || pFilePath == FS_STDOUT || pFilePath == FS_STDERR) { + return fs_file_alloc_and_open_or_info(pFS, pFilePath, openMode, ppFile, pInfo); + } + result = fs_validate_path(pFilePath, FS_NULL_TERMINATED, openMode); if (result != FS_SUCCESS) { return result; @@ -3097,80 +3637,21 @@ FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMo if ((openMode & FS_WRITE) != 0) { /* Opening in write mode. */ - if (pFS != NULL) { + if (pFS != NULL && (openMode & FS_IGNORE_MOUNTS) == 0) { fs_mount_point* pBestMountPoint = NULL; - const char* pBestMountPointPath = NULL; - const char* pBestMountPointFileSubPath = NULL; - - pBestMountPoint = fs_find_best_write_mount_point(pFS, pFilePath, &pBestMountPointPath, &pBestMountPointFileSubPath); + fs_string fileRealPath; + + pBestMountPoint = fs_find_best_write_mount_point(pFS, pFilePath, openMode, &fileRealPath); if (pBestMountPoint != NULL) { - char pActualPathStack[1024]; - char* pActualPathHeap = NULL; - char* pActualPath; - int actualPathLen; - char pActualPathCleanStack[1024]; - char* pActualPathCleanHeap = NULL; - char* pActualPathClean; - int actualPathCleanLen; - unsigned int cleanOptions = (openMode & FS_NO_ABOVE_ROOT_NAVIGATION); - - /* If the mount point starts with a root segment, i.e. "/", we cannot allow navigation above that. */ - if (pBestMountPointPath[0] == '/' || pBestMountPointPath[0] == '\\') { - cleanOptions |= FS_NO_ABOVE_ROOT_NAVIGATION; - } - - - /* Here is where we append the cleaned sub-path to the mount points actual path. */ - actualPathLen = fs_path_append(pActualPathStack, sizeof(pActualPathStack), pBestMountPointPath, pBestMountPoint->pathLen, pBestMountPointFileSubPath, FS_NULL_TERMINATED); - if (actualPathLen > 0 && (size_t)actualPathLen >= sizeof(pActualPathStack)) { - /* Not enough room on the stack. Allocate on the heap. */ - pActualPathHeap = (char*)fs_malloc(actualPathLen + 1, fs_get_allocation_callbacks(pFS)); - if (pActualPathHeap == NULL) { - return FS_OUT_OF_MEMORY; - } - - fs_path_append(pActualPathHeap, actualPathLen + 1, pBestMountPointPath, pBestMountPoint->pathLen, pBestMountPointFileSubPath, FS_NULL_TERMINATED); /* <-- This should never fail. */ - pActualPath = pActualPathHeap; - } else { - pActualPath = pActualPathStack; - } - - - /* Now we need to clean the path. */ - actualPathCleanLen = fs_path_normalize(pActualPathCleanStack, sizeof(pActualPathCleanStack), pActualPath, FS_NULL_TERMINATED, cleanOptions); - if (actualPathCleanLen < 0) { - fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS)); - return FS_INVALID_OPERATION; /* Most likely violating FS_NO_ABOVE_ROOT_NAVIGATION. */ - } - - if (actualPathCleanLen >= (int)sizeof(pActualPathCleanStack)) { - pActualPathCleanHeap = (char*)fs_malloc(actualPathCleanLen + 1, fs_get_allocation_callbacks(pFS)); - if (pActualPathCleanHeap == NULL) { - fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS)); - return FS_OUT_OF_MEMORY; - } - - fs_path_normalize(pActualPathCleanHeap, actualPathCleanLen + 1, pActualPath, FS_NULL_TERMINATED, cleanOptions); /* <-- This should never fail. */ - pActualPathClean = pActualPathCleanHeap; - } else { - pActualPathClean = pActualPathCleanStack; - } - - fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS)); - pActualPathHeap = NULL; - - /* We now have enough information to open the file. */ - result = fs_file_alloc_if_necessary_and_open_or_info(pFS, pActualPathClean, openMode, ppFile, pInfo); + result = fs_file_alloc_and_open_or_info(pFS, fs_string_cstr(&fileRealPath), openMode, ppFile, pInfo); + fs_string_free(&fileRealPath, fs_get_allocation_callbacks(pFS)); - fs_free(pActualPathCleanHeap, fs_get_allocation_callbacks(pFS)); - pActualPathCleanHeap = NULL; - - if (result == FS_SUCCESS) { - return FS_SUCCESS; - } else { - return FS_DOES_NOT_EXIST; /* Couldn't find the file from the best mount point. */ + if (result != FS_SUCCESS) { + return result; } + + return FS_SUCCESS; } else { return FS_DOES_NOT_EXIST; /* Couldn't find an appropriate mount point. */ } @@ -3180,7 +3661,7 @@ FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMo opening a file using `fopen()`. */ if ((openMode & FS_ONLY_MOUNTS) == 0) { - return fs_file_alloc_if_necessary_and_open_or_info(pFS, pFilePath, openMode, ppFile, pInfo); + return fs_file_alloc_and_open_or_info(pFS, pFilePath, openMode, ppFile, pInfo); } else { /* Getting here means only the mount points can be used to open the file (cannot open straight from @@ -3195,100 +3676,42 @@ FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMo if (pFS != NULL && (openMode & FS_IGNORE_MOUNTS) == 0) { for (mountPointIerationResult = fs_mount_list_first(pFS->pReadMountPoints, &iMountPoint); mountPointIerationResult == FS_SUCCESS; mountPointIerationResult = fs_mount_list_next(&iMountPoint)) { - /* - The first thing to do is check if the start of our file path matches the mount point. If it - doesn't match we just skip to the next mount point. - */ - char pFileSubPathCleanStack[1024]; - char* pFileSubPathCleanHeap = NULL; - char* pFileSubPathClean; - int fileSubPathCleanLen; - unsigned int cleanOptions = (openMode & FS_NO_ABOVE_ROOT_NAVIGATION); - - const char* pFileSubPath = fs_path_trim_base(pFilePath, FS_NULL_TERMINATED, iMountPoint.pMountPointPath, FS_NULL_TERMINATED); - if (pFileSubPath == NULL) { - continue; - } - - - /* If the mount point starts with a root segment, i.e. "/", we cannot allow navigation above that. */ - if (iMountPoint.pMountPointPath[0] == '/' || iMountPoint.pMountPointPath[0] == '\\') { - cleanOptions |= FS_NO_ABOVE_ROOT_NAVIGATION; - } - - /* We need to clean the file sub-path, but can skip it if FS_NO_SPECIAL_DIRS is specified since it's implied. */ - if ((openMode & FS_NO_SPECIAL_DIRS) == 0) { - fileSubPathCleanLen = fs_path_normalize(pFileSubPathCleanStack, sizeof(pFileSubPathCleanStack), pFileSubPath, FS_NULL_TERMINATED, cleanOptions); - if (fileSubPathCleanLen < 0) { - continue; /* Most likely violating FS_NO_ABOVE_ROOT_NAVIGATION. Keep looking. */ - } - - if (fileSubPathCleanLen >= (int)sizeof(pFileSubPathCleanStack)) { - pFileSubPathCleanHeap = (char*)fs_malloc(fileSubPathCleanLen + 1, fs_get_allocation_callbacks(pFS)); - if (pFileSubPathCleanHeap == NULL) { - return FS_OUT_OF_MEMORY; - } - - fs_path_normalize(pFileSubPathCleanHeap, fileSubPathCleanLen + 1, pFileSubPath, FS_NULL_TERMINATED, cleanOptions); /* <-- This should never fail. */ - pFileSubPathClean = pFileSubPathCleanHeap; - } else { - pFileSubPathClean = pFileSubPathCleanStack; - } - } else { - pFileSubPathClean = (char*)pFileSubPath; /* Safe cast. Will not be modified past this point. */ - fileSubPathCleanLen = (int)strlen(pFileSubPathClean); - } - - - /* The mount point could either be a directory or an archive. Both of these require slightly different handling. */ + /* We need to run a slightly different code path depending on whether or not the mount point is an archive. */ if (iMountPoint.pArchive != NULL) { - /* The mount point is an archive. This is the simpler case. We just load the file directly from the archive. */ - result = fs_file_open_or_info(iMountPoint.pArchive, pFileSubPathClean, openMode, ppFile, pInfo); - if (result == FS_SUCCESS) { - return FS_SUCCESS; - } else { - /* Failed to load from this archive. Keep looking. */ + /* The mount point is an archive. In this case we need to grab the file's sub-path and just open that from the archive. */ + fs_string fileSubPath; + + result = fs_resolve_sub_path_from_mount_point(pFS, iMountPoint.internal.pMountPoint, pFilePath, openMode, &fileSubPath); + if (result != FS_SUCCESS) { + continue; /* Not a valid mount point, or trying to navigate above the root. */ } + + result = fs_file_open_or_info(iMountPoint.pArchive, fs_string_cstr(&fileSubPath), openMode, ppFile, pInfo); + fs_string_free(&fileSubPath, fs_get_allocation_callbacks(pFS)); } else { - /* The mount point is a directory. We need to combine the sub-path with the mount point's original path and then load the file. */ - char pActualPathStack[1024]; - char* pActualPathHeap = NULL; - char* pActualPath; - int actualPathLen; + /* Not loading from an archive. In this case we need to resolve the real path and load the file from that. */ + fs_string fileRealPath; - actualPathLen = fs_path_append(pActualPathStack, sizeof(pActualPathStack), iMountPoint.pPath, FS_NULL_TERMINATED, pFileSubPathClean, fileSubPathCleanLen); - if (actualPathLen > 0 && (size_t)actualPathLen >= sizeof(pActualPathStack)) { - /* Not enough room on the stack. Allocate on the heap. */ - pActualPathHeap = (char*)fs_malloc(actualPathLen + 1, fs_get_allocation_callbacks(pFS)); - if (pActualPathHeap == NULL) { - return FS_OUT_OF_MEMORY; - } - - fs_path_append(pActualPathHeap, actualPathLen + 1, iMountPoint.pPath, FS_NULL_TERMINATED, pFileSubPathClean, fileSubPathCleanLen); /* <-- This should never fail. */ - pActualPath = pActualPathHeap; - } else { - pActualPath = pActualPathStack; + result = fs_resolve_real_path_from_mount_point(pFS, iMountPoint.internal.pMountPoint, pFilePath, openMode, &fileRealPath); + if (result != FS_SUCCESS) { + continue; /* Not a valid mount point, or trying to navigate above the root. */ } - result = fs_file_alloc_if_necessary_and_open_or_info(pFS, pActualPath, openMode, ppFile, pInfo); + result = fs_file_alloc_and_open_or_info(pFS, fs_string_cstr(&fileRealPath), openMode, ppFile, pInfo); + fs_string_free(&fileRealPath, fs_get_allocation_callbacks(pFS)); + } - if (pActualPathHeap != NULL) { - fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS)); - pActualPathHeap = NULL; - } - - if (result == FS_SUCCESS) { - return FS_SUCCESS; - } else { - /* Failed to load from this directory. Keep looking. */ - } + if (result == FS_SUCCESS) { + return FS_SUCCESS; + } else { + /* Failed to load from this mount point. Keep looking. */ } } } /* If we get here it means we couldn't find the file from our search paths. Try opening directly. */ if ((openMode & FS_ONLY_MOUNTS) == 0) { - result = fs_file_alloc_if_necessary_and_open_or_info(pFS, pFilePath, openMode, ppFile, pInfo); + result = fs_file_alloc_and_open_or_info(pFS, pFilePath, openMode, ppFile, pInfo); if (result == FS_SUCCESS) { return FS_SUCCESS; } @@ -3319,51 +3742,7 @@ FS_API fs_result fs_file_open(fs* pFS, const char* pFilePath, int openMode, fs_f *ppFile = NULL; - if ((openMode & FS_TEMP) == FS_TEMP) { - /* - We're creating a temporary file. We can use fs_mktmp() to generate a file path for us. The - input path will act as the prefix. - - We'll use a stack allocation for the temporary file path. We can make this more robust later - by checking for FS_PATH_TOO_LONG and allocating on the heap if necessary. - */ - char pTmpPath[4096]; - fs_result result; - - result = fs_mktmp(pFilePath, pTmpPath, sizeof(pTmpPath), FS_MKTMP_FILE); - if (result != FS_SUCCESS) { - return result; - } - - return fs_file_open_or_info(pFS, pTmpPath, openMode | FS_IGNORE_MOUNTS, ppFile, NULL); - } else { - return fs_file_open_or_info(pFS, pFilePath, openMode, ppFile, NULL); - } -} - -FS_API fs_result fs_file_open_from_handle(fs* pFS, void* hBackendFile, fs_file** ppFile) -{ - fs_result result; - - if (ppFile == NULL) { - return FS_INVALID_ARGS; - } - - *ppFile = NULL; - - result = fs_file_alloc_if_necessary(pFS, ppFile); - if (result != FS_SUCCESS) { - *ppFile = NULL; - return result; - } - - result = fs_backend_file_open_handle(fs_get_backend_or_default(pFS), pFS, hBackendFile, *ppFile); - if (result != FS_SUCCESS) { - fs_file_free(ppFile); - return result; - } - - return FS_SUCCESS; + return fs_file_open_or_info(pFS, pFilePath, openMode, ppFile, NULL); } static void fs_file_uninit(fs_file* pFile) @@ -3541,6 +3920,20 @@ FS_API fs_result fs_file_flush(fs_file* pFile) return fs_backend_file_flush(pBackend, pFile); } +FS_API fs_result fs_file_truncate(fs_file* pFile) +{ + const fs_backend* pBackend; + + if (pFile == NULL) { + return FS_INVALID_ARGS; + } + + pBackend = fs_file_get_backend(pFile); + FS_ASSERT(pBackend != NULL); + + return fs_backend_file_truncate(pBackend, pFile); +} + FS_API fs_result fs_file_get_info(fs_file* pFile, fs_file_info* pInfo) { const fs_backend* pBackend; @@ -3787,6 +4180,15 @@ static fs_iterator_internal* fs_iterator_internal_gather(fs_iterator_internal* p if (pFS != NULL && !FS_IS_OPAQUE(mode)) { fs_path_iterator iDirPathSeg; + /* + When we get here, we are iterating over the actual files in archives that are located in the + file system `pFS`. There's no real concept of mounts here. In order to make iteration work + as expected, we need to modify our mode flags to ensure it does not attempt to read from + mounts. + */ + mode |= FS_IGNORE_MOUNTS; + mode &= ~FS_ONLY_MOUNTS; + /* If no archive types have been configured we can abort early. */ if (pFS->archiveTypesAllocSize == 0) { return pIterator; @@ -3835,7 +4237,7 @@ static fs_iterator_internal* fs_iterator_internal_gather(fs_iterator_internal* p isArchive = FS_TRUE; - result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, iDirPathSeg.pFullPath, iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength, FS_READ | FS_IGNORE_MOUNTS | mode, &pArchive); + result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, iDirPathSeg.pFullPath, iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength, FS_READ | (mode & ~FS_WRITE), &pArchive); if (result != FS_SUCCESS) { /* We failed to open the archive. If it's due to the archive not existing we just continue searching. Otherwise @@ -3895,38 +4297,26 @@ static fs_iterator_internal* fs_iterator_internal_gather(fs_iterator_internal* p /* Looks like an archive. We can load this one up and try iterating from it. */ fs* pArchive; fs_iterator* pArchiveIterator; - char pArchivePathNTStack[1024]; - char* pArchivePathNTHeap = NULL; /* <-- Must be initialized to null. */ - char* pArchivePathNT; - size_t archivePathLen; + fs_string archivePath; - archivePathLen = iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength + 1 + pInnerIterator->nameLen; - if (archivePathLen >= sizeof(pArchivePathNTStack)) { - pArchivePathNTHeap = (char*)fs_malloc(archivePathLen + 1, fs_get_allocation_callbacks(pFS)); - if (pArchivePathNTHeap == NULL) { - fs_backend_free_iterator(pBackend, pInnerIterator); - return pIterator; - } - - pArchivePathNT = pArchivePathNTHeap; - } else { - pArchivePathNT = pArchivePathNTStack; + result = fs_string_alloc(iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength + 1 + pInnerIterator->nameLen, fs_get_allocation_callbacks(pFS), &archivePath); + if (result != FS_SUCCESS) { + fs_backend_free_iterator(pBackend, pInnerIterator); + return pIterator; } - FS_COPY_MEMORY(pArchivePathNT, iDirPathSeg.pFullPath, iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength); - pArchivePathNT[iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength] = '/'; - FS_COPY_MEMORY(pArchivePathNT + iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength + 1, pInnerIterator->pName, pInnerIterator->nameLen); - pArchivePathNT[archivePathLen] = '\0'; + fs_string_append_preallocated(&archivePath, iDirPathSeg.pFullPath, iDirPathSeg.segmentOffset + iDirPathSeg.segmentLength); + fs_string_append_preallocated(&archivePath, "/", FS_NULL_TERMINATED); + fs_string_append_preallocated(&archivePath, pInnerIterator->pName, pInnerIterator->nameLen); /* At this point we've constructed the archive name and we can now open it. */ - result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, pArchivePathNT, FS_NULL_TERMINATED, FS_READ | FS_IGNORE_MOUNTS | mode, &pArchive); - fs_free(pArchivePathNTHeap, fs_get_allocation_callbacks(pFS)); + result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, fs_string_cstr(&archivePath), FS_NULL_TERMINATED, FS_READ | (mode & ~FS_WRITE), &pArchive); + fs_string_free(&archivePath, fs_get_allocation_callbacks(pFS)); if (result != FS_SUCCESS) { /* <-- This is checking the result of fs_open_archive_ex(). */ continue; /* Failed to open this archive. Keep looking. */ } - if (dirPathRemainingLen == 0) { pArchiveIterator = fs_first_ex(pArchive, "", 0, mode); } else { @@ -3977,6 +4367,12 @@ FS_API fs_iterator* fs_first_ex(fs* pFS, const char* pDirectoryPath, size_t dire directoryPathLen = strlen(pDirectoryPath); } + /* Make sure we never try using mounts if we are not using a fs object. */ + if (pFS == NULL) { + mode |= FS_IGNORE_MOUNTS; + mode &= ~FS_ONLY_MOUNTS; + } + /* The first thing we need to do is gather files and directories from the backend. This needs to be done in the same order that we attempt to load files for reading: @@ -3988,99 +4384,56 @@ FS_API fs_iterator* fs_first_ex(fs* pFS, const char* pDirectoryPath, size_t dire to be consistent with what would happen when opening files. */ /* Gather files. */ - { + if ((mode & FS_WRITE) != 0) { + /* Write mode. */ + if (pFS != NULL && (mode & FS_IGNORE_MOUNTS) == 0) { + fs_mount_point* pBestMountPoint = NULL; + fs_string fileRealPath; + + pBestMountPoint = fs_find_best_write_mount_point(pFS, pDirectoryPath, mode, &fileRealPath); + if (pBestMountPoint != NULL) { + pIterator = fs_iterator_internal_gather(pIterator, pBackend, pFS, fs_string_cstr(&fileRealPath), fs_string_len(&fileRealPath), mode); + fs_string_free(&fileRealPath, fs_get_allocation_callbacks(pFS)); + } + } else { + /* No "fs" object was supplied, or we're ignoring mounts. Need to gather directly from the file system. */ + if ((mode & FS_ONLY_MOUNTS) == 0) { + pIterator = fs_iterator_internal_gather(pIterator, pBackend, pFS, pDirectoryPath, directoryPathLen, mode); + } + } + } else { + /* Read mode. */ fs_result mountPointIerationResult; fs_mount_list_iterator iMountPoint; /* Check mount points. */ if (pFS != NULL && (mode & FS_IGNORE_MOUNTS) == 0) { for (mountPointIerationResult = fs_mount_list_first(pFS->pReadMountPoints, &iMountPoint); mountPointIerationResult == FS_SUCCESS; mountPointIerationResult = fs_mount_list_next(&iMountPoint)) { - /* - Just like when opening a file, we need to check that the directory path starts with the mount point. If it - doesn't match we just skip to the next mount point. - */ - char pDirSubPathCleanStack[1024]; - char* pDirSubPathCleanHeap = NULL; - char* pDirSubPathClean; - int dirSubPathCleanLen; - unsigned int cleanOptions = (mode & FS_NO_ABOVE_ROOT_NAVIGATION); - - size_t dirSubPathLen; - const char* pDirSubPath = fs_path_trim_base(pDirectoryPath, directoryPathLen, iMountPoint.pMountPointPath, FS_NULL_TERMINATED); - if (pDirSubPath == NULL) { - continue; - } - - dirSubPathLen = directoryPathLen - (size_t)(pDirSubPath - pDirectoryPath); - - - /* If the mount point starts with a root segment, i.e. "/", we cannot allow navigation above that. */ - if (iMountPoint.pMountPointPath[0] == '/' || iMountPoint.pMountPointPath[0] == '\\') { - cleanOptions |= FS_NO_ABOVE_ROOT_NAVIGATION; - } - - /* We need to clean the file sub-path, but can skip it if FS_NO_SPECIAL_DIRS is specified since it's implied. */ - if ((mode & FS_NO_SPECIAL_DIRS) == 0) { - dirSubPathCleanLen = fs_path_normalize(pDirSubPathCleanStack, sizeof(pDirSubPathCleanStack), pDirSubPath, dirSubPathLen, cleanOptions); - if (dirSubPathCleanLen < 0) { - continue; /* Most likely violating FS_NO_ABOVE_ROOT_NAVIGATION. */ - } - - if (dirSubPathCleanLen >= (int)sizeof(pDirSubPathCleanStack)) { - pDirSubPathCleanHeap = (char*)fs_malloc(dirSubPathCleanLen + 1, fs_get_allocation_callbacks(pFS)); - if (pDirSubPathCleanHeap == NULL) { - return NULL; /* Out of memory. */ - } - - fs_path_normalize(pDirSubPathCleanHeap, dirSubPathCleanLen + 1, pDirSubPath, dirSubPathLen, cleanOptions); /* <-- This should never fail. */ - pDirSubPathClean = pDirSubPathCleanHeap; - } else { - pDirSubPathClean = pDirSubPathCleanStack; - } - } else { - pDirSubPathClean = (char*)pDirSubPath; /* Safe cast. Will not be modified past this point. */ - dirSubPathCleanLen = (int)dirSubPathLen; - } - - if (iMountPoint.pArchive != NULL) { - /* The mount point is an archive. We need to iterate over the contents of the archive. */ - pBackendIterator = fs_first_ex(iMountPoint.pArchive, pDirSubPathClean, dirSubPathCleanLen, mode); + fs_string dirSubPath; + + result = fs_resolve_sub_path_from_mount_point(pFS, iMountPoint.internal.pMountPoint, pDirectoryPath, mode, &dirSubPath); + if (result != FS_SUCCESS) { + continue; + } + + pBackendIterator = fs_first_ex(iMountPoint.pArchive, fs_string_cstr(&dirSubPath), fs_string_len(&dirSubPath), mode); while (pBackendIterator != NULL) { pIterator = fs_iterator_internal_append(pIterator, pBackendIterator, pFS, mode); pBackendIterator = fs_next(pBackendIterator); } + + fs_string_free(&dirSubPath, fs_get_allocation_callbacks(pFS)); } else { - /* - The mount point is a directory. We need to construct a path that is the concatenation of the mount point's internal path and - our input directory. This is the path we'll be using to iterate over the contents of the directory. - */ - char pInterpolatedPathStack[1024]; - char* pInterpolatedPathHeap = NULL; - char* pInterpolatedPath; - int interpolatedPathLen; + fs_string dirRealPath; - interpolatedPathLen = fs_path_append(pInterpolatedPathStack, sizeof(pInterpolatedPathStack), iMountPoint.pPath, FS_NULL_TERMINATED, pDirSubPathClean, dirSubPathCleanLen); - if (interpolatedPathLen > 0 && (size_t)interpolatedPathLen >= sizeof(pInterpolatedPathStack)) { - /* Not enough room on the stack. Allocate on the heap. */ - pInterpolatedPathHeap = (char*)fs_malloc(interpolatedPathLen + 1, fs_get_allocation_callbacks(pFS)); - if (pInterpolatedPathHeap == NULL) { - fs_free_iterator((fs_iterator*)pIterator); - return NULL; /* Out of memory. */ - } - - fs_path_append(pInterpolatedPathHeap, interpolatedPathLen + 1, iMountPoint.pPath, FS_NULL_TERMINATED, pDirSubPathClean, dirSubPathCleanLen); /* <-- This should never fail. */ - pInterpolatedPath = pInterpolatedPathHeap; - } else { - pInterpolatedPath = pInterpolatedPathStack; + result = fs_resolve_real_path_from_mount_point(pFS, iMountPoint.internal.pMountPoint, pDirectoryPath, mode, &dirRealPath); + if (result != FS_SUCCESS) { + continue; } - pIterator = fs_iterator_internal_gather(pIterator, pBackend, pFS, pInterpolatedPath, FS_NULL_TERMINATED, mode); - - if (pInterpolatedPathHeap != NULL) { - fs_free(pInterpolatedPathHeap, fs_get_allocation_callbacks(pFS)); - pInterpolatedPathHeap = NULL; - } + pIterator = fs_iterator_internal_gather(pIterator, pBackend, pFS, fs_string_cstr(&dirRealPath), fs_string_len(&dirRealPath), mode); + fs_string_free(&dirRealPath, fs_get_allocation_callbacks(pFS)); } } } @@ -4151,72 +4504,331 @@ FS_API void fs_free_iterator(fs_iterator* pIterator) } -static fs_result fs_mount_read(fs* pFS, const char* pActualPath, const char* pVirtualPath, int options) + +static void fs_on_refcount_changed_internal(void* pUserData, fs* pFS, fs_uint32 newRefCount, fs_uint32 oldRefCount) +{ + fs* pOwnerFS; + + (void)pUserData; + (void)pFS; + (void)newRefCount; + (void)oldRefCount; + + pOwnerFS = (fs*)pUserData; + FS_ASSERT(pOwnerFS != NULL); + + if (newRefCount == 1) { + /* In this case there are no more files referencing this archive. We'll want to do some garbage collection. */ + fs_gc_archives(pOwnerFS, FS_GC_POLICY_THRESHOLD); + } +} + +static fs_result fs_open_archive_nolock(fs* pFS, const fs_backend* pBackend, const void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive) { fs_result result; - fs_mount_list_iterator iterator; - fs_result iteratorResult; - fs_mount_list* pMountPoints; - fs_mount_point* pNewMountPoint; - fs_file_info fileInfo; - int openMode; - - FS_ASSERT(pFS != NULL); - FS_ASSERT(pActualPath != NULL); - FS_ASSERT(pVirtualPath != NULL); - FS_ASSERT((options & FS_READ) == FS_READ); + fs* pArchive; + fs_config archiveConfig; + fs_file* pArchiveFile; + char pArchivePathNTStack[1024]; + char* pArchivePathNTHeap = NULL; /* <-- Must be initialized to null. */ + char* pArchivePathNT; + fs_opened_archive* pOpenedArchive; /* - The first thing we're going to do is check for duplicates. We allow for the same path to be mounted - to different mount points, and different paths to be mounted to the same mount point, but we don't - want to have any duplicates where the same path is mounted to the same mount point. + The first thing to do is check if the archive has already been opened. If so, we just increment + the reference count and return the already-loaded fs object. */ - for (iteratorResult = fs_mount_list_first(pFS->pReadMountPoints, &iterator); iteratorResult == FS_SUCCESS; iteratorResult = fs_mount_list_next(&iterator)) { - if (strcmp(pActualPath, iterator.pPath) == 0 && strcmp(pVirtualPath, iterator.pMountPointPath) == 0) { - return FS_SUCCESS; /* Just pretend we're successful. */ - } - } - - /* - Getting here means we're not mounting a duplicate so we can now add it. We'll be either adding it to - the end of the list, or to the beginning of the list depending on the priority. - */ - pMountPoints = fs_mount_list_alloc(pFS->pReadMountPoints, pActualPath, pVirtualPath, ((options & FS_LOWEST_PRIORITY) == FS_LOWEST_PRIORITY) ? FS_MOUNT_PRIORITY_LOWEST : FS_MOUNT_PRIORITY_HIGHEST, fs_get_allocation_callbacks(pFS), &pNewMountPoint); - if (pMountPoints == NULL) { - return FS_OUT_OF_MEMORY; - } - - pFS->pReadMountPoints = pMountPoints; - - /* - We need to determine if we're mounting a directory or an archive. If it's an archive, we need to - open it. - */ - openMode = FS_READ | FS_VERBOSE; - - /* Must use fs_backend_info() instead of fs_info() because otherwise fs_info() will attempt to read from mounts when we're in the process of trying to add one (this function). */ - result = fs_backend_info(fs_get_backend_or_default(pFS), pFS, (pActualPath[0] != '\0') ? pActualPath : ".", FS_IGNORE_MOUNTS, &fileInfo); - if (result != FS_SUCCESS && result != FS_DOES_NOT_EXIST) { - return result; - } - - /* If we failed to find the info (result == FS_DOES_NOT_EXIST), we can just assume we're trying to mount a directory. */ - if (fileInfo.directory || result == FS_DOES_NOT_EXIST) { - pNewMountPoint->pArchive = NULL; - pNewMountPoint->closeArchiveOnUnmount = FS_FALSE; + pOpenedArchive = fs_find_opened_archive(pFS, pArchivePath, archivePathLen); + if (pOpenedArchive != NULL) { + pArchive = pOpenedArchive->pArchive; } else { - result = fs_open_archive(pFS, pActualPath, openMode, &pNewMountPoint->pArchive); + /* + Getting here means the archive is not cached. We'll need to open it. Unfortunately our path is + not null terminated so we'll need to do that now. We'll try to avoid a heap allocation if we + can. + */ + if (archivePathLen == FS_NULL_TERMINATED) { + pArchivePathNT = (char*)pArchivePath; /* <-- Safe cast. We won't be modifying this. */ + } else { + if (archivePathLen >= sizeof(pArchivePathNTStack)) { + pArchivePathNTHeap = (char*)fs_malloc(archivePathLen + 1, fs_get_allocation_callbacks(pFS)); + if (pArchivePathNTHeap == NULL) { + return FS_OUT_OF_MEMORY; + } + + pArchivePathNT = pArchivePathNTHeap; + } else { + pArchivePathNT = pArchivePathNTStack; + } + + FS_COPY_MEMORY(pArchivePathNT, pArchivePath, archivePathLen); + pArchivePathNT[archivePathLen] = '\0'; + } + + result = fs_file_open(pFS, pArchivePathNT, openMode, &pArchiveFile); if (result != FS_SUCCESS) { + fs_free(pArchivePathNTHeap, fs_get_allocation_callbacks(pFS)); return result; } - pNewMountPoint->closeArchiveOnUnmount = FS_TRUE; + archiveConfig = fs_config_init(pBackend, pBackendConfig, fs_file_get_stream(pArchiveFile)); + archiveConfig.pAllocationCallbacks = fs_get_allocation_callbacks(pFS); + archiveConfig.onRefCountChanged = fs_on_refcount_changed_internal; + archiveConfig.pRefCountChangedUserData = pFS; /* The user data is always the fs object that owns this archive. */ + + result = fs_init(&archiveConfig, &pArchive); + fs_free(pArchivePathNTHeap, fs_get_allocation_callbacks(pFS)); + + if (result != FS_SUCCESS) { /* <-- This is the result of fs_init().*/ + fs_file_close(pArchiveFile); + return result; + } + + /* + We need to support the ability to open archives within archives. To do this, the archive fs + object needs to inherit the registered archive types. Fortunately this is easy because we do + this as one single allocation which means we can just reference it directly. The API has a + restriction that archive type registration cannot be modified after a file has been opened. + */ + pArchive->pArchiveTypes = pFS->pArchiveTypes; + pArchive->archiveTypesAllocSize = pFS->archiveTypesAllocSize; + pArchive->isOwnerOfArchiveTypes = FS_FALSE; + + /* Add the new archive to the cache. */ + result = fs_add_opened_archive(pFS, pArchive, pArchivePath, archivePathLen); + if (result != FS_SUCCESS) { + fs_uninit(pArchive); + fs_file_close(pArchiveFile); + return result; + } } + FS_ASSERT(pArchive != NULL); + + *ppArchive = ((openMode & FS_NO_INCREMENT_REFCOUNT) == 0) ? fs_ref(pArchive) : pArchive; return FS_SUCCESS; } -FS_API fs_result fs_unmount_read(fs* pFS, const char* pActualPath, int options) +FS_API fs_result fs_open_archive_ex(fs* pFS, const fs_backend* pBackend, const void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive) +{ + fs_result result; + + if (ppArchive == NULL) { + return FS_INVALID_ARGS; + } + + *ppArchive = NULL; + + if (pFS == NULL || pBackend == NULL || pArchivePath == NULL || archivePathLen == 0) { + return FS_INVALID_ARGS; + } + + /* + It'd be nice to be able to resolve the path here to eliminate any "." and ".." segments thereby + making the path always consistent for a given archive. However, I cannot think of a way to do + this robustly without having a backend-specific function like a `resolve()` or whatnot. The + problem is that the path might be absolute, or it might be relative, and to get it right, + parcticularly when dealing with different operating systems' ways of specifying an absolute + path, you really need to have the support of the backend. I might add support for this later. + */ + + fs_mtx_lock(&pFS->archiveLock); + { + result = fs_open_archive_nolock(pFS, pBackend, pBackendConfig, pArchivePath, archivePathLen, openMode, ppArchive); + } + fs_mtx_unlock(&pFS->archiveLock); + + return result; +} + +FS_API fs_result fs_open_archive(fs* pFS, const char* pArchivePath, int openMode, fs** ppArchive) +{ + fs_result backendIteratorResult; + fs_registered_backend_iterator iBackend; + fs_result result; + + if (ppArchive == NULL) { + return FS_INVALID_ARGS; + } + + *ppArchive = NULL; /* Safety. */ + + if (pFS == NULL || pArchivePath == NULL) { + return FS_INVALID_ARGS; + } + + /* + There can be multiple backends registered to the same extension. We just iterate over each one in order + and use the first that works. + */ + result = FS_NO_BACKEND; + for (backendIteratorResult = fs_first_registered_backend(pFS, &iBackend); backendIteratorResult == FS_SUCCESS; backendIteratorResult = fs_next_registered_backend(&iBackend)) { + if (fs_path_extension_equal(pArchivePath, FS_NULL_TERMINATED, iBackend.pExtension, iBackend.extensionLen)) { + result = fs_open_archive_ex(pFS, iBackend.pBackend, iBackend.pBackendConfig, pArchivePath, FS_NULL_TERMINATED, openMode, ppArchive); + if (result == FS_SUCCESS) { + return FS_SUCCESS; + } + } + } + + /* Failed to open from any archive backend. */ + return result; +} + +FS_API void fs_close_archive(fs* pArchive) +{ + fs_uint32 newRefCount; + + if (pArchive == NULL) { + return; + } + + /* In fs_open_archive() we incremented the reference count. Now we need to decrement it. */ + newRefCount = fs_unref(pArchive); + + /* + If the reference count of the archive is 1 it means we don't currently have any files opened. We should + look at garbage collecting. + */ + if (newRefCount == 1) { + /* + This is a bit hacky and should probably change. When we initialized the archive in fs_open_archive() we set the user + data of the onRefCountChanged callback to be the fs object that owns this archive. We'll just use that to fire the + garbage collection process. + */ + fs* pArchiveOwnerFS = (fs*)pArchive->pRefCountChangedUserData; + FS_ASSERT(pArchiveOwnerFS != NULL); + + fs_gc(pArchiveOwnerFS, FS_GC_POLICY_FULL, pArchive); + } +} + +static void fs_gc_nolock(fs* pFS, int policy, fs* pSpecificArchive) +{ + size_t unreferencedCount = 0; + size_t collectionCount = 0; + size_t cursor = 0; + + FS_ASSERT(pFS != NULL); + + /* + If we're doing a full garbage collection we need to recursively run the garbage collection process + on opened archives. For specific GC, we only recursively collect the target archive. + */ + if ((policy & FS_GC_POLICY_FULL) != 0) { + /* For full GC, recursively collect all opened archives. */ + if (pSpecificArchive != NULL) { + fs_gc_archives(pSpecificArchive, FS_GC_POLICY_FULL); + } else { + cursor = 0; + while (cursor < pFS->openedArchivesSize) { + fs_opened_archive* pOpenedArchive = (fs_opened_archive*)FS_OFFSET_PTR(pFS->pOpenedArchives, cursor); + FS_ASSERT(pOpenedArchive != NULL); + + fs_gc_archives(pOpenedArchive->pArchive, policy); + cursor += FS_ALIGN(sizeof(fs*) + sizeof(size_t) + strlen(pOpenedArchive->pPath) + 1, FS_SIZEOF_PTR); + } + } + } + + /* Count unreferenced archives. */ + cursor = 0; + while (cursor < pFS->openedArchivesSize) { + fs_opened_archive* pOpenedArchive = (fs_opened_archive*)FS_OFFSET_PTR(pFS->pOpenedArchives, cursor); + + if (fs_refcount(pOpenedArchive->pArchive) == 1) { + unreferencedCount += 1; + } + + /* If this is a specific GC, check if this is the target archive. */ + if (pSpecificArchive != NULL && pSpecificArchive == pOpenedArchive->pArchive) { + if (fs_refcount(pSpecificArchive) == 1) { + break; + } else { + /* Archive is still referenced, so we can't collect it. */ + return; + } + } + + cursor += FS_ALIGN(sizeof(fs*) + sizeof(size_t) + strlen(pOpenedArchive->pPath) + 1, FS_SIZEOF_PTR); + } + + /* Determine how many archives to collect. */ + if ((policy & FS_GC_POLICY_THRESHOLD) != 0) { + if (unreferencedCount > fs_get_archive_gc_threshold(pFS)) { + collectionCount = unreferencedCount - fs_get_archive_gc_threshold(pFS); + } else { + collectionCount = 0; /* We're below the threshold. Don't collect anything. */ + } + } else if ((policy & FS_GC_POLICY_FULL) != 0) { + collectionCount = unreferencedCount; + } else { + FS_ASSERT(!"Invalid GC policy."); + } + + /* Collect archives. */ + cursor = 0; + while (collectionCount > 0 && cursor < pFS->openedArchivesSize) { + fs_opened_archive* pOpenedArchive = (fs_opened_archive*)FS_OFFSET_PTR(pFS->pOpenedArchives, cursor); + + if (fs_refcount(pOpenedArchive->pArchive) == 1 && (pSpecificArchive == NULL || pSpecificArchive == pOpenedArchive->pArchive)) { + fs_file* pArchiveFile = (fs_file*)pOpenedArchive->pArchive->pStream; + FS_ASSERT(pArchiveFile != NULL); + + fs_uninit(pOpenedArchive->pArchive); + fs_file_close(pArchiveFile); + fs_remove_opened_archive(pFS, pOpenedArchive); + + collectionCount -= 1; + /* Note that we're not advancing the cursor here because we just removed this entry. */ + } else { + cursor += FS_ALIGN(sizeof(fs*) + sizeof(size_t) + strlen(pOpenedArchive->pPath) + 1, FS_SIZEOF_PTR); + } + } +} + +static void fs_gc(fs* pFS, int policy, fs* pSpecificArchive) +{ + if (pFS == NULL) { + return; + } + + if (policy == 0 || ((policy & FS_GC_POLICY_THRESHOLD) != 0 && (policy & FS_GC_POLICY_FULL) != 0)) { + return; /* Invalid policy. Must specify FS_GC_POLICY_THRESHOLD or FS_GC_POLICY_FULL, but not both. */ + } + + fs_mtx_lock(&pFS->archiveLock); + { + fs_gc_nolock(pFS, policy, pSpecificArchive); + } + fs_mtx_unlock(&pFS->archiveLock); +} + +FS_API void fs_gc_archives(fs* pFS, int policy) +{ + fs_gc(pFS, policy, NULL); +} + +FS_API void fs_set_archive_gc_threshold(fs* pFS, size_t threshold) +{ + if (pFS == NULL) { + return; + } + + pFS->archiveGCThreshold = threshold; +} + +FS_API size_t fs_get_archive_gc_threshold(fs* pFS) +{ + if (pFS == NULL) { + return 0; + } + + return pFS->archiveGCThreshold; +} + + +static fs_result fs_unmount_read(fs* pFS, const char* pActualPath, int options) { fs_result iteratorResult; fs_mount_list_iterator iterator; @@ -4248,12 +4860,107 @@ FS_API fs_result fs_unmount_read(fs* pFS, const char* pActualPath, int options) return FS_SUCCESS; } +static fs_result fs_mount_read(fs* pFS, const char* pActualPath, const char* pVirtualPath, int options) +{ + fs_result result; + fs_mount_list_iterator iterator; + fs_result iteratorResult; + fs_mount_list* pMountPoints; + fs_mount_point* pNewMountPoint; + fs_file_info fileInfo; + + FS_ASSERT(pFS != NULL); + FS_ASSERT(pActualPath != NULL); + FS_ASSERT(pVirtualPath != NULL); + FS_ASSERT((options & FS_READ) == FS_READ); + + /* + The first thing we're going to do is check for duplicates. We allow for the same path to be mounted + to different mount points, and different paths to be mounted to the same mount point, but we don't + want to have any duplicates where the same path is mounted to the same mount point. + */ + for (iteratorResult = fs_mount_list_first(pFS->pReadMountPoints, &iterator); iteratorResult == FS_SUCCESS; iteratorResult = fs_mount_list_next(&iterator)) { + if (strcmp(pActualPath, iterator.pPath) == 0 && strcmp(pVirtualPath, iterator.pMountPointPath) == 0) { + return FS_SUCCESS; /* Just pretend we're successful. */ + } + } + + /* + Getting here means we're not mounting a duplicate so we can now add it. We'll be either adding it to + the end of the list, or to the beginning of the list depending on the priority. + */ + pMountPoints = fs_mount_list_alloc(pFS->pReadMountPoints, pActualPath, pVirtualPath, ((options & FS_LOWEST_PRIORITY) == FS_LOWEST_PRIORITY) ? FS_MOUNT_PRIORITY_LOWEST : FS_MOUNT_PRIORITY_HIGHEST, fs_get_allocation_callbacks(pFS), &pNewMountPoint); + if (pMountPoints == NULL) { + return FS_OUT_OF_MEMORY; + } + + pNewMountPoint->pArchive = NULL; + pNewMountPoint->closeArchiveOnUnmount = FS_FALSE; + + pFS->pReadMountPoints = pMountPoints; + + /* + We need to determine if we're mounting a directory or an archive. If it's an archive, we need to + open it. + */ + + /* Must use fs_backend_info() instead of fs_info() because otherwise fs_info() will attempt to read from mounts when we're in the process of trying to add one (this function). */ + result = fs_backend_info(fs_get_backend_or_default(pFS), pFS, (pActualPath[0] != '\0') ? pActualPath : ".", FS_IGNORE_MOUNTS, &fileInfo); + if (result != FS_SUCCESS) { + fs_unmount_read(pFS, pActualPath, options); + return result; + } + + /* if the path is not pointing to a directory, assume it's a file, and therefore an archive. */ + if (!fileInfo.directory) { + result = fs_open_archive(pFS, pActualPath, FS_READ | FS_VERBOSE, &pNewMountPoint->pArchive); + if (result != FS_SUCCESS) { + fs_unmount_read(pFS, pActualPath, options); + return result; + } + + pNewMountPoint->closeArchiveOnUnmount = FS_TRUE; + } + + return FS_SUCCESS; +} + + +static fs_result fs_unmount_write(fs* pFS, const char* pActualPath, int options) +{ + fs_result iteratorResult; + fs_mount_list_iterator iterator; + + FS_ASSERT(pFS != NULL); + FS_ASSERT(pActualPath != NULL); + + FS_UNUSED(options); + + for (iteratorResult = fs_mount_list_first(pFS->pWriteMountPoints, &iterator); iteratorResult == FS_SUCCESS && !fs_mount_list_at_end(&iterator); /*iteratorResult = fs_mount_list_next(&iterator)*/) { + if (strcmp(pActualPath, iterator.pPath) == 0) { + fs_mount_list_remove(pFS->pWriteMountPoints, iterator.internal.pMountPoint); + + /* + Since we just removed this item we don't want to advance the cursor. We do, however, need to re-resolve + the members in preparation for the next iteration. + */ + fs_mount_list_iterator_resolve_members(&iterator, iterator.internal.cursor); + } else { + iteratorResult = fs_mount_list_next(&iterator); + } + } + + return FS_SUCCESS; +} + static fs_result fs_mount_write(fs* pFS, const char* pActualPath, const char* pVirtualPath, int options) { + fs_result result; fs_mount_list_iterator iterator; fs_result iteratorResult; fs_mount_point* pNewMountPoint; fs_mount_list* pMountList; + fs_file_info fileInfo; if (pFS == NULL || pActualPath == NULL) { return FS_INVALID_ARGS; @@ -4278,39 +4985,31 @@ static fs_result fs_mount_write(fs* pFS, const char* pActualPath, const char* pV pFS->pWriteMountPoints = pMountList; - /* We don't support mounting archives. Explicitly disable this. */ + /* + We need to determine if we're mounting a directory or an archive. If it's an archive we need + to fail because we do not support mounting archives in write mode. + */ pNewMountPoint->pArchive = NULL; pNewMountPoint->closeArchiveOnUnmount = FS_FALSE; - /* Since we'll be wanting to write out files to the mount point we should ensure the folder actually exists. */ - if ((options & FS_NO_CREATE_DIRS) == 0) { - fs_mkdir(pFS, pActualPath, FS_IGNORE_MOUNTS); + /* Must use fs_backend_info() instead of fs_info() because otherwise fs_info() will attempt to read from mounts when we're in the process of trying to add one (this function). */ + result = fs_backend_info(fs_get_backend_or_default(pFS), pFS, (pActualPath[0] != '\0') ? pActualPath : ".", FS_IGNORE_MOUNTS, &fileInfo); + if (result != FS_SUCCESS && result != FS_DOES_NOT_EXIST) { + fs_unmount_write(pFS, pActualPath, options); + return result; } - return FS_SUCCESS; -} + if (!fileInfo.directory && result != FS_DOES_NOT_EXIST) { + fs_unmount_write(pFS, pActualPath, options); + return FS_INVALID_ARGS; + } -static fs_result fs_unmount_write(fs* pFS, const char* pActualPath, int options) -{ - fs_result iteratorResult; - fs_mount_list_iterator iterator; - - FS_ASSERT(pFS != NULL); - FS_ASSERT(pActualPath != NULL); - - FS_UNUSED(options); - - for (iteratorResult = fs_mount_list_first(pFS->pWriteMountPoints, &iterator); iteratorResult == FS_SUCCESS; /*iteratorResult = fs_mount_list_next(&iterator)*/) { - if (strcmp(pActualPath, iterator.pPath) == 0) { - fs_mount_list_remove(pFS->pWriteMountPoints, iterator.internal.pMountPoint); - - /* - Since we just removed this item we don't want to advance the cursor. We do, however, need to re-resolve - the members in preparation for the next iteration. - */ - fs_mount_list_iterator_resolve_members(&iterator, iterator.internal.cursor); - } else { - iteratorResult = fs_mount_list_next(&iterator); + /* Since we'll be wanting to write out files to the mount point we should ensure the folder actually exists. */ + if ((options & FS_NO_CREATE_DIRS) == 0) { + fs_result result = fs_mkdir(pFS, pActualPath, FS_IGNORE_MOUNTS); + if (result != FS_SUCCESS && result != FS_ALREADY_EXISTS) { + fs_unmount_write(pFS, pActualPath, options); + return result; } } @@ -4333,15 +5032,15 @@ FS_API fs_result fs_mount(fs* pFS, const char* pActualPath, const char* pVirtual return FS_INVALID_ARGS; } - if ((options & FS_READ) == FS_READ) { - fs_result result = fs_mount_read(pFS, pActualPath, pVirtualPath, options); + if ((options & FS_WRITE) == FS_WRITE) { + fs_result result = fs_mount_write(pFS, pActualPath, pVirtualPath, options); if (result != FS_SUCCESS) { return result; } } - if ((options & FS_WRITE) == FS_WRITE) { - fs_result result = fs_mount_write(pFS, pActualPath, pVirtualPath, options); + if ((options & FS_READ) == FS_READ) { + fs_result result = fs_mount_read(pFS, pActualPath, pVirtualPath, options); if (result != FS_SUCCESS) { return result; } @@ -4350,23 +5049,23 @@ FS_API fs_result fs_mount(fs* pFS, const char* pActualPath, const char* pVirtual return FS_SUCCESS; } -FS_API fs_result fs_unmount(fs* pFS, const char* pPathToMount_NotMountPoint, int options) +FS_API fs_result fs_unmount(fs* pFS, const char* pActualPath, int options) { fs_result result; - if (pFS == NULL || pPathToMount_NotMountPoint == NULL) { + if (pFS == NULL || pActualPath == NULL) { return FS_INVALID_ARGS; } if ((options & FS_READ) == FS_READ) { - result = fs_unmount_read(pFS, pPathToMount_NotMountPoint, options); + result = fs_unmount_read(pFS, pActualPath, options); if (result != FS_SUCCESS) { return result; } } if ((options & FS_WRITE) == FS_WRITE) { - result = fs_unmount_write(pFS, pPathToMount_NotMountPoint, options); + result = fs_unmount_write(pFS, pActualPath, options); if (result != FS_SUCCESS) { return result; } @@ -4580,7 +5279,7 @@ FS_API fs_result fs_file_open_and_read(fs* pFS, const char* pFilePath, fs_format fs_result result; fs_file* pFile; - if (pFilePath == NULL || ppData == NULL || pDataSize == NULL) { + if (pFilePath == NULL || ppData == NULL || (pDataSize == NULL && format != FS_FORMAT_TEXT)) { return FS_INVALID_ARGS; } @@ -4596,7 +5295,7 @@ FS_API fs_result fs_file_open_and_read(fs* pFS, const char* pFilePath, fs_format return result; } -FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, void* pData, size_t dataSize) +FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, const void* pData, size_t dataSize) { fs_result result; fs_file* pFile; @@ -4610,7 +5309,7 @@ FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, void* pD return FS_INVALID_ARGS; } - result = fs_file_open(pFS, pFilePath, FS_TRUNCATE, &pFile); + result = fs_file_open(pFS, pFilePath, FS_WRITE | FS_TRUNCATE, &pFile); if (result != FS_SUCCESS) { return result; } @@ -4625,186 +5324,99 @@ FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, void* pD } +/* BEG fs_backend_posix.c */ +#if !defined(_WIN32) /* <-- Add any platforms that lack POSIX support here. */ + #define FS_SUPPORTS_POSIX +#endif -/****************************************************************************** -* -* -* stdio Backend -* -* -******************************************************************************/ -#ifndef FS_NO_STDIO +#if !defined(FS_NO_POSIX) && defined(FS_SUPPORTS_POSIX) + #define FS_HAS_POSIX +#endif + +#if defined(FS_HAS_POSIX) + +/* For 64-bit seeks. */ +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +#include /* memset() */ #include -#include /* For wcstombs(). */ +#include +#include +#include +#include #include -#if defined(_WIN32) -#include /* For _mkdir() */ -#endif -#ifndef S_ISDIR -#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -#endif - -#ifndef S_ISLNK - #if defined(_WIN32) - #define S_ISLNK(m) (0) - #else - #define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) - #endif -#endif - -static int fs_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +static size_t fs_alloc_size_posix(const void* pBackendConfig) { -#if defined(_MSC_VER) && _MSC_VER >= 1400 - int err; -#endif + (void)pBackendConfig; + return 0; +} - if (ppFile != NULL) { - *ppFile = NULL; /* Safety. */ - } - - if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { - return EINVAL; - } - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - err = fopen_s(ppFile, pFilePath, pOpenMode); - if (err != 0) { - return err; - } -#else -#if defined(_WIN32) || defined(__APPLE__) - *ppFile = fopen(pFilePath, pOpenMode); -#else - #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) - *ppFile = fopen64(pFilePath, pOpenMode); - #else - *ppFile = fopen(pFilePath, pOpenMode); - #endif -#endif - if (*ppFile == NULL) { - int result = errno; - if (result == 0) { - result = ENOENT; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ - } - - return result; - } -#endif +static fs_result fs_init_posix(fs* pFS, const void* pBackendConfig, fs_stream* pStream) +{ + (void)pFS; + (void)pBackendConfig; + (void)pStream; return FS_SUCCESS; } -/* -_wfopen() isn't always available in all compilation environments. - - * Windows only. - * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). - * MinGW-64 (both 32- and 64-bit) seems to support it. - * MinGW wraps it in !defined(__STRICT_ANSI__). - * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). - -This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() -fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. -*/ -#if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) - #define FS_HAS_WFOPEN - #endif -#endif - -int fs_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode) +static void fs_uninit_posix(fs* pFS) { - if (ppFile != NULL) { - *ppFile = NULL; /* Safety. */ + (void)pFS; +} + +static fs_result fs_ioctl_posix(fs* pFS, int op, void* pArg) +{ + (void)pFS; + (void)op; + (void)pArg; + + return FS_NOT_IMPLEMENTED; +} + +static fs_result fs_remove_posix(fs* pFS, const char* pFilePath) +{ + int result = remove(pFilePath); + if (result < 0) { + return fs_result_from_errno(errno); } - if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { - return FS_INVALID_ARGS; + (void)pFS; + return FS_SUCCESS; +} + +static fs_result fs_rename_posix(fs* pFS, const char* pOldPath, const char* pNewPath) +{ + int result = rename(pOldPath, pNewPath); + if (result < 0) { + return fs_result_from_errno(errno); } - #if defined(FS_HAS_WFOPEN) - { - /* Use _wfopen() on Windows. */ - #if defined(_MSC_VER) && _MSC_VER >= 1400 - { - errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); - if (err != 0) { - return err; - } - } - #else - { - *ppFile = _wfopen(pFilePath, pOpenMode); - if (*ppFile == NULL) { - return errno; - } - } - #endif + (void)pFS; + return FS_SUCCESS; +} + +static fs_result fs_mkdir_posix(fs* pFS, const char* pPath) +{ + int result = mkdir(pPath, S_IRWXU); + if (result < 0) { + return fs_result_from_errno(errno); } - #else - { - /* - Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can - think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for - maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. - */ - mbstate_t mbs; - size_t lenMB; - const wchar_t* pFilePathTemp = pFilePath; - char* pFilePathMB = NULL; - char pOpenModeMB[32] = {0}; - /* Get the length first. */ - FS_ZERO_OBJECT(&mbs); - lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); - if (lenMB == (size_t)-1) { - return errno; - } - - pFilePathMB = (char*)fs_malloc(lenMB + 1, NULL); - if (pFilePathMB == NULL) { - return ENOMEM; - } - - pFilePathTemp = pFilePath; - FS_ZERO_OBJECT(&mbs); - wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); - - /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ - { - size_t i = 0; - for (;;) { - if (pOpenMode[i] == 0) { - pOpenModeMB[i] = '\0'; - break; - } - - pOpenModeMB[i] = (char)pOpenMode[i]; - i += 1; - } - } - - *ppFile = fopen(pFilePathMB, pOpenModeMB); - - fs_free(pFilePathMB, NULL); - - if (*ppFile == NULL) { - return errno; - } - } - #endif - - return 0; + (void)pFS; + return FS_SUCCESS; } -static fs_file_info fs_file_info_from_stat(struct stat* pStat) +static fs_file_info fs_file_info_from_stat_posix(struct stat* pStat) { fs_file_info info; - FS_ZERO_OBJECT(&info); + memset(&info, 0, sizeof(info)); info.size = pStat->st_size; info.lastAccessTime = pStat->st_atime; info.lastModifiedTime = pStat->st_mtime; @@ -4814,415 +5426,183 @@ static fs_file_info fs_file_info_from_stat(struct stat* pStat) return info; } -#if defined(_WIN32) -static fs_uint64 fs_FILETIME_to_unix(const FILETIME* pFT) -{ - ULARGE_INTEGER li; - - li.HighPart = pFT->dwHighDateTime; - li.LowPart = pFT->dwLowDateTime; - - return (fs_uint64)(li.QuadPart / 10000000UL - 11644473600UL); /* Convert from Windows epoch to Unix epoch. */ -} - -static fs_file_info fs_file_info_from_WIN32_FIND_DATAW(const WIN32_FIND_DATAW* pFD) -{ - fs_file_info info; - - FS_ZERO_OBJECT(&info); - info.size = ((fs_uint64)pFD->nFileSizeHigh << 32) | (fs_uint64)pFD->nFileSizeLow; - info.lastModifiedTime = fs_FILETIME_to_unix(&pFD->ftLastWriteTime); - info.lastAccessTime = fs_FILETIME_to_unix(&pFD->ftLastAccessTime); - info.directory = (pFD->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - info.symlink = (pFD->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; - - return info; -} -#endif - - -typedef struct fs_stdio_registered_file -{ - size_t pathLen; - FILE* pFile; -} fs_stdio_registered_file; - -typedef struct fs_stdio -{ - int _unused; -} fs_stdio; - -static size_t fs_alloc_size_stdio(const void* pBackendConfig) -{ - FS_UNUSED(pBackendConfig); - - return sizeof(fs_stdio); -} - -static fs_result fs_init_stdio(fs* pFS, const void* pBackendConfig, fs_stream* pStream) -{ - FS_UNUSED(pFS); - FS_UNUSED(pBackendConfig); - FS_UNUSED(pStream); - - return FS_SUCCESS; -} - -static void fs_uninit_stdio(fs* pFS) -{ - FS_UNUSED(pFS); - return; -} - -static fs_result fs_ioctl_stdio(fs* pFS, int op, void* pArgs) -{ - FS_UNUSED(pFS); - FS_UNUSED(op); - FS_UNUSED(pArgs); - - /* Not used by the stdio backend. */ - return FS_INVALID_OPERATION; -} - -static fs_result fs_remove_stdio(fs* pFS, const char* pFilePath) -{ - int result = remove(pFilePath); - if (result != 0) { - return fs_result_from_errno(errno); - } - - FS_UNUSED(pFS); - - return FS_SUCCESS; -} - -static fs_result fs_rename_stdio(fs* pFS, const char* pOldName, const char* pNewName) -{ - int result = rename(pOldName, pNewName); - if (result != 0) { - return fs_result_from_errno(errno); - } - - FS_UNUSED(pFS); - - return FS_SUCCESS; -} - - -#if defined(_WIN32) -static fs_result fs_mkdir_stdio_win32(const char* pPath) +static fs_result fs_info_posix(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo) { + struct stat info; int result; - /* If it's a drive letter segment just pretend it's successful. */ - if ((pPath[0] >= 'a' && pPath[0] <= 'z') || (pPath[0] >= 'A' && pPath[0] <= 'Z')) { - if (pPath[1] == ':' && pPath[2] == '\0') { - return FS_SUCCESS; - } + /* */ if (pPath == FS_STDIN ) { + result = fstat(STDIN_FILENO, &info); + } else if (pPath == FS_STDOUT) { + result = fstat(STDOUT_FILENO, &info); + } else if (pPath == FS_STDERR) { + result = fstat(STDERR_FILENO, &info); + } else { + result = stat(pPath, &info); } - - result = _mkdir(pPath); + if (result != 0) { return fs_result_from_errno(errno); } + *pInfo = fs_file_info_from_stat_posix(&info); + + (void)pFS; + (void)openMode; return FS_SUCCESS; } -#else -static fs_result fs_mkdir_stdio_posix(const char* pPath) + + +typedef struct fs_file_posix { - int result = mkdir(pPath, S_IRWXU); - if (result != 0) { - return fs_result_from_errno(errno); - } + int fd; + fs_bool32 isStandardHandle; + int openMode; /* The original open mode for duplication purposes. */ + char* pFilePath; /* A copy of the original file path for duplication purposes. */ + char pFilePathStack[128]; + char* pFilePathHeap; +} fs_file_posix; - return FS_SUCCESS; -} -#endif - -static fs_result fs_mkdir_stdio(fs* pFS, const char* pPath) +static size_t fs_file_alloc_size_posix(fs* pFS) { - fs_result result; - - FS_UNUSED(pFS); - -#if defined(_WIN32) - result = fs_mkdir_stdio_win32(pPath); -#else - result = fs_mkdir_stdio_posix(pPath); -#endif - - if (result == FS_DOES_NOT_EXIST) { - result = FS_SUCCESS; - } - - return result; + (void)pFS; + return sizeof(fs_file_posix); } -static fs_result fs_info_stdio(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo) +static fs_result fs_file_open_posix(fs* pFS, fs_stream* pStream, const char* pFilePath, int openMode, fs_file* pFile) { - /* We don't want to use stat() with Win32 because, from what I can tell, there's no way to determine if it's a symbolic link. S_IFLNK does not seem to be defined. */ - #if defined(_WIN32) - { - int pathLen; - wchar_t pPathWStack[1024]; - wchar_t* pPathWHeap = NULL; - wchar_t* pPathW; - HANDLE hFind; - WIN32_FIND_DATAW fd; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); + int fd; + int flags = 0; + size_t filePathLen; - /* Use Win32 to convert from UTF-8 to wchar_t. */ - pathLen = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, NULL, 0); - if (pathLen == 0) { - return fs_result_from_GetLastError(GetLastError()); - } - - if (pathLen <= (int)FS_COUNTOF(pPathWStack)) { - pPathW = pPathWStack; + if ((openMode & FS_READ) != 0) { + if ((openMode & FS_WRITE) != 0) { + flags |= O_RDWR | O_CREAT; } else { - pPathWHeap = (wchar_t*)fs_malloc(pathLen * sizeof(wchar_t), fs_get_allocation_callbacks(pFS)); /* pathLen includes the null terminator. */ - if (pPathWHeap == NULL) { + flags |= O_RDONLY; + } + } else if ((openMode & FS_WRITE) != 0) { + flags |= O_WRONLY | O_CREAT; + } + + if ((openMode & FS_WRITE) != 0) { + if ((openMode & FS_EXCLUSIVE) != 0) { + flags |= O_EXCL; + } else if ((openMode & FS_APPEND) != 0) { + flags |= O_APPEND; + } else if ((openMode & FS_TRUNCATE) != 0) { + flags |= O_TRUNC; + } + } + + + /* For ancient versions of Linux. */ + #if defined(O_LARGEFILE) + flags |= O_LARGEFILE; + #endif + + /* */ if (pFilePath == FS_STDIN ) { + fd = STDIN_FILENO; + pFilePosix->isStandardHandle = FS_TRUE; + } else if (pFilePath == FS_STDOUT) { + fd = STDOUT_FILENO; + pFilePosix->isStandardHandle = FS_TRUE; + } else if (pFilePath == FS_STDERR) { + fd = STDERR_FILENO; + pFilePosix->isStandardHandle = FS_TRUE; + } else { + fd = open(pFilePath, flags, 0666); + } + + if (fd < 0) { + return fs_result_from_errno(errno); + } + + pFilePosix->fd = fd; + + /* + In order to support duplication we need to keep track of the original file path and open modes. Using dup() is + not an option because that results in a shared read/write pointer, whereas we need them to be separate. We need + not do this for standard handles. + */ + if (!pFilePosix->isStandardHandle) { + pFilePosix->openMode = openMode; + + filePathLen = strlen(pFilePath); + + if (filePathLen < FS_COUNTOF(pFilePosix->pFilePathStack)) { + pFilePosix->pFilePath = pFilePosix->pFilePathStack; + } else { + pFilePosix->pFilePathHeap = (char*)fs_malloc(filePathLen + 1, fs_get_allocation_callbacks(pFS)); + if (pFilePosix->pFilePathHeap == NULL) { + close(fd); return FS_OUT_OF_MEMORY; } - pPathW = pPathWHeap; + pFilePosix->pFilePath = pFilePosix->pFilePathHeap; } - MultiByteToWideChar(CP_UTF8, 0, pPath, -1, pPathW, pathLen); - - hFind = FindFirstFileW(pPathW, &fd); - - fs_free(pPathWHeap, fs_get_allocation_callbacks(pFS)); - pPathWHeap = NULL; - - if (hFind == INVALID_HANDLE_VALUE) { - return fs_result_from_errno(GetLastError()); - } - - FindClose(hFind); - hFind = NULL; - - *pInfo = fs_file_info_from_WIN32_FIND_DATAW(&fd); + fs_strcpy(pFilePosix->pFilePath, pFilePath); } - #else - { - struct stat info; - FS_UNUSED(pFS); - - if (stat(pPath, &info) != 0) { - return fs_result_from_errno(errno); - } - - *pInfo = fs_file_info_from_stat(&info); - } - #endif - - (void)openMode; + (void)pFS; + (void)pStream; return FS_SUCCESS; } - -typedef struct fs_file_stdio +static void fs_file_close_posix(fs_file* pFile) { - FILE* pFile; - char openMode[4]; /* For duplication. */ - fs_bool32 isRegisteredOrHandle; /* When set to true, will not be closed with fs_file_close(). */ -} fs_file_stdio; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); -static size_t fs_file_alloc_size_stdio(fs* pFS) -{ - FS_UNUSED(pFS); - return sizeof(fs_file_stdio); -} - -static fs_result fs_file_open_stdio(fs* pFS, fs_stream* pStream, const char* pPath, int openMode, fs_file* pFile) -{ - fs_file_stdio* pFileStdio; - int result; - - FS_UNUSED(pFS); - FS_UNUSED(pStream); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - if (pFileStdio == NULL) { - return FS_INVALID_ARGS; - } - - if ((openMode & FS_WRITE) != 0) { - if ((openMode & FS_READ) != 0) { - /* Read and write. */ - if ((openMode & FS_APPEND) == FS_APPEND) { - pFileStdio->openMode[0] = 'a'; pFileStdio->openMode[1] = '+'; pFileStdio->openMode[2] = 'b'; pFileStdio->openMode[3] = 0; /* Read-and-write, appending. */ - } else if ((openMode & FS_OVERWRITE) == FS_OVERWRITE) { - pFileStdio->openMode[0] = 'r'; pFileStdio->openMode[1] = '+'; pFileStdio->openMode[2] = 'b'; pFileStdio->openMode[3] = 0; /* Read-and-write, overwriting. */ - } else { - pFileStdio->openMode[0] = 'w'; pFileStdio->openMode[1] = '+'; pFileStdio->openMode[2] = 'b'; pFileStdio->openMode[3] = 0; /* Read-and-write, truncating. */ - } - } else { - /* Write-only. */ - if ((openMode & FS_APPEND) == FS_APPEND) { - pFileStdio->openMode[0] = 'a'; pFileStdio->openMode[1] = 'b'; pFileStdio->openMode[2] = 0; /* Write-only, appending. */ - } else if ((openMode & FS_OVERWRITE) == FS_OVERWRITE) { - pFileStdio->openMode[0] = 'r'; pFileStdio->openMode[1] = '+'; pFileStdio->openMode[2] = 'b'; pFileStdio->openMode[3] = 0; /* Write-only, overwriting. Need to use the "+" option here because there does not appear to be an option for a write-only overwrite mode. */ - } else { - pFileStdio->openMode[0] = 'w'; pFileStdio->openMode[1] = 'b'; pFileStdio->openMode[2] = 0; /* Write-only, truncating. */ - } - } - } else { - if ((openMode & FS_READ) != 0) { - pFileStdio->openMode[0] = 'r'; pFileStdio->openMode[1] = 'b'; pFileStdio->openMode[2] = 0; /* Read-only. */ - } else { - return FS_INVALID_ARGS; - } - } - - #if defined(_WIN32) && defined(FS_HAS_WFOPEN) - { - size_t i; - int pathLen; - wchar_t pOpenModeW[4]; - wchar_t pFilePathWStack[1024]; - wchar_t* pFilePathWHeap = NULL; - wchar_t* pFilePathW; - - /* Use Win32 to convert from UTF-8 to wchar_t. */ - pathLen = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, NULL, 0); - if (pathLen > 0) { - if (pathLen <= (int)FS_COUNTOF(pFilePathWStack)) { - pFilePathW = pFilePathWStack; - } else { - pFilePathWHeap = (wchar_t*)fs_malloc(pathLen * sizeof(wchar_t), fs_get_allocation_callbacks(pFS)); - if (pFilePathWHeap == NULL) { - return FS_OUT_OF_MEMORY; - } - - pFilePathW = pFilePathWHeap; - } - - MultiByteToWideChar(CP_UTF8, 0, pPath, -1, pFilePathW, pathLen); - - for (i = 0; i < FS_COUNTOF(pOpenModeW); i += 1) { - pOpenModeW[i] = (wchar_t)pFileStdio->openMode[i]; - } - - result = fs_wfopen(&pFileStdio->pFile, pFilePathW, pOpenModeW); - - fs_free(pFilePathWHeap, fs_get_allocation_callbacks(pFS)); - pFilePathWHeap = NULL; - - if (result == 0) { - return FS_SUCCESS; - } - } - } - #endif - - /* Getting here means we're either not opening with wfopen(), or wfopen() failed (or the conversion from char to wchar_t). */ - result = fs_fopen(&pFileStdio->pFile, pPath, pFileStdio->openMode); - if (result != 0) { - return fs_result_from_errno(result); - } - - return FS_SUCCESS; -} - -static fs_result fs_file_open_handle_stdio(fs* pFS, void* hBackendFile, fs_file* pFile) -{ - fs_file_stdio* pFileStdio; - - FS_UNUSED(pFS); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - if (pFileStdio == NULL) { - return FS_INVALID_ARGS; - } - - pFileStdio->pFile = (FILE*)hBackendFile; - pFileStdio->isRegisteredOrHandle = FS_TRUE; - - return FS_SUCCESS; -} - -static void fs_file_close_stdio(fs_file* pFile) -{ - fs_file_stdio* pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - if (pFileStdio == NULL) { + /* No need to do anything if the file was opened from stdin, stdout, or stderr. */ + if (pFilePosix->isStandardHandle) { return; } - if (!pFileStdio->isRegisteredOrHandle) { - fclose(pFileStdio->pFile); - } + close(pFilePosix->fd); + fs_free(pFilePosix->pFilePathHeap, fs_get_allocation_callbacks(fs_file_get_fs(pFile))); } -static fs_result fs_file_read_stdio(fs_file* pFile, void* pDst, size_t bytesToRead, size_t* pBytesRead) +static fs_result fs_file_read_posix(fs_file* pFile, void* pDst, size_t bytesToRead, size_t* pBytesRead) { - size_t bytesRead; - fs_file_stdio* pFileStdio; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); + ssize_t bytesRead; - /* These were all validated at a higher level. */ - FS_ASSERT(pFile != NULL); - FS_ASSERT(pDst != NULL); - FS_ASSERT(pBytesRead != NULL); + bytesRead = read(pFilePosix->fd, pDst, bytesToRead); + if (bytesRead < 0) { + return fs_result_from_errno(errno); + } - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); + *pBytesRead = (size_t)bytesRead; - bytesRead = fread(pDst, 1, bytesToRead, pFileStdio->pFile); - - *pBytesRead = bytesRead; - - /* If the value returned by fread is less than the bytes requested, it was either EOF or an error. We don't return EOF unless the number of bytes read is 0. */ - if (bytesRead != bytesToRead) { - if (feof(pFileStdio->pFile)) { - if (bytesRead == 0) { - return FS_AT_END; - } - } else { - return fs_result_from_errno(ferror(pFileStdio->pFile)); - } + if (*pBytesRead == 0) { + return FS_AT_END; } return FS_SUCCESS; } -static fs_result fs_file_write_stdio(fs_file* pFile, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten) +static fs_result fs_file_write_posix(fs_file* pFile, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten) { - size_t bytesWritten; - fs_file_stdio* pFileStdio; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); + ssize_t bytesWritten; - /* These were all validated at a higher level. */ - FS_ASSERT(pFile != NULL); - FS_ASSERT(pSrc != NULL); - FS_ASSERT(pBytesWritten != NULL); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); - - bytesWritten = fwrite(pSrc, 1, bytesToWrite, pFileStdio->pFile); - - *pBytesWritten = bytesWritten; - - if (bytesWritten != bytesToWrite) { - return fs_result_from_errno(ferror(pFileStdio->pFile)); + bytesWritten = write(pFilePosix->fd, pSrc, bytesToWrite); + if (bytesWritten < 0) { + return fs_result_from_errno(errno); } + *pBytesWritten = (size_t)bytesWritten; return FS_SUCCESS; } -static fs_result fs_file_seek_stdio(fs_file* pFile, fs_int64 offset, fs_seek_origin origin) +static fs_result fs_file_seek_posix(fs_file* pFile, fs_int64 offset, fs_seek_origin origin) { - fs_file_stdio* pFileStdio; - int result; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); int whence; - - /* These were all validated at a higher level. */ - FS_ASSERT(pFile != NULL); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); + off_t result; if (origin == FS_SEEK_SET) { whence = SEEK_SET; @@ -5232,359 +5612,175 @@ static fs_result fs_file_seek_stdio(fs_file* pFile, fs_int64 offset, fs_seek_ori whence = SEEK_CUR; } -#if defined(_WIN32) - #if defined(_MSC_VER) && _MSC_VER > 1200 - result = _fseeki64(pFileStdio->pFile, offset, whence); + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 + { + result = lseek(pFilePosix->fd, (off_t)offset, whence); + } #else - /* No _fseeki64() so restrict to 31 bits. */ - if (origin > 0x7FFFFFFF) { - return FS_OUT_OF_RANGE; + { + if (offset < -2147483648 || offset > 2147483647) { + return FS_BAD_SEEK; /* Offset is too large. */ } - result = fseek(pFileStdio->pFile, (int)offset, whence); + result = lseek(pFilePosix->fd, (off_t)(int)offset, whence); + } #endif -#else - result = fseek(pFileStdio->pFile, (long int)offset, whence); -#endif - if (result != 0) { + + if (result < 0) { return fs_result_from_errno(errno); } return FS_SUCCESS; } -static fs_result fs_file_tell_stdio(fs_file* pFile, fs_int64* pCursor) +static fs_result fs_file_tell_posix(fs_file* pFile, fs_int64* pCursor) { - fs_file_stdio* pFileStdio; - fs_int64 result; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); + fs_int64 cursor; - /* These were all validated at a higher level. */ - FS_ASSERT(pFile != NULL); - FS_ASSERT(pCursor != NULL); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); - -#if defined(_WIN32) - #if defined(_MSC_VER) && _MSC_VER > 1200 - result = _ftelli64(pFileStdio->pFile); - #else - result = ftell(pFileStdio->pFile); - #endif -#else - result = ftell(pFileStdio->pFile); -#endif - - *pCursor = result; + cursor = (fs_int64)lseek(pFilePosix->fd, 0, SEEK_CUR); + if (cursor < 0) { + return fs_result_from_errno(errno); + } + *pCursor = cursor; return FS_SUCCESS; } -static fs_result fs_file_flush_stdio(fs_file* pFile) +static fs_result fs_file_flush_posix(fs_file* pFile) { - fs_file_stdio* pFileStdio; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); int result; - /* These were all validated at a higher level. */ - FS_ASSERT(pFile != NULL); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); - - result = fflush(pFileStdio->pFile); - if (result != 0) { - return fs_result_from_errno(ferror(pFileStdio->pFile)); + result = fsync(pFilePosix->fd); + if (result < 0) { + return fs_result_from_errno(errno); } return FS_SUCCESS; } -/* Please submit a bug report if you get an error about fileno(). */ -#if !defined(_MSC_VER) && !((defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)) && !(defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) -int fileno(FILE *stream); +/* +The availability of ftruncate() is annoyingly tricky because it is not available with `-std=c89` unless the +application opts into it by defining _POSIX_C_SOURCE or _XOPEN_SOURCE +*/ +#if !defined(FS_HAS_FTRUNCATE) && (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500) /* Opted in by the application via a feature macro. */ + #define FS_HAS_FTRUNCATE +#endif +#if !defined(FS_HAS_FTRUNCATE) && !defined(__STRICT_ANSI__) /* Not using strict ANSI. Assume available. Might need to massage this later. */ + #define FS_HAS_FTRUNCATE #endif -static fs_result fs_file_info_stdio(fs_file* pFile, fs_file_info* pInfo) +static fs_result fs_file_truncate_posix(fs_file* pFile) { - fs_file_stdio* pFileStdio; - int fd; + #if defined(FS_HAS_FTRUNCATE) + { + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); + off_t currentPos; + + /* Our truncation is based on the current write position. */ + currentPos = lseek(pFilePosix->fd, 0, SEEK_CUR); + if (currentPos < 0) { + return fs_result_from_errno(errno); + } + + if (ftruncate(pFilePosix->fd, currentPos) < 0) { + return fs_result_from_errno(errno); + } + + return FS_SUCCESS; + } + #else + { + (void)pFile; + return FS_NOT_IMPLEMENTED; + } + #endif +} + +static fs_result fs_file_info_posix(fs_file* pFile, fs_file_info* pInfo) +{ + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); struct stat info; - /* These were all validated at a higher level. */ - FS_ASSERT(pFile != NULL); - FS_ASSERT(pInfo != NULL); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); - -#if defined(_MSC_VER) - fd = _fileno(pFileStdio->pFile); -#else - fd = fileno(pFileStdio->pFile); -#endif - - if (fstat(fd, &info) != 0) { - return fs_result_from_errno(ferror(pFileStdio->pFile)); + if (fstat(pFilePosix->fd, &info) < 0) { + return fs_result_from_errno(errno); } - *pInfo = fs_file_info_from_stat(&info); + *pInfo = fs_file_info_from_stat_posix(&info); return FS_SUCCESS; } -/* Iteration is platform-specific. */ -#define FS_STDIO_MIN_ITERATOR_ALLOCATION_SIZE 1024 - -#if defined(_WIN32) -#include -#include - -FS_API fs_result fs_file_duplicate_stdio(fs_file* pFile, fs_file* pDuplicatedFile) +static fs_result fs_file_duplicate_posix(fs_file* pFile, fs_file* pDuplicate) { - fs_file_stdio* pFileStdio; - fs_file_stdio* pDuplicatedFileStdio; - int fd; - int fdDuplicate; - HANDLE hFile; - HANDLE hFileDuplicate; + fs_file_posix* pFilePosix = (fs_file_posix*)fs_file_get_backend_data(pFile); + fs_file_posix* pDuplicatePosix = (fs_file_posix*)fs_file_get_backend_data(pDuplicate); + fs_result result; + struct stat st1, st2; - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); + /* Simple case for standard handles. */ + if (pFilePosix->isStandardHandle) { + pDuplicatePosix->fd = pFilePosix->fd; + pDuplicatePosix->isStandardHandle = FS_TRUE; - pDuplicatedFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pDuplicatedFile); - FS_ASSERT(pDuplicatedFileStdio != NULL); - - fd = _fileno(pFileStdio->pFile); - if (fd == -1) { - return fs_result_from_errno(errno); + return FS_SUCCESS; } - hFile = (HANDLE)_get_osfhandle(fd); - if (hFile == INVALID_HANDLE_VALUE) { - return fs_result_from_errno(errno); - } - - if (!DuplicateHandle(GetCurrentProcess(), hFile, GetCurrentProcess(), &hFileDuplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)) { - return fs_result_from_GetLastError(GetLastError()); - } - - fdDuplicate = _open_osfhandle((fs_intptr)hFileDuplicate, _O_RDONLY); - if (fdDuplicate == -1) { - CloseHandle(hFileDuplicate); - return fs_result_from_errno(errno); - } - - pDuplicatedFileStdio->pFile = _fdopen(fdDuplicate, pFileStdio->openMode); - if (pDuplicatedFileStdio->pFile == NULL) { - _close(fdDuplicate); - return fs_result_from_errno(errno); - } - - return FS_SUCCESS; -} - - -typedef struct fs_iterator_stdio -{ - fs_iterator iterator; - HANDLE hFind; -} fs_iterator_stdio; - -FS_API void fs_free_iterator_stdio(fs_iterator* pIterator) -{ - fs_iterator_stdio* pIteratorStdio = (fs_iterator_stdio*)pIterator; - - FindClose(pIteratorStdio->hFind); - fs_free(pIteratorStdio, fs_get_allocation_callbacks(pIterator->pFS)); -} - -static fs_iterator* fs_iterator_stdio_resolve(fs_iterator_stdio* pIteratorStdio, fs* pFS, HANDLE hFind, const WIN32_FIND_DATAW* pFD) -{ - fs_iterator_stdio* pNewIteratorStdio; - size_t allocSize; - int nameLen; - /* - The name is stored at the end of the struct. In order to know how much memory to allocate we'll - need to calculate the length of the name. + We cannot duplicate the handle with dup() because that will result in a shared read/write pointer. We + need to open the file again with the same path and flags. We're not going to allow duplication of files + that were opened in write mode. */ - nameLen = WideCharToMultiByte(CP_UTF8, 0, pFD->cFileName, -1, NULL, 0, NULL, NULL); - if (nameLen == 0) { - fs_free_iterator_stdio((fs_iterator*)pIteratorStdio); - return NULL; + if ((pFilePosix->openMode & FS_WRITE) != 0) { + return FS_INVALID_OPERATION; } - allocSize = FS_MAX(sizeof(fs_iterator_stdio) + nameLen, FS_STDIO_MIN_ITERATOR_ALLOCATION_SIZE); /* "nameLen" includes the null terminator. 1KB just to try to avoid excessive internal reallocations inside realloc(). */ - - pNewIteratorStdio = (fs_iterator_stdio*)fs_realloc(pIteratorStdio, allocSize, fs_get_allocation_callbacks(pFS)); - if (pNewIteratorStdio == NULL) { - fs_free_iterator_stdio((fs_iterator*)pIteratorStdio); - return NULL; + result = fs_file_open_posix(fs_file_get_fs(pFile), NULL, pFilePosix->pFilePath, pFilePosix->openMode, pDuplicate); + if (result != FS_SUCCESS) { + return result; } - pNewIteratorStdio->iterator.pFS = pFS; - pNewIteratorStdio->hFind = hFind; - - /* Name. */ - pNewIteratorStdio->iterator.pName = (char*)pNewIteratorStdio + sizeof(fs_iterator_stdio); - pNewIteratorStdio->iterator.nameLen = (size_t)nameLen - 1; /* nameLen includes the null terminator. */ - WideCharToMultiByte(CP_UTF8, 0, pFD->cFileName, -1, (char*)pNewIteratorStdio->iterator.pName, nameLen, NULL, NULL); /* const-cast is safe here. */ - - /* Info. */ - pNewIteratorStdio->iterator.info = fs_file_info_from_WIN32_FIND_DATAW(pFD); - - return (fs_iterator*)pNewIteratorStdio; -} - -FS_API fs_iterator* fs_first_stdio(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen) -{ - size_t i; - int queryLen; - int cbMultiByte; - wchar_t pQueryStack[1024]; - wchar_t* pQueryHeap = NULL; - wchar_t* pQuery; - HANDLE hFind; - WIN32_FIND_DATAW fd; - - /* An empty path means the current directory. Win32 will want us to specify "." in this case. */ - if (pDirectoryPath == NULL || pDirectoryPath[0] == '\0') { - pDirectoryPath = "."; - directoryPathLen = 1; - } - - if (directoryPathLen == FS_NULL_TERMINATED) { - cbMultiByte = -1; - } else { - if (directoryPathLen > 0xFFFFFFFF) { - return NULL; - } - - cbMultiByte = (int)directoryPathLen; - } - - /* When iterating over files using Win32 you specify a wildcard pattern. The "+ 3" you see in the code below is for the wildcard pattern. We also need to make everything a backslash. */ - queryLen = MultiByteToWideChar(CP_UTF8, 0, pDirectoryPath, cbMultiByte, NULL, 0); - if (queryLen == 0) { - return NULL; - } - - if ((queryLen + 3) > (int)FS_COUNTOF(pQueryStack)) { - pQueryHeap = (wchar_t*)fs_malloc((queryLen + 3) * sizeof(wchar_t), fs_get_allocation_callbacks(pFS)); - if (pQueryHeap == NULL) { - return NULL; - } - - pQuery = pQueryHeap; - } - else { - pQuery = pQueryStack; - } - - MultiByteToWideChar(CP_UTF8, 0, pDirectoryPath, cbMultiByte, pQuery, queryLen); - - if (directoryPathLen == FS_NULL_TERMINATED) { - queryLen -= 1; /* Remove the null terminator. Will not include the null terminator if the input string is not null terminated, hence why this is inside the conditional. */ - } - - /* Remove the trailing slash, if any. */ - if (pQuery[queryLen - 1] == L'\\' || pQuery[queryLen - 1] == L'/') { - queryLen -= 1; - } - - pQuery[queryLen + 0] = L'\\'; - pQuery[queryLen + 1] = L'*'; - pQuery[queryLen + 2] = L'\0'; - - /* Convert to backslashes. */ - for (i = 0; i < (size_t)queryLen; i += 1) { - if (pQuery[i] == L'/') { - pQuery[i] = L'\\'; - } - } - - hFind = FindFirstFileW(pQuery, &fd); - fs_free(pQueryHeap, fs_get_allocation_callbacks(pFS)); - - if (hFind == INVALID_HANDLE_VALUE) { - return NULL; - } - - return fs_iterator_stdio_resolve(NULL, pFS, hFind, &fd); -} - -FS_API fs_iterator* fs_next_stdio(fs_iterator* pIterator) -{ - fs_iterator_stdio* pIteratorStdio = (fs_iterator_stdio*)pIterator; - WIN32_FIND_DATAW fd; - - if (!FindNextFileW(pIteratorStdio->hFind, &fd)) { - fs_free_iterator_stdio(pIterator); - return NULL; - } - - return fs_iterator_stdio_resolve(pIteratorStdio, pIterator->pFS, pIteratorStdio->hFind, &fd); -} -#else -#include -#include - -FS_API fs_result fs_file_duplicate_stdio(fs_file* pFile, fs_file* pDuplicatedFile) -{ - fs_file_stdio* pFileStdio; - fs_file_stdio* pDuplicatedFileStdio; - FILE* pDuplicatedFileHandle; - - /* These were all validated at a higher level. */ - FS_ASSERT(pFile != NULL); - FS_ASSERT(pDuplicatedFile != NULL); - - pFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pFile); - FS_ASSERT(pFileStdio != NULL); - - pDuplicatedFileStdio = (fs_file_stdio*)fs_file_get_backend_data(pDuplicatedFile); - FS_ASSERT(pDuplicatedFileStdio != NULL); - - pDuplicatedFileHandle = fdopen(dup(fileno(pFileStdio->pFile)), pFileStdio->openMode); - if (pDuplicatedFileHandle == NULL) { + /* Do a quick check that it's still pointing to the same file. */ + if (fstat(pFilePosix->fd, &st1) < 0) { + fs_file_close_posix(pDuplicate); return fs_result_from_errno(errno); } - pDuplicatedFileStdio->pFile = pDuplicatedFileHandle; - FS_COPY_MEMORY(pDuplicatedFileStdio->openMode, pFileStdio->openMode, sizeof(pFileStdio->openMode)); + if (fstat(pDuplicatePosix->fd, &st2) < 0) { + fs_file_close_posix(pDuplicate); + return fs_result_from_errno(errno); + } + + if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) { + fs_file_close_posix(pDuplicate); + return FS_INVALID_OPERATION; /* It looks like the files have changed. */ + } return FS_SUCCESS; } -typedef struct fs_iterator_stdio +#define FS_POSIX_MIN_ITERATOR_ALLOCATION_SIZE 1024 + +typedef struct fs_iterator_posix { fs_iterator iterator; DIR* pDir; char* pFullFilePath; /* Points to the end of the structure. */ size_t directoryPathLen; /* The length of the directory section. */ -} fs_iterator_stdio; +} fs_iterator_posix; -FS_API void fs_free_iterator_stdio(fs_iterator* pIterator) +static void fs_free_iterator_posix(fs_iterator* pIterator); + +static fs_iterator* fs_first_posix(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen) { - fs_iterator_stdio* pIteratorStdio = (fs_iterator_stdio*)pIterator; - - FS_ASSERT(pIteratorStdio != NULL); - - closedir(pIteratorStdio->pDir); - fs_free(pIteratorStdio, fs_get_allocation_callbacks(pIterator->pFS)); -} - -FS_API fs_iterator* fs_first_stdio(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen) -{ - fs_iterator_stdio* pIteratorStdio; + fs_iterator_posix* pIteratorPosix; struct dirent* info; struct stat statInfo; size_t fileNameLen; - FS_ASSERT(pDirectoryPath != NULL); - /* Our input string isn't necessarily null terminated so we'll need to make a copy. This isn't the end of the world because we need to keep a copy of it anyway for when we need to stat @@ -5612,30 +5808,30 @@ FS_API fs_iterator* fs_first_stdio(fs* pFS, const char* pDirectoryPath, size_t d Now that we know the length of the directory we can allocate space for the iterator. The directory path will be placed at the end of the structure. */ - pIteratorStdio = (fs_iterator_stdio*)fs_malloc(FS_MAX(sizeof(*pIteratorStdio) + directoryPathLen + 1, FS_STDIO_MIN_ITERATOR_ALLOCATION_SIZE), fs_get_allocation_callbacks(pFS)); /* +1 for null terminator. */ - if (pIteratorStdio == NULL) { + pIteratorPosix = (fs_iterator_posix*)fs_malloc(FS_MAX(sizeof(*pIteratorPosix) + directoryPathLen + 1, FS_POSIX_MIN_ITERATOR_ALLOCATION_SIZE), fs_get_allocation_callbacks(pFS)); /* +1 for null terminator. */ + if (pIteratorPosix == NULL) { return NULL; } /* Point pFullFilePath to the end of structure to where the path is located. */ - pIteratorStdio->pFullFilePath = (char*)pIteratorStdio + sizeof(*pIteratorStdio); - pIteratorStdio->directoryPathLen = directoryPathLen; + pIteratorPosix->pFullFilePath = (char*)pIteratorPosix + sizeof(*pIteratorPosix); + pIteratorPosix->directoryPathLen = directoryPathLen; /* We can now copy over the directory path. This will null terminate the path which will allow us to call opendir(). */ - fs_strncpy_s(pIteratorStdio->pFullFilePath, directoryPathLen + 1, pDirectoryPath, directoryPathLen); + fs_strncpy_s(pIteratorPosix->pFullFilePath, directoryPathLen + 1, pDirectoryPath, directoryPathLen); /* We can now open the directory. */ - pIteratorStdio->pDir = opendir(pIteratorStdio->pFullFilePath); - if (pIteratorStdio->pDir == NULL) { - fs_free(pIteratorStdio, fs_get_allocation_callbacks(pFS)); + pIteratorPosix->pDir = opendir(pIteratorPosix->pFullFilePath); + if (pIteratorPosix->pDir == NULL) { + fs_free(pIteratorPosix, fs_get_allocation_callbacks(pFS)); return NULL; } /* We now need to get information about the first file. */ - info = readdir(pIteratorStdio->pDir); + info = readdir(pIteratorPosix->pDir); if (info == NULL) { - closedir(pIteratorStdio->pDir); - fs_free(pIteratorStdio, fs_get_allocation_callbacks(pFS)); + closedir(pIteratorPosix->pDir); + fs_free(pIteratorPosix, fs_get_allocation_callbacks(pFS)); return NULL; } @@ -5647,50 +5843,48 @@ FS_API fs_iterator* fs_first_stdio(fs* pFS, const char* pDirectoryPath, size_t d separating slash. */ { - fs_iterator_stdio* pNewIteratorStdio= (fs_iterator_stdio*)fs_realloc(pIteratorStdio, FS_MAX(sizeof(*pIteratorStdio) + directoryPathLen + 1 + fileNameLen + 1, FS_STDIO_MIN_ITERATOR_ALLOCATION_SIZE), fs_get_allocation_callbacks(pFS)); /* +1 for null terminator. */ - if (pNewIteratorStdio == NULL) { - closedir(pIteratorStdio->pDir); - fs_free(pIteratorStdio, fs_get_allocation_callbacks(pFS)); + fs_iterator_posix* pNewIteratorPosix= (fs_iterator_posix*)fs_realloc(pIteratorPosix, FS_MAX(sizeof(*pIteratorPosix) + directoryPathLen + 1 + fileNameLen + 1, FS_POSIX_MIN_ITERATOR_ALLOCATION_SIZE), fs_get_allocation_callbacks(pFS)); /* +1 for null terminator. */ + if (pNewIteratorPosix == NULL) { + closedir(pIteratorPosix->pDir); + fs_free(pIteratorPosix, fs_get_allocation_callbacks(pFS)); return NULL; } - pIteratorStdio = pNewIteratorStdio; + pIteratorPosix = pNewIteratorPosix; } /* Memory has been allocated. Copy over the separating slash and file name. */ - pIteratorStdio->pFullFilePath = (char*)pIteratorStdio + sizeof(*pIteratorStdio); - pIteratorStdio->pFullFilePath[directoryPathLen] = '/'; - fs_strcpy(pIteratorStdio->pFullFilePath + directoryPathLen + 1, info->d_name); + pIteratorPosix->pFullFilePath = (char*)pIteratorPosix + sizeof(*pIteratorPosix); + pIteratorPosix->pFullFilePath[directoryPathLen] = '/'; + fs_strcpy(pIteratorPosix->pFullFilePath + directoryPathLen + 1, info->d_name); /* The pFileName member of the base iterator needs to be set to the file name. */ - pIteratorStdio->iterator.pName = pIteratorStdio->pFullFilePath + directoryPathLen + 1; - pIteratorStdio->iterator.nameLen = fileNameLen; + pIteratorPosix->iterator.pName = pIteratorPosix->pFullFilePath + directoryPathLen + 1; + pIteratorPosix->iterator.nameLen = fileNameLen; /* We can now get the file information. */ - if (stat(pIteratorStdio->pFullFilePath, &statInfo) != 0) { - closedir(pIteratorStdio->pDir); - fs_free(pIteratorStdio, fs_get_allocation_callbacks(pFS)); + if (stat(pIteratorPosix->pFullFilePath, &statInfo) != 0) { + closedir(pIteratorPosix->pDir); + fs_free(pIteratorPosix, fs_get_allocation_callbacks(pFS)); return NULL; } - pIteratorStdio->iterator.info = fs_file_info_from_stat(&statInfo); + pIteratorPosix->iterator.info = fs_file_info_from_stat_posix(&statInfo); - return (fs_iterator*)pIteratorStdio; + return (fs_iterator*)pIteratorPosix; } -FS_API fs_iterator* fs_next_stdio(fs_iterator* pIterator) +static fs_iterator* fs_next_posix(fs_iterator* pIterator) { - fs_iterator_stdio* pIteratorStdio = (fs_iterator_stdio*)pIterator; + fs_iterator_posix* pIteratorPosix = (fs_iterator_posix*)pIterator; struct dirent* info; struct stat statInfo; size_t fileNameLen; - FS_ASSERT(pIteratorStdio != NULL); - /* We need to get information about the next file. */ - info = readdir(pIteratorStdio->pDir); + info = readdir(pIteratorPosix->pDir); if (info == NULL) { - fs_free_iterator_stdio((fs_iterator*)pIteratorStdio); + fs_free_iterator_posix((fs_iterator*)pIteratorPosix); return NULL; /* The end of the directory. */ } @@ -5698,64 +5892,981 @@ FS_API fs_iterator* fs_next_stdio(fs_iterator* pIterator) /* We need to reallocate the iterator to account for the new file name. */ { - fs_iterator_stdio* pNewIteratorStdio = (fs_iterator_stdio*)fs_realloc(pIteratorStdio, FS_MAX(sizeof(*pIteratorStdio) + pIteratorStdio->directoryPathLen + 1 + fileNameLen + 1, FS_STDIO_MIN_ITERATOR_ALLOCATION_SIZE), fs_get_allocation_callbacks(pIterator->pFS)); /* +1 for null terminator. */ - if (pNewIteratorStdio == NULL) { - fs_free_iterator_stdio((fs_iterator*)pIteratorStdio); + fs_iterator_posix* pNewIteratorPosix = (fs_iterator_posix*)fs_realloc(pIteratorPosix, FS_MAX(sizeof(*pIteratorPosix) + pIteratorPosix->directoryPathLen + 1 + fileNameLen + 1, FS_POSIX_MIN_ITERATOR_ALLOCATION_SIZE), fs_get_allocation_callbacks(pIterator->pFS)); /* +1 for null terminator. */ + if (pNewIteratorPosix == NULL) { + fs_free_iterator_posix((fs_iterator*)pIteratorPosix); return NULL; } - pIteratorStdio = pNewIteratorStdio; + pIteratorPosix = pNewIteratorPosix; } /* Memory has been allocated. Copy over the file name. */ - pIteratorStdio->pFullFilePath = (char*)pIteratorStdio + sizeof(*pIteratorStdio); - fs_strcpy(pIteratorStdio->pFullFilePath + pIteratorStdio->directoryPathLen + 1, info->d_name); + pIteratorPosix->pFullFilePath = (char*)pIteratorPosix + sizeof(*pIteratorPosix); + fs_strcpy(pIteratorPosix->pFullFilePath + pIteratorPosix->directoryPathLen + 1, info->d_name); /* The pFileName member of the base iterator needs to be set to the file name. */ - pIteratorStdio->iterator.pName = pIteratorStdio->pFullFilePath + pIteratorStdio->directoryPathLen + 1; - pIteratorStdio->iterator.nameLen = fileNameLen; + pIteratorPosix->iterator.pName = pIteratorPosix->pFullFilePath + pIteratorPosix->directoryPathLen + 1; + pIteratorPosix->iterator.nameLen = fileNameLen; /* We can now get the file information. */ - if (stat(pIteratorStdio->pFullFilePath, &statInfo) != 0) { - fs_free_iterator_stdio((fs_iterator*)pIteratorStdio); + if (stat(pIteratorPosix->pFullFilePath, &statInfo) != 0) { + fs_free_iterator_posix((fs_iterator*)pIteratorPosix); return NULL; } - pIteratorStdio->iterator.info = fs_file_info_from_stat(&statInfo); + pIteratorPosix->iterator.info = fs_file_info_from_stat_posix(&statInfo); - return (fs_iterator*)pIteratorStdio; + return (fs_iterator*)pIteratorPosix; } + +static void fs_free_iterator_posix(fs_iterator* pIterator) +{ + fs_iterator_posix* pIteratorPosix = (fs_iterator_posix*)pIterator; + + closedir(pIteratorPosix->pDir); + fs_free(pIteratorPosix, fs_get_allocation_callbacks(pIterator->pFS)); +} + +static fs_backend fs_posix_backend = +{ + fs_alloc_size_posix, + fs_init_posix, + fs_uninit_posix, + fs_ioctl_posix, + fs_remove_posix, + fs_rename_posix, + fs_mkdir_posix, + fs_info_posix, + fs_file_alloc_size_posix, + fs_file_open_posix, + fs_file_close_posix, + fs_file_read_posix, + fs_file_write_posix, + fs_file_seek_posix, + fs_file_tell_posix, + fs_file_flush_posix, + fs_file_truncate_posix, + fs_file_info_posix, + fs_file_duplicate_posix, + fs_first_posix, + fs_next_posix, + fs_free_iterator_posix +}; + +const fs_backend* FS_BACKEND_POSIX = &fs_posix_backend; +#else +const fs_backend* FS_BACKEND_POSIX = NULL; +#endif +/* END fs_backend_posix.c */ + + +/* BEG fs_backend_win32.c */ +#if defined(_WIN32) + #define FS_SUPPORTS_WIN32 #endif -fs_backend fs_stdio_backend = -{ - fs_alloc_size_stdio, - fs_init_stdio, - fs_uninit_stdio, - fs_ioctl_stdio, - fs_remove_stdio, - fs_rename_stdio, - fs_mkdir_stdio, - fs_info_stdio, - fs_file_alloc_size_stdio, - fs_file_open_stdio, - fs_file_open_handle_stdio, - fs_file_close_stdio, - fs_file_read_stdio, - fs_file_write_stdio, - fs_file_seek_stdio, - fs_file_tell_stdio, - fs_file_flush_stdio, - fs_file_info_stdio, - fs_file_duplicate_stdio, - fs_first_stdio, - fs_next_stdio, - fs_free_iterator_stdio -}; -const fs_backend* FS_STDIO = &fs_stdio_backend; -#else -const fs_backend* FS_STDIO = NULL; +#if !defined(FS_NO_WIN32) && defined(FS_SUPPORTS_WIN32) + #define FS_HAS_WIN32 #endif + +#if defined(_WIN32) +#include + +#if defined(UNICODE) || defined(_UNICODE) +#define fs_win32_char wchar_t +#else +#define fs_win32_char char +#endif + +typedef struct +{ + size_t len; + fs_win32_char* path; + fs_win32_char pathStack[256]; + fs_win32_char* pathHeap; +} fs_win32_path; + +static void fs_win32_path_init_internal(fs_win32_path* pPath) +{ + pPath->len = 0; + pPath->path = pPath->pathStack; + pPath->pathStack[0] = '\0'; + pPath->pathHeap = NULL; +} + +static fs_result fs_win32_path_init(fs_win32_path* pPath, const char* pPathUTF8, size_t pathUTF8Len, const fs_allocation_callbacks* pAllocationCallbacks) +{ + size_t i; + + fs_win32_path_init_internal(pPath); + + #if defined(UNICODE) || defined(_UNICODE) + { + int wideCharLen; + int cbMultiByte; + + if (pathUTF8Len == (size_t)-1) { + cbMultiByte = (int)-1; + } else { + cbMultiByte = (int)pathUTF8Len + 1; + } + + wideCharLen = MultiByteToWideChar(CP_UTF8, 0, pPathUTF8, cbMultiByte, NULL, 0); + if (wideCharLen == 0) { + return FS_ERROR; + } + + /* Use the stack if possible. If not, allocate on the heap. */ + if (wideCharLen <= (int)FS_COUNTOF(pPath->pathStack)) { + pPath->path = pPath->pathStack; + } else { + pPath->pathHeap = (fs_win32_char*)fs_malloc(sizeof(fs_win32_char) * wideCharLen, pAllocationCallbacks); + if (pPath->pathHeap == NULL) { + return FS_OUT_OF_MEMORY; + } + + pPath->path = pPath->pathHeap; + } + + MultiByteToWideChar(CP_UTF8, 0, pPathUTF8, cbMultiByte, pPath->path, wideCharLen); + pPath->len = wideCharLen - 1; /* The count returned by MultiByteToWideChar() includes the null terminator, so subtract 1 to compensate. */ + + /* Convert forward slashes to back slashes for compatibility. */ + for (i = 0; i < pPath->len; i += 1) { + if (pPath->path[i] == '/') { + pPath->path[i] = '\\'; + } + } + + return FS_SUCCESS; + } + #else + { + /* + Not doing any conversion here. Just assuming the path is an ANSI path. We need to copy over the string + and convert slashes to backslashes. + */ + if (pathUTF8Len == (size_t)-1) { + pPath->len = strlen(pPathUTF8); + } else { + pPath->len = pathUTF8Len; + } + + if (pPath->len >= sizeof(pPath->pathStack)) { + pPath->pathHeap = (fs_win32_char*)fs_malloc(sizeof(fs_win32_char) * (pPath->len + 1), pAllocationCallbacks); + if (pPath->pathHeap == NULL) { + return FS_OUT_OF_MEMORY; + } + + pPath->path = pPath->pathHeap; + } + + fs_strcpy(pPath->path, pPathUTF8); + for (i = 0; i < pPath->len; i += 1) { + if (pPath->path[i] == '/') { + pPath->path[i] = '\\'; + } + } + + return FS_SUCCESS; + } + #endif +} + + + +static fs_result fs_win32_path_append(fs_win32_path* pPath, const char* pAppendUTF8, const fs_allocation_callbacks* pAllocationCallbacks) +{ + fs_result result; + fs_win32_path append; + size_t newLen; + + result = fs_win32_path_init(&append, pAppendUTF8, (size_t)-1, pAllocationCallbacks); + if (result != FS_SUCCESS) { + return result; + } + + newLen = pPath->len + append.len; + + if (pPath->path == pPath->pathHeap) { + /* It's on the heap. Just realloc. */ + fs_win32_char* pNewHeap = (fs_win32_char*)fs_realloc(pPath->pathHeap, sizeof(fs_win32_char) * (newLen + 1), pAllocationCallbacks); + if (pNewHeap == NULL) { + return FS_OUT_OF_MEMORY; + } + + pPath->pathHeap = pNewHeap; + pPath->path = pNewHeap; + } else { + /* Getting here means it's on the stack. We may need to transfer to the heap. */ + if (newLen >= FS_COUNTOF(pPath->pathStack)) { + /* There's not enough room on the stack. We need to move the string from the stack to the heap. */ + pPath->pathHeap = (fs_win32_char*)fs_malloc(sizeof(fs_win32_char) * (newLen + 1), pAllocationCallbacks); + if (pPath->pathHeap == NULL) { + return FS_OUT_OF_MEMORY; + } + + memcpy(pPath->pathHeap, pPath->pathStack, sizeof(fs_win32_char) * (pPath->len + 1)); + pPath->path = pPath->pathHeap; + } else { + /* There's enough room on the stack. No modifications needed. */ + } + } + + /* Now we can append. */ + memcpy(pPath->path + pPath->len, append.path, sizeof(fs_win32_char) * (append.len + 1)); /* Null terminator copied in-place. */ + pPath->len = newLen; + + return FS_SUCCESS; +} + + + +static void fs_win32_path_uninit(fs_win32_path* pPath, const fs_allocation_callbacks* pAllocationCallbacks) +{ + if (pPath->pathHeap) { + fs_free(pPath->pathHeap, pAllocationCallbacks); + pPath->pathHeap = NULL; + } +} + + +FS_API fs_uint64 fs_FILETIME_to_unix(const FILETIME* pFT) +{ + ULARGE_INTEGER li; + + li.HighPart = pFT->dwHighDateTime; + li.LowPart = pFT->dwLowDateTime; + + return (fs_uint64)(li.QuadPart / 10000000UL - ((fs_uint64)116444736UL * 100UL)); /* Convert from Windows epoch to Unix epoch. */ +} + +static fs_file_info fs_file_info_from_WIN32_FIND_DATA(const WIN32_FIND_DATA* pFD) +{ + fs_file_info info; + + FS_ZERO_OBJECT(&info); + info.size = ((fs_uint64)pFD->nFileSizeHigh << 32) | (fs_uint64)pFD->nFileSizeLow; + info.lastModifiedTime = fs_FILETIME_to_unix(&pFD->ftLastWriteTime); + info.lastAccessTime = fs_FILETIME_to_unix(&pFD->ftLastAccessTime); + info.directory = (pFD->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info.symlink = ((pFD->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) && (pFD->dwReserved0 == 0xA000000C); /* <-- The use of dwReserved0 is documented for WIN32_FIND_DATA. */ + + return info; +} + +static fs_file_info fs_file_info_from_HANDLE_FILE_INFORMATION(const BY_HANDLE_FILE_INFORMATION* pFileInfo) +{ + fs_file_info info; + + FS_ZERO_OBJECT(&info); + info.size = ((fs_uint64)pFileInfo->nFileSizeHigh << 32) | (fs_uint64)pFileInfo->nFileSizeLow; + info.lastModifiedTime = fs_FILETIME_to_unix(&pFileInfo->ftLastWriteTime); + info.lastAccessTime = fs_FILETIME_to_unix(&pFileInfo->ftLastAccessTime); + info.directory = (pFileInfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + + return info; +} + + +static size_t fs_alloc_size_win32(const void* pBackendConfig) +{ + (void)pBackendConfig; + return 0; +} + +static fs_result fs_init_win32(fs* pFS, const void* pBackendConfig, fs_stream* pStream) +{ + (void)pFS; + (void)pBackendConfig; + (void)pStream; + + return FS_SUCCESS; +} + +static void fs_uninit_win32(fs* pFS) +{ + (void)pFS; +} + +static fs_result fs_ioctl_win32(fs* pFS, int op, void* pArg) +{ + (void)pFS; + (void)op; + (void)pArg; + + return FS_NOT_IMPLEMENTED; +} + +static fs_result fs_remove_win32(fs* pFS, const char* pFilePath) +{ + BOOL resultWin32; + fs_result result; + fs_win32_path path; + + result = fs_win32_path_init(&path, pFilePath, (size_t)-1, fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + return result; + } + + resultWin32 = DeleteFile(path.path); + if (resultWin32 == FS_FALSE) { + /* It may have been a directory. */ + DWORD error = GetLastError(); + if (error == ERROR_ACCESS_DENIED || error == ERROR_FILE_NOT_FOUND) { + DWORD attributes = GetFileAttributes(path.path); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY)) { + resultWin32 = RemoveDirectory(path.path); + if (resultWin32 == FS_FALSE) { + result = fs_result_from_GetLastError(); + goto done; + } else { + return FS_SUCCESS; + } + } else { + result = fs_result_from_GetLastError(); + goto done; + } + } else { + result = fs_result_from_GetLastError(); + goto done; + } + + result = fs_result_from_GetLastError(); + goto done; + } else { + result = FS_SUCCESS; + } + +done: + fs_win32_path_uninit(&path, fs_get_allocation_callbacks(pFS)); + + (void)pFS; + return result; +} + +static fs_result fs_rename_win32(fs* pFS, const char* pOldPath, const char* pNewPath) +{ + BOOL resultWin32; + fs_result result; + fs_win32_path pathOld; + fs_win32_path pathNew; + + result = fs_win32_path_init(&pathOld, pOldPath, (size_t)-1, fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + return result; + } + + result = fs_win32_path_init(&pathNew, pNewPath, (size_t)-1, fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + fs_win32_path_uninit(&pathOld, fs_get_allocation_callbacks(pFS)); + return result; + } + + resultWin32 = MoveFile(pathOld.path, pathNew.path); + if (resultWin32 == FS_FALSE) { + result = fs_result_from_GetLastError(); + } + + fs_win32_path_uninit(&pathOld, fs_get_allocation_callbacks(pFS)); + fs_win32_path_uninit(&pathNew, fs_get_allocation_callbacks(pFS)); + + (void)pFS; + return result; +} + +static fs_result fs_mkdir_win32(fs* pFS, const char* pPath) +{ + BOOL resultWin32; + fs_result result; + fs_win32_path path; + + /* If it's a drive letter segment just pretend it's successful. */ + if ((pPath[0] >= 'a' && pPath[0] <= 'z') || (pPath[0] >= 'A' && pPath[0] <= 'Z')) { + if (pPath[1] == ':' && pPath[2] == '\0') { + return FS_SUCCESS; + } + } + + result = fs_win32_path_init(&path, pPath, (size_t)-1, fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + return result; + } + + resultWin32 = CreateDirectory(path.path, NULL); + if (resultWin32 == FS_FALSE) { + result = fs_result_from_GetLastError(); + goto done; + } + +done: + fs_win32_path_uninit(&path, fs_get_allocation_callbacks(pFS)); + + (void)pFS; + return result; +} + +static fs_result fs_info_from_stdio_win32(HANDLE hFile, fs_file_info* pInfo) +{ + BY_HANDLE_FILE_INFORMATION fileInfo; + + if (GetFileInformationByHandle(hFile, &fileInfo) == FS_FALSE) { + return fs_result_from_GetLastError(); + } + + *pInfo = fs_file_info_from_HANDLE_FILE_INFORMATION(&fileInfo); + + return FS_SUCCESS; +} + +static fs_result fs_info_win32(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo) +{ + HANDLE hFind; + WIN32_FIND_DATA fd; + fs_result result; + fs_win32_path path; + + /* Special case for standard IO files. */ + /* */ if (pPath == FS_STDIN ) { + return fs_info_from_stdio_win32(GetStdHandle(STD_INPUT_HANDLE ), pInfo); + } else if (pPath == FS_STDOUT) { + return fs_info_from_stdio_win32(GetStdHandle(STD_OUTPUT_HANDLE), pInfo); + } else if (pPath == FS_STDERR) { + return fs_info_from_stdio_win32(GetStdHandle(STD_ERROR_HANDLE ), pInfo); + } + + result = fs_win32_path_init(&path, pPath, (size_t)-1, fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + return result; + } + + hFind = FindFirstFile(path.path, &fd); + + fs_win32_path_uninit(&path, fs_get_allocation_callbacks(pFS)); + + if (hFind == INVALID_HANDLE_VALUE) { + result = fs_result_from_GetLastError(); + goto done; + } else { + result = FS_SUCCESS; + } + + FindClose(hFind); + hFind = NULL; + + *pInfo = fs_file_info_from_WIN32_FIND_DATA(&fd); + +done: + (void)openMode; + (void)pFS; + return result; +} + + +typedef struct fs_file_win32 +{ + HANDLE hFile; + fs_bool32 isStandardHandle; + int openMode; /* The original open mode for duplication purposes. */ + char* pFilePath; + char pFilePathStack[256]; + char* pFilePathHeap; +} fs_file_win32; + +static size_t fs_file_alloc_size_win32(fs* pFS) +{ + (void)pFS; + return sizeof(fs_file_win32); +} + +static fs_result fs_file_open_win32(fs* pFS, fs_stream* pStream, const char* pFilePath, int openMode, fs_file* pFile) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + fs_result result; + fs_win32_path path; + HANDLE hFile; + DWORD dwDesiredAccess = 0; + DWORD dwShareMode = 0; + DWORD dwCreationDisposition = OPEN_EXISTING; + + /* */ if (pFilePath == FS_STDIN ) { + hFile = GetStdHandle(STD_INPUT_HANDLE); + pFileWin32->isStandardHandle = FS_TRUE; + } else if (pFilePath == FS_STDOUT) { + hFile = GetStdHandle(STD_OUTPUT_HANDLE); + pFileWin32->isStandardHandle = FS_TRUE; + } else if (pFilePath == FS_STDERR) { + hFile = GetStdHandle(STD_ERROR_HANDLE); + pFileWin32->isStandardHandle = FS_TRUE; + } else { + pFileWin32->isStandardHandle = FS_FALSE; + } + + if (pFileWin32->isStandardHandle) { + pFileWin32->hFile = hFile; + return FS_SUCCESS; + } + + + if ((openMode & FS_READ) != 0) { + dwDesiredAccess |= GENERIC_READ; + dwShareMode |= FILE_SHARE_READ; + dwCreationDisposition = OPEN_EXISTING; /* In read mode, our default is to open an existing file, and fail if it doesn't exist. This can be overwritten in the write case below. */ + } + + if ((openMode & FS_WRITE) != 0) { + dwShareMode |= FILE_SHARE_WRITE; + + if ((openMode & FS_EXCLUSIVE) != 0) { + dwDesiredAccess |= GENERIC_WRITE; + dwCreationDisposition = CREATE_NEW; + } else if ((openMode & FS_APPEND) != 0) { + dwDesiredAccess |= FILE_APPEND_DATA; + dwCreationDisposition = OPEN_ALWAYS; + } else if ((openMode & FS_TRUNCATE) != 0) { + dwDesiredAccess |= GENERIC_WRITE; + dwCreationDisposition = CREATE_ALWAYS; + } else { + dwDesiredAccess |= GENERIC_WRITE; + dwCreationDisposition = OPEN_ALWAYS; + } + } + + /* As an added safety check, make sure one or both of read and write was specified. */ + if (dwDesiredAccess == 0) { + return FS_INVALID_ARGS; + } + + result = fs_win32_path_init(&path, pFilePath, (size_t)-1, fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + return result; + } + + hFile = CreateFile(path.path, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + result = fs_result_from_GetLastError(); + } else { + result = FS_SUCCESS; + pFileWin32->hFile = hFile; + } + + if (result != FS_SUCCESS) { + return result; + } + + /* We need to keep track of the open mode for duplication purposes. */ + pFileWin32->openMode = openMode; + + /* We need to make a copy of the path for duplication purposes. */ + if (path.len < FS_COUNTOF(pFileWin32->pFilePathStack)) { + pFileWin32->pFilePath = pFileWin32->pFilePathStack; + } else { + pFileWin32->pFilePathHeap = (char*)fs_malloc(path.len + 1, fs_get_allocation_callbacks(pFS)); + if (pFileWin32->pFilePathHeap == NULL) { + result = FS_OUT_OF_MEMORY; + if (pFileWin32->isStandardHandle == FS_FALSE) { + CloseHandle(pFileWin32->hFile); + } + + fs_win32_path_uninit(&path, fs_get_allocation_callbacks(pFS)); + return result; + } + + pFileWin32->pFilePath = pFileWin32->pFilePathHeap; + } + + fs_strcpy(pFileWin32->pFilePath, pFilePath); + + + /* All done. */ + fs_win32_path_uninit(&path, fs_get_allocation_callbacks(pFS)); + + (void)pFS; + (void)pStream; + return FS_SUCCESS; +} + +static void fs_file_close_win32(fs_file* pFile) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + + if (pFileWin32->isStandardHandle == FS_FALSE) { + CloseHandle(pFileWin32->hFile); + fs_free(pFileWin32->pFilePathHeap, fs_get_allocation_callbacks(fs_file_get_fs(pFile))); + } +} + +static fs_result fs_file_read_win32(fs_file* pFile, void* pDst, size_t bytesToRead, size_t* pBytesRead) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + BOOL resultWin32; + size_t bytesRemaining = bytesToRead; + char* pRunningDst = (char*)pDst; + + /* + ReadFile() expects a DWORD for the number of bytes to read which means we'll need to run it in a loop in case + our bytesToRead argument is larger than 4GB. + */ + while (bytesRemaining > 0) { + DWORD bytesToReadNow = (DWORD)FS_MIN(bytesRemaining, (size_t)0xFFFFFFFF); + DWORD bytesReadNow; + + resultWin32 = ReadFile(pFileWin32->hFile, pRunningDst, bytesToReadNow, &bytesReadNow, NULL); + if (resultWin32 == FS_FALSE) { + return fs_result_from_GetLastError(); + } + + if (bytesReadNow == 0) { + break; + } + + bytesRemaining -= bytesReadNow; + pRunningDst += bytesReadNow; + } + + *pBytesRead = bytesToRead - bytesRemaining; + + if (*pBytesRead == 0) { + return FS_AT_END; + } + + return FS_SUCCESS; +} + +static fs_result fs_file_write_win32(fs_file* pFile, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + BOOL resultWin32; + size_t bytesRemaining = bytesToWrite; + const char* pRunningSrc = (const char*)pSrc; + + /* + WriteFile() expects a DWORD for the number of bytes to write which means we'll need to run it in a loop in case + our bytesToWrite argument is larger than 4GB. + */ + while (bytesRemaining > 0) { + DWORD bytesToWriteNow = (DWORD)FS_MIN(bytesRemaining, (size_t)0xFFFFFFFF); + DWORD bytesWrittenNow; + + resultWin32 = WriteFile(pFileWin32->hFile, pRunningSrc, bytesToWriteNow, &bytesWrittenNow, NULL); + if (resultWin32 == FS_FALSE) { + return fs_result_from_GetLastError(); + } + + if (bytesWrittenNow == 0) { + break; + } + + bytesRemaining -= bytesWrittenNow; + pRunningSrc += bytesWrittenNow; + } + + *pBytesWritten = bytesToWrite - bytesRemaining; + + return FS_SUCCESS; +} + +static fs_result fs_file_seek_win32(fs_file* pFile, fs_int64 offset, fs_seek_origin origin) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + LARGE_INTEGER liDistanceToMove; + DWORD dwMoveMethod; + + switch (origin) { + case FS_SEEK_SET: + dwMoveMethod = FILE_BEGIN; + liDistanceToMove.QuadPart = offset; + break; + case FS_SEEK_CUR: + dwMoveMethod = FILE_CURRENT; + liDistanceToMove.QuadPart = offset; + break; + case FS_SEEK_END: + dwMoveMethod = FILE_END; + liDistanceToMove.QuadPart = offset; + break; + default: + return FS_INVALID_ARGS; + } + + /* + Use SetFilePointer() instead of SetFilePointerEx() for compatibility with old Windows. + + Note from MSDN: + + If you do not need the high order 32-bits, this pointer must be set to NULL. + */ + if (SetFilePointer(pFileWin32->hFile, liDistanceToMove.LowPart, (liDistanceToMove.HighPart == 0 ? NULL : &liDistanceToMove.HighPart), dwMoveMethod) == INVALID_SET_FILE_POINTER) { + return fs_result_from_GetLastError(); + } + + return FS_SUCCESS; +} + +static fs_result fs_file_tell_win32(fs_file* pFile, fs_int64* pCursor) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + LARGE_INTEGER liCursor; + + liCursor.HighPart = 0; + liCursor.LowPart = SetFilePointer(pFileWin32->hFile, 0, &liCursor.HighPart, FILE_CURRENT); + + if (liCursor.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { + return fs_result_from_GetLastError(); + } + + *pCursor = liCursor.QuadPart; + + return FS_SUCCESS; +} + +static fs_result fs_file_flush_win32(fs_file* pFile) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + + if (FlushFileBuffers(pFileWin32->hFile) == FS_FALSE) { + return fs_result_from_GetLastError(); + } + + return FS_SUCCESS; +} + +static fs_result fs_file_truncate_win32(fs_file* pFile) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + + if (SetEndOfFile(pFileWin32->hFile) == FS_FALSE) { + return fs_result_from_GetLastError(); + } + + return FS_SUCCESS; +} + +static fs_result fs_file_info_win32(fs_file* pFile, fs_file_info* pInfo) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + BY_HANDLE_FILE_INFORMATION fileInfo; + + if (GetFileInformationByHandle(pFileWin32->hFile, &fileInfo) == FS_FALSE) { + return fs_result_from_GetLastError(); + } + + *pInfo = fs_file_info_from_HANDLE_FILE_INFORMATION(&fileInfo); + + return FS_SUCCESS; +} + +static fs_result fs_file_duplicate_win32(fs_file* pFile, fs_file* pDuplicate) +{ + fs_file_win32* pFileWin32 = (fs_file_win32*)fs_file_get_backend_data(pFile); + fs_file_win32* pDuplicateWin32 = (fs_file_win32*)fs_file_get_backend_data(pDuplicate); + fs_result result; + BY_HANDLE_FILE_INFORMATION info1, info2; + + if (pFileWin32->isStandardHandle) { + pDuplicateWin32->hFile = pFileWin32->hFile; + pDuplicateWin32->isStandardHandle = FS_TRUE; + + return FS_SUCCESS; + } + + /* + We cannot duplicate the handle because that will result in a shared read/write pointer. We need to + open the file again with the same path and flags. We're not going to allow duplication of files + that were opened in write mode. + */ + if ((pFileWin32->openMode & FS_WRITE) != 0) { + return FS_INVALID_OPERATION; + } + + result = fs_file_open_win32(fs_file_get_fs(pFile), NULL, pFileWin32->pFilePath, pFileWin32->openMode, pDuplicate); + if (result != FS_SUCCESS) { + return result; + } + + /* Now check the file information in case it got replaced with a different file from under us. */ + if (GetFileInformationByHandle(pFileWin32->hFile, &info1) == FS_FALSE) { + fs_file_close_win32(pDuplicate); + return fs_result_from_GetLastError(); + } + if (GetFileInformationByHandle(pDuplicateWin32->hFile, &info2) == FS_FALSE) { + fs_file_close_win32(pDuplicate); + return fs_result_from_GetLastError(); + } + + if ((info1.dwVolumeSerialNumber != info2.dwVolumeSerialNumber) || (info1.nFileIndexLow != info2.nFileIndexLow) || (info1.nFileIndexHigh != info2.nFileIndexHigh)) { + fs_file_close_win32(pDuplicate); + return FS_INVALID_OPERATION; + } + + return FS_SUCCESS; +} + + +#define FS_WIN32_MIN_ITERATOR_ALLOCATION_SIZE 1024 + +typedef struct fs_iterator_win32 +{ + fs_iterator iterator; + HANDLE hFind; + WIN32_FIND_DATAA findData; + char* pFullFilePath; /* Points to the end of the structure. */ + size_t directoryPathLen; /* The length of the directory section. */ +} fs_iterator_win32; + +static void fs_free_iterator_win32(fs_iterator* pIterator); + +static fs_iterator* fs_iterator_win32_resolve(fs_iterator_win32* pIteratorWin32, fs* pFS, HANDLE hFind, const WIN32_FIND_DATA* pFD) +{ + fs_iterator_win32* pNewIteratorWin32; + size_t allocSize; + int nameLenIncludingNullTerminator; + + /* + The name is stored at the end of the struct. In order to know how much memory to allocate we'll + need to calculate the length of the name. + */ + #if defined(UNICODE) || defined(_UNICODE) + { + nameLenIncludingNullTerminator = WideCharToMultiByte(CP_UTF8, 0, pFD->cFileName, -1, NULL, 0, NULL, NULL); + if (nameLenIncludingNullTerminator == 0) { + fs_free_iterator_win32((fs_iterator*)pIteratorWin32); + return NULL; + } + } + #else + { + nameLenIncludingNullTerminator = (int)strlen(pFD->cFileName) + 1; /* +1 for the null terminator. */ + } + #endif + + allocSize = FS_MAX(sizeof(fs_iterator_win32) + nameLenIncludingNullTerminator, FS_WIN32_MIN_ITERATOR_ALLOCATION_SIZE); /* 1KB just to try to avoid excessive internal reallocations inside realloc(). */ + + pNewIteratorWin32 = (fs_iterator_win32*)fs_realloc(pIteratorWin32, allocSize, fs_get_allocation_callbacks(pFS)); + if (pNewIteratorWin32 == NULL) { + fs_free_iterator_win32((fs_iterator*)pIteratorWin32); + return NULL; + } + + pNewIteratorWin32->iterator.pFS = pFS; + pNewIteratorWin32->hFind = hFind; + + /* Name. */ + pNewIteratorWin32->iterator.pName = (char*)pNewIteratorWin32 + sizeof(fs_iterator_win32); + pNewIteratorWin32->iterator.nameLen = (size_t)nameLenIncludingNullTerminator - 1; + + #if defined(UNICODE) || defined(_UNICODE) + { + WideCharToMultiByte(CP_UTF8, 0, pFD->cFileName, -1, (char*)pNewIteratorWin32->iterator.pName, nameLenIncludingNullTerminator, NULL, NULL); /* const-cast is safe here. */ + } + #else + { + fs_strcpy((char*)pNewIteratorWin32->iterator.pName, pFD->cFileName); /* const-cast is safe here. */ + } + #endif + + /* Info. */ + pNewIteratorWin32->iterator.info = fs_file_info_from_WIN32_FIND_DATA(pFD); + + return (fs_iterator*)pNewIteratorWin32; +} + +static fs_iterator* fs_first_win32(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen) +{ + HANDLE hFind; + WIN32_FIND_DATA fd; + fs_result result; + fs_win32_path query; + + /* An empty path means the current directory. Win32 will want us to specify "." in this case. */ + if (pDirectoryPath == NULL || pDirectoryPath[0] == '\0') { + pDirectoryPath = "."; + directoryPathLen = 1; + } + + result = fs_win32_path_init(&query, pDirectoryPath, directoryPathLen, fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + return NULL; + } + + /* + At this point we have converted the first part of the query. Now we need to append "\*" to it. To do this + properly, we'll first need to remove any trailing slash, if any. + */ + if (query.len > 0 && query.path[query.len - 1] == '\\') { + query.len -= 1; + query.path[query.len] = '\0'; + } + + result = fs_win32_path_append(&query, "\\*", fs_get_allocation_callbacks(pFS)); + if (result != FS_SUCCESS) { + fs_win32_path_uninit(&query, fs_get_allocation_callbacks(pFS)); + return NULL; + } + + hFind = FindFirstFile(query.path, &fd); + fs_win32_path_uninit(&query, fs_get_allocation_callbacks(pFS)); + + if (hFind == INVALID_HANDLE_VALUE) { + return NULL; + } + + return fs_iterator_win32_resolve(NULL, pFS, hFind, &fd); +} + +static fs_iterator* fs_next_win32(fs_iterator* pIterator) +{ + fs_iterator_win32* pIteratorWin32 = (fs_iterator_win32*)pIterator; + WIN32_FIND_DATA fd; + + if (!FindNextFile(pIteratorWin32->hFind, &fd)) { + fs_free_iterator_win32(pIterator); + return NULL; + } + + return fs_iterator_win32_resolve(pIteratorWin32, pIterator->pFS, pIteratorWin32->hFind, &fd); +} + +static void fs_free_iterator_win32(fs_iterator* pIterator) +{ + fs_iterator_win32* pIteratorWin32 = (fs_iterator_win32*)pIterator; + + FindClose(pIteratorWin32->hFind); + fs_free(pIteratorWin32, fs_get_allocation_callbacks(pIterator->pFS)); +} + +static fs_backend fs_win32_backend = +{ + fs_alloc_size_win32, + fs_init_win32, + fs_uninit_win32, + fs_ioctl_win32, + fs_remove_win32, + fs_rename_win32, + fs_mkdir_win32, + fs_info_win32, + fs_file_alloc_size_win32, + fs_file_open_win32, + fs_file_close_win32, + fs_file_read_win32, + fs_file_write_win32, + fs_file_seek_win32, + fs_file_tell_win32, + fs_file_flush_win32, + fs_file_truncate_win32, + fs_file_info_win32, + fs_file_duplicate_win32, + fs_first_win32, + fs_next_win32, + fs_free_iterator_win32 +}; + +const fs_backend* FS_BACKEND_WIN32 = &fs_win32_backend; +#else +const fs_backend* FS_BACKEND_WIN32 = NULL; +#endif +/* END fs_backend_win32.c */ /* END fs.c */ @@ -6065,12 +7176,252 @@ FS_API size_t fs_sysdir(fs_sysdir_type type, char* pDst, size_t dstCap) } #endif + /* Check if there's a trailing slash, and if so, delete it. */ + if (pDst != NULL && fullLength < dstCap && fullLength > 0) { + if (pDst[fullLength - 1] == '/' || pDst[fullLength - 1] == '\\') { + pDst[fullLength - 1] = '\0'; + } + } + return fullLength; } /* END fs_sysdir.c */ /* BEG fs_mktmp.c */ +#ifndef _WIN32 + +/* +We need to detect whether or not mkdtemp() and mkstemp() are available. If it's not we'll fall back to a +non-optimal implementation. These are the rules: + + - POSIX.1-2008 and later + - glibc 2.1.91+ (when _GNU_SOURCE is defined) + - Not available in strict C89 mode + - Not available on some older systems +*/ +#if !defined(FS_USE_MKDTEMP_FALLBACK) + #if !defined(FS_HAS_MKDTEMP) && (defined(_GNU_SOURCE) || defined(_BSD_SOURCE)) + #define FS_HAS_MKDTEMP + #endif + #if !defined(FS_HAS_MKDTEMP) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + #define FS_HAS_MKDTEMP + #endif + #if !defined(FS_HAS_MKDTEMP) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 + #define FS_HAS_MKDTEMP + #endif + #if !defined(FS_HAS_MKDTEMP) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L && !defined(__STRICT_ANSI__) + #define FS_HAS_MKDTEMP + #endif +#endif + +#if !defined(FS_HAS_MKDTEMP) +#include +#include +#include + +static int fs_get_random_bytes(unsigned char* pBytes, size_t count) +{ + int fd; + ssize_t bytesRead; + + /* Try /dev/urandom first. */ + fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + bytesRead = read(fd, pBytes, count); + close(fd); + + if ((size_t)bytesRead == count) { + return 0; /* Success. */ + } + + /* Getting here means reading failed. Fall through to the fallback case. */ + } + + /* Getting here means /dev/urandom failed. We can fall back to a simple time/pid/stack based entropy. */ + { + unsigned long stackAddress = 0; + unsigned long seed; + size_t i; + + /* Create a seed using multiple entropy sources. */ + seed = (unsigned long)time(NULL); + seed ^= (unsigned long)getpid(); + seed ^= (unsigned long)(size_t)&stackAddress; /* <-- Stack address entropy. */ + seed ^= (unsigned long)clock(); + + /* LCG */ + for (i = 0; i < count; i += 1) { + seed = seed * 1103515245U + 12345U; + pBytes[i] = (unsigned char)(seed >> 16); + } + + return 0; + } +} + +static char* fs_mkdtemp_fallback(char* pTemplate) +{ + size_t templateLen; + char* pXXXXXX; + int attempts; + int i; + + if (pTemplate == NULL) { + return NULL; + } + + /* The template must end with XXXXXX. */ + templateLen = strlen(pTemplate); + if (templateLen < 6) { + return NULL; + } + + pXXXXXX = pTemplate + templateLen - 6; + for (i = 0; i < 6; i += 1) { + if (pXXXXXX[i] != 'X') { + return NULL; /* Last 6 characters are not "XXXXXX". */ + } + } + + /* We can now fill out the random part and try creating the directory. */ + for (attempts = 0; attempts < 100; attempts += 1) { + unsigned char randomBytes[6]; + + if (fs_get_random_bytes(randomBytes, 6) != 0) { + return NULL; + } + + /* Fill out the random part using the random bytes */ + for (i = 0; i < 6; i += 1) { + int r = randomBytes[i] % 62; + if (r < 10) { + pXXXXXX[i] = '0' + r; + } else if (r < 36) { + pXXXXXX[i] = 'A' + (r - 10); + } else { + pXXXXXX[i] = 'a' + (r - 36); + } + } + + /* With the random part filled out we're now ready to try creating the directory */ + if (mkdir(pTemplate, S_IRWXU) == 0) { + return pTemplate; /* Success */ + } + + /* + Getting here means we failed to create the directory. If it's because the directory already exists (EEXIST) + we can just continue iterating. Otherwise it was an unexpected error and we need to bomb out. + */ + if (errno != EEXIST) { + return NULL; + } + } + + /* Getting here means we failed after several attempts. */ + return NULL; +} + +static int fs_mkstemp_fallback(char* pTemplate) +{ + size_t templateLen; + char* pXXXXXX; + int attempts; + int i; + int fd; + + if (pTemplate == NULL) { + return -1; + } + + /* The template must end with XXXXXX. */ + templateLen = strlen(pTemplate); + if (templateLen < 6) { + return -1; + } + + pXXXXXX = pTemplate + templateLen - 6; + for (i = 0; i < 6; i += 1) { + if (pXXXXXX[i] != 'X') { + return -1; /* Last 6 characters are not "XXXXXX". */ + } + } + + /* We can now fill out the random part and try creating the file. */ + for (attempts = 0; attempts < 100; attempts += 1) { + unsigned char randomBytes[6]; + + if (fs_get_random_bytes(randomBytes, 6) != 0) { + return -1; + } + + /* Fill out the random part using the random bytes */ + for (i = 0; i < 6; i += 1) { + int r = randomBytes[i] % 62; + if (r < 10) { + pXXXXXX[i] = '0' + r; + } else if (r < 36) { + pXXXXXX[i] = 'A' + (r - 10); + } else { + pXXXXXX[i] = 'a' + (r - 36); + } + } + + /* With the random part filled out we're now ready to try creating the file */ + fd = open(pTemplate, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + if (fd >= 0) { + return fd; /* Success */ + } + + /* + Getting here means we failed to create the file. If it's because the file already exists (EEXIST) + we can just continue iterating. Otherwise it was an unexpected error and we need to bomb out. + */ + if (errno != EEXIST) { + return -1; + } + } + + /* Getting here means we failed after several attempts. */ + return -1; +} +#endif + +static char* fs_mkdtemp(char* pTemplate) +{ + if (pTemplate == NULL) { + return NULL; + } + + #if defined(FS_HAS_MKDTEMP) + { + return mkdtemp(pTemplate); + } + #else + { + return fs_mkdtemp_fallback(pTemplate); + } + #endif +} + +static int fs_mkstemp(char* pTemplate) +{ + if (pTemplate == NULL) { + return -1; + } + + #if defined(FS_HAS_MKDTEMP) + { + return mkstemp(pTemplate); + } + #else + { + return fs_mkstemp_fallback(pTemplate); + } + #endif +} +#endif + FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap, int options) { size_t baseDirLen; @@ -6170,7 +7521,7 @@ FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap } if (GetTempFileNameA(pTmpPath, pPrefixName, 0, pTmpPathWin) == 0) { - return fs_result_from_GetLastError(GetLastError()); + return fs_result_from_GetLastError(); } /* @@ -6192,7 +7543,7 @@ FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap DeleteFileA(pTmpPathWin); if (CreateDirectoryA(pTmpPathWin, NULL) == 0) { - return fs_result_from_GetLastError(GetLastError()); + return fs_result_from_GetLastError(); } } else { /* We're creating a temp file. The OS will have already created the file in GetTempFileNameA() so no need to create it explicitly. */ @@ -6220,12 +7571,12 @@ FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap /* At this point the full path has been constructed. We can now create the file or directory. */ if ((options & FS_MKTMP_DIR) != 0) { /* We're creating a temp directory. */ - if (mkdtemp(pTmpPath) == NULL) { + if (fs_mkdtemp(pTmpPath) == NULL) { return fs_result_from_errno(errno); } } else { /* We're creating a temp file. */ - int fd = mkstemp(pTmpPath); + int fd = fs_mkstemp(pTmpPath); if (fd == -1) { return fs_result_from_errno(errno); } @@ -6240,26 +7591,6 @@ FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap /* END fs_mktmp.c */ -/* BEG fs_errno.c */ -FS_API fs_result fs_result_from_errno(int error) -{ - switch (error) - { - case 0: return FS_SUCCESS; - case ENOENT: return FS_DOES_NOT_EXIST; - case EEXIST: return FS_ALREADY_EXISTS; - case ENOTDIR: return FS_NOT_DIRECTORY; - case ENOMEM: return FS_OUT_OF_MEMORY; - case EINVAL: return FS_INVALID_ARGS; - default: break; - } - - /* Fall back to a generic error. */ - return FS_ERROR; -} -/* END fs_errno.c */ - - /* BEG fs_path.c */ FS_API fs_result fs_path_first(const char* pPath, size_t pathLen, fs_path_iterator* pIterator) @@ -6617,16 +7948,12 @@ FS_API const char* fs_path_trim_base(const char* pPath, size_t pathLen, const ch /* We just keep iterating until we find a mismatch or reach the end of the base path. */ for (;;) { - if (iPath.segmentLength != iBase.segmentLength) { - return NULL; - } - - if (fs_strncmp(iPath.pFullPath + iPath.segmentOffset, iBase.pFullPath + iBase.segmentOffset, iPath.segmentLength) != 0) { + if (iPath.segmentLength != iBase.segmentLength || fs_strncmp(iPath.pFullPath + iPath.segmentOffset, iBase.pFullPath + iBase.segmentOffset, iPath.segmentLength) != 0) { return NULL; } result = fs_path_next(&iBase); - if (result != FS_SUCCESS) { + if (result != FS_SUCCESS || (iBase.segmentLength == 0 && fs_path_is_last(&iBase))) { fs_path_next(&iPath); /* Move to the next segment in the path to ensure our iterators are in sync. */ break; } @@ -6691,7 +8018,7 @@ FS_API int fs_path_append(char* pDst, size_t dstCap, const char* pBasePath, size /* Don't move the base path if we're appending in-place. */ if (pDst != pBasePath) { - FS_COPY_MEMORY(pDst, pBasePath, FS_MIN(basePathLen, dstCap)); + FS_COPY_MEMORY(pDst, pBasePath, bytesToCopy); } } @@ -6722,12 +8049,22 @@ FS_API int fs_path_append(char* pDst, size_t dstCap, const char* pBasePath, size } FS_COPY_MEMORY(pDst, pPathToAppend, bytesToCopy); - pDst[bytesToCopy] = '\0'; } + + pDst += bytesToCopy; + dstCap -= bytesToCopy; } dstLen += pathToAppendLen; + /* Null terminator. */ + if (pDst != NULL) { + if (dstCap > 0) { + pDst[0] = '\0'; + } + } + + if (dstLen > 0x7FFFFFFF) { return -1; /* Path is too long to convert to an int. */ } @@ -6889,14 +8226,18 @@ static fs_result fs_memory_stream_tell_internal(fs_stream* pStream, fs_int64* pC return result; } -#if defined(__clang__) +#if defined(__clang__) || defined(__GNUC__) #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" + #elif defined(__GNUC__) + #pragma GCC diagnostic ignored "-Wtype-limits" + #endif #endif if (cursor > FS_INT64_MAX) { return FS_ERROR; } -#if defined(__clang__) +#if defined(__clang__) || defined(__GNUC__) #pragma GCC diagnostic pop #endif diff --git a/external/fs/fs.h b/external/fs/fs.h index ebfbf48e..03dea67f 100644 --- a/external/fs/fs.h +++ b/external/fs/fs.h @@ -84,11 +84,41 @@ if (result != FS_SUCCESS) { result = fs_file_write(pFile, pBuffer, bytesToWrite, &bytesWritten); ``` -The `FS_WRITE` option will default to `FS_TRUNCATE`. You can also use `FS_APPEND` and -`FS_OVERWRITE`: +Formatted writing is also supported: ```c -fs_file_open(pFS, "file.txt", FS_APPEND, &pFile); // You need not specify FS_WRITE when using FS_TRUNCATE, FS_APPEND or FS_OVERWRITE as it is implied. +result = fs_file_writef(pFile, "Hello %s!\n", "World"); +if (result != FS_SUCCESS) { + // Failed to write file. +} + +va_list args; +va_start(args, format); +result = fs_file_writefv(pFile, "Hello %s!\n", args); +va_end(args); +``` + +The `FS_WRITE` option will default to overwrite mode. You can use `FS_TRUNCATE` if you want to +truncate the file instead of overwriting it. + +```c +fs_file_open(pFS, "file.txt", FS_WRITE | FS_TRUNCATE, &pFile); +``` + +You can also open a file in append mode with `FS_APPEND`: + +```c +fs_file_open(pFS, "file.txt", FS_WRITE | FS_APPEND, &pFile); +``` + +When using `FS_APPEND` mode, the file will always append to the end of the file and can never +overwrite existing content. This follows POSIX semantics. In this mode, it is not possible to +create sparse files. + +To open a file in write mode, but fail if the file already exists, you can use `FS_EXCLUSIVE`: + +```c +fs_file_open(pFS, "file.txt", FS_WRITE | FS_EXCLUSIVE, &pFile); ``` Files can be opened for both reading and writing by simply combining the two: @@ -106,18 +136,24 @@ fs_int64 cursorPos; fs_file_tell(pFile, &cursorPos); ``` -Retrieving information about a file is done with `fs_file_info()`: +When seeking, you can seek beyond the end of the file. Attempting to read from beyond the end of +the file will return `FS_AT_END`. Attempting to write beyond the end of the file will create a +hole if supported by the file system, or fill the space with data (the filled data can be left +undefined). When seeking from the end of the file with a negative offset, it will seek backwards +from the end. Seeking to before the start of the file is not allowed and will return an error. + +Retrieving information about a file is done with `fs_file_get_info()`: ```c fs_file_info info; -fs_file_info(pFile, &info); +fs_file_get_info(pFile, &info); ``` If you want to get information about a file without opening it, you can use `fs_info()`: ```c fs_file_info info; -fs_info(pFS, "file.txt", &info); +fs_info(pFS, "file.txt", FS_READ, &info); // FS_READ tells it to check read-only mounts (explained later) ``` A file handle can be duplicated with `fs_file_duplicate()`: @@ -128,7 +164,13 @@ fs_file_duplicate(pFile, &pFileDup); ``` Note that this will only duplicate the file handle. It does not make a copy of the file on the file -system itself. The duplicated file handle will be entirely independent of the original handle. +system itself. The duplicated file handle will be entirely independent of the original handle, +including having its own separate read/write cursor position. The initial position of the cursor of +the new file handle is undefined and you should explicitly seek to the appropriate location. + +Important: `fs_file_duplicate()` can fail or work incorrectly if you use relative paths for mounts +and something changes the working directory. The reason being that it reopens the file based on the +original path to do the duplication. To delete a file, use `fs_remove()`: @@ -136,6 +178,8 @@ To delete a file, use `fs_remove()`: fs_remove(pFS, "file.txt"); ``` +Note that files are deleted permanently. There is no recycle bin or trash functionality. + Files can be renamed and moved with `fs_rename()`: ```c @@ -171,7 +215,7 @@ fs_archive_type pArchiveTypes[] = {FS_PAK, "pak"} }; -fs_config fsConfig = fs_config_init(FS_STDIO, NULL, NULL); +fs_config fsConfig = fs_config_init_default(); fsConfig.pArchiveTypes = pArchiveTypes; fsConfig.archiveTypeCount = sizeof(pArchiveTypes) / sizeof(pArchiveTypes[0]); @@ -181,8 +225,8 @@ fs_init(&fsConfig, &pFS); In the code above we are registering support for ZIP archives (`FS_ZIP`) and Quake PAK archives (`FS_PAK`). Whenever a file with a "zip" or "pak" extension is found, the library will be able to -access the archive. The library will determine whether or not a file is an archive based on it's -extension. You can use whatever extension you would like for a backend, and you can associated +access the archive. The library will determine whether or not a file is an archive based on its +extension. You can use whatever extension you would like for a backend, and you can associate multiple extensions to the same backend. You can also associate different backends to the same extension, in which case the library will use the first one that works. If the extension of a file does not match with one of the registered archive types it'll assume it's not an archive and will @@ -297,9 +341,10 @@ archive should be compressed. ------------- There is no ability to change the working directory in this library. Instead you can mount a physical directory to a virtual path, similar in concept to Unix operating systems. The difference, -however, is that you can mount multiple directories to the same mount point in which case a -prioritization system will be used. There are separate mount points for reading and writing. Below -is an example of mounting for reading: +however, is that you can mount multiple directories to the same virtual path in which case a +prioritization system will be used (only for reading - in write mode only a single mount is used). +There are separate mount points for reading and writing. Below is an example of mounting for +reading: ```c fs_mount(pFS, "/some/actual/path", NULL, FS_READ); @@ -412,8 +457,13 @@ opening a file with this kind of mount point, you would need to specify the lead fs_file_open(pFS, "/gamedata/file.txt", FS_READ, &pFile); // Note how the path starts with "/". ``` +Important: When using mount points that start with "/", if the file cannot be opened from the mount, +it will fall back to trying the actual absolute path. To prevent this and ensure files are only +loaded from the mount point, use the `FS_ONLY_MOUNTS` flag when opening files. Alternatively, +simply avoid using "/" prefixed mounts and instead use `FS_NO_ABOVE_ROOT_NAVIGATION` for security. -You can also mount a archives to a virtual path: + +You can also mount an archive to a virtual path: ```c fs_mount(pFS, "/usr/share/mygame/gamedata.zip", "gamedata", FS_READ); @@ -422,7 +472,7 @@ fs_mount(pFS, "/usr/share/mygame/gamedata.zip", "gamedata", FS_READ); In order to do this, the `fs` object must have been configured with support for the given archive type. Note that writing directly into an archive is not supported by this API. To write into an archive, the backend itself must support writing, and you will need to manually initialize a `fs` -object for the archive an write into it directly. +object for the archive and write into it directly. The examples above have been hard coding paths, but you can use `fs_mount_sysdir()` to mount a @@ -467,6 +517,15 @@ using the standard file system. That is, it'll work exactly as if you were using and you will not have access to mount points. Keep in mind that there is no notion of a "current directory" in this library so you'll be stuck with the initial working directory. +You can also skip mount points when opening a file by using the `FS_IGNORE_MOUNTS` flag: + +```c +fs_file_open(pFS, "/absolute/path/to/file.txt", FS_READ | FS_IGNORE_MOUNTS, &pFile); +``` + +This can be useful when you want to access a file directly without going through the mount system, +such as when working with temporary files. + 1.4. Enumeration @@ -565,21 +624,6 @@ if (result != FS_SUCCESS) { } ``` -If you just want to create a temporary file and don't care about the name, you can use -`fs_file_open()` with the `FS_TEMP` flag. In this case, the library will treat the file path -as the prefix: - -```c -fs_file* pFile; -result = fs_file_open(pFS, "prefix", FS_TEMP, &pFile); -if (result != FS_SUCCESS) { - // Failed to open temporary file. -} -``` - -The use of temporary files is only valid with `fs` objects that make use of the standard file -system, such as the stdio backend. - The prefix can include subdirectories, such as "myapp/subdir". In this case the library will create the directory hierarchy for you, unless you pass in `FS_NO_CREATE_DIRS`. Note that not all platforms treat the name portion of the prefix the same. In particular, Windows will only use up to @@ -610,11 +654,28 @@ The following points apply regarding thread safety. -3. Backends +3. Platform Considerations +============================ + +3.1. Windows +-------------- +On Windows, Unicode support is determined by the `UNICODE` preprocessor define. When `UNICODE` is +defined, the library will use the wide character versions of Windows APIs. When not defined, it +will use the ANSI versions. + +3.2. POSIX +------------ +On POSIX platforms, `ftruncate()` is unavailable with `-std=c89` unless `_XOPEN_SOURCE` is defined +to >= 500. This may affect the availability of file truncation functionality when using strict C89 +compilation. + + + +4. Backends =========== -You can implement custom backends to support different file systems and archive formats. A stdio -backend is the default backend and is built into the library. A backend implements the functions -in the `fs_backend` structure. +You can implement custom backends to support different file systems and archive formats. A POSIX +or Win32 backend is the default backend depending on the platform, and is built into the library. +A backend implements the functions in the `fs_backend` structure. A ZIP backend is included in the "extras" folder of this library's repository. Refer to this for a complete example for how to implement a backend (not including write support, but I'm sure @@ -653,93 +714,170 @@ This pattern will be a central part of how backends are implemented. If you don' backend-specific data, you can just return 0 from `alloc_size()` and simply not reference the backend data pointer. -The main library will allocate the `fs` object, including any additional space specified by the -`alloc_size` function. Once this is done, it'll call the `init` function to initialize the backend. -This function will take a pointer to the `fs` object, the backend-specific configuration data, and -a stream object. The stream is used to provide the backend with the raw data of an archive, which -will be required for archive backends like ZIP. If your backend requires this, you should check -for if the stream is null, and if so, return an error. See section "4. Streams" for more details -on how to use streams. You need not take a copy of the stream pointer for use outside of `init()`. -Instead you can just use `fs_get_stream()` to get the stream object when you need it. You should -not ever close or otherwise take ownership of the stream - that will be handled at a higher level. -The `uninit` function is where you should do any cleanup. Do not close the stream here. +4.1. Backend Functions +---------------------- +alloc_size + This function should return the size of the backend-specific data to associate with the `fs` + object. If no additional data is required, return 0. + + The main library will allocate the `fs` object, including any additional space specified by the + `alloc_size` function. -The `ioctl` function is optional. You can use this to implement custom IO control commands. Return -`FS_INVALID_COMMAND` if the command is not recognized. The format of the `pArg` parameter is -command specific. If the backend does not need to implement this function, it can be left as `NULL` -or return `FS_NOT_IMPLEMENTED`. +init + This function is called after `alloc_size()` and after the `fs` object has been allocated. This + is where you should initialize the backend. -The `remove` function is used to delete a file or directory. This is not recursive. If the path is -a directory, the backend should return an error if it is not empty. Backends do not need to -implement this function in which case they can leave the callback pointer as `NULL`, or have it -return `FS_NOT_IMPLEMENTED`. + This function will take a pointer to the `fs` object, the backend-specific configuration data, + and a stream object. The stream is used to provide the backend with the raw data of an archive, + which will be required for archive backends like ZIP. If your backend requires this, you should + check for if the stream is null, and if so, return an error. See section "5. Streams" for more + details on how to use streams. You need not take a copy of the stream pointer for use outside + of `init`. Instead you can just use `fs_get_stream()` to get the stream object when you need it. + You should not ever close or otherwise take ownership of the stream - that will be handled + at a higher level. -The `rename` function is used to rename a file. This will act as a move if the source and -destination are in different directories. If the destination already exists, it should be -overwritten. This function is optional and can be left as `NULL` or return `FS_NOT_IMPLEMENTED`. +uninit + This is where you should do any cleanup. Do not close the stream here. -The `mkdir` function is used to create a directory. This is not recursive. If the directory already -exists, the backend should return `FS_SUCCESS`. This function is optional and can be left as `NULL` -or return `FS_NOT_IMPLEMENTED`. +ioctl + This function is optional. You can use this to implement custom IO control commands. Return + `FS_INVALID_OPERATION` if the command is not recognized. The format of the `pArg` parameter is + command specific. If the backend does not need to implement this function, it can be left as + `NULL` or return `FS_NOT_IMPLEMENTED`. -The `info` function is used to get information about a file. If the backend does not have the -notion of the last modified or access time, it can set those values to 0. Set `directory` to 1 (or -FS_TRUE) if it's a directory. Likewise, set `symlink` to 1 if it's a symbolic link. It is important -that this function return the info of the exact file that would be opened with `file_open()`. This -function is mandatory. +remove + This function is used to delete a file or directory. This is not recursive. If the path is + a directory, the backend should return an error if it is not empty. Backends do not need to + implement this function in which case they can leave the callback pointer as `NULL`, or have + it return `FS_NOT_IMPLEMENTED`. -Like when initializing a `fs` object, the library needs to know how much backend-specific data to -allocate for the `fs_file` object. This is done with the `file_alloc_size` function. This function -is basically the same as `alloc_size` for the `fs` object, but for `fs_file`. If the backend does -not need any additional data, it can return 0. The backend can access this data via -`fs_file_get_backend_data()`. +rename + This function is used to rename a file. This will act as a move if the source and destination + are in different directories. If the destination already exists, it should be overwritten. This + function is optional and can be left as `NULL` or return `FS_NOT_IMPLEMENTED`. -The `file_open` function is where the backend should open the file. If the `fs` object that owns -the file was initialized with a stream, i.e. it's an archive, the stream will be non-null. You -should store this pointer for later use in `file_read`, etc. Do *not* make a duplicate of the -stream with `fs_stream_duplicate()`. Instead just take a copy of the pointer. The `openMode` -parameter will be a combination of `FS_READ`, `FS_WRITE`, `FS_TRUNCATE`, `FS_APPEND` and -`FS_OVERWRITE`. When opening in write mode (`FS_WRITE`), it will default to truncate mode. You -should ignore the `FS_OPAQUE`, `FS_VERBOSE` and `FS_TRANSPARENT` flags. If the file does not exist, -the backend should return `FS_DOES_NOT_EXIST`. If the file is a directory, it should return -`FS_IS_DIRECTORY`. +mkdir + This function is used to create a directory. This is not recursive. If the directory already + exists, the backend should return `FS_ALREADY_EXISTS`. If a parent directory does not exist, + the backend should return `FS_DOES_NOT_EXIST`. This function is optional and can be left as + `NULL` or return `FS_NOT_IMPLEMENTED`. -The file should be closed with `file_close`. This is where the backend should release any resources -associated with the file. Do not uninitialize the stream here - it'll be cleaned up at a higher -level. +info + This function is used to get information about a file. If the backend does not have the notion + of the last modified or access time, it can set those values to 0. Set `directory` to 1 (or + `FS_TRUE`) if it's a directory. Likewise, set `symlink` to 1 if it's a symbolic link. It is + important that this function return the info of the exact file that would be opened with + `file_open()`. This function is mandatory. -The `file_read` function is used to read data from the file. The backend should return `FS_AT_END` -when the end of the file is reached, but only if the number of bytes read is 0. +file_alloc_size + Like when initializing a `fs` object, the library needs to know how much backend-specific data + to allocate for the `fs_file` object. This is done with the `file_alloc_size` function. This + function is basically the same as `alloc_size` for the `fs` object, but for `fs_file`. If the + backend does not need any additional data, it can return 0. The backend can access this data + via `fs_file_get_backend_data()`. -The `file_write` function is used to write data to the file. If the file is opened in append mode, -the backend should seek to the end of the file before writing. This is optional and need only be -specified if the backend supports writing. +file_open + The `file_open` function is where the backend should open the file. + + If the `fs` object that owns the file was initialized with a stream, the stream will be + non-null. If your backends requires a stream, you should check that the stream is null, and if + so, return an error. The reason you need to check for this is that the application itself may + erroneously attempt to initialize a `fs` object for your backend without a stream, and since + the library cannot know whether or not a backend requires a stream, it cannot check this on + your behalf. -The `file_seek` function is used to seek the cursor. The backend should return `FS_BAD_SEEK` if the -seek is out of bounds. + If the backend requires a stream, it should take a copy of only the pointer and store it for + later use. Do *not* make a duplicate of the stream with `fs_stream_duplicate()`. -The `file_tell` function is used to get the current cursor position. There is only one cursor, even -when the file is opened in read and write mode. + Backends need only handle the following open mode flags: -The `file_flush` function is used to flush any buffered data to the file. This is optional and can -be left as `NULL` or return `FS_NOT_IMPLEMENTED`. + FS_READ + FS_WRITE + FS_TRUNCATE + FS_APPEND + FS_EXCLUSIVE -The `file_info` function is used to get information about an opened file. It returns the same -information as `info` but for an opened file. This is mandatory. + All other flags are for use at a higher level and should be ignored. -The `file_duplicate` function is used to duplicate a file. The destination file will be a new file -and already allocated. The backend need only copy the necessary backend-specific data to the new -file. + When opening in write mode (`FS_WRITE`), the backend should default to overwrite mode. If + `FS_TRUNCATE` is specified, the file should be truncated to 0 length. If `FS_APPEND` is + specified, all writes should happen at the end of the file regardless of the position of the + write cursor. If `FS_EXCLUSIVE` is specified, opening should fail if the file already exists. + In all write modes, the file should be created if it does not already exist. -The `first`, `next` and `free_iterator` functions are used to enumerate the contents of a directory. -If the directory is empty, or an error occurs, `fs_first` should return `NULL`. The `next` function -should return `NULL` when there are no more entries. When `next` returns `NULL`, the backend needs -to free the iterator object. The `free_iterator` function is used to free the iterator object -explicitly. The backend is responsible for any memory management of the name string. A typical way -to deal with this is to allocate the allocate additional space for the name immediately after the -`fs_iterator` allocation. + If any flags cannot be supported, the backend should return an error. + When opening in read mode, if the file does not exist, `FS_DOES_NOT_EXIST` should be returned. + Similarly, if the file is a directory, `FS_IS_DIRECTORY` should be returned. + + Before calling into this function, the library will normalize all paths to use forward slashes. + Therefore, backends must support forward slashes ("/") as path separators. + +file_close + This function is where the file should be closed. This is where the backend should release any + resources associated with the file. Do not uninitialize the stream here - it'll be cleaned up + at a higher level. + +file_read + This is used to read data from the file. The backend should return `FS_AT_END` when the end of + the file is reached, but only if the number of bytes read is 0. + +file_write + This is used to write data to the file. If the file is opened in append mode, the backend + should always ensure writes are appended to the end, regardless of the position of the write + cursor. This is optional and need only be specified if the backend supports writing. + +file_seek + The `file_seek` function is used to seek the read/write cursor. The backend should allow + seeking beyond the end of the file. If the file is opened in write mode and data is written + beyond the end of the file, the file should be extended, and if possible made into a sparse + file. If sparse files are not supported, the backend should fill the gap with data, preferably + with zeros if possible. + + Attempting to seek to before the start of the file should return `FS_BAD_SEEK`. + +file_tell + The `file_tell` function is used to get the current cursor position. There is only one cursor, + even when the file is opened in read and write mode. + +file_flush + The `file_flush` function is used to flush any buffered data to the file. This is optional and + can be left as `NULL` or return `FS_NOT_IMPLEMENTED`. + +file_truncate + The `file_truncate` function is used to truncate a file to the current cursor position. This is + only useful for write mode, so is therefore optional and can be left as `NULL` or return + `FS_NOT_IMPLEMENTED`. + +file_info + The `file_info` function is used to get information about an opened file. It returns the same + information as `info` but for an opened file. This is mandatory. + +file_duplicate + The `file_duplicate` function is used to duplicate a file. The destination file will be a new + file and already allocated. The backend need only copy the necessary backend-specific data to + the new file. The backend must ensure that the duplicated file is totally independent of the + original file and has its own independent read/write pointer. If the backend is unable to + support duplicated files having their own independent read/write pointer, it must return an + error. + + If a backend cannot support duplication, it can leave this as `NULL` or return + `FS_NOT_IMPLEMENTED`. However, if this is not implemented, the backend will not be able to open + files within archives. + +first, next, free_iterator + The `first`, `next` and `free_iterator` functions are used to enumerate the contents of a + directory. If the directory is empty, or an error occurs, `fs_first` should return `NULL`. The + `next` function should return `NULL` when there are no more entries. When `next` returns + `NULL`, the backend needs to free the iterator object. The `free_iterator` function is used to + free the iterator object explicitly. The backend is responsible for any memory management of + the name string. A typical way to deal with this is to allocate additional space for the name + immediately after the `fs_iterator` allocation. + + +4.2. Thread Safety +------------------ Backends are responsible for guaranteeing thread-safety of different files across different threads. This should typically be quite easy since most system backends, such as stdio, are already thread-safe, and archive backends are typically read-only which should make thread-safety trivial @@ -748,12 +886,12 @@ But when you have two different file handles, they must be able to be used on tw at the same time. -4. Streams +5. Streams ========== Streams are the data delivery mechanism for archive backends. You can implement custom streams, but this should be uncommon because `fs_file` itself is a stream, and a memory stream is included in the library called `fs_memory_stream`. Between these two the majority of use cases should be -covered. +covered. You can retrieve the stream associated with a `fs_file` using `fs_file_get_stream()`. A stream is initialized using a specialized initialization function depending on the stream type. For `fs_file`, simply opening the file is enough. For `fs_memory_stream`, you need to call @@ -923,37 +1061,71 @@ typedef unsigned int fs_bool32; /* BEG fs_result.h */ -typedef enum fs_result +typedef enum { - /* Compression Non-Error Result Codes. */ - FS_HAS_MORE_OUTPUT = 102, /* Some stream has more output data to be read, but there's not enough room in the output buffer. */ - FS_NEEDS_MORE_INPUT = 100, /* Some stream needs more input data before it can be processed. */ + FS_SUCCESS = 0, + FS_ERROR = -1, /* Generic, unknown error. */ + FS_INVALID_ARGS = -2, + FS_INVALID_OPERATION = -3, + FS_OUT_OF_MEMORY = -4, + FS_OUT_OF_RANGE = -5, + FS_ACCESS_DENIED = -6, + FS_DOES_NOT_EXIST = -7, + FS_ALREADY_EXISTS = -8, + FS_TOO_MANY_OPEN_FILES = -9, + FS_INVALID_FILE = -10, + FS_TOO_BIG = -11, + FS_PATH_TOO_LONG = -12, + FS_NAME_TOO_LONG = -13, + FS_NOT_DIRECTORY = -14, + FS_IS_DIRECTORY = -15, + FS_DIRECTORY_NOT_EMPTY = -16, + FS_AT_END = -17, + FS_NO_SPACE = -18, + FS_BUSY = -19, + FS_IO_ERROR = -20, + FS_INTERRUPT = -21, + FS_UNAVAILABLE = -22, + FS_ALREADY_IN_USE = -23, + FS_BAD_ADDRESS = -24, + FS_BAD_SEEK = -25, + FS_BAD_PIPE = -26, + FS_DEADLOCK = -27, + FS_TOO_MANY_LINKS = -28, + FS_NOT_IMPLEMENTED = -29, + FS_NO_MESSAGE = -30, + FS_BAD_MESSAGE = -31, + FS_NO_DATA_AVAILABLE = -32, + FS_INVALID_DATA = -33, + FS_TIMEOUT = -34, + FS_NO_NETWORK = -35, + FS_NOT_UNIQUE = -36, + FS_NOT_SOCKET = -37, + FS_NO_ADDRESS = -38, + FS_BAD_PROTOCOL = -39, + FS_PROTOCOL_UNAVAILABLE = -40, + FS_PROTOCOL_NOT_SUPPORTED = -41, + FS_PROTOCOL_FAMILY_NOT_SUPPORTED = -42, + FS_ADDRESS_FAMILY_NOT_SUPPORTED = -43, + FS_SOCKET_NOT_SUPPORTED = -44, + FS_CONNECTION_RESET = -45, + FS_ALREADY_CONNECTED = -46, + FS_NOT_CONNECTED = -47, + FS_CONNECTION_REFUSED = -48, + FS_NO_HOST = -49, + FS_IN_PROGRESS = -50, + FS_CANCELLED = -51, + FS_MEMORY_ALREADY_MAPPED = -52, + FS_DIFFERENT_DEVICE = -53, + FS_CHECKSUM_MISMATCH = -100, + FS_NO_BACKEND = -101, - /* Main Result Codes. */ - FS_SUCCESS = 0, - FS_ERROR = -1, /* Generic, unknown error. */ - FS_INVALID_ARGS = -2, - FS_INVALID_OPERATION = -3, - FS_OUT_OF_MEMORY = -4, - FS_OUT_OF_RANGE = -5, - FS_ACCESS_DENIED = -6, - FS_DOES_NOT_EXIST = -7, - FS_ALREADY_EXISTS = -8, - FS_INVALID_FILE = -10, - FS_TOO_BIG = -11, - FS_PATH_TOO_LONG = -12, - FS_NOT_DIRECTORY = -14, - FS_IS_DIRECTORY = -15, - FS_DIRECTORY_NOT_EMPTY = -16, - FS_AT_END = -17, - FS_BUSY = -19, - FS_INTERRUPT = -21, - FS_BAD_SEEK = -25, - FS_NOT_IMPLEMENTED = -29, - FS_TIMEOUT = -34, - FS_CHECKSUM_MISMATCH = -100, - FS_NO_BACKEND = -101 + /* Non-Error Result Codes. */ + FS_NEEDS_MORE_INPUT = 100, /* Some stream needs more input data before it can be processed. */ + FS_HAS_MORE_OUTPUT = 102 /* Some stream has more output data to be read, but there's not enough room in the output buffer. */ } fs_result; + +FS_API const char* fs_result_to_string(fs_result result); /* END fs_result.h */ @@ -1078,41 +1250,146 @@ typedef enum fs_sysdir_type FS_SYSDIR_CACHE } fs_sysdir_type; -FS_API size_t fs_sysdir(fs_sysdir_type type, char* pDst, size_t dstCap); /* Returns the length of the string, or 0 on failure. If the return value is >= to dstCap it means the output buffer was too small. Use the returned value to know how big to make the buffer. Set pDst to NULL to calculate the required length. */ +/* +Get the path of a known system directory. + +The returned path will be null-terminated. If the output buffer is too small, the required size +will be returned, not including the null terminator. + + +Parameters +---------- +type : (in) + The type of system directory to query. See `fs_sysdir_type` for recognized values. + +pDst : (out, optional) + A pointer to a buffer that will receive the path. If NULL, the function will return the + required length of the buffer, not including the null terminator. + +dstCap : (in) + The capacity of the output buffer, in bytes. This is ignored if `pDst` is NULL. + + +Return Value +------------ +Returns the length of the string, not including the null terminator. Returns 0 on failure. If the +return value is >= to `dstCap` it means the output buffer was too small. Use the returned value to +know how big to make the buffer. + + +Example 1 - Querying the Home Directory +--------------------------------------- +```c +size_t len = fs_sysdir(FS_SYSDIR_HOME, NULL, 0); +if (len == 0) { + // Failed to query the length of the home directory path. +} + +char* pPath = (char*)malloc(len + 1); // +1 for null terminator. +if (pPath == NULL) { + // Out of memory. +} + +len = fs_sysdir(FS_SYSDIR_HOME, pPath, len + 1); +if (len == 0) { + // Failed to get the home directory path. +} + +printf("Home directory: %s\n", pPath); +free(pPath); +``` + + +See Also +-------- +fs_sysdir_type +fs_mktmp() +*/ +FS_API size_t fs_sysdir(fs_sysdir_type type, char* pDst, size_t dstCap); /* END fs_sysdir.h */ /* BEG fs_mktmp.h */ -/* Make sure these options do not conflict with FS_NO_CREATE_DIRS. */ -#define FS_MKTMP_DIR 0x0800 /* Create a temporary directory. */ -#define FS_MKTMP_FILE 0x1000 /* Create a temporary file. */ +/* +Create a temporary file or directory. -FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap, int options); /* Returns FS_PATH_TOO_LONG if the output buffer is too small. Use FS_MKTMP_FILE to create a file and FS_MKTMP_DIR to create a directory. Use FS_MKTMP_BASE_DIR to query the system base temp folder. pPrefix should not include the name of the system's base temp directory. Do not include paths like "/tmp" in the prefix. The output path will include the system's base temp directory and the prefix. */ +This function creates a temporary file or directory with a unique name based on the provided +prefix. The full path to the created file or directory is returned in `pTmpPath`. + +Use the option flag `FS_MKTMP_FILE` to create a temporary file, or `FS_MKTMP_DIR` to create a +temporary directory. + + +Parameters +---------- +pPrefix : (in) + A prefix for the temporary file or directory name. This should not include the system's base + temp directory path. Do not include paths like "/tmp" in the prefix. The output path will + include the system's base temp directory and the prefix. + + The prefix can include subdirectories, such as "myapp/subdir". In this case the library will + create the directory hierarchy for you, unless you pass in `FS_NO_CREATE_DIRS`. Note that not + all platforms treat the name portion of the prefix the same. In particular, Windows will only + use up to the first 3 characters of the name portion of the prefix. + +pTmpPath : (out) + A pointer to a buffer that will receive the full path of the created temporary file or + directory. This will be null-terminated. + +tmpPathCap : (in) + The capacity of the output buffer, in bytes. + +options : (in) + Options for creating the temporary file or directory. Can be a combination of the following: + FS_MKTMP_FILE + Creates a temporary file. Cannot be used with FS_MKTMP_DIR. + + FS_MKTMP_DIR + Creates a temporary directory. Cannot be used with FS_MKTMP_FILE. + + FS_NO_CREATE_DIRS + Do not create parent directories if they do not exist. If this flag is not set, + parent directories will be created as needed. + + +Return Value +------------ +Returns `FS_SUCCESS` on success; any other error code on failure. Will return `FS_PATH_TOO_LONG` if +the output buffer is too small. +*/ +FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap, int options); /* Returns FS_PATH_TOO_LONG if the output buffer is too small. Use FS_MKTMP_FILE to create a file and FS_MKTMP_DIR to create a directory. pPrefix should not include the name of the system's base temp directory. Do not include paths like "/tmp" in the prefix. The output path will include the system's base temp directory and the prefix. */ /* END fs_mktmp.h */ /* BEG fs.h */ -/* Open mode flags. */ -#define FS_READ 0x0001 -#define FS_WRITE 0x0002 -#define FS_APPEND (FS_WRITE | 0x0004) -#define FS_OVERWRITE (FS_WRITE | 0x0008) -#define FS_TRUNCATE (FS_WRITE) -#define FS_TEMP (FS_TRUNCATE | 0x0010) +/************************************************************************************************** -#define FS_TRANSPARENT 0x0000 /* Default. Opens a file such that archives of a known type are handled transparently. For example, "somefolder/archive.zip/file.txt" can be opened with "somefolder/file.txt" (the "archive.zip" part need not be specified). This assumes the `fs` object has been initialized with support for the relevant archive types. */ -#define FS_OPAQUE 0x0010 /* When used, files inside archives cannot be opened automatically. For example, "somefolder/archive.zip/file.txt" will fail. Mounted archives work fine. */ -#define FS_VERBOSE 0x0020 /* When used, files inside archives can be opened, but the name of the archive must be specified explicitly in the path, such as "somefolder/archive.zip/file.txt" */ +Open Mode Flags -#define FS_NO_CREATE_DIRS 0x0040 /* When used, directories will not be created automatically when opening files for writing. */ -#define FS_IGNORE_MOUNTS 0x0080 /* When used, mounted directories and archives will be ignored when opening and iterating files. */ -#define FS_ONLY_MOUNTS 0x0100 /* When used, only mounted directories and archives will be considered when opening and iterating files. */ -#define FS_NO_SPECIAL_DIRS 0x0200 /* When used, the presence of special directories like "." and ".." will be result in an error when opening files. */ -#define FS_NO_ABOVE_ROOT_NAVIGATION 0x0400 /* When used, navigating above the mount point with leading ".." segments will result in an error. Can be also be used with fs_path_normalize(). */ +**************************************************************************************************/ +#define FS_READ 0x0001 /* Used by: fs_file_open(), fs_info(), fs_first(), fs_open_archive*(), fs_mount*() */ +#define FS_WRITE 0x0002 /* Used by: fs_file_open(), fs_info(), fs_first(), fs_open_archive*(), fs_mount*() */ +#define FS_TRUNCATE 0x0004 /* Used by: fs_file_open() */ +#define FS_APPEND 0x0008 /* Used by: fs_file_open() */ +#define FS_EXCLUSIVE 0x0010 /* Used by: fs_file_open() */ -#define FS_LOWEST_PRIORITY 0x2000 /* Only used with mounting. When set will create the mount with a lower priority to existing mounts. */ +#define FS_TRANSPARENT 0x0000 /* Used by: fs_file_open(), fs_info(), fs_first() */ +#define FS_OPAQUE 0x0020 /* Used by: fs_file_open(), fs_info(), fs_first() */ +#define FS_VERBOSE 0x0040 /* Used by: fs_file_open(), fs_info(), fs_first() */ + +#define FS_NO_CREATE_DIRS 0x0080 /* Used by: fs_file_open(), fs_info(), fs_mount(), fs_mkdir(), fs_mktmp() */ +#define FS_IGNORE_MOUNTS 0x0100 /* Used by: fs_file_open(), fs_info(), fs_first() */ +#define FS_ONLY_MOUNTS 0x0200 /* Used by: fs_file_open(), fs_info(), fs_first() */ +#define FS_NO_SPECIAL_DIRS 0x0400 /* Used by: fs_file_open(), fs_info(), fs_first() */ +#define FS_NO_ABOVE_ROOT_NAVIGATION 0x0800 /* Used by: fs_file_open(), fs_info(), fs_first() */ + +#define FS_LOWEST_PRIORITY 0x1000 /* Used by: fs_mount*() */ + +#define FS_MKTMP_DIR 0x2000 /* Used by: fs_mktmp() */ +#define FS_MKTMP_FILE 0x4000 /* Used by: fs_mktmp() */ + +#define FS_NO_INCREMENT_REFCOUNT 0x8000 /* Do not use. Internal use only. Used with fs_open_archive_ex() internally. */ -#define FS_NO_INCREMENT_REFCOUNT 0x4000 /* Internal use only. Used with fs_open_archive_ex() internally. */ /* Garbage collection policies.*/ #define FS_GC_POLICY_THRESHOLD 0x0001 /* Only garbage collect unreferenced opened archives until the count is below the configured threshold. */ @@ -1126,6 +1403,13 @@ typedef struct fs_file_info fs_file_info; typedef struct fs_iterator fs_iterator; typedef struct fs_backend fs_backend; + +/* File paths for stdin, stdout and stderr. Use these with fs_file_open(). */ +extern const char* FS_STDIN; +extern const char* FS_STDOUT; +extern const char* FS_STDERR; + + /* This callback is fired when the reference count of a fs object changes. This is useful if you want to do some kind of advanced memory management, such as garbage collection. If the new reference count @@ -1139,6 +1423,9 @@ typedef struct fs_archive_type const char* pExtension; } fs_archive_type; +FS_API fs_archive_type fs_archive_type_init(const fs_backend* pBackend, const char* pExtension); + + struct fs_file_info { fs_uint64 size; @@ -1169,7 +1456,7 @@ struct fs_config }; FS_API fs_config fs_config_init_default(void); -FS_API fs_config fs_config_init(const fs_backend* pBackend, void* pBackendConfig, fs_stream* pStream); +FS_API fs_config fs_config_init(const fs_backend* pBackend, const void* pBackendConfig, fs_stream* pStream); struct fs_backend @@ -1179,18 +1466,18 @@ struct fs_backend void (* uninit )(fs* pFS); fs_result (* ioctl )(fs* pFS, int op, void* pArg); /* Optional. */ fs_result (* remove )(fs* pFS, const char* pFilePath); - fs_result (* rename )(fs* pFS, const char* pOldName, const char* pNewName); - fs_result (* mkdir )(fs* pFS, const char* pPath); /* This is not recursive. Return FS_SUCCESS if directory already exists. */ + fs_result (* rename )(fs* pFS, const char* pOldPath, const char* pNewPath); /* Return FS_DIFFERENT_DEVICE if the old and new paths are on different devices and would require a copy. */ + fs_result (* mkdir )(fs* pFS, const char* pPath); /* This is not recursive. Return FS_ALREADY_EXISTS if directory already exists. Return FS_DOES_NOT_EXIST if a parent directory does not exist. */ fs_result (* info )(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo); /* openMode flags can be ignored by most backends. It's primarily used by passthrough style backends. */ size_t (* file_alloc_size )(fs* pFS); fs_result (* file_open )(fs* pFS, fs_stream* pStream, const char* pFilePath, int openMode, fs_file* pFile); /* Return 0 on success or an errno result code on error. Return FS_DOES_NOT_EXIST if the file does not exist. pStream will be null if the backend does not need a stream (the `pFS` object was not initialized with one). */ - fs_result (* file_open_handle)(fs* pFS, void* hBackendFile, fs_file* pFile); /* Optional. Open a file from a file handle. Backend-specific. The format of hBackendFile will be specified by the backend. */ void (* file_close )(fs_file* pFile); fs_result (* file_read )(fs_file* pFile, void* pDst, size_t bytesToRead, size_t* pBytesRead); /* Return 0 on success, or FS_AT_END on end of file. Only return FS_AT_END if *pBytesRead is 0. Return an errno code on error. Implementations must support reading when already at EOF, in which case FS_AT_END should be returned and *pBytesRead should be 0. */ fs_result (* file_write )(fs_file* pFile, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten); fs_result (* file_seek )(fs_file* pFile, fs_int64 offset, fs_seek_origin origin); fs_result (* file_tell )(fs_file* pFile, fs_int64* pCursor); fs_result (* file_flush )(fs_file* pFile); + fs_result (* file_truncate )(fs_file* pFile); fs_result (* file_info )(fs_file* pFile, fs_file_info* pInfo); fs_result (* file_duplicate )(fs_file* pFile, fs_file* pDuplicate); /* Duplicate the file handle. */ fs_iterator* (* first )(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen); @@ -1198,56 +1485,1793 @@ struct fs_backend void (* free_iterator )(fs_iterator* pIterator); /* <-- Free the `fs_iterator` object here since `first` and `next` were the ones who allocated it. Also do any uninitialization routines. */ }; +/* +Initializes a file system object. + +This is the main object that you will use to open files. There are different types of file system +backends, such as the standard file system, ZIP archives, etc. which you can configure via the +config. + +The config is used to select which backend to use and to register archive types against known +file extensions. If you just want to use the regular file system and don't care about archives, +you can just pass in NULL for the config. + +By registering archive types, you'll be able to open files from within them straight from a file +path without without needing to do any manual management. For example, if you register ZIP archives +to the ".zip" extension, you can open a file from a path like this: + + somefolder/archive.zip/somefile.txt + +These can also be handled transparently, so the above path can be opened with this: + + somefolder/somefile.txt + +Note that the `archive.zip` part is not needed. If you want this functionality, you must register +the archive types with the config. + +Most of the time you will use a `fs` object that represents the normal file system, which is the +default backend if you don't pass in a config, but sometimes you may want to have a `fs` object +that represents an archive, such as a ZIP archive. To do this, you need to provide a stream that +reads the actual data of the archive. Most of the time you will just use the stream provided by +a `fs_file` object you opened earlier from the regular file system, but if you would rather source +your data from elsewhere, like a memory buffer, you can pass in your own stream. You also need to +specify the backend to use, such as `FS_ZIP` in the case of ZIP archives. See examples below for +more information. + +If you want to use custom allocation callbacks, you can do so by passing in a pointer to a +`fs_allocation_callbacks` struct into the config. If you pass in NULL, the default allocation +callbacks which use malloc/realloc/free will be used. If you pass in non-NULL, this function will +make a copy of the struct, so you can free or modify the struct after this function returns. + + +Parameters +---------- +pConfig : (in, optional) + A pointer to a configuration struct. Can be NULL, in which case the regular file system will be + used, and archives will not be supported unless explicitly mounted later with `fs_mount_fs()`. + +ppFS : (out) + A pointer to a pointer which will receive the initialized file system object. The object must + be uninitialized with `fs_uninit()` when no longer needed. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. + + +Example 1 - Basic Usage +----------------------- +The example below shows how to initialize a `fs` object which uses the regular file system and does +not support archives. This is the most basic usage of the `fs` object. + +```c +#include "fs.h" + +... + +fs* pFS; +fs_result result = fs_init(NULL, &pFS); +if (result != FS_SUCCESS) { + // Handle error. +} + +... + +fs_uninit(pFS); +``` + + +Example 2 - Supporting Archives +------------------------------- +The example below shows how to initialize a `fs` object which uses the regular file system and +supports ZIP archives. Error checking has been omitted for clarity. + +```c +#include "fs.h" +#include "extras/backends/zip/fs_zip.h" // For FS_ZIP backend. + +... + +fs* pFS; +fs_config fsConfig; + +// Archive types are supported by mapping a backend (`FS_ZIP` in this case) to a file extension. +fs_archive_type pArchiveTypes[] = +{ + {FS_ZIP, "zip"} +}; + +// The archive types are registered via the config. +fsConfig = fs_config_init_default(); +fsConfig.pArchiveTypes = pArchiveTypes; +fsConfig.archiveTypeCount = sizeof(pArchiveTypes) / sizeof(pArchiveTypes[0]); + +// Once the config is ready, initialize the fs object. +fs_init(&fsConfig, &pFS); + +// Now you can open files from within ZIP archives from a file path. +fs_file* pFileInArchive; +fs_file_open(pFS, "somefolder/archive.zip/somefile.txt", FS_READ, &pFileInArchive); +``` + + +Example 3 - Archive Backends +---------------------------- +This example shows how you can open an archive file directly, and then create a new `fs` object +which uses the archive as its backend. This is useful if, for example, you want to use a ZIP file +as a virtual file system. + +```c +#include "fs.h" +#include "extras/backends/zip/fs_zip.h" // For FS_ZIP backend. + +... + +fs* pFS; // Main file system object. +fs* pArchiveFS; // File system object for the archive. +fs_config archiveConfig; +fs_file* pArchiveFile; // The actual archive file. + +// Open the archive file itself first, usually from the regular file system. +fs_file_open(pFS, "somefolder/archive.zip", FS_READ, &pArchiveFile); + +... + +// Setup the config for the archive `fs` object such that it uses the ZIP backend (FS_ZIP), and +// reads from the stream of the actual archive file (pArchiveFile) which was opened earlier. +archiveConfig = fs_config_init(FS_ZIP, NULL, fs_file_get_stream(pArchiveFile)); + +// With the config ready we can now initialize the `fs` object for the archive. +fs_init(&archiveConfig, &pArchiveFS); + +... + +// Now that we have a `fs` object representing the archive, we can open files from within it like +// normal. +fs_file* pFileInArchive; +fs_file_open(pArchiveFS, "fileinsidearchive.txt", FS_READ, &pFileInArchive); +``` + + +See Also +-------- +fs_uninit() +*/ FS_API fs_result fs_init(const fs_config* pConfig, fs** ppFS); + + +/* +Uninitializes a file system object. + +This does not do any file closing for you. You must close any opened files yourself before calling +this function. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object to uninitialize. Must not be NULL. + + +See Also +-------- +fs_init() +*/ FS_API void fs_uninit(fs* pFS); + + +/* +Performs a control operation on the file system. + +This is backend-specific. Check the documentation for the backend you are using to see what +operations are supported. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +op : (in) + The operation to perform. This is backend-specific. + +pArg : (in, optional) + An optional pointer to an argument struct. This is backend-specific. Can be NULL if the + operation does not require any arguments. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. May return FS_NOT_IMPLEMENTED if +the operation is not supported by the backend. +*/ FS_API fs_result fs_ioctl(fs* pFS, int op, void* pArg); -FS_API fs_result fs_remove(fs* pFS, const char* pFilePath); /* Does not consider mounts. */ -FS_API fs_result fs_rename(fs* pFS, const char* pOldName, const char* pNewName); /* Does not consider mounts. */ -FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options); /* Recursive. Will consider mounts unless FS_IGNORE_MOUNTS is specified. Returns FS_SUCCESS if directory already exists. */ -FS_API fs_result fs_info(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo); /* openMode flags specify same options as openMode in file_open(), but FS_READ, FS_WRITE, FS_TRUNCATE, FS_APPEND, and FS_OVERWRITE are ignored. */ + + +/* +Removes a file or empty directory. + +This function will delete a file or an empty directory from the file system. It will consider write +mount points unless the FS_IGNORE_MOUNTS flag is specified in the options parameter in which case +the path will be treated as a real path. + +See fs_file_open() for information about the options flags. + + +Parameters +---------- +pFS : (in, optional) + A pointer to the file system object. Can be NULL to use the native file system. + +pFilePath : (in) + The path to the file or directory to remove. Must not be NULL. + +options : (in) + Options for the operation. Can be 0 or a combination of the following flags: + FS_IGNORE_MOUNTS + FS_NO_SPECIAL_DIRS + FS_NO_ABOVE_ROOT_NAVIGATION + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. Returns FS_DOES_NOT_EXIST if the +file or directory does not exist. Returns FS_DIRECTORY_NOT_EMPTY if attempting to remove a +non-empty directory. + + +See Also +-------- +fs_file_open() +*/ +FS_API fs_result fs_remove(fs* pFS, const char* pFilePath, int options); + + +/* +Renames or moves a file or directory. + +This function will rename or move a file or directory from one location to another. It will +consider write mount points unless the FS_IGNORE_MOUNTS flag is specified in the options parameter +in which case the paths will be treated as real paths. + +This will fail with FS_DIFFERENT_DEVICE if the source and destination are on different devices. + +See fs_file_open() for information about the options flags. + + +Parameters +---------- +pFS : (in, optional) + A pointer to the file system object. Can be NULL to use the native file system. + +pOldPath : (in) + The current path of the file or directory to rename/move. Must not be NULL. + +pNewPath : (in) + The new path for the file or directory. Must not be NULL. + +options : (in) + Options for the operation. Can be 0 or a combination of the following flags: + FS_IGNORE_MOUNTS + FS_NO_SPECIAL_DIRS + FS_NO_ABOVE_ROOT_NAVIGATION + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. Returns FS_DOES_NOT_EXIST if the +source file or directory does not exist. Returns FS_ALREADY_EXISTS if the destination path already +exists. + + +See Also +-------- +fs_file_open() +*/ +FS_API fs_result fs_rename(fs* pFS, const char* pOldPath, const char* pNewPath, int options); + + +/* +Creates a directory. + +This function creates a directory at the specified path. By default, it will create the entire +directory hierarchy if parent directories do not exist. It will consider write mount points unless +the FS_IGNORE_MOUNTS flag is specified in the options parameter in which case the path will be +treated as a real path. + +See fs_file_open() for information about the options flags. + + +Parameters +---------- +pFS : (in, optional) + A pointer to the file system object. Can be NULL to use the native file system. + +pPath : (in) + The path of the directory to create. Must not be NULL. + +options : (in) + Options for the operation. Can be 0 or a combination of the following flags: + FS_IGNORE_MOUNTS + FS_NO_CREATE_DIRS + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. Returns FS_ALREADY_EXISTS if the +directory already exists. Returns FS_DOES_NOT_EXIST if FS_NO_CREATE_DIRS is specified and a +parent directory does not exist. + + +See Also +-------- +fs_file_open() +*/ +FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options); + + +/* +Retrieves information about a file or directory without opening it. + +This function gets information about a file or directory such as its size, modification time, +and whether it is a directory or symbolic link. The openMode parameter accepts the same flags as +fs_file_open() but FS_READ, FS_WRITE, FS_TRUNCATE, FS_APPEND, and FS_EXCLUSIVE are ignored. + + +Parameters +---------- +pFS : (in, optional) + A pointer to the file system object. Can be NULL to use the native file system. + +pPath : (in) + The path to the file or directory to get information about. Must not be NULL. + +openMode : (in) + Open mode flags that may affect how the file is accessed. See fs_file_open() for details. + +pInfo : (out) + A pointer to a fs_file_info structure that will receive the file information. Must not be NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. Returns FS_DOES_NOT_EXIST if the +file or directory does not exist. + + +See Also +-------- +fs_file_get_info() +fs_file_open() +*/ +FS_API fs_result fs_info(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo); + + +/* +Retrieves a pointer to the stream used by the file system object. + +This is only relevant if the file system will initialized with a stream (such as when opening an +archive). If the file system was not initialized with a stream, this will return NULL. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns a pointer to the stream used by the file system object, or NULL if no stream was provided +at initialization time. +*/ FS_API fs_stream* fs_get_stream(fs* pFS); + + +/* +Retrieves a pointer to the allocation callbacks used by the file system object. + +Note that this will *not* return the same pointer that was specified in the config at initialization +time. This function returns a pointer to the internal copy of the struct. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns a pointer to the allocation callbacks used by the file system object. If `pFS` is NULL, this +will return NULL. +*/ FS_API const fs_allocation_callbacks* fs_get_allocation_callbacks(fs* pFS); -FS_API void* fs_get_backend_data(fs* pFS); /* For use by the backend. Will be the size returned by the alloc_size() function in the vtable. */ + + +/* +For use only by backend implementations. Retrieves a pointer to the backend-specific data +associated with the file system object. + +You should never call this function unless you are implementing a custom backend. The size of the +data can be retrieved with `fs_get_backend_data_size()`. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns a pointer to the backend-specific data associated with the file system object, or NULL if no +backend data is available. + + +See Also +-------- +fs_get_backend_data_size() +*/ +FS_API void* fs_get_backend_data(fs* pFS); + + +/* +For use only by backend implementations. Retrieves the size of the backend-specific data +associated with the file system object. + +You should never call this function unless you are implementing a custom backend. The data can be +accessed with `fs_get_backend_data()`. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns the size of the backend-specific data associated with the file system object, or 0 if no +backend data is available. + + +See Also +-------- +fs_get_backend_data() +*/ FS_API size_t fs_get_backend_data_size(fs* pFS); -FS_API fs* fs_ref(fs* pFS); /* Increments the reference count. Returns pFS. */ -FS_API fs_uint32 fs_unref(fs* pFS); /* Decrements the reference count. Does not uninitialize. */ + + +/* +Increments the reference count of the file system object. + +This function would be used to prevent garbage collection of opened archives. It should be rare to +ever need to call this function directly. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns `pFS`. + + +See Also +-------- +fs_unref() +fs_refcount() +*/ +FS_API fs* fs_ref(fs* pFS); + +/* +Decrements the reference count of the file system object. + +This does not uninitialize the object once the reference count hits zero. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns the new reference count. + + +See Also +-------- +fs_ref() +fs_refcount() +*/ +FS_API fs_uint32 fs_unref(fs* pFS); + +/* +Retrieves the current reference count of the file system object. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns the current reference count of the file system object. + + +See Also +-------- +fs_ref() +fs_unref() +*/ FS_API fs_uint32 fs_refcount(fs* pFS); -FS_API fs_result fs_open_archive_ex(fs* pFS, const fs_backend* pBackend, void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive); -FS_API fs_result fs_open_archive(fs* pFS, const char* pArchivePath, int openMode, fs** ppArchive); -FS_API void fs_close_archive(fs* pArchive); -FS_API void fs_gc_archives(fs* pFS, int policy); -FS_API void fs_set_archive_gc_threshold(fs* pFS, size_t threshold); -FS_API size_t fs_get_archive_gc_threshold(fs* pFS); +/* +Opens a file. + +If the file path is prefixed with the virtual path of a mount point, this function will first try +opening the file from that mount. If that fails, it will fall back to the native file system and +treat the path as a real path. If the FS_ONLY_MOUNTS flag is specified in the openMode parameter, +the last step of falling back to the native file system will be skipped. + +By default, opening a file will transparently look inside archives of known types (registered at +initialization time of the `fs` object). This can slow, and if you would rather not have this +behavior, consider using the `FS_OPAQUE` option (see below). + +This function opens a file for reading and/or writing. The openMode parameter specifies how the +file should be opened. It can be a combination of the following flags: + +FS_READ + Open the file for reading. If used with `FS_WRITE`, the file will be opened in read/write mode. + When opening in read-only mode, the file must exist. + +FS_WRITE + Open the file for writing. If used with `FS_READ`, the file will be opened in read/write mode. + When opening in write-only mode, the file will be created if it does not exist. By default, the + file will be opened in overwrite mode. To change this, combine this with either one of + the `FS_TRUNCATE` or `FS_APPEND` flags. + +FS_TRUNCATE + Only valid when used with `FS_WRITE`. If the file already exists, it will be truncated to zero + length when opened. If the file does not exist, it will be created. Not compatible with + `FS_APPEND`. + +FS_APPEND + Only valid when used with `FS_WRITE`. All writes will occur at the end of the file, regardless + of the current cursor position. If the file does not exist, it will be created. Not compatible + with `FS_TRUNCATE`. + +FS_EXCLUSIVE + Only valid when used with `FS_WRITE`. The file will be created, but if it already exists, the + open will fail with FS_ALREADY_EXISTS. + +FS_TRANSPARENT + This is the default behavior. When used, files inside archives can be opened transparently. For + example, "somefolder/archive.zip/file.txt" can be opened with "somefolder/file.txt" (the + "archive.zip" part need not be specified). This assumes the `fs` object has been initialized + with support for the relevant archive types. + + Transparent mode is the slowest mode since it requires searching through the file system for + archives, and then open those archives, and then searching through the archive for the file. If + this is prohibitive, consider using `FS_OPAQUE` (fastest) or `FS_VERBOSE` modes instead. + Furthermore, you can consider having a rule in your application that instead of opening files + inside archives from a transparent path, that you instead mount the archive, and then open all + files with `FS_OPAQUE`, but with a virtual path that points to the archive. For example: + + fs_mount(pFS, "somefolder/archive.zip", "assets", FS_READ); + fs_file_open(pFS, "assets/file.txt", FS_READ | FS_OPAQUE, &pFile); + + Here the archive is mounted to the virtual path "assets". Because the path "assets/file.txt" + is prefixed with "assets", the file system knows to look inside the mounted archive without + having to search for it. + +FS_OPAQUE + When used, archives will be treated as opaque, meaning attempting to open a file from an + unmounted archive will fail. For example, "somefolder/archive.zip/file.txt" will fail because + it is inside an archive. This is the fastest mode, but you will not be able to open files from + inside archives unless it is sourced from a mount. + +FS_VERBOSE + When used, files inside archives can be opened, but the name of the archive must be specified + explicitly in the path, such as "somefolder/archive.zip/file.txt". This is faster than + `FS_TRANSPARENT` mode since it does not require searching for archives. + +FS_NO_CREATE_DIRS + When opening a file in write mode, the default behavior is to create the directory structure + automatically if required. When this options is used, directories will *not* be created + automatically. If the necessary parent directories do not exist, the open will fail with + FS_DOES_NOT_EXIST. + +FS_IGNORE_MOUNTS + When used, mounted directories and archives will be ignored when opening files. The path will + be treated as a real path. + +FS_ONLY_MOUNTS + When used, only mounted directories and archives will be considered when opening files. When a + file is opened, it will first search through mounts, and if the file is not found in any of + those it will fall back to the native file system and try treating the path as a real path. + When this flag is set, that last step of falling back to the native file system is skipped. + +FS_NO_SPECIAL_DIRS + When used, the presence of special directories like "." and ".." in the path will result in an + error. When using this option, you need not specify FS_NO_ABOVE_ROOT_NAVIGATION since it is + implied. + +FS_NO_ABOVE_ROOT_NAVIGATION + When used, navigating above the mount point with leading ".." segments will result in an error. + This option is implied when using FS_NO_SPECIAL_DIRS. + + +Close the file with `fs_file_close()` when no longer needed. The file will not be closed +automatically when the `fs` object is uninitialized. + + +Parameters +---------- +pFS : (in, optional) + A pointer to the file system object. Can be NULL to use the native file system. Note that when + this is NULL, archives and mounts will not be supported. + +pFilePath : (in) + The path to the file to open. Must not be NULL. + +openMode : (in) + The mode to open the file with. A combination of the flags described above. + +ppFile : (out) + A pointer to a pointer which will receive the opened file object. Must not be NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. Returns FS_DOES_NOT_EXIST if the +file does not exist when opening for reading. Returns FS_ALREADY_EXISTS if the file already exists +when opening with FS_EXCLUSIVE. Returns FS_IS_DIRECTORY if the path refers to a directory. + + +Example 1 - Basic Usage +----------------------- +The example below shows how to open a file for reading from the regular file system. + +```c +fs_result result; +fs_file* pFile; + +result = fs_file_open(pFS, "somefolder/somefile.txt", FS_READ, &pFile); +if (result != FS_SUCCESS) { + // Handle error. +} + +// Use the file... + +// Close the file when no longer needed. +fs_file_close(pFile); +``` + +Example 2 - Opening from a Mount +-------------------------------- +The example below shows how to mount a directory and then open a file from it. Error checking has +been omitted for clarity. + +```c +// "assets" is the virtual path. FS_READ indicates this is a mount for use when opening a file in +// read-only mode (write mounts would use FS_WRITE). +fs_mount(pFS, "some_actual_path", "assets", FS_READ); + +... + +// The file path is prefixed with the virtual path "assets" so the file system will look inside the +// mounted directory first. Since the "assets" virtual path points to "some_actual_path", the file +// that will actually be opened is "some_actual_path/file.txt". +fs_file_open(pFS, "assets/file.txt", FS_READ, &pFile); +``` + +Example 3 - Opening from an Archive +----------------------------------- +The example below shows how to open a file from within a ZIP archive. This assumes the `fs` object +was initialized with support for ZIP archives (see fs_init() documentation for more information). + +```c +// Opening the file directly from the archive by specifying the archive name in the path. +fs_file_open(pFS, "somefolder/archive.zip/somefile.txt", FS_READ, &pFile); + +// Same as above. The "archive.zip" part is not needed because transparent mode is used by default. +fs_file_open(pFS, "somefolder/somefile.txt", FS_READ, &pFile); + +// Opening a file in verbose mode. The archive name must be specified in the path. +fs_file_open(pFS, "somefolder/archive.zip/somefile.txt", FS_READ | FS_VERBOSE, &pFile); // This is a valid path in verbose mode. + +// This will fail because the archive name is not specified in the path. +fs_file_open(pFS, "somefolder/somefile.txt", FS_READ | FS_VERBOSE, &pFile); // This will fail because verbose mode requires explicit archive names. + +// This will fail because opaque mode treats archives as opaque. +fs_file_open(pFS, "somefolder/archive.zip/somefile.txt", FS_READ | FS_OPAQUE, &pFile); +``` + +Example 4 - Opening from a Mounted Archive +------------------------------------------ +It is OK to use opaque mode when opening files from a mounted archive. This is the only way to open +files from an archive when using opaque mode. + +```c +// Mount the archive to the virtual path "assets". +fs_mount(pFS, "somefolder/archive.zip", "assets", FS_READ); + +// Now you can open files from the archive in opaque mode. Note how the path is prefixed with the +// virtual path "assets" which is how the mapping back to "somefolder/archive.zip" is made. +fs_file_open(pFS, "assets/somefile.txt", FS_READ | FS_OPAQUE, &pFile); +``` + + +See Also +-------- +fs_file_close() +fs_file_read() +fs_file_write() +fs_file_seek() +fs_file_tell() +fs_file_flush() +fs_file_truncate() +fs_file_get_info() +fs_file_duplicate() +*/ FS_API fs_result fs_file_open(fs* pFS, const char* pFilePath, int openMode, fs_file** ppFile); -FS_API fs_result fs_file_open_from_handle(fs* pFS, void* hBackendFile, fs_file** ppFile); + + +/* +Closes a file. + +You must close any opened files with this function when they are no longer needed. The owner `fs` +object will not close files automatically when it is uninitialized with `fs_uninit()`. + + +Parameters +---------- +pFile : (in) + A pointer to the file to close. Must not be NULL. + + +See Also +-------- +fs_file_open() +*/ FS_API void fs_file_close(fs_file* pFile); + + +/* +Reads data from a file. + +This function reads up to `bytesToRead` bytes from the file into the buffer pointed to by `pDst`. +The number of bytes actually read will be stored in the variable pointed to by `pBytesRead`. + +If the end of the file is reached before any bytes are read, this function will return `FS_AT_END` +and `*pBytesRead` will be set to 0. `FS_AT_END` will only be returned if `*pBytesRead` is 0. + + +Parameters +---------- +pFile : (in) + A pointer to the file to read from. Must not be NULL. + +pDst : (out) + A pointer to the buffer that will receive the read data. Must not be NULL. + +bytesToRead : (in) + The maximum number of bytes to read from the file. + +pBytesRead : (out, optional) + A pointer to a variable that will receive the number of bytes actually read. Can be NULL if you + do not care about this information. If NULL, the function will return an error if not all + requested bytes could be read. + + +Return Value +------------ +Returns `FS_SUCCESS` on success, `FS_AT_END` on end of file, or an error code otherwise. Will only +return `FS_AT_END` if `*pBytesRead` is 0. + +If `pBytesRead` is NULL, the function will return an error if not all requested bytes could be +read. Otherwise, if `pBytesRead` is not NULL, the function will return `FS_SUCCESS` even if fewer +than `bytesToRead` bytes were read. + + +See Also +-------- +fs_file_open() +fs_file_write() +fs_file_seek() +fs_file_tell() +*/ FS_API fs_result fs_file_read(fs_file* pFile, void* pDst, size_t bytesToRead, size_t* pBytesRead); /* Returns 0 on success, FS_AT_END on end of file, or an errno result code on error. Will only return FS_AT_END if *pBytesRead is 0. */ + + +/* +Writes data to a file. + +This function writes up to `bytesToWrite` bytes from the buffer pointed to by `pSrc` to the file. +The number of bytes actually written will be stored in the variable pointed to by `pBytesWritten`. + + +Parameters +---------- +pFile : (in) + A pointer to the file to write to. Must not be NULL. + +pSrc : (in) + A pointer to the buffer containing the data to write. Must not be NULL. + +bytesToWrite : (in) + The number of bytes to write to the file. + +pBytesWritten : (out, optional) + A pointer to a variable that will receive the number of bytes actually written. Can be NULL if + you do not care about this information. If NULL, the function will return an error if not all + requested bytes could be written. + + +Return Value +------------ +Returns `FS_SUCCESS` on success, or an error code otherwise. + +If `pBytesWritten` is NULL, the function will return an error if not all requested bytes could be +written. Otherwise, if `pBytesWritten` is not NULL, the function will return `FS_SUCCESS` even if +fewer than `bytesToWrite` bytes were written. + + +See Also +-------- +fs_file_open() +fs_file_read() +fs_file_seek() +fs_file_tell() +fs_file_flush() +fs_file_truncate() +*/ FS_API fs_result fs_file_write(fs_file* pFile, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten); + +/* +A helper for writing formatted data to a file. + + +Parameters +---------- +pFile : (in) + A pointer to the file to write to. Must not be NULL. + +fmt : (in) + A printf-style format string. Must not be NULL. + +... : (in) + Additional arguments as required by the format string. + + +Return Value +------------ +Same as `fs_file_write()`. + + +See Also +-------- +fs_file_write() +fs_file_writefv() +*/ FS_API fs_result fs_file_writef(fs_file* pFile, const char* fmt, ...) FS_ATTRIBUTE_FORMAT(2, 3); + +/* +A helper for writing formatted data to a file. + + +Parameters +---------- +pFile : (in) + A pointer to the file to write to. Must not be NULL. + +fmt : (in) + A printf-style format string. Must not be NULL. + +args : (in) + Additional arguments as required by the format string. + + +Return Value +------------ +Same as `fs_file_write()`. + + +See Also +-------- +fs_file_write() +fs_file_writef() +*/ FS_API fs_result fs_file_writefv(fs_file* pFile, const char* fmt, va_list args); + +/* +Moves the read/write cursor of a file. + +You can seek relative to the start of the file, the current cursor position, or the end of the file. +A negative offset seeks backwards. + +It is not an error to seek beyond the end of the file. If you seek beyond the end of the file and +then write, the exact behavior depends on the backend. On POSIX systems, it will most likely result +in a sparse file. In read mode, attempting to read beyond the end of the file will simply result +in zero bytes being read, and `FS_AT_END` being returned by `fs_file_read()`. + +It is an error to try seeking to before the start of the file. + + +Parameters +---------- +pFile : (in) + A pointer to the file to seek. Must not be NULL. + +offset : (in) + The offset to seek to, relative to the position specified by `origin`. A negative value seeks + backwards. + +origin : (in) + The origin from which to seek. One of the following values: + FS_SEEK_SET + Seek from the start of the file. + + FS_SEEK_CUR + Seek from the current cursor position. + + FS_SEEK_END + Seek from the end of the file. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. + + +See Also +-------- +fs_file_tell() +fs_file_read() +fs_file_write() +*/ FS_API fs_result fs_file_seek(fs_file* pFile, fs_int64 offset, fs_seek_origin origin); + +/* +Retrieves the current position of the read/write cursor in a file. + + +Parameters +---------- +pFile : (in) + A pointer to the file to query. Must not be NULL. + +pCursor : (out) + A pointer to a variable that will receive the current cursor position. Must not be NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. + + +See Also +-------- +fs_file_seek() +fs_file_read() +fs_file_write() +*/ FS_API fs_result fs_file_tell(fs_file* pFile, fs_int64* pCursor); + + +/* +Flushes any buffered data to disk. + + +Parameters +---------- +pFile : (in) + A pointer to the file to flush. Must not be NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. +*/ FS_API fs_result fs_file_flush(fs_file* pFile); + + +/* +Truncates a file to the current cursor position. + +It is possible for a backend to not support truncation, in which case this function will return +`FS_NOT_IMPLEMENTED`. + + +Parameters +---------- +pFile : (in) + A pointer to the file to truncate. Must not be NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. Will return `FS_NOT_IMPLEMENTED` if +the backend does not support truncation. +*/ +FS_API fs_result fs_file_truncate(fs_file* pFile); + + +/* +Retrieves information about an opened file. + + +Parameters +---------- +pFile : (in) + A pointer to the file to query. Must not be NULL. + +pInfo : (out) + A pointer to a fs_file_info structure that will receive the file information. Must not be NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. +*/ FS_API fs_result fs_file_get_info(fs_file* pFile, fs_file_info* pInfo); -FS_API fs_result fs_file_duplicate(fs_file* pFile, fs_file** ppDuplicate); /* Duplicate the file handle. */ + + +/* +Duplicates a file handle. + +This creates a new file handle that refers to the same underlying file as the original. The new +file handle will have its own independent cursor position. The initial position of the new file's +cursor will be undefined. You should call `fs_file_seek()` to set it to a known position before +using it. + +Note that this does not duplicate the actual file on the file system itself. It just creates a +new `fs_file` object that refers to the same file. + + +Parameters +---------- +pFile : (in) + A pointer to the file to duplicate. Must not be NULL. + +ppDuplicate : (out) + A pointer to a pointer which will receive the duplicated file handle. Must not be NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. +*/ +FS_API fs_result fs_file_duplicate(fs_file* pFile, fs_file** ppDuplicate); + +/* +Retrieves the backend-specific data associated with a file. + +You should never call this function unless you are implementing a custom backend. The size of the +data can be retrieved with `fs_file_get_backend_data_size()`. + + +Parameters +---------- +pFile : (in) + A pointer to the file to query. Must not be NULL. + + +Return Value +------------ +Returns a pointer to the backend-specific data associated with the file, or NULL if there is no +such data. + + +See Also +-------- +fs_file_get_backend_data_size() +*/ FS_API void* fs_file_get_backend_data(fs_file* pFile); + +/* +Retrieves the size of the backend-specific data associated with a file. + +You should never call this function unless you are implementing a custom backend. The data can be +accessed with `fs_file_get_backend_data()`. + + +Parameters +---------- +pFile : (in) + A pointer to the file to query. Must not be NULL. + + +Return Value +------------ +Returns the size of the backend-specific data associated with the file, or 0 if there is no such +data. + + +See Also +-------- +fs_file_get_backend_data() +*/ FS_API size_t fs_file_get_backend_data_size(fs_file* pFile); -FS_API fs_stream* fs_file_get_stream(fs_file* pFile); /* Files are streams. They can be cast directly to fs_stream*, but this function is here for people who prefer function style getters. */ + +/* +Files are streams. This function returns a pointer to the `fs_stream` interface of the file. + + +Parameters +---------- +pFile : (in) + A pointer to the file whose stream pointer is being retrieved. Must not be NULL. + + +Return Value +------------ +Returns a pointer to the `fs_stream` interface of the file, or NULL if `pFile` is NULL. + + +See Also +-------- +fs_file_get_fs() +*/ +FS_API fs_stream* fs_file_get_stream(fs_file* pFile); + +/* +Retrieves the file system that owns a file. + + +Parameters +---------- +pFile : (in) + A pointer to the file whose file system pointer is being retrieved. Must not be NULL. + + +Return Value +------------ +Returns a pointer to the `fs` interface of the file's file system, or NULL if `pFile` is NULL. + + +See Also +-------- +fs_file_get_stream() +*/ FS_API fs* fs_file_get_fs(fs_file* pFile); + +/* +The same as `fs_first()`, but with the length of the directory path specified explicitly. + + +Parameters +---------- +pFS : (in, optional) + A pointer to the file system object. This can be NULL in which case the native file system will + be used. + +pDirectoryPath : (in) + The path to the directory to iterate. Must not be NULL. + +directoryPathLen : (in) + The length of the directory path. Can be set to `FS_NULL_TERMINATED` if the path is + null-terminated. + +mode : (in) + Options for the iterator. See `fs_file_open()` for a description of the available flags. + + +Return Value +------------ +Same as `fs_first()`. +*/ FS_API fs_iterator* fs_first_ex(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen, int mode); + +/* +Creates an iterator for the first entry in a directory. + +This function creates an iterator that can be used to iterate over the entries in a directory. This +will be the first function called when iterating over the files inside a directory. + +To get the next entry in the directory, call `fs_next()`. When `fs_next()` returns NULL, there are +no more entries in the directory. If you want to end iteration early, use `fs_free_iterator()` to +free the iterator. + +See `fs_file_open()` for a description of the available flags that can be used in the `mode` +parameter. When `FS_WRITE` is specified, it will look at write mounts. Otherwise, it will look at +read mounts. + + +Parameter +--------- +pFS : (in, optional) + A pointer to the file system object. This can be NULL in which case the native file system will + be used. + +pDirectoryPath : (in) + The path to the directory to iterate. Must not be NULL. + +mode : (in) + Options for the iterator. See `fs_file_open()` for a description of the available flags. + + +Return Value +------------ +Returns a pointer to an iterator object on success; NULL on failure or if the directory is empty. + + +Example +------- +The example below shows how to iterate over all entries in a directory. Error checking has been +omitted for clarity. + +```c +fs_iterator* pIterator = fs_first(pFS, "somefolder", FS_READ); +while (pIterator != NULL) { + // Use pIterator->name and pIterator->info here... + printf("Found entry: %.*s\n", (int)pIterator->nameLength, pIterator->pName); + pIterator = fs_next(pIterator); +} + +fs_free_iterator(pIterator); // This is only needed if you want to terminate iteration early. If `fs_next()` returns NULL, you need not call this. +``` + +See Also +-------- +fs_next() +fs_free_iterator() +fs_first_ex() +*/ FS_API fs_iterator* fs_first(fs* pFS, const char* pDirectoryPath, int mode); + +/* +Gets the next entry in a directory iteration. + +This function is used to get the next entry in a directory iteration. It should be called after +`fs_first()` or `fs_first_ex()` to retrieve the first entry, and then subsequently called to +retrieve each following entry. + +If there are no more entries in the directory, this function will return NULL, and an explicit call +to `fs_free_iterator()` is not needed. + + +Parameters +---------- +pIterator : (in) + A pointer to the iterator object. Must not be NULL. + + +Return Value +------------ +Returns a pointer to the next iterator object on success; NULL if there are no more entries. If +NULL is returned, you need not call `fs_free_iterator()`. If you want to terminate iteration early, +you must call `fs_free_iterator()` to free the iterator. + +You cannot assume that the returned pointer is the same as the input pointer. It may need to be +reallocated internally to hold the data of the next entry. + + +See Also +-------- +fs_first() +fs_first_ex() +fs_free_iterator() +*/ FS_API fs_iterator* fs_next(fs_iterator* pIterator); + +/* +Frees an iterator object. + +This function frees an iterator object that was created by `fs_first()` or `fs_first_ex()`. You +need not call this if `fs_next()` returned NULL from an earlier iteration. However, if you want to +terminate iteration early, you must call this function to free the iterator. + +It is safe to call this function with a NULL pointer, in which case it will do nothing. + + +Parameters +---------- +pIterator : (in, optional) + A pointer to the iterator object. Can be NULL. + + +See Also +-------- +fs_first() +fs_first_ex() +fs_next() +*/ FS_API void fs_free_iterator(fs_iterator* pIterator); + +/* +The same as `fs_open_archive()`, but with the ability to explicitly specify the backend to use. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +pBackend : (in) + A pointer to the backend to use for opening the archive. Must not be NULL. + +pBackendConfig : (in, optional) + A pointer to backend-specific configuration data. Can be NULL if the backend does not require + any configuration. + +pArchivePath : (in) + The path to the archive to open. Must not be NULL. + +archivePathLen : (in) + The length of the archive path. Can be set to `FS_NULL_TERMINATED` if the path is null-terminated. + +openMode : (in) + The mode to open the archive with. + +ppArchive : (out) + A pointer to a pointer which will receive the opened archive file system object. Must not be + NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. + + +See Also +-------- +fs_open_archive() +fs_close_archive() +*/ +FS_API fs_result fs_open_archive_ex(fs* pFS, const fs_backend* pBackend, const void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive); + +/* +Helper function for initializing a file system object for an archive, such as a ZIP file. + +To uninitialize the archive, you must use `fs_close_archive()`. Do not use `fs_uninit()` to +uninitialize an archive. The reason for this is that archives opened in this way are garbage +collected, and there are reference counting implications. + +Note that opening the archive in write mode (`FS_WRITE`) does not automatically mean you will be +able to write to it. None of the stock backends support writing to archives at this time. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +pArchivePath : (in) + The path to the archive to open. Must not be NULL. + +openMode : (in) + The open mode flags to open the archive with. See `fs_file_open()` for a description of the + available flags. + +ppArchive : (out) + A pointer to a pointer which will receive the opened archive file system object. Must not be + NULL. + + +Return Value +------------ +Returns FS_SUCCESS on success; any other result code otherwise. + + +See Also +-------- +fs_close_archive() +fs_open_archive_ex() +*/ +FS_API fs_result fs_open_archive(fs* pFS, const char* pArchivePath, int openMode, fs** ppArchive); + + +/* +Closes an archive that was previously opened with `fs_open_archive()`. + +You must use this function to close an archive opened with `fs_open_archive()`. Do not use +`fs_uninit()` to uninitialize an archive. + +Note that when an archive is closed, it does not necessarily mean that the underlying file is +closed immediately. This is because archives are reference counted and garbage collected. You can +force garbage collection of unused archives with `fs_gc_archives()`. + + +Parameters +---------- +pArchive : (in) + A pointer to the archive file system object to close. Must not be NULL. + + +See Also +-------- +fs_open_archive() +fs_open_archive_ex() +*/ +FS_API void fs_close_archive(fs* pArchive); + + +/* +Garbage collects unused archives. + +This function will close any opened archives that are no longer in use depending on the specified +policy. + +You should rarely need to call this function directly. Archives will automatically be garbage collected +when the `fs` object is uninitialized with `fs_uninit()`. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +policy : (in) + The garbage collection policy to use. Set this to FS_GC_POLICY_THRESHOLD to only collect archives + if the number of opened archives exceeds the threshold set with `fs_set_archive_gc_threshold()` + which defaults to 10. Set this to FS_GC_POLICY_ALL to collect all unused archives regardless of the + threshold. + + +See Also +-------- +fs_open_archive() +fs_close_archive() +fs_set_archive_gc_threshold() +*/ +FS_API void fs_gc_archives(fs* pFS, int policy); + +/* +Sets the threshold for garbage collecting unused archives. + +When an archive is no longer in use (its reference count drops to zero), it will not be closed +immediately. Instead, it will be kept open in case it is needed again soon. The threshold is what +determines how many unused archives will be kept open before they are garbage collected. The +default threshold is 10. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +threshold : (in) + The threshold for garbage collecting unused archives. + + +See Also +-------- +fs_gc_archives() +*/ +FS_API void fs_set_archive_gc_threshold(fs* pFS, size_t threshold); + +/* +Retrieves the threshold for garbage collecting unused archives. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + + +Return Value +------------ +Returns the threshold for garbage collecting unused archives. +*/ +FS_API size_t fs_get_archive_gc_threshold(fs* pFS); + +/* +A helper function for checking if a path looks like it could be an archive. + +This only checks the path string itself. It does not actually attempt to open and validate the +archive itself. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +pPath : (in) + The path to check. Must not be NULL. + +pathLen : (in) + The length of the path string. Can be set to `FS_NULL_TERMINATED` if the path is null-terminated. + +Return Value +------------ +Returns FS_TRUE if the path looks like an archive, FS_FALSE otherwise. +*/ +FS_API fs_bool32 fs_path_looks_like_archive(fs* pFS, const char* pPath, size_t pathLen); /* Does not validate that it's an actual valid archive. */ + + +/* +Mounts a real directory or archive to a virtual path. + +You must specify the actual path to the directory or archive on the file system referred to by +`pFS`. The virtual path can be NULL, in which case it will be treated as an empty string. + +The virtual path is the path prefix that will be used when opening files. For example, if you mount +the actual path "somefolder" to the virtual path "assets", then when you open a file with the path +"assets/somefile.txt", it will actually open "somefolder/somefile.txt". + +There are two groups of mounts - read-only and write. Read-only mounts are used when opening a file +in read-only mode (i.e. without the `FS_WRITE` flag). Write mounts are used when opening a file in +write mode (i.e. with the `FS_WRITE` flag). To control this, set the appropriate flag in the +`options` parameter. + +The following flags are supported in the `options` parameter: + + FS_READ + This is a read-only mount. It will be used when opening files without the `FS_WRITE` flag. + + FS_WRITE + This is a write mount. It will be used when opening files with the `FS_WRITE + + FS_LOWEST_PRIORITY + By default, mounts are searched in the reverse order that they were added. This means that + the most recently added mount has the highest priority. When this flag is specified, the + mount will have the lowest priority instead. + +For a read-only mount, you can have multiple mounts with the same virtual path in which case they +will be searched in order or priority when opening a file. + +For write mounts, you can have multiple mounts with the same virtual path, but when opening a file +for writing, only the first matching mount will be used. You can have multiple write mounts where +the virtual path is a sub-path of another write mount. For example, you could have one write +mount with the virtual path "assets" and another with the virtual path "assets/images". When +opening a file for writing, if the path starts with "assets/images", that mount will be used +because it is a more specific match. Otherwise, if the path starts with "assets" but not +"assets/images", the other mount will be used. + +You can specify both `FS_READ` and `FS_WRITE` in the `options` parameter to create one read-only +mount, and one write mount in a single call. This is equivalent to calling `fs_mount()` twice - +once with `FS_READ`, and again with `FS_WRITE`. + +Unmounting a directory or archive is done with `fs_unmount()`. You must specify the actual path +when unmounting. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +pActualPath : (in) + The actual path to the directory or archive to mount. Must not be NULL. + +pVirtualPath : (in, optional) + The virtual path to mount the directory or archive to. Can be NULL in which case it will be + treated as an empty string. + +options : (in) + Options for the mount. A combination of the flags described above. + + +Return Value +------------ +Returns `FS_SUCCESS` on success; any other result code otherwise. If an identical mount already exists, +`FS_SUCCESS` will be returned. + + +Example 1 - Basic Usage +----------------------- +```c +// Mount two directories to the same virtual path. +fs_mount(pFS, "some/actual/path", "assets", FS_READ); // Lowest priority. +fs_mount(pFS, "some/other/path", "assets", FS_READ); + +// Mount a directory for writing. +fs_mount(pFS, "some/write/path", "assets", FS_WRITE); +fs_mount(pFS, "some/more/write/path", "assets/images", FS_WRITE); // More specific write mount. + +// Mount a read-only mount, and a write mount in a single call. +fs_mount(pFS, "some/actual/path", "assets", FS_READ | FS_WRITE); +``` + + +Example 2 - Mounting an Archive +------------------------------- +```c +// Mount a ZIP archive to the virtual path "assets". +fs_mount(pFS, "some/actual/path/archive.zip", "assets", FS_READ); +``` + + +See Also +-------- +fs_unmount() +fs_mount_sysdir() +fs_mount_fs() +*/ FS_API fs_result fs_mount(fs* pFS, const char* pActualPath, const char* pVirtualPath, int options); + +/* +Unmounts a directory or archive that was previously mounted with `fs_mount()`. + +You must specify the actual path to the directory or archive that was used when mounting. The +virtual path is not needed. + +The only options that matter here are `FS_READ` and `FS_WRITE`. If you want to unmount a read-only +mount, you must specify `FS_READ`. If you want to unmount a write mount, you must specify +`FS_WRITE`. If you want to unmount both a read-only mount, and a write mount in a single call, you +can specify both flags. Using both flags is the same as calling `fs_unmount()` twice - once for the +read-only mount, and once for the write mount. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +pActualPath : (in) + The actual path to the directory or archive to unmount. Must not be NULL. + +options : (in) + Either `FS_READ`, `FS_WRITE`, or both to unmount the corresponding mounts. + + +Return Value +------------ +Returns `FS_SUCCESS` on success; any other result code otherwise. If no matching mount could be +found, `FS_SUCCESS` will be returned (it will just be a no-op). +*/ FS_API fs_result fs_unmount(fs* pFS, const char* pActualPath, int options); + +/* +A helper function for mounting a standard system directory to a virtual path. + +When calling this function you specify the type of system directory you want to mount. The actual +path of the system directory will often be generic, like "/home/yourname/" which is not useful for +a real program. For this reason, this function forces you to specify a sub-directory that will be +used with the system directory. This would often be something like the name of your application, +such as "myapp". It can also include sub-directories, such as "mycompany/myapp". + +Otherwise, this function behaves exactly like `fs_mount()`. + +Unmount the directory with `fs_unmount_sysdir()`. You must specify the same type and sub-directory +that was used when mounting. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +type : (in) + The type of system directory to mount. + +pSubDir : (in) + The sub-directory to use with the system directory. Must not be NULL nor an empty string. + +pVirtualPath : (in, optional) + The virtual path to mount the system directory to. Can be NULL in which case it will be treated + as an empty string. + +options : (in) + Options for the mount. A combination of the flags described in `fs_mount()`. + + +Return Value +------------ +Returns `FS_SUCCESS` on success; any other result code otherwise. If an identical mount already +exists, `FS_SUCCESS` will be returned. + + +See Also +-------- +fs_mount() +fs_unmount_sysdir() +*/ FS_API fs_result fs_mount_sysdir(fs* pFS, fs_sysdir_type type, const char* pSubDir, const char* pVirtualPath, int options); + +/* +Unmounts a system directory that was previously mounted with `fs_mount_sysdir()`. + +This is the same as `fs_unmount()`, but follows the "type" and sub-directory semantics of +`fs_mount_sysdir()`. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +type : (in) + The type of system directory to unmount. + +pSubDir : (in) + The sub-directory that was used with the system directory when mounting. Must not be NULL nor + an empty string. + +options : (in) + Either `FS_READ`, `FS_WRITE`, or both to unmount the corresponding mounts. + + +Return Value +------------ +Returns `FS_SUCCESS` on success; any other result code otherwise. If no matching mount could be +found, `FS_SUCCESS` will be returned (it will just be a no-op). +*/ FS_API fs_result fs_unmount_sysdir(fs* pFS, fs_sysdir_type type, const char* pSubDir, int options); + +/* +Mounts another `fs` object to a virtual path. + +This is the same as `fs_mount()`, but instead of specifying an actual path to a directory or +archive, you specify another `fs` object. + +Use `fs_unmount_fs()` to unmount the file system. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +pOtherFS : (in) + A pointer to the other file system object to mount. Must not be NULL. + +pVirtualPath : (in, optional) + The virtual path to mount the other file system to. Can be NULL in which case it will be treated + as an empty string. + +options : (in) + Options for the mount. A combination of the flags described in `fs_mount()`. + + +Return Value +------------ +Returns `FS_SUCCESS` on success; any other result code otherwise. If an identical mount already +exists, `FS_SUCCESS` will be returned. + + +See Also +-------- +fs_mount() +fs_unmount_fs() +*/ FS_API fs_result fs_mount_fs(fs* pFS, fs* pOtherFS, const char* pVirtualPath, int options); -FS_API fs_result fs_unmount_fs(fs* pFS, fs* pOtherFS, int options); /* Must be matched up with fs_mount_fs(). */ + +/* +Unmounts a file system that was previously mounted with `fs_mount_fs()`. + + +Parameters +---------- +pFS : (in) + A pointer to the file system object. Must not be NULL. + +pOtherFS : (in) + A pointer to the other file system object to unmount. Must not be NULL. + +options : (in) + Options for the unmount. A combination of the flags described in `fs_unmount()`. + + +Return Value +------------ +Returns `FS_SUCCESS` on success; any other result code otherwise. If no matching mount could be +found, `FS_SUCCESS` will be returned (it will just be a no-op). + + +See Also +-------- +fs_unmount() +fs_mount_fs() +*/ +FS_API fs_result fs_unmount_fs(fs* pFS, fs* pOtherFS, int options); + /* Helper functions for reading the entire contents of a file, starting from the current cursor position. Free @@ -1264,11 +3288,16 @@ in fixed sized chunks. */ FS_API fs_result fs_file_read_to_end(fs_file* pFile, fs_format format, void** ppData, size_t* pDataSize); FS_API fs_result fs_file_open_and_read(fs* pFS, const char* pFilePath, fs_format format, void** ppData, size_t* pDataSize); -FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, void* pData, size_t dataSize); +FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, const void* pData, size_t dataSize); -/* Default Backend. */ -extern const fs_backend* FS_STDIO; /* The default stdio backend. The handle for fs_file_open_from_handle() is a FILE*. */ +/* BEG fs_backend_posix.h */ +extern const fs_backend* FS_BACKEND_POSIX; +/* END fs_backend_posix.h */ + +/* BEG fs_backend_win32.h */ +extern const fs_backend* FS_BACKEND_WIN32; +/* END fs_backend_win32.h */ /* END fs.h */ diff --git a/extras/miniaudio_split/miniaudio.c b/extras/miniaudio_split/miniaudio.c index f6b28450..2ad76837 100644 --- a/extras/miniaudio_split/miniaudio.c +++ b/extras/miniaudio_split/miniaudio.c @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.22 - 2025-02-24 +miniaudio - v0.11.23 - 2025-09-11 David Reid - mackron@gmail.com @@ -49,17 +49,23 @@ GitHub: https://github.com/mackron/miniaudio #endif #if !defined(MA_WIN32) -#include -#include /* select() (used for ma_sleep()). */ -#include + #if !defined(MA_NO_THREADING) + #include + #include /* For pthreads. */ + #endif + + #include /* select() (used for ma_sleep()). */ + #include /* For nanosleep() */ + #include #endif -#ifdef MA_NX -#include /* For nanosleep() */ +/* For fstat(), etc. */ +#if defined(MA_XBOX_NXDK) + #include /* Suggestion for NXDK: Add a sys/stat.h wrapper for compatibility. */ +#else + #include #endif -#include /* For fstat(), etc. */ - #ifdef MA_EMSCRIPTEN #include #endif @@ -366,7 +372,7 @@ static MA_INLINE ma_bool32 ma_has_neon(void) #endif #ifndef MA_RESTRICT - #if defined(__clang__) || defined(__GNUC__) || defined(_MSC_VER) + #if defined(__clang__) || defined(_MSC_VER) || (defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95))) #define MA_RESTRICT __restrict #else #define MA_RESTRICT @@ -460,7 +466,7 @@ static void ma_sleep__posix(ma_uint32 milliseconds) (void)milliseconds; MA_ASSERT(MA_FALSE); /* The Emscripten build should never sleep. */ #else - #if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || defined(MA_NX) + #if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || defined(MA_SWITCH) struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = milliseconds % 1000 * 1000000; @@ -502,7 +508,7 @@ static MA_INLINE void ma_yield(void) #endif #endif #else - __asm__ __volatile__ ("pause"); + __asm__ __volatile__ ("rep; nop"); #endif #elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(_M_ARM64) || (defined(_M_ARM) && _M_ARM >= 7) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) /* ARM */ @@ -525,7 +531,7 @@ static MA_INLINE unsigned int ma_disable_denormals(void) { unsigned int prevState; - #if defined(_MSC_VER) + #if defined(_MSC_VER) && !defined(MA_XBOX_NXDK) { /* Older versions of Visual Studio don't support the "safe" versions of _controlfp_s(). I don't @@ -548,7 +554,7 @@ static MA_INLINE unsigned int ma_disable_denormals(void) } #elif defined(MA_X86) || defined(MA_X64) { - #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__) || defined(__COSMOPOLITAN__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + #if defined(MA_SUPPORT_SSE2) && defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__) || defined(__COSMOPOLITAN__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ { prevState = _mm_getcsr(); _mm_setcsr(prevState | MA_MM_DENORMALS_ZERO_MASK | MA_MM_FLUSH_ZERO_MASK); @@ -572,7 +578,7 @@ static MA_INLINE unsigned int ma_disable_denormals(void) static MA_INLINE void ma_restore_denormals(unsigned int prevState) { - #if defined(_MSC_VER) + #if defined(_MSC_VER) && !defined(MA_XBOX_NXDK) { /* Older versions of Visual Studio do not support _controlfp_s(). See ma_disable_denormals(). */ #if _MSC_VER <= 1200 @@ -588,7 +594,7 @@ static MA_INLINE void ma_restore_denormals(unsigned int prevState) } #elif defined(MA_X86) || defined(MA_X64) { - #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__) || defined(__COSMOPOLITAN__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + #if defined(MA_SUPPORT_SSE2) && defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__) || defined(__COSMOPOLITAN__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ { _mm_setcsr(prevState); } @@ -1224,6 +1230,29 @@ MA_API MA_NO_INLINE int ma_strcmp(const char* str1, const char* str2) return ((unsigned char*)str1)[0] - ((unsigned char*)str2)[0]; } +MA_API MA_NO_INLINE int ma_wcscmp(const wchar_t* str1, const wchar_t* str2) +{ + if (str1 == str2) return 0; + + /* These checks differ from the standard implementation. It's not important, but I prefer it just for sanity. */ + if (str1 == NULL) return -1; + if (str2 == NULL) return 1; + + for (;;) { + if (str1[0] == L'\0') { + break; + } + if (str1[0] != str2[0]) { + break; + } + + str1 += 1; + str2 += 1; + } + + return ((unsigned short*)str1)[0] - ((unsigned short*)str2)[0]; +} + MA_API MA_NO_INLINE int ma_strappend(char* dst, size_t dstSize, const char* srcA, const char* srcB) { int result; @@ -1241,6 +1270,22 @@ MA_API MA_NO_INLINE int ma_strappend(char* dst, size_t dstSize, const char* srcA return result; } +MA_API MA_NO_INLINE size_t ma_wcslen(const wchar_t* str) +{ + const wchar_t* end; + + if (str == NULL) { + return 0; + } + + end = str; + while (end[0] != '\0') { + end += 1; + } + + return end - str; +} + MA_API MA_NO_INLINE char* ma_copy_string(const char* src, const ma_allocation_callbacks* pAllocationCallbacks) { size_t sz; @@ -1263,7 +1308,7 @@ MA_API MA_NO_INLINE char* ma_copy_string(const char* src, const ma_allocation_ca MA_API MA_NO_INLINE wchar_t* ma_copy_string_w(const wchar_t* src, const ma_allocation_callbacks* pAllocationCallbacks) { - size_t sz = wcslen(src)+1; + size_t sz = ma_wcslen(src)+1; wchar_t* dst = (wchar_t*)ma_malloc(sz * sizeof(*dst), pAllocationCallbacks); if (dst == NULL) { return NULL; @@ -1694,7 +1739,7 @@ MA_API ma_result ma_fopen(FILE** ppFile, const char* pFilePath, const char* pOpe return MA_INVALID_ARGS; } -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if (defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(MA_XBOX_NXDK) err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return ma_result_from_errno(err); @@ -1736,7 +1781,7 @@ _wfopen() isn't always available in all compilation environments. This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ -#if defined(_WIN32) +#if defined(_WIN32) && !defined(MA_XBOX_NXDK) #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define MA_HAS_WFOPEN #endif @@ -1752,29 +1797,34 @@ MA_API ma_result ma_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_ return MA_INVALID_ARGS; } -#if defined(MA_HAS_WFOPEN) + #if defined(MA_HAS_WFOPEN) { /* Use _wfopen() on Windows. */ - #if defined(_MSC_VER) && _MSC_VER >= 1400 - errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); - if (err != 0) { - return ma_result_from_errno(err); + #if defined(_MSC_VER) && _MSC_VER >= 1400 + { + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return ma_result_from_errno(err); + } } - #else - *ppFile = _wfopen(pFilePath, pOpenMode); - if (*ppFile == NULL) { - return ma_result_from_errno(errno); + #else + { + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return ma_result_from_errno(errno); + } } - #endif + #endif + (void)pAllocationCallbacks; } -#else - /* - Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can - think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for - maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. - */ + #elif !defined(MA_XBOX_NXDK) && !defined(MA_DOS) /* If your compiler does not support wcsrtombs(), add it here. */ { + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can + think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. + */ mbstate_t mbs; size_t lenMB; const wchar_t* pFilePathTemp = pFilePath; @@ -1815,11 +1865,16 @@ MA_API ma_result ma_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_ ma_free(pFilePathMB, pAllocationCallbacks); } + #else + { + /* Getting here means there is no way to open the file with a wide character string. */ + *ppFile = NULL; + } + #endif if (*ppFile == NULL) { return MA_ERROR; } -#endif return MA_SUCCESS; } @@ -1828,7 +1883,7 @@ MA_API ma_result ma_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_ static MA_INLINE void ma_copy_memory_64(void* dst, const void* src, ma_uint64 sizeInBytes) { -#if 0xFFFFFFFFFFFFFFFF <= MA_SIZE_MAX +#if MA_SIZE_MAX > 0xFFFFFFFF MA_COPY_MEMORY(dst, src, (size_t)sizeInBytes); #else while (sizeInBytes > 0) { @@ -1848,7 +1903,7 @@ static MA_INLINE void ma_copy_memory_64(void* dst, const void* src, ma_uint64 si static MA_INLINE void ma_zero_memory_64(void* dst, ma_uint64 sizeInBytes) { -#if 0xFFFFFFFFFFFFFFFF <= MA_SIZE_MAX +#if MA_SIZE_MAX > 0xFFFFFFFF MA_ZERO_MEMORY(dst, (size_t)sizeInBytes); #else while (sizeInBytes > 0) { @@ -1977,6 +2032,18 @@ static ma_result ma_allocation_callbacks_init_copy(ma_allocation_callbacks* pDst Logging **************************************************************************************************************************************************************/ +#ifndef ma_va_copy + #if !defined(_MSC_VER) || _MSC_VER >= 1800 + #if (defined(__GNUC__) && __GNUC__ < 3) + #define ma_va_copy(dst, src) ((dst) = (src)) /* This is untested. Not sure if this is correct for old GCC. */ + #else + #define ma_va_copy(dst, src) va_copy((dst), (src)) + #endif + #else + #define ma_va_copy(dst, src) ((dst) = (src)) + #endif +#endif + MA_API const char* ma_log_level_to_string(ma_uint32 logLevel) { switch (logLevel) @@ -2217,9 +2284,15 @@ MA_API ma_result ma_log_postv(ma_log* pLog, ma_uint32 level, const char* pFormat int length; char pFormattedMessageStack[1024]; char* pFormattedMessageHeap = NULL; + va_list args2; /* First try formatting into our fixed sized stack allocated buffer. If this is too small we'll fallback to a heap allocation. */ - length = vsnprintf(pFormattedMessageStack, sizeof(pFormattedMessageStack), pFormat, args); + ma_va_copy(args2, args); + { + length = vsnprintf(pFormattedMessageStack, sizeof(pFormattedMessageStack), pFormat, args2); + } + va_end(args2); + if (length < 0) { return MA_INVALID_OPERATION; /* An error occurred when trying to convert the buffer. */ } @@ -2260,17 +2333,10 @@ MA_API ma_result ma_log_postv(ma_log* pLog, ma_uint32 level, const char* pFormat char* pFormattedMessage = NULL; va_list args2; - #if _MSC_VER >= 1800 + ma_va_copy(args2, args); { - va_copy(args2, args); + formattedLen = ma_vscprintf(&pLog->allocationCallbacks, pFormat, args2); } - #else - { - args2 = args; - } - #endif - - formattedLen = ma_vscprintf(&pLog->allocationCallbacks, pFormat, args2); va_end(args2); if (formattedLen <= 0) { @@ -2469,7 +2535,7 @@ miniaudio's purposes. #define MA_LCG_A 48271 #define MA_LCG_C 0 -static ma_lcg g_maLCG = {MA_DEFAULT_LCG_SEED}; /* Non-zero initial seed. Use ma_seed() to use an explicit seed. */ +static ma_lcg g_maLCG = {MA_DEFAULT_LCG_SEED}; /* Non-zero initial seed. Use ma_lcg_seed() to use an explicit seed. */ static MA_INLINE void ma_lcg_seed(ma_lcg* pLCG, ma_int32 seed) { @@ -2518,7 +2584,7 @@ static MA_INLINE ma_int32 ma_lcg_rand_range_s32(ma_lcg* pLCG, ma_int32 lo, ma_in } - +#if 0 /* Currently unused. */ static MA_INLINE void ma_seed(ma_int32 seed) { ma_lcg_seed(&g_maLCG, seed); @@ -2543,6 +2609,7 @@ static MA_INLINE float ma_rand_f32(void) { return ma_lcg_rand_f32(&g_maLCG); } +#endif static MA_INLINE float ma_rand_range_f32(float lo, float hi) { @@ -2602,6 +2669,7 @@ Atomics **************************************************************************************************************************************************************/ /* c89atomic.h begin */ #ifndef ma_atomic_h +#define ma_atomic_h #if defined(__cplusplus) extern "C" { #endif @@ -2613,11 +2681,40 @@ extern "C" { #endif #endif typedef int ma_atomic_memory_order; -#define MA_ATOMIC_HAS_8 -#define MA_ATOMIC_HAS_16 -#define MA_ATOMIC_HAS_32 -#define MA_ATOMIC_HAS_64 -#if (defined(_MSC_VER) ) || defined(__WATCOMC__) || defined(__DMC__) +#if !defined(MA_ATOMIC_MODERN_MSVC) && \ + !defined(MA_ATOMIC_LEGACY_MSVC) && \ + !defined(MA_ATOMIC_LEGACY_MSVC_ASM) && \ + !defined(MA_ATOMIC_MODERN_GCC) && \ + !defined(MA_ATOMIC_LEGACY_GCC) && \ + !defined(MA_ATOMIC_LEGACY_GCC_ASM) + #if defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__) || defined(__BORLANDC__) + #if (defined(_MSC_VER) && _MSC_VER > 1600) + #define MA_ATOMIC_MODERN_MSVC + #else + #if defined(MA_X64) + #define MA_ATOMIC_LEGACY_MSVC + #else + #define MA_ATOMIC_LEGACY_MSVC_ASM + #endif + #endif + #elif (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) || defined(__clang__) + #define MA_ATOMIC_MODERN_GCC + #else + #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) + #define MA_ATOMIC_LEGACY_GCC + #else + #define MA_ATOMIC_LEGACY_GCC_ASM + #endif + #endif +#endif +#if defined(MA_ATOMIC_MODERN_MSVC) || defined(MA_ATOMIC_LEGACY_MSVC) + #include + #define ma_atomic_memory_order_relaxed 1 + #define ma_atomic_memory_order_consume 2 + #define ma_atomic_memory_order_acquire 3 + #define ma_atomic_memory_order_release 4 + #define ma_atomic_memory_order_acq_rel 5 + #define ma_atomic_memory_order_seq_cst 6 #define MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, intrin, ma_atomicType, msvcType) \ ma_atomicType result; \ switch (order) \ @@ -2643,720 +2740,1501 @@ typedef int ma_atomic_memory_order; } break; \ } \ return result; - #define MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, expected, desired, order, intrin, ma_atomicType, msvcType) \ + typedef ma_uint32 ma_atomic_flag; + static MA_INLINE ma_atomic_flag ma_atomic_flag_test_and_set_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, 1, order, _InterlockedExchange, ma_atomic_flag, long); + } + #else + { + (void)order; + return (ma_atomic_flag)_InterlockedExchange((volatile long*)dst, (long)1); + } + #endif + } + static MA_INLINE void ma_atomic_flag_clear_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, 0, order, _InterlockedExchange, ma_atomic_flag, long); + } + #else + { + (void)order; + _InterlockedExchange((volatile long*)dst, (long)0); + } + #endif + } + static MA_INLINE ma_atomic_flag ma_atomic_flag_load_explicit(volatile const ma_atomic_flag* dst, ma_atomic_memory_order order) + { + (void)order; + return (ma_uint32)_InterlockedCompareExchange((volatile long*)dst, 0, 0); + } +#endif +#if defined(MA_ATOMIC_LEGACY_MSVC_ASM) + #define ma_atomic_memory_order_relaxed 1 + #define ma_atomic_memory_order_consume 2 + #define ma_atomic_memory_order_acquire 3 + #define ma_atomic_memory_order_release 4 + #define ma_atomic_memory_order_acq_rel 5 + #define ma_atomic_memory_order_seq_cst 6 + typedef ma_uint32 ma_atomic_flag; + static MA_INLINE ma_atomic_flag ma_atomic_flag_test_and_set_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + ma_atomic_flag result = 0; + (void)order; + __asm { + mov ecx, dst + mov eax, 1 + xchg [ecx], eax + mov result, eax + } + return result; + } + static MA_INLINE void ma_atomic_flag_clear_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov dword ptr [esi], 0 + } + } else { + __asm { + mov esi, dst + mov eax, 0 + xchg [esi], eax + } + } + } + static MA_INLINE ma_atomic_flag ma_atomic_flag_load_explicit(volatile const ma_atomic_flag* dst, ma_atomic_memory_order order) + { + ma_atomic_flag result = 0; + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov eax, [esi] + mov result, eax + } + } else if (order <= ma_atomic_memory_order_release) { + __asm { + mov esi, dst + mov eax, [esi] + lock add dword ptr [esp], 0 + mov result, eax + } + } else { + __asm { + lock add dword ptr [esp], 0 + mov esi, dst + mov eax, [esi] + mov result, eax + lock add dword ptr [esp], 0 + } + } + return result; + } +#endif +#if defined(MA_ATOMIC_MODERN_GCC) + #define ma_atomic_memory_order_relaxed __ATOMIC_RELAXED + #define ma_atomic_memory_order_consume __ATOMIC_CONSUME + #define ma_atomic_memory_order_acquire __ATOMIC_ACQUIRE + #define ma_atomic_memory_order_release __ATOMIC_RELEASE + #define ma_atomic_memory_order_acq_rel __ATOMIC_ACQ_REL + #define ma_atomic_memory_order_seq_cst __ATOMIC_SEQ_CST + typedef ma_uint32 ma_atomic_flag; + #define ma_atomic_flag_test_and_set_explicit(dst, order) __atomic_exchange_n(dst, 1, order) + #define ma_atomic_flag_clear_explicit(dst, order) __atomic_store_n(dst, 0, order) + #define ma_atomic_flag_load_explicit(dst, order) __atomic_load_n(dst, order) +#endif +#if defined(MA_ATOMIC_LEGACY_GCC) + #define ma_atomic_memory_order_relaxed 1 + #define ma_atomic_memory_order_consume 2 + #define ma_atomic_memory_order_acquire 3 + #define ma_atomic_memory_order_release 4 + #define ma_atomic_memory_order_acq_rel 5 + #define ma_atomic_memory_order_seq_cst 6 + typedef ma_uint32 ma_atomic_flag; + static MA_INLINE ma_atomic_flag ma_atomic_flag_test_and_set_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + if (order > ma_atomic_memory_order_acquire) { + __sync_synchronize(); + } + return __sync_lock_test_and_set(dst, 1); + } + static MA_INLINE void ma_atomic_flag_clear_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + if (order > ma_atomic_memory_order_release) { + __sync_synchronize(); + } + __sync_lock_release(dst); + } + static MA_INLINE ma_atomic_flag ma_atomic_flag_load_explicit(volatile const ma_atomic_flag* dst, ma_atomic_memory_order order) + { + (void)order; + return __sync_val_compare_and_swap((ma_atomic_flag*)dst, 0, 0); + } +#endif +#if defined(MA_ATOMIC_LEGACY_GCC_ASM) + #define ma_atomic_memory_order_relaxed 1 + #define ma_atomic_memory_order_consume 2 + #define ma_atomic_memory_order_acquire 3 + #define ma_atomic_memory_order_release 4 + #define ma_atomic_memory_order_acq_rel 5 + #define ma_atomic_memory_order_seq_cst 6 + #if defined(MA_X86) + #define ma_atomic_thread_fence(order) __asm__ __volatile__("lock; addl $0, (%%esp)" ::: "memory") + #elif defined(MA_X64) + #define ma_atomic_thread_fence(order) __asm__ __volatile__("lock; addq $0, (%%rsp)" ::: "memory") + #else + #error Unsupported architecture. + #endif + #define MA_ATOMIC_XCHG_GCC_X86(instructionSizeSuffix, result, dst, src) \ + __asm__ __volatile__( \ + "xchg"instructionSizeSuffix" %0, %1" \ + : "=r"(result), \ + "=m"(*dst) \ + : "0"(src), \ + "m"(*dst) \ + : "memory" \ + ) + #define MA_ATOMIC_LOAD_RELAXED_GCC_X86(instructionSizeSuffix, result, dst) \ + __asm__ __volatile__( \ + "mov"instructionSizeSuffix" %1, %0" \ + : "=r"(result) \ + : "m"(*dst) \ + ) + #define MA_ATOMIC_LOAD_RELEASE_GCC_X86(instructionSizeSuffix, result, dst) \ + ma_atomic_thread_fence(ma_atomic_memory_order_release); \ + __asm__ __volatile__( \ + "mov"instructionSizeSuffix" %1, %0" \ + : "=r"(result) \ + : "m"(*dst) \ + : "memory" \ + ) + #define MA_ATOMIC_LOAD_SEQ_CST_GCC_X86(instructionSizeSuffix, result, dst) \ + ma_atomic_thread_fence(ma_atomic_memory_order_seq_cst); \ + __asm__ __volatile__( \ + "mov"instructionSizeSuffix" %1, %0" \ + : "=r"(result) \ + : "m"(*dst) \ + : "memory" \ + ); \ + ma_atomic_thread_fence(ma_atomic_memory_order_seq_cst) + typedef ma_uint32 ma_atomic_flag; + static MA_INLINE ma_atomic_flag ma_atomic_flag_test_and_set_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + ma_atomic_flag result; + #if defined(MA_X86) || defined(MA_X64) + { + (void)order; + MA_ATOMIC_XCHG_GCC_X86("l", result, dst, 1); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + static MA_INLINE void ma_atomic_flag_clear_explicit(volatile ma_atomic_flag* dst, ma_atomic_memory_order order) + { + #if defined(MA_X86) || defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm__ __volatile__( + "movl $0, %0" + : "=m"(*dst) + ); + } else if (order == ma_atomic_memory_order_release) { + __asm__ __volatile__( + "movl $0, %0" + : "=m"(*dst) + : + : "memory" + ); + } else { + ma_atomic_flag tmp = 0; + __asm__ __volatile__( + "xchgl %0, %1" + : "=r"(tmp), + "=m"(*dst) + : "0"(tmp), + "m"(*dst) + : "memory" + ); + } + } + #else + { + #error Unsupported architecture. + } + #endif + } + static MA_INLINE ma_atomic_flag ma_atomic_flag_load_explicit(volatile const ma_atomic_flag* dst, ma_atomic_memory_order order) + { + #if defined(MA_X86) || defined(MA_X64) + { + ma_atomic_flag result; + if (order == ma_atomic_memory_order_relaxed) { + MA_ATOMIC_LOAD_RELAXED_GCC_X86("l", result, dst); + } else if (order <= ma_atomic_memory_order_release) { + MA_ATOMIC_LOAD_RELEASE_GCC_X86("l", result, dst); + } else { + MA_ATOMIC_LOAD_SEQ_CST_GCC_X86("l", result, dst); + } + return result; + } + #else + { + #error Unsupported architecture. + } + #endif + } +#endif +#define ma_atomic_flag_test_and_set(dst) ma_atomic_flag_test_and_set_explicit(dst, ma_atomic_memory_order_acquire) +#define ma_atomic_flag_clear(dst) ma_atomic_flag_clear_explicit(dst, ma_atomic_memory_order_release) +typedef ma_atomic_flag ma_atomic_spinlock; +static MA_INLINE void ma_atomic_spinlock_lock(volatile ma_atomic_spinlock* pSpinlock) +{ + for (;;) { + if (ma_atomic_flag_test_and_set_explicit(pSpinlock, ma_atomic_memory_order_acquire) == 0) { + break; + } + while (ma_atomic_flag_load_explicit(pSpinlock, ma_atomic_memory_order_relaxed) == 1) { + } + } +} +static MA_INLINE void ma_atomic_spinlock_unlock(volatile ma_atomic_spinlock* pSpinlock) +{ + ma_atomic_flag_clear_explicit(pSpinlock, ma_atomic_memory_order_release); +} +ma_atomic_spinlock ma_atomic_global_lock; +#if defined(MA_ATOMIC_MODERN_MSVC) || defined(MA_ATOMIC_LEGACY_MSVC) || defined(MA_ATOMIC_LEGACY_MSVC_ASM) || defined(MA_ATOMIC_LEGACY_GCC) || defined(MA_ATOMIC_LEGACY_GCC_ASM) + #if defined(MA_X64) || (defined(MA_X86) && ((defined(__GNUC__) && defined(__i486__)) || (defined(_M_IX86) && _M_IX86 >= 400))) + #if defined(MA_ATOMIC_LEGACY_MSVC) && defined(MA_X64) + #else + #define MA_ATOMIC_IS_LOCK_FREE_8 1 + #define MA_ATOMIC_IS_LOCK_FREE_16 1 + #endif + #define MA_ATOMIC_IS_LOCK_FREE_32 1 + #if defined(MA_X64) || (defined(MA_X86) && ((defined(__GNUC__) && defined(__i586__)) || (defined(_M_IX86) && _M_IX86 >= 500))) + #define MA_ATOMIC_IS_LOCK_FREE_64 1 + #else + #endif + #else + #endif + #if defined(MA_ARM32) || defined(MA_ARM64) + #define MA_ATOMIC_IS_LOCK_FREE_8 1 + #define MA_ATOMIC_IS_LOCK_FREE_16 1 + #define MA_ATOMIC_IS_LOCK_FREE_32 1 + #if defined(MA_ARM64) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) + #define MA_ATOMIC_IS_LOCK_FREE_64 1 + #endif + #endif + #if defined(MA_ATOMIC_PPC32) || defined(MA_ATOMIC_PPC64) + #if (defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7))) && !defined(__clang__) + #else + #define MA_ATOMIC_IS_LOCK_FREE_8 1 + #define MA_ATOMIC_IS_LOCK_FREE_16 1 + #endif + #define MA_ATOMIC_IS_LOCK_FREE_32 1 + #if defined(MA_ATOMIC_PPC64) + #define MA_ATOMIC_IS_LOCK_FREE_64 1 + #endif + #endif + static MA_INLINE ma_bool32 ma_atomic_is_lock_free_8(volatile void* ptr) + { + (void)ptr; + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + return 1; + #else + return 0; + #endif + } + static MA_INLINE ma_bool32 ma_atomic_is_lock_free_16(volatile void* ptr) + { + (void)ptr; + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + return 1; + #else + return 0; + #endif + } + static MA_INLINE ma_bool32 ma_atomic_is_lock_free_32(volatile void* ptr) + { + (void)ptr; + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + return 1; + #else + return 0; + #endif + } + static MA_INLINE ma_bool32 ma_atomic_is_lock_free_64(volatile void* ptr) + { + (void)ptr; + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + return 1; + #else + return 0; + #endif + } +#endif +#define MA_ATOMIC_COMPARE_AND_SWAP_LOCK(sizeInBits, dst, expected, replacement) \ + ma_uint##sizeInBits result; \ + ma_atomic_spinlock_lock(&ma_atomic_global_lock); \ + { \ + result = *dst; \ + if (result == expected) { \ + *dst = replacement; \ + } \ + } \ + ma_atomic_spinlock_unlock(&ma_atomic_global_lock); \ + return result +#define MA_ATOMIC_LOAD_EXPLICIT_LOCK(sizeInBits, ptr, order) \ + ma_uint##sizeInBits result; \ + ma_atomic_spinlock_lock(&ma_atomic_global_lock); \ + { \ + result = *ptr; \ + (void)order; \ + } \ + ma_atomic_spinlock_unlock(&ma_atomic_global_lock); \ + return result +#define MA_ATOMIC_STORE_EXPLICIT_LOCK(sizeInBits, dst, src, order) \ + ma_atomic_spinlock_lock(&ma_atomic_global_lock); \ + { \ + *dst = src; \ + (void)order; \ + } \ + ma_atomic_spinlock_unlock(&ma_atomic_global_lock) +#define MA_ATOMIC_STORE_EXPLICIT_CAS(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits oldValue; \ + do { \ + oldValue = ma_atomic_load_explicit_##sizeInBits(dst, ma_atomic_memory_order_relaxed); \ + } while (ma_atomic_compare_and_swap_##sizeInBits(dst, oldValue, src) != oldValue); \ + (void)order +#define MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits result; \ + ma_atomic_spinlock_lock(&ma_atomic_global_lock); \ + { \ + result = *dst; \ + *dst = src; \ + (void)order; \ + } \ + ma_atomic_spinlock_unlock(&ma_atomic_global_lock); \ + return result +#define MA_ATOMIC_EXCHANGE_EXPLICIT_CAS(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits oldValue; \ + do { \ + oldValue = ma_atomic_load_explicit_##sizeInBits(dst, ma_atomic_memory_order_relaxed); \ + } while (ma_atomic_compare_and_swap_##sizeInBits(dst, oldValue, src) != oldValue); \ + (void)order; \ + return oldValue +#define MA_ATOMIC_FETCH_ADD_LOCK(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits result; \ + ma_atomic_spinlock_lock(&ma_atomic_global_lock); \ + { \ + result = *dst; \ + *dst += src; \ + (void)order; \ + } \ + ma_atomic_spinlock_unlock(&ma_atomic_global_lock); \ + return result +#define MA_ATOMIC_FETCH_ADD_CAS(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits oldValue; \ + ma_uint##sizeInBits newValue; \ + do { \ + oldValue = ma_atomic_load_explicit_##sizeInBits(dst, ma_atomic_memory_order_relaxed); \ + newValue = oldValue + src; \ + } while (ma_atomic_compare_and_swap_##sizeInBits(dst, oldValue, newValue) != oldValue); \ + (void)order; \ + return oldValue +#define MA_ATOMIC_FETCH_AND_CAS(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits oldValue; \ + ma_uint##sizeInBits newValue; \ + do { \ + oldValue = ma_atomic_load_explicit_##sizeInBits(dst, ma_atomic_memory_order_relaxed); \ + newValue = (ma_uint##sizeInBits)(oldValue & src); \ + } while (ma_atomic_compare_and_swap_##sizeInBits(dst, oldValue, newValue) != oldValue); \ + (void)order; \ + return oldValue +#define MA_ATOMIC_FETCH_OR_CAS(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits oldValue; \ + ma_uint##sizeInBits newValue; \ + do { \ + oldValue = ma_atomic_load_explicit_##sizeInBits(dst, ma_atomic_memory_order_relaxed); \ + newValue = (ma_uint##sizeInBits)(oldValue | src); \ + } while (ma_atomic_compare_and_swap_##sizeInBits(dst, oldValue, newValue) != oldValue); \ + (void)order; \ + return oldValue +#define MA_ATOMIC_FETCH_XOR_CAS(sizeInBits, dst, src, order) \ + ma_uint##sizeInBits oldValue; \ + ma_uint##sizeInBits newValue; \ + do { \ + oldValue = ma_atomic_load_explicit_##sizeInBits(dst, ma_atomic_memory_order_relaxed); \ + newValue = (ma_uint##sizeInBits)(oldValue ^ src); \ + } while (ma_atomic_compare_and_swap_##sizeInBits(dst, oldValue, newValue) != oldValue); \ + (void)order; \ + return oldValue +#if defined(MA_ATOMIC_MODERN_MSVC) || defined(MA_ATOMIC_LEGACY_MSVC) + #define MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, expected, replacement, order, intrin, ma_atomicType, msvcType) \ ma_atomicType result; \ switch (order) \ { \ case ma_atomic_memory_order_relaxed: \ { \ - result = (ma_atomicType)intrin##_nf((volatile msvcType*)ptr, (msvcType)expected, (msvcType)desired); \ + result = (ma_atomicType)intrin##_nf((volatile msvcType*)ptr, (msvcType)expected, (msvcType)replacement); \ } break; \ case ma_atomic_memory_order_consume: \ case ma_atomic_memory_order_acquire: \ { \ - result = (ma_atomicType)intrin##_acq((volatile msvcType*)ptr, (msvcType)expected, (msvcType)desired); \ + result = (ma_atomicType)intrin##_acq((volatile msvcType*)ptr, (msvcType)expected, (msvcType)replacement); \ } break; \ case ma_atomic_memory_order_release: \ { \ - result = (ma_atomicType)intrin##_rel((volatile msvcType*)ptr, (msvcType)expected, (msvcType)desired); \ + result = (ma_atomicType)intrin##_rel((volatile msvcType*)ptr, (msvcType)expected, (msvcType)replacement); \ } break; \ case ma_atomic_memory_order_acq_rel: \ case ma_atomic_memory_order_seq_cst: \ default: \ { \ - result = (ma_atomicType)intrin((volatile msvcType*)ptr, (msvcType)expected, (msvcType)desired); \ + result = (ma_atomicType)intrin((volatile msvcType*)ptr, (msvcType)expected, (msvcType)replacement); \ } break; \ } \ return result; - #define ma_atomic_memory_order_relaxed 0 - #define ma_atomic_memory_order_consume 1 - #define ma_atomic_memory_order_acquire 2 - #define ma_atomic_memory_order_release 3 - #define ma_atomic_memory_order_acq_rel 4 - #define ma_atomic_memory_order_seq_cst 5 - #if _MSC_VER < 1600 && defined(MA_X86) - #define MA_ATOMIC_MSVC_USE_INLINED_ASSEMBLY - #endif - #if _MSC_VER < 1600 - #undef MA_ATOMIC_HAS_8 - #undef MA_ATOMIC_HAS_16 - #endif - #if !defined(MA_ATOMIC_MSVC_USE_INLINED_ASSEMBLY) - #include - #endif - #if defined(MA_ATOMIC_MSVC_USE_INLINED_ASSEMBLY) - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 desired) - { - ma_uint8 result = 0; - __asm { - mov ecx, dst - mov al, expected - mov dl, desired - lock cmpxchg [ecx], dl - mov result, al - } - return result; - } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 desired) - { - ma_uint16 result = 0; - __asm { - mov ecx, dst - mov ax, expected - mov dx, desired - lock cmpxchg [ecx], dx - mov result, ax - } - return result; - } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 desired) - { - ma_uint32 result = 0; - __asm { - mov ecx, dst - mov eax, expected - mov edx, desired - lock cmpxchg [ecx], edx - mov result, eax - } - return result; - } - #endif - #if defined(MA_ATOMIC_HAS_64) - static MA_INLINE ma_uint64 __stdcall ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) - { - ma_uint32 resultEAX = 0; - ma_uint32 resultEDX = 0; - __asm { - mov esi, dst - mov eax, dword ptr expected - mov edx, dword ptr expected + 4 - mov ebx, dword ptr desired - mov ecx, dword ptr desired + 4 - lock cmpxchg8b qword ptr [esi] - mov resultEAX, eax - mov resultEDX, edx - } - return ((ma_uint64)resultEDX << 32) | resultEAX; - } - #endif + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + #define ma_atomic_compare_and_swap_8( dst, expected, replacement) (ma_uint8 )_InterlockedCompareExchange8((volatile char*)dst, (char)replacement, (char)expected) #else - #if defined(MA_ATOMIC_HAS_8) - #define ma_atomic_compare_and_swap_8( dst, expected, desired) (ma_uint8 )_InterlockedCompareExchange8((volatile char*)dst, (char)desired, (char)expected) - #endif - #if defined(MA_ATOMIC_HAS_16) - #define ma_atomic_compare_and_swap_16(dst, expected, desired) (ma_uint16)_InterlockedCompareExchange16((volatile short*)dst, (short)desired, (short)expected) - #endif - #if defined(MA_ATOMIC_HAS_32) - #define ma_atomic_compare_and_swap_32(dst, expected, desired) (ma_uint32)_InterlockedCompareExchange((volatile long*)dst, (long)desired, (long)expected) - #endif - #if defined(MA_ATOMIC_HAS_64) - #define ma_atomic_compare_and_swap_64(dst, expected, desired) (ma_uint64)_InterlockedCompareExchange64((volatile ma_int64*)dst, (ma_int64)desired, (ma_int64)expected) - #endif + static MA_INLINE ma_uint8 __stdcall ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 replacement) + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(8, dst, expected, replacement); + } #endif - #if defined(MA_ATOMIC_MSVC_USE_INLINED_ASSEMBLY) - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_exchange_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - ma_uint8 result = 0; - (void)order; - __asm { - mov ecx, dst - mov al, src - lock xchg [ecx], al - mov result, al - } - return result; - } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_exchange_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { - ma_uint16 result = 0; - (void)order; - __asm { - mov ecx, dst - mov ax, src - lock xchg [ecx], ax - mov result, ax - } - return result; - } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_exchange_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - ma_uint32 result = 0; - (void)order; - __asm { - mov ecx, dst - mov eax, src - lock xchg [ecx], eax - mov result, eax - } - return result; - } - #endif + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + #define ma_atomic_compare_and_swap_16(dst, expected, replacement) (ma_uint16)_InterlockedCompareExchange16((volatile short*)dst, (short)replacement, (short)expected) #else - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_exchange_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { + static MA_INLINE ma_uint16 __stdcall ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 replacement) + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(16, dst, expected, replacement); + } + #endif + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + #define ma_atomic_compare_and_swap_32(dst, expected, replacement) (ma_uint32)_InterlockedCompareExchange((volatile long*)dst, (long)replacement, (long)expected) + #else + static MA_INLINE ma_uint32 __stdcall ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 replacement) + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(32, dst, expected, replacement); + } + #endif + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + #define ma_atomic_compare_and_swap_64(dst, expected, replacement) (ma_uint64)_InterlockedCompareExchange64((volatile ma_int64*)dst, (ma_int64)replacement, (ma_int64)expected) + #else + static MA_INLINE ma_uint64 __stdcall ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 replacement) + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(64, dst, expected, replacement); + } + #endif + static MA_INLINE ma_uint8 ma_atomic_load_explicit_8(volatile const ma_uint8* ptr, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchange8, ma_uint8, char); + { + MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange8, ma_uint8, char); + } #else + { + (void)order; + return ma_atomic_compare_and_swap_8((volatile ma_uint8*)ptr, 0, 0); + } + #endif + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(8, ptr, order); + } + #endif + } + static MA_INLINE ma_uint16 ma_atomic_load_explicit_16(volatile const ma_uint16* ptr, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange16, ma_uint16, short); + } + #else + { + (void)order; + return ma_atomic_compare_and_swap_16((volatile ma_uint16*)ptr, 0, 0); + } + #endif + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(16, ptr, order); + } + #endif + } + static MA_INLINE ma_uint32 ma_atomic_load_explicit_32(volatile const ma_uint32* ptr, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange, ma_uint32, long); + } + #else + { + (void)order; + return ma_atomic_compare_and_swap_32((volatile ma_uint32*)ptr, 0, 0); + } + #endif + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(32, ptr, order); + } + #endif + } + static MA_INLINE ma_uint64 ma_atomic_load_explicit_64(volatile const ma_uint64* ptr, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange64, ma_uint64, long long); + } + #else + { + (void)order; + return ma_atomic_compare_and_swap_64((volatile ma_uint64*)ptr, 0, 0); + } + #endif + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(64, ptr, order); + } + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_exchange_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchange8, ma_uint8, char); + } + #else + { (void)order; return (ma_uint8)_InterlockedExchange8((volatile char*)dst, (char)src); - #endif } + #endif + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(8, dst, src, order); + } #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_exchange_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_exchange_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { #if defined(MA_ARM) + { MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchange16, ma_uint16, short); + } #else + { (void)order; return (ma_uint16)_InterlockedExchange16((volatile short*)dst, (short)src); - #endif } + #endif + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(16, dst, src, order); + } #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_exchange_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_exchange_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { #if defined(MA_ARM) + { MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchange, ma_uint32, long); + } #else + { (void)order; return (ma_uint32)_InterlockedExchange((volatile long*)dst, (long)src); - #endif } - #endif - #if defined(MA_ATOMIC_HAS_64) && defined(MA_64BIT) - static MA_INLINE ma_uint64 __stdcall ma_atomic_exchange_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchange64, ma_uint64, long long); - #else - (void)order; - return (ma_uint64)_InterlockedExchange64((volatile long long*)dst, (long long)src); #endif - } - #else - #endif - #endif - #if defined(MA_ATOMIC_HAS_64) && !defined(MA_64BIT) - static MA_INLINE ma_uint64 __stdcall ma_atomic_exchange_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - ma_uint64 oldValue; - do { - oldValue = *dst; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, src) != oldValue); - (void)order; - return oldValue; } - #endif - #if defined(MA_ATOMIC_MSVC_USE_INLINED_ASSEMBLY) - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_add_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - ma_uint8 result = 0; - (void)order; - __asm { - mov ecx, dst - mov al, src - lock xadd [ecx], al - mov result, al - } - return result; - } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(32, dst, src, order); + } #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_add_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_exchange_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + #if defined(MA_32BIT) { - ma_uint16 result = 0; - (void)order; - __asm { - mov ecx, dst - mov ax, src - lock xadd [ecx], ax - mov result, ax - } - return result; + MA_ATOMIC_EXCHANGE_EXPLICIT_CAS(64, dst, src, order); } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_add_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - ma_uint32 result = 0; - (void)order; - __asm { - mov ecx, dst - mov eax, src - lock xadd [ecx], eax - mov result, eax - } - return result; - } - #endif - #else - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_add_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchangeAdd8, ma_uint8, char); #else + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchange64, ma_uint64, long long); + } + #else + { + (void)order; + return (ma_uint64)_InterlockedExchange64((volatile long long*)dst, (long long)src); + } + #endif + } + #endif + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_add_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchangeAdd8, ma_uint8, char); + } + #else + { (void)order; return (ma_uint8)_InterlockedExchangeAdd8((volatile char*)dst, (char)src); - #endif } + #endif + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(8, dst, src, order); + } #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_add_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_add_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { #if defined(MA_ARM) + { MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchangeAdd16, ma_uint16, short); + } #else + { (void)order; return (ma_uint16)_InterlockedExchangeAdd16((volatile short*)dst, (short)src); - #endif } + #endif + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(16, dst, src, order); + } #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_add_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_add_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { #if defined(MA_ARM) + { MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchangeAdd, ma_uint32, long); + } #else + { (void)order; return (ma_uint32)_InterlockedExchangeAdd((volatile long*)dst, (long)src); - #endif } - #endif - #if defined(MA_ATOMIC_HAS_64) && defined(MA_64BIT) - static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_add_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchangeAdd64, ma_uint64, long long); - #else - (void)order; - return (ma_uint64)_InterlockedExchangeAdd64((volatile long long*)dst, (long long)src); #endif - } + } #else - #endif - #endif - #if defined(MA_ATOMIC_HAS_64) && !defined(MA_64BIT) - static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_add_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) { - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue + src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + MA_ATOMIC_FETCH_ADD_LOCK(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_add_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + #if defined(MA_32BIT) + { + MA_ATOMIC_FETCH_ADD_CAS(64, dst, src, order); + } + #else + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedExchangeAdd64, ma_uint64, long long); + } + #else + { + (void)order; + return (ma_uint64)_InterlockedExchangeAdd64((volatile long long*)dst, (long long)src); + } + #endif + } + #endif + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_sub_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_8(dst, (ma_uint8)(-(ma_int8)src), order); + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_sub_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_16(dst, (ma_uint16)(-(ma_int16)src), order); + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_sub_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_32(dst, (ma_uint32)(-(ma_int32)src), order); + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_sub_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_64(dst, (ma_uint64)(-(ma_int64)src), order); + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_and_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd8, ma_uint8, char); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(8, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_and_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd16, ma_uint16, short); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(16, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_and_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd, ma_uint32, long); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_and_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd64, ma_uint64, long long); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_or_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr8, ma_uint8, char); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(8, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_or_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr16, ma_uint16, short); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(16, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_or_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr, ma_uint32, long); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_or_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr64, ma_uint64, long long); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_xor_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor8, ma_uint8, char); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(8, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_xor_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor16, ma_uint16, short); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(16, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_xor_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor, ma_uint32, long); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_xor_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ARM) + { + MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor64, ma_uint64, long long); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(64, dst, src, order); + } + #endif + } + #define ma_atomic_store_explicit_8( dst, src, order) (void)ma_atomic_exchange_explicit_8 (dst, src, order) + #define ma_atomic_store_explicit_16(dst, src, order) (void)ma_atomic_exchange_explicit_16(dst, src, order) + #define ma_atomic_store_explicit_32(dst, src, order) (void)ma_atomic_exchange_explicit_32(dst, src, order) + #define ma_atomic_store_explicit_64(dst, src, order) (void)ma_atomic_exchange_explicit_64(dst, src, order) + #if defined(MA_X64) + #define ma_atomic_thread_fence(order) __faststorefence(), (void)order + #elif defined(MA_ARM64) + #define ma_atomic_thread_fence(order) __dmb(_ARM64_BARRIER_ISH), (void)order + #else + static MA_INLINE void ma_atomic_thread_fence(ma_atomic_memory_order order) + { + volatile ma_uint32 barrier = 0; + ma_atomic_fetch_add_explicit_32(&barrier, 0, order); } #endif - #if defined(MA_ATOMIC_MSVC_USE_INLINED_ASSEMBLY) - static MA_INLINE void __stdcall ma_atomic_thread_fence(ma_atomic_memory_order order) + #define ma_atomic_signal_fence(order) _ReadWriteBarrier(), (void)order +#endif +#if defined(MA_ATOMIC_LEGACY_MSVC_ASM) + static MA_INLINE ma_uint8 __stdcall ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) { + ma_uint8 result = 0; + __asm { + mov ecx, dst + mov al, expected + mov dl, replacement + lock cmpxchg [ecx], dl + mov result, al + } + return result; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(8, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + ma_uint16 result = 0; + __asm { + mov ecx, dst + mov ax, expected + mov dx, replacement + lock cmpxchg [ecx], dx + mov result, ax + } + return result; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(16, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + ma_uint32 result = 0; + __asm { + mov ecx, dst + mov eax, expected + mov edx, replacement + lock cmpxchg [ecx], edx + mov result, eax + } + return result; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(32, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + ma_uint32 resultEAX = 0; + ma_uint32 resultEDX = 0; + __asm { + mov esi, dst + mov eax, dword ptr expected + mov edx, dword ptr expected + 4 + mov ebx, dword ptr replacement + mov ecx, dword ptr replacement + 4 + lock cmpxchg8b qword ptr [esi] + mov resultEAX, eax + mov resultEDX, edx + } + return ((ma_uint64)resultEDX << 32) | resultEAX; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(64, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint8 ma_atomic_load_explicit_8(volatile const ma_uint8* dst, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + ma_uint8 result = 0; + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov al, [esi] + mov result, al + } + } else if (order <= ma_atomic_memory_order_release) { + __asm { + mov esi, dst + mov al, [esi] + lock add dword ptr [esp], 0 + mov result, al + } + } else { + __asm { + lock add dword ptr [esp], 0 + mov esi, dst + mov al, [esi] + mov result, al + lock add dword ptr [esp], 0 + } + } + return result; + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(8, dst, order); + } + #endif + } + static MA_INLINE ma_uint16 ma_atomic_load_explicit_16(volatile const ma_uint16* dst, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + ma_uint16 result = 0; + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov ax, [esi] + mov result, ax + } + } else if (order <= ma_atomic_memory_order_release) { + __asm { + mov esi, dst + mov ax, [esi] + lock add dword ptr [esp], 0 + mov result, ax + } + } else { + __asm { + lock add dword ptr [esp], 0 + mov esi, dst + mov ax, [esi] + mov result, ax + lock add dword ptr [esp], 0 + } + } + return result; + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(16, dst, order); + } + #endif + } + static MA_INLINE ma_uint32 ma_atomic_load_explicit_32(volatile const ma_uint32* dst, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + ma_uint32 result = 0; + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov eax, [esi] + mov result, eax + } + } else if (order <= ma_atomic_memory_order_release) { + __asm { + mov esi, dst + mov eax, [esi] + lock add dword ptr [esp], 0 + mov result, eax + } + } else { + __asm { + lock add dword ptr [esp], 0 + mov esi, dst + mov eax, [esi] + mov result, eax + lock add dword ptr [esp], 0 + } + } + return result; + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(32, dst, order); + } + #endif + } + static MA_INLINE ma_uint64 ma_atomic_load_explicit_64(volatile const ma_uint64* dst, ma_atomic_memory_order order) + { + (void)order; + return ma_atomic_compare_and_swap_64((volatile ma_uint64*)dst, 0, 0); + } + static MA_INLINE void __stdcall ma_atomic_store_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov al, src + mov [esi], al + } + } else { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + __asm { + mov esi, dst + mov al, src + xchg [esi], al + } + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(8, dst, src, order); + } + #endif + } + } + static MA_INLINE void __stdcall ma_atomic_store_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov ax, src + mov [esi], ax + } + } else { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + __asm { + mov esi, dst + mov ax, src + xchg [esi], ax + } + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(16, dst, src, order); + } + #endif + } + } + static MA_INLINE void __stdcall ma_atomic_store_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm { + mov esi, dst + mov eax, src + mov [esi], eax + } + } else { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + __asm { + mov esi, dst + mov eax, src + xchg [esi], eax + } + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(32, dst, src, order); + } + #endif + } + } + static MA_INLINE void __stdcall ma_atomic_store_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + MA_ATOMIC_STORE_EXPLICIT_CAS(64, dst, src, order); + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_exchange_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + ma_uint8 result = 0; (void)order; __asm { - lock add [esp], 0 + mov ecx, dst + mov al, src + lock xchg [ecx], al + mov result, al } + return result; } - #else - #if defined(MA_X64) - #define ma_atomic_thread_fence(order) __faststorefence(), (void)order - #elif defined(MA_ARM64) - #define ma_atomic_thread_fence(order) __dmb(_ARM64_BARRIER_ISH), (void)order #else - static MA_INLINE void ma_atomic_thread_fence(ma_atomic_memory_order order) - { - volatile ma_uint32 barrier = 0; - ma_atomic_fetch_add_explicit_32(&barrier, 0, order); + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(8, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_exchange_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + ma_uint16 result = 0; + (void)order; + __asm { + mov ecx, dst + mov ax, src + lock xchg [ecx], ax + mov result, ax } - #endif - #endif - #define ma_atomic_compiler_fence() ma_atomic_thread_fence(ma_atomic_memory_order_seq_cst) - #define ma_atomic_signal_fence(order) ma_atomic_thread_fence(order) - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 ma_atomic_load_explicit_8(volatile const ma_uint8* ptr, ma_atomic_memory_order order) - { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange8, ma_uint8, char); + return result; + } #else - (void)order; - return ma_atomic_compare_and_swap_8((volatile ma_uint8*)ptr, 0, 0); - #endif - } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 ma_atomic_load_explicit_16(volatile const ma_uint16* ptr, ma_atomic_memory_order order) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange16, ma_uint16, short); + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(16, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_exchange_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + ma_uint32 result = 0; + (void)order; + __asm { + mov ecx, dst + mov eax, src + xchg [ecx], eax + mov result, eax + } + return result; + } #else - (void)order; - return ma_atomic_compare_and_swap_16((volatile ma_uint16*)ptr, 0, 0); - #endif - } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 ma_atomic_load_explicit_32(volatile const ma_uint32* ptr, ma_atomic_memory_order order) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange, ma_uint32, long); + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_exchange_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + MA_ATOMIC_EXCHANGE_EXPLICIT_CAS(64, dst, src, order); + } #else - (void)order; - return ma_atomic_compare_and_swap_32((volatile ma_uint32*)ptr, 0, 0); - #endif - } - #endif - #if defined(MA_ATOMIC_HAS_64) - static MA_INLINE ma_uint64 ma_atomic_load_explicit_64(volatile const ma_uint64* ptr, ma_atomic_memory_order order) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC_COMPARE_EXCHANGE(ptr, 0, 0, order, _InterlockedCompareExchange64, ma_uint64, long long); + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_add_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + ma_uint8 result = 0; + (void)order; + __asm { + mov ecx, dst + mov al, src + lock xadd [ecx], al + mov result, al + } + return result; + } #else - (void)order; - return ma_atomic_compare_and_swap_64((volatile ma_uint64*)ptr, 0, 0); + { + MA_ATOMIC_FETCH_ADD_LOCK(8, dst, src, order); + } #endif - } - #endif - #if defined(MA_ATOMIC_HAS_8) - #define ma_atomic_store_explicit_8( dst, src, order) (void)ma_atomic_exchange_explicit_8 (dst, src, order) - #endif - #if defined(MA_ATOMIC_HAS_16) - #define ma_atomic_store_explicit_16(dst, src, order) (void)ma_atomic_exchange_explicit_16(dst, src, order) - #endif - #if defined(MA_ATOMIC_HAS_32) - #define ma_atomic_store_explicit_32(dst, src, order) (void)ma_atomic_exchange_explicit_32(dst, src, order) - #endif - #if defined(MA_ATOMIC_HAS_64) - #define ma_atomic_store_explicit_64(dst, src, order) (void)ma_atomic_exchange_explicit_64(dst, src, order) - #endif - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_sub_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_add_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) { - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue - src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + ma_uint16 result = 0; (void)order; - return oldValue; + __asm { + mov ecx, dst + mov ax, src + lock xadd [ecx], ax + mov result, ax + } + return result; } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_sub_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue - src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_sub_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue - src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - #endif - #if defined(MA_ATOMIC_HAS_64) - static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_sub_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue - src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - #endif - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_and_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd8, ma_uint8, char); #else - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue & src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif - } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_and_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd16, ma_uint16, short); - #else - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue & src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif + MA_ATOMIC_FETCH_ADD_LOCK(16, dst, src, order); } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_and_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + #endif + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_add_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd, ma_uint32, long); - #else - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue & src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + ma_uint32 result = 0; (void)order; - return oldValue; - #endif + __asm { + mov ecx, dst + mov eax, src + lock xadd [ecx], eax + mov result, eax + } + return result; } - #endif - #if defined(MA_ATOMIC_HAS_64) - static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_and_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + #else { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedAnd64, ma_uint64, long long); - #else - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue & src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif + MA_ATOMIC_FETCH_ADD_LOCK(32, dst, src, order); } - #endif - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_xor_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_add_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor8, ma_uint8, char); - #else - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue ^ src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif + MA_ATOMIC_FETCH_ADD_CAS(64, dst, src, order); } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_xor_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + #else { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor16, ma_uint16, short); - #else - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue ^ src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif + MA_ATOMIC_FETCH_ADD_LOCK(64, dst, src, order); } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_xor_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + #endif + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_sub_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor, ma_uint32, long); - #else - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue ^ src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + ma_uint8 result = 0; (void)order; - return oldValue; - #endif + __asm { + mov ecx, dst + mov al, src + neg al + lock xadd [ecx], al + mov result, al + } + return result; } - #endif - #if defined(MA_ATOMIC_HAS_64) - static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_xor_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + #else { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedXor64, ma_uint64, long long); - #else - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue ^ src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif + MA_ATOMIC_FETCH_ADD_LOCK(8, dst, (ma_uint8)(-(ma_int8)src), order); } - #endif - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_or_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + #endif + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_sub_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr8, ma_uint8, char); - #else - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue | src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + ma_uint16 result = 0; (void)order; - return oldValue; - #endif + __asm { + mov ecx, dst + mov ax, src + neg ax + lock xadd [ecx], ax + mov result, ax + } + return result; } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_or_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + #else { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr16, ma_uint16, short); - #else - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue | src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif + MA_ATOMIC_FETCH_ADD_LOCK(16, dst, (ma_uint16)(-(ma_int16)src), order); } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_or_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + #endif + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_sub_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr, ma_uint32, long); - #else - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue | src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + ma_uint32 result = 0; (void)order; - return oldValue; - #endif + __asm { + mov ecx, dst + mov eax, src + neg eax + lock xadd [ecx], eax + mov result, eax + } + return result; } - #endif - #if defined(MA_ATOMIC_HAS_64) - static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_or_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + #else { - #if defined(MA_ARM) - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, _InterlockedOr64, ma_uint64, long long); - #else - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue | src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - #endif + MA_ATOMIC_FETCH_ADD_LOCK(32, dst, (ma_uint32)(-(ma_int32)src), order); } - #endif - #if defined(MA_ATOMIC_HAS_8) - #define ma_atomic_test_and_set_explicit_8( dst, order) ma_atomic_exchange_explicit_8 (dst, 1, order) - #endif - #if defined(MA_ATOMIC_HAS_16) - #define ma_atomic_test_and_set_explicit_16(dst, order) ma_atomic_exchange_explicit_16(dst, 1, order) - #endif - #if defined(MA_ATOMIC_HAS_32) - #define ma_atomic_test_and_set_explicit_32(dst, order) ma_atomic_exchange_explicit_32(dst, 1, order) - #endif - #if defined(MA_ATOMIC_HAS_64) - #define ma_atomic_test_and_set_explicit_64(dst, order) ma_atomic_exchange_explicit_64(dst, 1, order) - #endif - #if defined(MA_ATOMIC_HAS_8) - #define ma_atomic_clear_explicit_8( dst, order) ma_atomic_store_explicit_8 (dst, 0, order) - #endif - #if defined(MA_ATOMIC_HAS_16) - #define ma_atomic_clear_explicit_16(dst, order) ma_atomic_store_explicit_16(dst, 0, order) - #endif - #if defined(MA_ATOMIC_HAS_32) - #define ma_atomic_clear_explicit_32(dst, order) ma_atomic_store_explicit_32(dst, 0, order) - #endif - #if defined(MA_ATOMIC_HAS_64) - #define ma_atomic_clear_explicit_64(dst, order) ma_atomic_store_explicit_64(dst, 0, order) - #endif - #if defined(MA_ATOMIC_HAS_8) - typedef ma_uint8 ma_atomic_flag; - #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_8(ptr, order) - #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_8(ptr, order) - #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) - #else - typedef ma_uint32 ma_atomic_flag; - #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_32(ptr, order) - #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_32(ptr, order) - #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_32(ptr, order) - #endif -#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) + #endif + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_sub_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_ADD_CAS(64, dst, (ma_uint64)(-(ma_int64)src), order); + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_and_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(8, dst, src, order); + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_and_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(16, dst, src, order); + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_and_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(32, dst, src, order); + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_and_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(64, dst, src, order); + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_or_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(8, dst, src, order); + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_or_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(16, dst, src, order); + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_or_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(32, dst, src, order); + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_or_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(64, dst, src, order); + } + static MA_INLINE ma_uint8 __stdcall ma_atomic_fetch_xor_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(8, dst, src, order); + } + static MA_INLINE ma_uint16 __stdcall ma_atomic_fetch_xor_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(16, dst, src, order); + } + static MA_INLINE ma_uint32 __stdcall ma_atomic_fetch_xor_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(32, dst, src, order); + } + static MA_INLINE ma_uint64 __stdcall ma_atomic_fetch_xor_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(64, dst, src, order); + } + static MA_INLINE void __stdcall ma_atomic_thread_fence(ma_atomic_memory_order order) + { + (void)order; + __asm { + lock add dword ptr [esp], 0 + } + } + #define ma_atomic_signal_fence(order) __asm {}; (void)order +#endif +#if defined(MA_ATOMIC_MODERN_GCC) #define MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE - #define MA_ATOMIC_HAS_NATIVE_IS_LOCK_FREE - #define ma_atomic_memory_order_relaxed __ATOMIC_RELAXED - #define ma_atomic_memory_order_consume __ATOMIC_CONSUME - #define ma_atomic_memory_order_acquire __ATOMIC_ACQUIRE - #define ma_atomic_memory_order_release __ATOMIC_RELEASE - #define ma_atomic_memory_order_acq_rel __ATOMIC_ACQ_REL - #define ma_atomic_memory_order_seq_cst __ATOMIC_SEQ_CST - #define ma_atomic_compiler_fence() __asm__ __volatile__("":::"memory") #define ma_atomic_thread_fence(order) __atomic_thread_fence(order) #define ma_atomic_signal_fence(order) __atomic_signal_fence(order) #define ma_atomic_is_lock_free_8(ptr) __atomic_is_lock_free(1, ptr) #define ma_atomic_is_lock_free_16(ptr) __atomic_is_lock_free(2, ptr) #define ma_atomic_is_lock_free_32(ptr) __atomic_is_lock_free(4, ptr) #define ma_atomic_is_lock_free_64(ptr) __atomic_is_lock_free(8, ptr) - #define ma_atomic_test_and_set_explicit_8( dst, order) __atomic_exchange_n(dst, 1, order) - #define ma_atomic_test_and_set_explicit_16(dst, order) __atomic_exchange_n(dst, 1, order) - #define ma_atomic_test_and_set_explicit_32(dst, order) __atomic_exchange_n(dst, 1, order) - #define ma_atomic_test_and_set_explicit_64(dst, order) __atomic_exchange_n(dst, 1, order) - #define ma_atomic_clear_explicit_8( dst, order) __atomic_store_n(dst, 0, order) - #define ma_atomic_clear_explicit_16(dst, order) __atomic_store_n(dst, 0, order) - #define ma_atomic_clear_explicit_32(dst, order) __atomic_store_n(dst, 0, order) - #define ma_atomic_clear_explicit_64(dst, order) __atomic_store_n(dst, 0, order) #define ma_atomic_store_explicit_8( dst, src, order) __atomic_store_n(dst, src, order) #define ma_atomic_store_explicit_16(dst, src, order) __atomic_store_n(dst, src, order) #define ma_atomic_store_explicit_32(dst, src, order) __atomic_store_n(dst, src, order) @@ -3369,14 +4247,14 @@ typedef int ma_atomic_memory_order; #define ma_atomic_exchange_explicit_16(dst, src, order) __atomic_exchange_n(dst, src, order) #define ma_atomic_exchange_explicit_32(dst, src, order) __atomic_exchange_n(dst, src, order) #define ma_atomic_exchange_explicit_64(dst, src, order) __atomic_exchange_n(dst, src, order) - #define ma_atomic_compare_exchange_strong_explicit_8( dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 0, successOrder, failureOrder) - #define ma_atomic_compare_exchange_strong_explicit_16(dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 0, successOrder, failureOrder) - #define ma_atomic_compare_exchange_strong_explicit_32(dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 0, successOrder, failureOrder) - #define ma_atomic_compare_exchange_strong_explicit_64(dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 0, successOrder, failureOrder) - #define ma_atomic_compare_exchange_weak_explicit_8( dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 1, successOrder, failureOrder) - #define ma_atomic_compare_exchange_weak_explicit_16(dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 1, successOrder, failureOrder) - #define ma_atomic_compare_exchange_weak_explicit_32(dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 1, successOrder, failureOrder) - #define ma_atomic_compare_exchange_weak_explicit_64(dst, expected, desired, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, desired, 1, successOrder, failureOrder) + #define ma_atomic_compare_exchange_strong_explicit_8( dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 0, successOrder, failureOrder) + #define ma_atomic_compare_exchange_strong_explicit_16(dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 0, successOrder, failureOrder) + #define ma_atomic_compare_exchange_strong_explicit_32(dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 0, successOrder, failureOrder) + #define ma_atomic_compare_exchange_strong_explicit_64(dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 0, successOrder, failureOrder) + #define ma_atomic_compare_exchange_weak_explicit_8( dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 1, successOrder, failureOrder) + #define ma_atomic_compare_exchange_weak_explicit_16(dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 1, successOrder, failureOrder) + #define ma_atomic_compare_exchange_weak_explicit_32(dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 1, successOrder, failureOrder) + #define ma_atomic_compare_exchange_weak_explicit_64(dst, expected, replacement, successOrder, failureOrder) __atomic_compare_exchange_n(dst, expected, replacement, 1, successOrder, failureOrder) #define ma_atomic_fetch_add_explicit_8( dst, src, order) __atomic_fetch_add(dst, src, order) #define ma_atomic_fetch_add_explicit_16(dst, src, order) __atomic_fetch_add(dst, src, order) #define ma_atomic_fetch_add_explicit_32(dst, src, order) __atomic_fetch_add(dst, src, order) @@ -3397,19 +4275,19 @@ typedef int ma_atomic_memory_order; #define ma_atomic_fetch_and_explicit_16(dst, src, order) __atomic_fetch_and(dst, src, order) #define ma_atomic_fetch_and_explicit_32(dst, src, order) __atomic_fetch_and(dst, src, order) #define ma_atomic_fetch_and_explicit_64(dst, src, order) __atomic_fetch_and(dst, src, order) - static MA_INLINE ma_uint8 ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 desired) + static MA_INLINE ma_uint8 ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 replacement) { - __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + __atomic_compare_exchange_n(dst, &expected, replacement, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } - static MA_INLINE ma_uint16 ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 desired) + static MA_INLINE ma_uint16 ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 replacement) { - __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + __atomic_compare_exchange_n(dst, &expected, replacement, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } - static MA_INLINE ma_uint32 ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 desired) + static MA_INLINE ma_uint32 ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 replacement) { - __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + __atomic_compare_exchange_n(dst, &expected, replacement, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } #if defined(__clang__) @@ -3418,636 +4296,1134 @@ typedef int ma_atomic_memory_order; #pragma clang diagnostic ignored "-Watomic-alignment" #endif #endif - static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) + static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 replacement) { - __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + __atomic_compare_exchange_n(dst, &expected, replacement, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } #if defined(__clang__) #pragma clang diagnostic pop #endif - typedef ma_uint8 ma_atomic_flag; - #define ma_atomic_flag_test_and_set_explicit(dst, order) (ma_bool32)__atomic_test_and_set(dst, order) - #define ma_atomic_flag_clear_explicit(dst, order) __atomic_clear(dst, order) - #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) -#else - #define ma_atomic_memory_order_relaxed 1 - #define ma_atomic_memory_order_consume 2 - #define ma_atomic_memory_order_acquire 3 - #define ma_atomic_memory_order_release 4 - #define ma_atomic_memory_order_acq_rel 5 - #define ma_atomic_memory_order_seq_cst 6 - #define ma_atomic_compiler_fence() __asm__ __volatile__("":::"memory") - #if defined(__GNUC__) +#endif +#if defined(MA_ATOMIC_LEGACY_GCC) || defined(MA_ATOMIC_LEGACY_GCC_ASM) + #define ma_atomic_signal_fence(order) __asm__ __volatile__("":::"memory") + #if defined(MA_ATOMIC_LEGACY_GCC) #define ma_atomic_thread_fence(order) __sync_synchronize(), (void)order - static MA_INLINE ma_uint8 ma_atomic_exchange_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint8 ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 replacement) { - if (order > ma_atomic_memory_order_acquire) { - __sync_synchronize(); + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + return __sync_val_compare_and_swap(dst, expected, replacement); } - return __sync_lock_test_and_set(dst, src); + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(8, dst, expected, replacement); + } + #endif } - static MA_INLINE ma_uint16 ma_atomic_exchange_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint16 ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 replacement) { - ma_uint16 oldValue; - do { - oldValue = *dst; - } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + return __sync_val_compare_and_swap(dst, expected, replacement); + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(16, dst, expected, replacement); + } + #endif } - static MA_INLINE ma_uint32 ma_atomic_exchange_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint32 ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 replacement) { - ma_uint32 oldValue; - do { - oldValue = *dst; - } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + return __sync_val_compare_and_swap(dst, expected, replacement); + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(32, dst, expected, replacement); + } + #endif } - static MA_INLINE ma_uint64 ma_atomic_exchange_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 replacement) { - ma_uint64 oldValue; - do { - oldValue = *dst; - } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + return __sync_val_compare_and_swap(dst, expected, replacement); + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(64, dst, expected, replacement); + } + #endif } - static MA_INLINE ma_uint8 ma_atomic_fetch_add_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint8 ma_atomic_load_explicit_8(volatile const ma_uint8* ptr, ma_atomic_memory_order order) { - (void)order; - return __sync_fetch_and_add(dst, src); + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + (void)order; + return ma_atomic_compare_and_swap_8((ma_uint8*)ptr, 0, 0); + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(8, ptr, order); + } + #endif } - static MA_INLINE ma_uint16 ma_atomic_fetch_add_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint16 ma_atomic_load_explicit_16(volatile const ma_uint16* ptr, ma_atomic_memory_order order) { - (void)order; - return __sync_fetch_and_add(dst, src); + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + (void)order; + return ma_atomic_compare_and_swap_16((ma_uint16*)ptr, 0, 0); + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(16, ptr, order); + } + #endif } - static MA_INLINE ma_uint32 ma_atomic_fetch_add_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint32 ma_atomic_load_explicit_32(volatile const ma_uint32* ptr, ma_atomic_memory_order order) { - (void)order; - return __sync_fetch_and_add(dst, src); + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + (void)order; + return ma_atomic_compare_and_swap_32((ma_uint32*)ptr, 0, 0); + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(32, ptr, order); + } + #endif } - static MA_INLINE ma_uint64 ma_atomic_fetch_add_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + static MA_INLINE ma_uint64 ma_atomic_load_explicit_64(volatile const ma_uint64* ptr, ma_atomic_memory_order order) { - (void)order; - return __sync_fetch_and_add(dst, src); - } - static MA_INLINE ma_uint8 ma_atomic_fetch_sub_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_sub(dst, src); - } - static MA_INLINE ma_uint16 ma_atomic_fetch_sub_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_sub(dst, src); - } - static MA_INLINE ma_uint32 ma_atomic_fetch_sub_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_sub(dst, src); - } - static MA_INLINE ma_uint64 ma_atomic_fetch_sub_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_sub(dst, src); - } - static MA_INLINE ma_uint8 ma_atomic_fetch_or_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_or(dst, src); - } - static MA_INLINE ma_uint16 ma_atomic_fetch_or_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_or(dst, src); - } - static MA_INLINE ma_uint32 ma_atomic_fetch_or_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_or(dst, src); - } - static MA_INLINE ma_uint64 ma_atomic_fetch_or_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_or(dst, src); - } - static MA_INLINE ma_uint8 ma_atomic_fetch_xor_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_xor(dst, src); - } - static MA_INLINE ma_uint16 ma_atomic_fetch_xor_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_xor(dst, src); - } - static MA_INLINE ma_uint32 ma_atomic_fetch_xor_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_xor(dst, src); - } - static MA_INLINE ma_uint64 ma_atomic_fetch_xor_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_xor(dst, src); - } - static MA_INLINE ma_uint8 ma_atomic_fetch_and_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_and(dst, src); - } - static MA_INLINE ma_uint16 ma_atomic_fetch_and_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_and(dst, src); - } - static MA_INLINE ma_uint32 ma_atomic_fetch_and_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_and(dst, src); - } - static MA_INLINE ma_uint64 ma_atomic_fetch_and_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - (void)order; - return __sync_fetch_and_and(dst, src); - } - #define ma_atomic_compare_and_swap_8( dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #define ma_atomic_compare_and_swap_16(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #define ma_atomic_compare_and_swap_32(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #define ma_atomic_compare_and_swap_64(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #else - #if defined(MA_X86) - #define ma_atomic_thread_fence(order) __asm__ __volatile__("lock; addl $0, (%%esp)" ::: "memory", "cc") - #elif defined(MA_X64) - #define ma_atomic_thread_fence(order) __asm__ __volatile__("lock; addq $0, (%%rsp)" ::: "memory", "cc") - #else - #error Unsupported architecture. Please submit a feature request. - #endif - static MA_INLINE ma_uint8 ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 desired) - { - ma_uint8 result; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; - } - static MA_INLINE ma_uint16 ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 desired) - { - ma_uint16 result; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; - } - static MA_INLINE ma_uint32 ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 desired) - { - ma_uint32 result; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; - } - static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) - { - volatile ma_uint64 result; - #if defined(MA_X86) - ma_uint32 resultEAX; - ma_uint32 resultEDX; - __asm__ __volatile__("push %%ebx; xchg %5, %%ebx; lock; cmpxchg8b %0; pop %%ebx" : "+m"(*dst), "=a"(resultEAX), "=d"(resultEDX) : "a"(expected & 0xFFFFFFFF), "d"(expected >> 32), "r"(desired & 0xFFFFFFFF), "c"(desired >> 32) : "cc"); - result = ((ma_uint64)resultEDX << 32) | resultEAX; - #elif defined(MA_X64) - __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + (void)order; + return ma_atomic_compare_and_swap_64((ma_uint64*)ptr, 0, 0); + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(64, ptr, order); + } + #endif } static MA_INLINE ma_uint8 ma_atomic_exchange_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) { - ma_uint8 result = 0; - (void)order; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + if (order > ma_atomic_memory_order_acquire) { + __sync_synchronize(); + } + return __sync_lock_test_and_set(dst, src); + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(8, dst, src, order); + } + #endif } static MA_INLINE ma_uint16 ma_atomic_exchange_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) { - ma_uint16 result = 0; - (void)order; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + if (order > ma_atomic_memory_order_acquire) { + __sync_synchronize(); + } + return __sync_lock_test_and_set(dst, src); + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(16, dst, src, order); + } + #endif } static MA_INLINE ma_uint32 ma_atomic_exchange_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) { - ma_uint32 result; - (void)order; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + if (order > ma_atomic_memory_order_acquire) { + __sync_synchronize(); + } + return __sync_lock_test_and_set(dst, src); + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(32, dst, src, order); + } + #endif } static MA_INLINE ma_uint64 ma_atomic_exchange_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) { - ma_uint64 result; - (void)order; - #if defined(MA_X86) - do { - result = *dst; - } while (ma_atomic_compare_and_swap_64(dst, result, src) != result); - #elif defined(MA_X64) - __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + if (order > ma_atomic_memory_order_acquire) { + __sync_synchronize(); + } + return __sync_lock_test_and_set(dst, src); + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(64, dst, src, order); + } + #endif } + #define ma_atomic_store_explicit_8( dst, src, order) (void)ma_atomic_exchange_explicit_8 (dst, src, order) + #define ma_atomic_store_explicit_16(dst, src, order) (void)ma_atomic_exchange_explicit_16(dst, src, order) + #define ma_atomic_store_explicit_32(dst, src, order) (void)ma_atomic_exchange_explicit_32(dst, src, order) + #define ma_atomic_store_explicit_64(dst, src, order) (void)ma_atomic_exchange_explicit_64(dst, src, order) static MA_INLINE ma_uint8 ma_atomic_fetch_add_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) { - ma_uint8 result; - (void)order; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(8, dst, src, order); + } + #endif } static MA_INLINE ma_uint16 ma_atomic_fetch_add_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) { - ma_uint16 result; - (void)order; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(16, dst, src, order); + } + #endif } static MA_INLINE ma_uint32 ma_atomic_fetch_add_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) { - ma_uint32 result; - (void)order; - #if defined(MA_X86) || defined(MA_X64) - __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); - #else - #error Unsupported architecture. Please submit a feature request. - #endif - return result; + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(32, dst, src, order); + } + #endif } static MA_INLINE ma_uint64 ma_atomic_fetch_add_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) { - #if defined(MA_X86) - ma_uint64 oldValue; - ma_uint64 newValue; - (void)order; - do { - oldValue = *dst; - newValue = oldValue + src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - return oldValue; - #elif defined(MA_X64) - ma_uint64 result; - (void)order; - __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); - return result; - #endif + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(64, dst, src, order); + } + #endif } static MA_INLINE ma_uint8 ma_atomic_fetch_sub_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) { - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue - src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(8, dst, (ma_uint8)(-(ma_int8)src), order); + } + #endif } static MA_INLINE ma_uint16 ma_atomic_fetch_sub_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) { - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue - src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(16, dst, (ma_uint16)(-(ma_int16)src), order); + } + #endif } static MA_INLINE ma_uint32 ma_atomic_fetch_sub_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) { - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue - src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(32, dst, (ma_uint32)(-(ma_int32)src), order); + } + #endif } static MA_INLINE ma_uint64 ma_atomic_fetch_sub_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) { - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue - src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(64, dst, (ma_uint64)(-(ma_int64)src), order); + } + #endif } static MA_INLINE ma_uint8 ma_atomic_fetch_and_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) { - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue & src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(8, dst, src, order); + } + #endif } static MA_INLINE ma_uint16 ma_atomic_fetch_and_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) { - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue & src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(16, dst, src, order); + } + #endif } static MA_INLINE ma_uint32 ma_atomic_fetch_and_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) { - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue & src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(32, dst, src, order); + } + #endif } static MA_INLINE ma_uint64 ma_atomic_fetch_and_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) { - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue & src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static MA_INLINE ma_uint8 ma_atomic_fetch_xor_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) - { - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue ^ src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static MA_INLINE ma_uint16 ma_atomic_fetch_xor_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) - { - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue ^ src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static MA_INLINE ma_uint32 ma_atomic_fetch_xor_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) - { - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue ^ src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static MA_INLINE ma_uint64 ma_atomic_fetch_xor_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) - { - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue ^ src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + #else + { + MA_ATOMIC_FETCH_AND_CAS(64, dst, src, order); + } + #endif } static MA_INLINE ma_uint8 ma_atomic_fetch_or_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) { - ma_uint8 oldValue; - ma_uint8 newValue; - do { - oldValue = *dst; - newValue = (ma_uint8)(oldValue | src); - } while (ma_atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(8, dst, src, order); + } + #endif } static MA_INLINE ma_uint16 ma_atomic_fetch_or_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) { - ma_uint16 oldValue; - ma_uint16 newValue; - do { - oldValue = *dst; - newValue = (ma_uint16)(oldValue | src); - } while (ma_atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(16, dst, src, order); + } + #endif } static MA_INLINE ma_uint32 ma_atomic_fetch_or_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) { - ma_uint32 oldValue; - ma_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue | src; - } while (ma_atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(32, dst, src, order); + } + #endif } static MA_INLINE ma_uint64 ma_atomic_fetch_or_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) { - ma_uint64 oldValue; - ma_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue | src; - } while (ma_atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + #else + { + MA_ATOMIC_FETCH_OR_CAS(64, dst, src, order); + } + #endif } + static MA_INLINE ma_uint8 ma_atomic_fetch_xor_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(8, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint16 ma_atomic_fetch_xor_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(16, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint32 ma_atomic_fetch_xor_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 ma_atomic_fetch_xor_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + #else + { + MA_ATOMIC_FETCH_XOR_CAS(64, dst, src, order); + } + #endif + } + #elif defined(MA_ATOMIC_LEGACY_GCC_ASM) + #define MA_ATOMIC_CMPXCHG_GCC_X86(instructionSizeSuffix, result, dst, expected, replacement) \ + __asm__ __volatile__( \ + "lock; cmpxchg"instructionSizeSuffix" %2, %1" \ + : "=a"(result), \ + "=m"(*dst) \ + : "r"(replacement), \ + "0"(expected), \ + "m"(*dst) \ + : "cc", "memory") + #define MA_ATOMIC_XADD_GCC_X86(instructionSizeSuffix, result, dst, src) \ + __asm__ __volatile__( \ + "lock; xadd"instructionSizeSuffix" %0, %1" \ + : "=a"(result), \ + "=m"(*dst) \ + : "0"(src), \ + "m"(*dst) \ + : "cc", "memory") + static MA_INLINE ma_uint8 ma_atomic_compare_and_swap_8(volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint8 result; + #if defined(MA_X86) || defined(MA_X64) + { + MA_ATOMIC_CMPXCHG_GCC_X86("b", result, dst, expected, replacement); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(8, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint16 ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint16 result; + #if defined(MA_X86) || defined(MA_X64) + { + MA_ATOMIC_CMPXCHG_GCC_X86("w", result, dst, expected, replacement); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(16, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint32 ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint32 result; + #if defined(MA_X86) || defined(MA_X64) + { + MA_ATOMIC_CMPXCHG_GCC_X86("l", result, dst, expected, replacement); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(32, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 replacement) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint64 result; + #if defined(MA_X86) + { + ma_uint32 resultEAX; + ma_uint32 resultEDX; + __asm__ __volatile__( + "pushl %%ebx\n" + "movl %4, %%ebx\n" + "lock cmpxchg8b (%%edi)\n" + "popl %%ebx\n" + : "=a"(resultEAX), + "=d"(resultEDX) + : "a"((ma_uint32)(expected & 0xFFFFFFFF)), + "d"((ma_uint32)(expected >> 32)), + "r"((ma_uint32)(replacement & 0xFFFFFFFF)), + "c"((ma_uint32)(replacement >> 32)), + "D"(dst) + : "memory", "cc"); + result = ((ma_uint64)resultEDX << 32) | resultEAX; + } + #elif defined(MA_X64) + { + MA_ATOMIC_CMPXCHG_GCC_X86("q", result, dst, expected, replacement); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_COMPARE_AND_SWAP_LOCK(64, dst, expected, replacement); + } + #endif + } + static MA_INLINE ma_uint8 ma_atomic_load_explicit_8(volatile const ma_uint8* dst, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint8 result; + #if defined(MA_X86) || defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + MA_ATOMIC_LOAD_RELAXED_GCC_X86("b", result, dst); + } else if (order <= ma_atomic_memory_order_release) { + MA_ATOMIC_LOAD_RELEASE_GCC_X86("b", result, dst); + } else { + MA_ATOMIC_LOAD_SEQ_CST_GCC_X86("b", result, dst); + } + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(8, dst, order); + } + #endif + } + static MA_INLINE ma_uint16 ma_atomic_load_explicit_16(volatile const ma_uint16* dst, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint16 result; + #if defined(MA_X86) || defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + MA_ATOMIC_LOAD_RELAXED_GCC_X86("w", result, dst); + } else if (order <= ma_atomic_memory_order_release) { + MA_ATOMIC_LOAD_RELEASE_GCC_X86("w", result, dst); + } else { + MA_ATOMIC_LOAD_SEQ_CST_GCC_X86("w", result, dst); + } + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(16, dst, order); + } + #endif + } + static MA_INLINE ma_uint32 ma_atomic_load_explicit_32(volatile const ma_uint32* dst, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint32 result; + #if defined(MA_X86) || defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + MA_ATOMIC_LOAD_RELAXED_GCC_X86("l", result, dst); + } else if (order <= ma_atomic_memory_order_release) { + MA_ATOMIC_LOAD_RELEASE_GCC_X86("l", result, dst); + } else { + MA_ATOMIC_LOAD_SEQ_CST_GCC_X86("l", result, dst); + } + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(32, dst, order); + } + #endif + } + static MA_INLINE ma_uint64 ma_atomic_load_explicit_64(volatile const ma_uint64* dst, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint64 result; + #if defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + MA_ATOMIC_LOAD_RELAXED_GCC_X86("q", result, dst); + } else if (order <= ma_atomic_memory_order_release) { + MA_ATOMIC_LOAD_RELEASE_GCC_X86("q", result, dst); + } else { + MA_ATOMIC_LOAD_SEQ_CST_GCC_X86("q", result, dst); + } + } + #elif defined(MA_X86) + { + (void)order; + return ma_atomic_compare_and_swap_64((volatile ma_uint64*)dst, 0, 0); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_LOAD_EXPLICIT_LOCK(64, dst, order); + } + #endif + } + static MA_INLINE ma_uint8 ma_atomic_exchange_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint8 result; + (void)order; + #if defined(MA_X86) || defined(MA_X64) + { + MA_ATOMIC_XCHG_GCC_X86("b", result, dst, src); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(8, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint16 ma_atomic_exchange_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint16 result; + (void)order; + #if defined(MA_X86) || defined(MA_X64) + { + MA_ATOMIC_XCHG_GCC_X86("w", result, dst, src); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(16, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint32 ma_atomic_exchange_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint32 result; + (void)order; + #if defined(MA_X86) || defined(MA_X64) + { + MA_ATOMIC_XCHG_GCC_X86("l", result, dst, src); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 ma_atomic_exchange_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) && (defined(MA_X86) || defined(MA_X64)) + { + ma_uint64 result; + (void)order; + #if defined(MA_X86) + { + MA_ATOMIC_EXCHANGE_EXPLICIT_CAS(64, dst, src, order); + } + #elif defined(MA_X64) + { + MA_ATOMIC_XCHG_GCC_X86("q", result, dst, src); + } + #else + { + #error Unsupported architecture. + } + #endif + return result; + } + #else + { + MA_ATOMIC_EXCHANGE_EXPLICIT_LOCK(64, dst, src, order); + } + #endif + } + static MA_INLINE void ma_atomic_store_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X86) || defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm__ __volatile__ ( + "movb %1, %0" + : "=m"(*dst) + : "r"(src) + ); + } else { + __asm__ __volatile__ ( + "xchgb %1, %0" + : "=m"(*dst) + : "r"(src) + : "memory" + ); + } + } + #else + { + #error Unsupported architecture. + } + #endif + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(8, dst, src, order); + } + #endif + } + static MA_INLINE void ma_atomic_store_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X86) || defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm__ __volatile__ ( + "movw %1, %0" + : "=m"(*dst) + : "r"(src) + ); + } else { + __asm__ __volatile__ ( + "xchgw %1, %0" + : "=m"(*dst) + : "r"(src) + : "memory" + ); + } + } + #else + { + #error Unsupported architecture. + } + #endif + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(16, dst, src, order); + } + #endif + } + static MA_INLINE void ma_atomic_store_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X86) || defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm__ __volatile__ ( + "movl %1, %0" + : "=m"(*dst) + : "r"(src) + ); + } else { + __asm__ __volatile__ ( + "xchgl %1, %0" + : "=m"(*dst) + : "r"(src) + : "memory" + ); + } + } + #else + { + #error Unsupported architecture. + } + #endif + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(32, dst, src, order); + } + #endif + } + static MA_INLINE void ma_atomic_store_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X64) + { + if (order == ma_atomic_memory_order_relaxed) { + __asm__ __volatile__ ( + "movq %1, %0" + : "=m"(*dst) + : "r"(src) + ); + } else { + __asm__ __volatile__ ( + "xchgq %1, %0" + : "=m"(*dst) + : "r"(src) + : "memory" + ); + } + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_CAS(64, dst, src, order); + } + #endif + } + #else + { + MA_ATOMIC_STORE_EXPLICIT_LOCK(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 ma_atomic_fetch_add_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_8) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X86) || defined(MA_X64) + { + ma_uint8 result; + (void)order; + MA_ATOMIC_XADD_GCC_X86("b", result, dst, src); + return result; + } + #else + { + #error Unsupported architecture. + } + #endif + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(8, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint16 ma_atomic_fetch_add_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_16) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X86) || defined(MA_X64) + { + ma_uint16 result; + (void)order; + MA_ATOMIC_XADD_GCC_X86("w", result, dst, src); + return result; + } + #else + { + #error Unsupported architecture. + } + #endif + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(16, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint32 ma_atomic_fetch_add_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_32) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X86) || defined(MA_X64) + { + ma_uint32 result; + (void)order; + MA_ATOMIC_XADD_GCC_X86("l", result, dst, src); + return result; + } + #else + { + #error Unsupported architecture. + } + #endif + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(32, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint64 ma_atomic_fetch_add_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + #if defined(MA_ATOMIC_IS_LOCK_FREE_64) && (defined(MA_X86) || defined(MA_X64)) + { + #if defined(MA_X86) + { + MA_ATOMIC_FETCH_ADD_CAS(64, dst, src, order); + } + #elif defined(MA_X64) + { + ma_uint64 result; + MA_ATOMIC_XADD_GCC_X86("q", result, dst, src); + (void)order; + return result; + } + #else + { + #error Unsupported architecture. + } + #endif + } + #else + { + MA_ATOMIC_FETCH_ADD_LOCK(64, dst, src, order); + } + #endif + } + static MA_INLINE ma_uint8 ma_atomic_fetch_sub_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_8(dst, (ma_uint8)(-(ma_int8)src), order); + } + static MA_INLINE ma_uint16 ma_atomic_fetch_sub_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_16(dst, (ma_uint16)(-(ma_int16)src), order); + } + static MA_INLINE ma_uint32 ma_atomic_fetch_sub_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_32(dst, (ma_uint32)(-(ma_int32)src), order); + } + static MA_INLINE ma_uint64 ma_atomic_fetch_sub_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + return ma_atomic_fetch_add_explicit_64(dst, (ma_uint64)(-(ma_int64)src), order); + } + static MA_INLINE ma_uint8 ma_atomic_fetch_and_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(8, dst, src, order); + } + static MA_INLINE ma_uint16 ma_atomic_fetch_and_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(16, dst, src, order); + } + static MA_INLINE ma_uint32 ma_atomic_fetch_and_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(32, dst, src, order); + } + static MA_INLINE ma_uint64 ma_atomic_fetch_and_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_AND_CAS(64, dst, src, order); + } + static MA_INLINE ma_uint8 ma_atomic_fetch_or_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(8, dst, src, order); + } + static MA_INLINE ma_uint16 ma_atomic_fetch_or_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(16, dst, src, order); + } + static MA_INLINE ma_uint32 ma_atomic_fetch_or_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(32, dst, src, order); + } + static MA_INLINE ma_uint64 ma_atomic_fetch_or_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_OR_CAS(64, dst, src, order); + } + static MA_INLINE ma_uint8 ma_atomic_fetch_xor_explicit_8(volatile ma_uint8* dst, ma_uint8 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(8, dst, src, order); + } + static MA_INLINE ma_uint16 ma_atomic_fetch_xor_explicit_16(volatile ma_uint16* dst, ma_uint16 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(16, dst, src, order); + } + static MA_INLINE ma_uint32 ma_atomic_fetch_xor_explicit_32(volatile ma_uint32* dst, ma_uint32 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(32, dst, src, order); + } + static MA_INLINE ma_uint64 ma_atomic_fetch_xor_explicit_64(volatile ma_uint64* dst, ma_uint64 src, ma_atomic_memory_order order) + { + MA_ATOMIC_FETCH_XOR_CAS(64, dst, src, order); + } + #else + #error Unsupported compiler. #endif - #define ma_atomic_signal_fence(order) ma_atomic_thread_fence(order) - static MA_INLINE ma_uint8 ma_atomic_load_explicit_8(volatile const ma_uint8* ptr, ma_atomic_memory_order order) - { - (void)order; - return ma_atomic_compare_and_swap_8((ma_uint8*)ptr, 0, 0); - } - static MA_INLINE ma_uint16 ma_atomic_load_explicit_16(volatile const ma_uint16* ptr, ma_atomic_memory_order order) - { - (void)order; - return ma_atomic_compare_and_swap_16((ma_uint16*)ptr, 0, 0); - } - static MA_INLINE ma_uint32 ma_atomic_load_explicit_32(volatile const ma_uint32* ptr, ma_atomic_memory_order order) - { - (void)order; - return ma_atomic_compare_and_swap_32((ma_uint32*)ptr, 0, 0); - } - static MA_INLINE ma_uint64 ma_atomic_load_explicit_64(volatile const ma_uint64* ptr, ma_atomic_memory_order order) - { - (void)order; - return ma_atomic_compare_and_swap_64((ma_uint64*)ptr, 0, 0); - } - #define ma_atomic_store_explicit_8( dst, src, order) (void)ma_atomic_exchange_explicit_8 (dst, src, order) - #define ma_atomic_store_explicit_16(dst, src, order) (void)ma_atomic_exchange_explicit_16(dst, src, order) - #define ma_atomic_store_explicit_32(dst, src, order) (void)ma_atomic_exchange_explicit_32(dst, src, order) - #define ma_atomic_store_explicit_64(dst, src, order) (void)ma_atomic_exchange_explicit_64(dst, src, order) - #define ma_atomic_test_and_set_explicit_8( dst, order) ma_atomic_exchange_explicit_8 (dst, 1, order) - #define ma_atomic_test_and_set_explicit_16(dst, order) ma_atomic_exchange_explicit_16(dst, 1, order) - #define ma_atomic_test_and_set_explicit_32(dst, order) ma_atomic_exchange_explicit_32(dst, 1, order) - #define ma_atomic_test_and_set_explicit_64(dst, order) ma_atomic_exchange_explicit_64(dst, 1, order) - #define ma_atomic_clear_explicit_8( dst, order) ma_atomic_store_explicit_8 (dst, 0, order) - #define ma_atomic_clear_explicit_16(dst, order) ma_atomic_store_explicit_16(dst, 0, order) - #define ma_atomic_clear_explicit_32(dst, order) ma_atomic_store_explicit_32(dst, 0, order) - #define ma_atomic_clear_explicit_64(dst, order) ma_atomic_store_explicit_64(dst, 0, order) - typedef ma_uint8 ma_atomic_flag; - #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_8(ptr, order) - #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_8(ptr, order) - #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) #endif #if !defined(MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE) - #if defined(MA_ATOMIC_HAS_8) - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_8(volatile ma_uint8* dst, ma_uint8* expected, ma_uint8 desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) - { - ma_uint8 expectedValue; - ma_uint8 result; - (void)successOrder; - (void)failureOrder; - expectedValue = ma_atomic_load_explicit_8(expected, ma_atomic_memory_order_seq_cst); - result = ma_atomic_compare_and_swap_8(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - ma_atomic_store_explicit_8(expected, result, failureOrder); - return 0; - } - } - #endif - #if defined(MA_ATOMIC_HAS_16) - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_16(volatile ma_uint16* dst, ma_uint16* expected, ma_uint16 desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) - { - ma_uint16 expectedValue; - ma_uint16 result; - (void)successOrder; - (void)failureOrder; - expectedValue = ma_atomic_load_explicit_16(expected, ma_atomic_memory_order_seq_cst); - result = ma_atomic_compare_and_swap_16(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - ma_atomic_store_explicit_16(expected, result, failureOrder); - return 0; - } - } - #endif - #if defined(MA_ATOMIC_HAS_32) - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_32(volatile ma_uint32* dst, ma_uint32* expected, ma_uint32 desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) - { - ma_uint32 expectedValue; - ma_uint32 result; - (void)successOrder; - (void)failureOrder; - expectedValue = ma_atomic_load_explicit_32(expected, ma_atomic_memory_order_seq_cst); - result = ma_atomic_compare_and_swap_32(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - ma_atomic_store_explicit_32(expected, result, failureOrder); - return 0; - } - } - #endif - #if defined(MA_ATOMIC_HAS_64) - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_64(volatile ma_uint64* dst, volatile ma_uint64* expected, ma_uint64 desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) - { - ma_uint64 expectedValue; - ma_uint64 result; - (void)successOrder; - (void)failureOrder; - expectedValue = ma_atomic_load_explicit_64(expected, ma_atomic_memory_order_seq_cst); - result = ma_atomic_compare_and_swap_64(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - ma_atomic_store_explicit_64(expected, result, failureOrder); - return 0; - } - } - #endif - #define ma_atomic_compare_exchange_weak_explicit_8( dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_8 (dst, expected, desired, successOrder, failureOrder) - #define ma_atomic_compare_exchange_weak_explicit_16(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_16(dst, expected, desired, successOrder, failureOrder) - #define ma_atomic_compare_exchange_weak_explicit_32(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_32(dst, expected, desired, successOrder, failureOrder) - #define ma_atomic_compare_exchange_weak_explicit_64(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_64(dst, expected, desired, successOrder, failureOrder) -#endif -#if !defined(MA_ATOMIC_HAS_NATIVE_IS_LOCK_FREE) - static MA_INLINE ma_bool32 ma_atomic_is_lock_free_8(volatile void* ptr) + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_8(volatile ma_uint8* dst, ma_uint8* expected, ma_uint8 replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { - (void)ptr; - return 1; - } - static MA_INLINE ma_bool32 ma_atomic_is_lock_free_16(volatile void* ptr) - { - (void)ptr; - return 1; - } - static MA_INLINE ma_bool32 ma_atomic_is_lock_free_32(volatile void* ptr) - { - (void)ptr; - return 1; - } - static MA_INLINE ma_bool32 ma_atomic_is_lock_free_64(volatile void* ptr) - { - (void)ptr; - #if defined(MA_64BIT) - return 1; - #else - #if defined(MA_X86) || defined(MA_X64) + ma_uint8 result; + (void)successOrder; + (void)failureOrder; + result = ma_atomic_compare_and_swap_8(dst, *expected, replacement); + if (result == *expected) { return 1; - #else + } else { + *expected = result; return 0; - #endif - #endif + } } + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_16(volatile ma_uint16* dst, ma_uint16* expected, ma_uint16 replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) + { + ma_uint16 result; + (void)successOrder; + (void)failureOrder; + result = ma_atomic_compare_and_swap_16(dst, *expected, replacement); + if (result == *expected) { + return 1; + } else { + *expected = result; + return 0; + } + } + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_32(volatile ma_uint32* dst, ma_uint32* expected, ma_uint32 replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) + { + ma_uint32 result; + (void)successOrder; + (void)failureOrder; + result = ma_atomic_compare_and_swap_32(dst, *expected, replacement); + if (result == *expected) { + return 1; + } else { + *expected = result; + return 0; + } + } + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_64(volatile ma_uint64* dst, volatile ma_uint64* expected, ma_uint64 replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) + { + ma_uint64 result; + (void)successOrder; + (void)failureOrder; + result = ma_atomic_compare_and_swap_64(dst, *expected, replacement); + if (result == *expected) { + return 1; + } else { + *expected = result; + return 0; + } + } + #define ma_atomic_compare_exchange_weak_explicit_8( dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_8 (dst, expected, replacement, successOrder, failureOrder) + #define ma_atomic_compare_exchange_weak_explicit_16(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_16(dst, expected, replacement, successOrder, failureOrder) + #define ma_atomic_compare_exchange_weak_explicit_32(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_32(dst, expected, replacement, successOrder, failureOrder) + #define ma_atomic_compare_exchange_weak_explicit_64(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_64(dst, expected, replacement, successOrder, failureOrder) #endif #if defined(MA_64BIT) static MA_INLINE ma_bool32 ma_atomic_is_lock_free_ptr(volatile void** ptr) @@ -4066,17 +5442,17 @@ typedef int ma_atomic_memory_order; { return (void*)ma_atomic_exchange_explicit_64((volatile ma_uint64*)dst, (ma_uint64)src, order); } - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { - return ma_atomic_compare_exchange_strong_explicit_64((volatile ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)desired, successOrder, failureOrder); + return ma_atomic_compare_exchange_strong_explicit_64((volatile ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)replacement, successOrder, failureOrder); } - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { - return ma_atomic_compare_exchange_weak_explicit_64((volatile ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)desired, successOrder, failureOrder); + return ma_atomic_compare_exchange_weak_explicit_64((volatile ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)replacement, successOrder, failureOrder); } - static MA_INLINE void* ma_atomic_compare_and_swap_ptr(volatile void** dst, void* expected, void* desired) + static MA_INLINE void* ma_atomic_compare_and_swap_ptr(volatile void** dst, void* expected, void* replacement) { - return (void*)ma_atomic_compare_and_swap_64((volatile ma_uint64*)dst, (ma_uint64)expected, (ma_uint64)desired); + return (void*)ma_atomic_compare_and_swap_64((volatile ma_uint64*)dst, (ma_uint64)expected, (ma_uint64)replacement); } #elif defined(MA_32BIT) static MA_INLINE ma_bool32 ma_atomic_is_lock_free_ptr(volatile void** ptr) @@ -4095,36 +5471,26 @@ typedef int ma_atomic_memory_order; { return (void*)ma_atomic_exchange_explicit_32((volatile ma_uint32*)dst, (ma_uint32)src, order); } - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { - return ma_atomic_compare_exchange_strong_explicit_32((volatile ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)desired, successOrder, failureOrder); + return ma_atomic_compare_exchange_strong_explicit_32((volatile ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)replacement, successOrder, failureOrder); } - static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) + static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { - return ma_atomic_compare_exchange_weak_explicit_32((volatile ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)desired, successOrder, failureOrder); + return ma_atomic_compare_exchange_weak_explicit_32((volatile ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)replacement, successOrder, failureOrder); } - static MA_INLINE void* ma_atomic_compare_and_swap_ptr(volatile void** dst, void* expected, void* desired) + static MA_INLINE void* ma_atomic_compare_and_swap_ptr(volatile void** dst, void* expected, void* replacement) { - return (void*)ma_atomic_compare_and_swap_32((volatile ma_uint32*)dst, (ma_uint32)expected, (ma_uint32)desired); + return (void*)ma_atomic_compare_and_swap_32((volatile ma_uint32*)dst, (ma_uint32)expected, (ma_uint32)replacement); } #else #error Unsupported architecture. #endif -#define ma_atomic_flag_test_and_set(ptr) ma_atomic_flag_test_and_set_explicit(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_flag_clear(ptr) ma_atomic_flag_clear_explicit(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_store_ptr(dst, src) ma_atomic_store_explicit_ptr((volatile void**)dst, (void*)src, ma_atomic_memory_order_seq_cst) -#define ma_atomic_load_ptr(ptr) ma_atomic_load_explicit_ptr((volatile void**)ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_exchange_ptr(dst, src) ma_atomic_exchange_explicit_ptr((volatile void**)dst, (void*)src, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_ptr(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_ptr(dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_8( ptr) ma_atomic_test_and_set_explicit_8( ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_16(ptr) ma_atomic_test_and_set_explicit_16(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_32(ptr) ma_atomic_test_and_set_explicit_32(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_64(ptr) ma_atomic_test_and_set_explicit_64(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_8( ptr) ma_atomic_clear_explicit_8( ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_16(ptr) ma_atomic_clear_explicit_16(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_32(ptr) ma_atomic_clear_explicit_32(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_64(ptr) ma_atomic_clear_explicit_64(ptr, ma_atomic_memory_order_seq_cst) +#define ma_atomic_store_ptr(dst, src) ma_atomic_store_explicit_ptr((volatile void**)dst, (void*)src, ma_atomic_memory_order_seq_cst) +#define ma_atomic_load_ptr(ptr) ma_atomic_load_explicit_ptr((volatile void**)ptr, ma_atomic_memory_order_seq_cst) +#define ma_atomic_exchange_ptr(dst, src) ma_atomic_exchange_explicit_ptr((volatile void**)dst, (void*)src, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_ptr(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (void**)expected, (void*)replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_ptr(dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (void**)expected, (void*)replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) #define ma_atomic_store_8( dst, src) ma_atomic_store_explicit_8( dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_store_16(dst, src) ma_atomic_store_explicit_16(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_store_32(dst, src) ma_atomic_store_explicit_32(dst, src, ma_atomic_memory_order_seq_cst) @@ -4137,14 +5503,14 @@ typedef int ma_atomic_memory_order; #define ma_atomic_exchange_16(dst, src) ma_atomic_exchange_explicit_16(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_exchange_32(dst, src) ma_atomic_exchange_explicit_32(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_exchange_64(dst, src) ma_atomic_exchange_explicit_64(dst, src, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_8( dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_8( dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_16(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_16(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_32(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_32(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_64(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_64(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_8( dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_8( dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_16( dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_16(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_32( dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_32(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_64( dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_64(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_8( dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_8( dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_16(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_16(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_32(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_32(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_64(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_64(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_8( dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_8( dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_16( dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_16(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_32( dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_32(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_64( dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_64(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_8( dst, src) ma_atomic_fetch_add_explicit_8( dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_16(dst, src) ma_atomic_fetch_add_explicit_16(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_32(dst, src) ma_atomic_fetch_add_explicit_32(dst, src, ma_atomic_memory_order_seq_cst) @@ -4165,14 +5531,6 @@ typedef int ma_atomic_memory_order; #define ma_atomic_fetch_and_16(dst, src) ma_atomic_fetch_and_explicit_16(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_and_32(dst, src) ma_atomic_fetch_and_explicit_32(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_and_64(dst, src) ma_atomic_fetch_and_explicit_64(dst, src, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_explicit_i8( ptr, order) (ma_int8 )ma_atomic_test_and_set_explicit_8( (ma_uint8* )ptr, order) -#define ma_atomic_test_and_set_explicit_i16(ptr, order) (ma_int16)ma_atomic_test_and_set_explicit_16((ma_uint16*)ptr, order) -#define ma_atomic_test_and_set_explicit_i32(ptr, order) (ma_int32)ma_atomic_test_and_set_explicit_32((ma_uint32*)ptr, order) -#define ma_atomic_test_and_set_explicit_i64(ptr, order) (ma_int64)ma_atomic_test_and_set_explicit_64((ma_uint64*)ptr, order) -#define ma_atomic_clear_explicit_i8( ptr, order) ma_atomic_clear_explicit_8( (ma_uint8* )ptr, order) -#define ma_atomic_clear_explicit_i16(ptr, order) ma_atomic_clear_explicit_16((ma_uint16*)ptr, order) -#define ma_atomic_clear_explicit_i32(ptr, order) ma_atomic_clear_explicit_32((ma_uint32*)ptr, order) -#define ma_atomic_clear_explicit_i64(ptr, order) ma_atomic_clear_explicit_64((ma_uint64*)ptr, order) #define ma_atomic_store_explicit_i8( dst, src, order) ma_atomic_store_explicit_8( (ma_uint8* )dst, (ma_uint8 )src, order) #define ma_atomic_store_explicit_i16(dst, src, order) ma_atomic_store_explicit_16((ma_uint16*)dst, (ma_uint16)src, order) #define ma_atomic_store_explicit_i32(dst, src, order) ma_atomic_store_explicit_32((ma_uint32*)dst, (ma_uint32)src, order) @@ -4185,14 +5543,14 @@ typedef int ma_atomic_memory_order; #define ma_atomic_exchange_explicit_i16(dst, src, order) (ma_int16)ma_atomic_exchange_explicit_16((ma_uint16*)dst, (ma_uint16)src, order) #define ma_atomic_exchange_explicit_i32(dst, src, order) (ma_int32)ma_atomic_exchange_explicit_32((ma_uint32*)dst, (ma_uint32)src, order) #define ma_atomic_exchange_explicit_i64(dst, src, order) (ma_int64)ma_atomic_exchange_explicit_64((ma_uint64*)dst, (ma_uint64)src, order) -#define ma_atomic_compare_exchange_strong_explicit_i8( dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_8( (ma_uint8* )dst, (ma_uint8* )expected, (ma_uint8 )desired, successOrder, failureOrder) -#define ma_atomic_compare_exchange_strong_explicit_i16(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_16((ma_uint16*)dst, (ma_uint16*)expected, (ma_uint16)desired, successOrder, failureOrder) -#define ma_atomic_compare_exchange_strong_explicit_i32(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_32((ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)desired, successOrder, failureOrder) -#define ma_atomic_compare_exchange_strong_explicit_i64(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_64((ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)desired, successOrder, failureOrder) -#define ma_atomic_compare_exchange_weak_explicit_i8( dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_8( (ma_uint8* )dst, (ma_uint8* )expected, (ma_uint8 )desired, successOrder, failureOrder) -#define ma_atomic_compare_exchange_weak_explicit_i16(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_16((ma_uint16*)dst, (ma_uint16*)expected, (ma_uint16)desired, successOrder, failureOrder) -#define ma_atomic_compare_exchange_weak_explicit_i32(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_32((ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)desired, successOrder, failureOrder) -#define ma_atomic_compare_exchange_weak_explicit_i64(dst, expected, desired, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_64((ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)desired, successOrder, failureOrder) +#define ma_atomic_compare_exchange_strong_explicit_i8( dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_8( (ma_uint8* )dst, (ma_uint8* )expected, (ma_uint8 )replacement, successOrder, failureOrder) +#define ma_atomic_compare_exchange_strong_explicit_i16(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_16((ma_uint16*)dst, (ma_uint16*)expected, (ma_uint16)replacement, successOrder, failureOrder) +#define ma_atomic_compare_exchange_strong_explicit_i32(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_32((ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)replacement, successOrder, failureOrder) +#define ma_atomic_compare_exchange_strong_explicit_i64(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_strong_explicit_64((ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)replacement, successOrder, failureOrder) +#define ma_atomic_compare_exchange_weak_explicit_i8( dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_8( (ma_uint8* )dst, (ma_uint8* )expected, (ma_uint8 )replacement, successOrder, failureOrder) +#define ma_atomic_compare_exchange_weak_explicit_i16(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_16((ma_uint16*)dst, (ma_uint16*)expected, (ma_uint16)replacement, successOrder, failureOrder) +#define ma_atomic_compare_exchange_weak_explicit_i32(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_32((ma_uint32*)dst, (ma_uint32*)expected, (ma_uint32)replacement, successOrder, failureOrder) +#define ma_atomic_compare_exchange_weak_explicit_i64(dst, expected, replacement, successOrder, failureOrder) ma_atomic_compare_exchange_weak_explicit_64((ma_uint64*)dst, (ma_uint64*)expected, (ma_uint64)replacement, successOrder, failureOrder) #define ma_atomic_fetch_add_explicit_i8( dst, src, order) (ma_int8 )ma_atomic_fetch_add_explicit_8( (ma_uint8* )dst, (ma_uint8 )src, order) #define ma_atomic_fetch_add_explicit_i16(dst, src, order) (ma_int16)ma_atomic_fetch_add_explicit_16((ma_uint16*)dst, (ma_uint16)src, order) #define ma_atomic_fetch_add_explicit_i32(dst, src, order) (ma_int32)ma_atomic_fetch_add_explicit_32((ma_uint32*)dst, (ma_uint32)src, order) @@ -4213,14 +5571,6 @@ typedef int ma_atomic_memory_order; #define ma_atomic_fetch_and_explicit_i16(dst, src, order) (ma_int16)ma_atomic_fetch_and_explicit_16((ma_uint16*)dst, (ma_uint16)src, order) #define ma_atomic_fetch_and_explicit_i32(dst, src, order) (ma_int32)ma_atomic_fetch_and_explicit_32((ma_uint32*)dst, (ma_uint32)src, order) #define ma_atomic_fetch_and_explicit_i64(dst, src, order) (ma_int64)ma_atomic_fetch_and_explicit_64((ma_uint64*)dst, (ma_uint64)src, order) -#define ma_atomic_test_and_set_i8( ptr) ma_atomic_test_and_set_explicit_i8( ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_i16(ptr) ma_atomic_test_and_set_explicit_i16(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_i32(ptr) ma_atomic_test_and_set_explicit_i32(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_test_and_set_i64(ptr) ma_atomic_test_and_set_explicit_i64(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_i8( ptr) ma_atomic_clear_explicit_i8( ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_i16(ptr) ma_atomic_clear_explicit_i16(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_i32(ptr) ma_atomic_clear_explicit_i32(ptr, ma_atomic_memory_order_seq_cst) -#define ma_atomic_clear_i64(ptr) ma_atomic_clear_explicit_i64(ptr, ma_atomic_memory_order_seq_cst) #define ma_atomic_store_i8( dst, src) ma_atomic_store_explicit_i8( dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_store_i16(dst, src) ma_atomic_store_explicit_i16(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_store_i32(dst, src) ma_atomic_store_explicit_i32(dst, src, ma_atomic_memory_order_seq_cst) @@ -4233,14 +5583,14 @@ typedef int ma_atomic_memory_order; #define ma_atomic_exchange_i16(dst, src) ma_atomic_exchange_explicit_i16(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_exchange_i32(dst, src) ma_atomic_exchange_explicit_i32(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_exchange_i64(dst, src) ma_atomic_exchange_explicit_i64(dst, src, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_i8( dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_i8( dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_i16(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_i16(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_i32(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_i32(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_i64(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_i64(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_i8( dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_i8( dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_i16(dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_i16(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_i32(dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_i32(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_i64(dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_i64(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_i8( dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_i8( dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_i16(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_i16(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_i32(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_i32(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_i64(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_i64(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_i8( dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_i8( dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_i16(dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_i16(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_i32(dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_i32(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_i64(dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_i64(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_i8( dst, src) ma_atomic_fetch_add_explicit_i8( dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_i16(dst, src) ma_atomic_fetch_add_explicit_i16(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_i32(dst, src) ma_atomic_fetch_add_explicit_i32(dst, src, ma_atomic_memory_order_seq_cst) @@ -4317,28 +5667,28 @@ static MA_INLINE double ma_atomic_exchange_explicit_f64(volatile double* dst, do r.i = ma_atomic_exchange_explicit_64((volatile ma_uint64*)dst, x.i, order); return r.f; } -static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_f32(volatile float* dst, float* expected, float desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) +static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_f32(volatile float* dst, float* expected, float replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { ma_atomic_if32 d; - d.f = desired; + d.f = replacement; return ma_atomic_compare_exchange_strong_explicit_32((volatile ma_uint32*)dst, (ma_uint32*)expected, d.i, successOrder, failureOrder); } -static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_f64(volatile double* dst, double* expected, double desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) +static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_f64(volatile double* dst, double* expected, double replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { ma_atomic_if64 d; - d.f = desired; + d.f = replacement; return ma_atomic_compare_exchange_strong_explicit_64((volatile ma_uint64*)dst, (ma_uint64*)expected, d.i, successOrder, failureOrder); } -static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_f32(volatile float* dst, float* expected, float desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) +static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_f32(volatile float* dst, float* expected, float replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { ma_atomic_if32 d; - d.f = desired; + d.f = replacement; return ma_atomic_compare_exchange_weak_explicit_32((volatile ma_uint32*)dst, (ma_uint32*)expected, d.i, successOrder, failureOrder); } -static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_f64(volatile double* dst, double* expected, double desired, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) +static MA_INLINE ma_bool32 ma_atomic_compare_exchange_weak_explicit_f64(volatile double* dst, double* expected, double replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { ma_atomic_if64 d; - d.f = desired; + d.f = replacement; return ma_atomic_compare_exchange_weak_explicit_64((volatile ma_uint64*)dst, (ma_uint64*)expected, d.i, successOrder, failureOrder); } static MA_INLINE float ma_atomic_fetch_add_explicit_f32(volatile float* dst, float src, ma_atomic_memory_order order) @@ -4429,10 +5779,10 @@ static MA_INLINE double ma_atomic_fetch_and_explicit_f64(volatile double* dst, d #define ma_atomic_load_f64(ptr) (double)ma_atomic_load_explicit_f64(ptr, ma_atomic_memory_order_seq_cst) #define ma_atomic_exchange_f32(dst, src) (float )ma_atomic_exchange_explicit_f32(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_exchange_f64(dst, src) (double)ma_atomic_exchange_explicit_f64(dst, src, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_f32(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_f32(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_strong_f64(dst, expected, desired) ma_atomic_compare_exchange_strong_explicit_f64(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_f32(dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_f32(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) -#define ma_atomic_compare_exchange_weak_f64(dst, expected, desired) ma_atomic_compare_exchange_weak_explicit_f64(dst, expected, desired, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_f32(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_f32(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_strong_f64(dst, expected, replacement) ma_atomic_compare_exchange_strong_explicit_f64(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_f32(dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_f32(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) +#define ma_atomic_compare_exchange_weak_f64(dst, expected, replacement) ma_atomic_compare_exchange_weak_explicit_f64(dst, expected, replacement, ma_atomic_memory_order_seq_cst, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_f32(dst, src) ma_atomic_fetch_add_explicit_f32(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_add_f64(dst, src) ma_atomic_fetch_add_explicit_f64(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_sub_f32(dst, src) ma_atomic_fetch_sub_explicit_f32(dst, src, ma_atomic_memory_order_seq_cst) @@ -4443,39 +5793,24 @@ static MA_INLINE double ma_atomic_fetch_and_explicit_f64(volatile double* dst, d #define ma_atomic_fetch_xor_f64(dst, src) ma_atomic_fetch_xor_explicit_f64(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_and_f32(dst, src) ma_atomic_fetch_and_explicit_f32(dst, src, ma_atomic_memory_order_seq_cst) #define ma_atomic_fetch_and_f64(dst, src) ma_atomic_fetch_and_explicit_f64(dst, src, ma_atomic_memory_order_seq_cst) -static MA_INLINE float ma_atomic_compare_and_swap_f32(volatile float* dst, float expected, float desired) +static MA_INLINE float ma_atomic_compare_and_swap_f32(volatile float* dst, float expected, float replacement) { ma_atomic_if32 r; ma_atomic_if32 e, d; e.f = expected; - d.f = desired; + d.f = replacement; r.i = ma_atomic_compare_and_swap_32((volatile ma_uint32*)dst, e.i, d.i); return r.f; } -static MA_INLINE double ma_atomic_compare_and_swap_f64(volatile double* dst, double expected, double desired) +static MA_INLINE double ma_atomic_compare_and_swap_f64(volatile double* dst, double expected, double replacement) { ma_atomic_if64 r; ma_atomic_if64 e, d; e.f = expected; - d.f = desired; + d.f = replacement; r.i = ma_atomic_compare_and_swap_64((volatile ma_uint64*)dst, e.i, d.i); return r.f; } -typedef ma_atomic_flag ma_atomic_spinlock; -static MA_INLINE void ma_atomic_spinlock_lock(volatile ma_atomic_spinlock* pSpinlock) -{ - for (;;) { - if (ma_atomic_flag_test_and_set_explicit(pSpinlock, ma_atomic_memory_order_acquire) == 0) { - break; - } - while (ma_atomic_flag_load_explicit(pSpinlock, ma_atomic_memory_order_relaxed) == 1) { - } - } -} -static MA_INLINE void ma_atomic_spinlock_unlock(volatile ma_atomic_spinlock* pSpinlock) -{ - ma_atomic_flag_clear_explicit(pSpinlock, ma_atomic_memory_order_release); -} #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif @@ -4681,7 +6016,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority int result; pthread_attr_t* pAttr = NULL; -#if !defined(__EMSCRIPTEN__) && !defined(__3DS__) +#if !defined(MA_EMSCRIPTEN) && !defined(MA_3DS) && !defined(MA_SWITCH) /* Try setting the thread priority. It's not critical if anything fails here. */ pthread_attr_t attr; if (pthread_attr_init(&attr) == 0) { @@ -4713,9 +6048,18 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority } #endif - if (stackSize > 0) { - pthread_attr_setstacksize(&attr, stackSize); + #if defined(_POSIX_THREAD_ATTR_STACKSIZE) && _POSIX_THREAD_ATTR_STACKSIZE >= 0 + { + if (stackSize > 0) { + pthread_attr_setstacksize(&attr, stackSize); + } } + #else + { + (void)stackSize; /* Suppress unused parameter warning. */ + } + #endif + if (scheduler != -1) { int priorityMin = sched_get_priority_min(scheduler); @@ -4772,6 +6116,21 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority } if (result != 0) { + /* + There have been reports that attempting to create a realtime thread can sometimes fail. In this case, + fall back to a normal priority thread. + + I'm including a compile-time option here to disable this functionality for those who have a hard + requirement on realtime threads and would rather an explicit failure. + */ + #ifndef MA_NO_PTHREAD_REALTIME_PRIORITY_FALLBACK + { + if(result == EPERM && priority == ma_thread_priority_realtime) { + return ma_thread_create__posix(pThread, ma_thread_priority_normal, stackSize, entryProc, pData); + } + } + #endif + return ma_result_from_errno(result); } @@ -5043,7 +6402,7 @@ static ma_result ma_event_signal__win32(ma_event* pEvent) static ma_result ma_semaphore_init__win32(int initialValue, ma_semaphore* pSemaphore) { - *pSemaphore = CreateSemaphoreW(NULL, (LONG)initialValue, LONG_MAX, NULL); + *pSemaphore = CreateSemaphore(NULL, (LONG)initialValue, LONG_MAX, NULL); if (*pSemaphore == NULL) { return ma_result_from_GetLastError(GetLastError()); } @@ -5937,10 +7296,12 @@ static MA_INLINE ma_uint16 ma_job_extract_slot(ma_uint64 toc) return (ma_uint16)(toc & 0x0000FFFF); } +#if 0 /* Currently unused, but might make use of this later. */ static MA_INLINE ma_uint16 ma_job_extract_code(ma_uint64 toc) { return (ma_uint16)((toc & 0xFFFF0000) >> 16); } +#endif static MA_INLINE ma_uint64 ma_job_toc_to_allocation(ma_uint64 toc) { @@ -6405,6 +7766,13 @@ MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob) Dynamic Linking *******************************************************************************/ +/* Disable run-time linking on certain backends and platforms. */ +#ifndef MA_NO_RUNTIME_LINKING + #if defined(MA_EMSCRIPTEN) || defined(MA_ORBIS) || defined(MA_PROSPERO) || defined(MA_SWITCH) || defined(MA_DOS) + #define MA_NO_RUNTIME_LINKING + #endif +#endif + #ifdef MA_POSIX /* No need for dlfcn.h if we're not using runtime linking. */ #ifndef MA_NO_RUNTIME_LINKING @@ -6414,104 +7782,124 @@ Dynamic Linking MA_API ma_handle ma_dlopen(ma_log* pLog, const char* filename) { -#ifndef MA_NO_RUNTIME_LINKING - ma_handle handle; + #ifndef MA_NO_RUNTIME_LINKING + { + ma_handle handle; - ma_log_postf(pLog, MA_LOG_LEVEL_DEBUG, "Loading library: %s\n", filename); + ma_log_postf(pLog, MA_LOG_LEVEL_DEBUG, "Loading library: %s\n", filename); - #ifdef MA_WIN32 - /* From MSDN: Desktop applications cannot use LoadPackagedLibrary; if a desktop application calls this function it fails with APPMODEL_ERROR_NO_PACKAGE.*/ - #if !defined(MA_WIN32_UWP) || !(defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP))) - handle = (ma_handle)LoadLibraryA(filename); + #ifdef MA_WIN32 + /* From MSDN: Desktop applications cannot use LoadPackagedLibrary; if a desktop application calls this function it fails with APPMODEL_ERROR_NO_PACKAGE.*/ + #if !defined(MA_WIN32_UWP) || !(defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP))) + handle = (ma_handle)LoadLibraryA(filename); + #else + /* *sigh* It appears there is no ANSI version of LoadPackagedLibrary()... */ + WCHAR filenameW[4096]; + if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, sizeof(filenameW)) == 0) { + handle = NULL; + } else { + handle = (ma_handle)LoadPackagedLibrary(filenameW, 0); + } + #endif #else - /* *sigh* It appears there is no ANSI version of LoadPackagedLibrary()... */ - WCHAR filenameW[4096]; - if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, sizeof(filenameW)) == 0) { - handle = NULL; - } else { - handle = (ma_handle)LoadPackagedLibrary(filenameW, 0); - } + handle = (ma_handle)dlopen(filename, RTLD_NOW); #endif - #else - handle = (ma_handle)dlopen(filename, RTLD_NOW); - #endif - /* - I'm not considering failure to load a library an error nor a warning because seamlessly falling through to a lower-priority - backend is a deliberate design choice. Instead I'm logging it as an informational message. - */ - if (handle == NULL) { - ma_log_postf(pLog, MA_LOG_LEVEL_INFO, "Failed to load library: %s\n", filename); + /* + I'm not considering failure to load a library an error nor a warning because seamlessly falling through to a lower-priority + backend is a deliberate design choice. Instead I'm logging it as an informational message. + */ + if (handle == NULL) { + ma_log_postf(pLog, MA_LOG_LEVEL_INFO, "Failed to load library: %s\n", filename); + } + + return handle; } - - return handle; -#else - /* Runtime linking is disabled. */ - (void)pLog; - (void)filename; - return NULL; -#endif + #else + { + /* Runtime linking is disabled. */ + (void)pLog; + (void)filename; + return NULL; + } + #endif } MA_API void ma_dlclose(ma_log* pLog, ma_handle handle) { -#ifndef MA_NO_RUNTIME_LINKING - #ifdef MA_WIN32 - FreeLibrary((HMODULE)handle); - #else - /* Hack for Android bug (see https://github.com/android/ndk/issues/360). Calling dlclose() pre-API 28 may segfault. */ - #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + #ifndef MA_NO_RUNTIME_LINKING + { + #ifdef MA_WIN32 { - dlclose((void*)handle); + FreeLibrary((HMODULE)handle); } #else { - (void)handle; + /* Hack for Android bug (see https://github.com/android/ndk/issues/360). Calling dlclose() pre-API 28 may segfault. */ + #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + { + dlclose((void*)handle); + } + #else + { + (void)handle; + } + #endif } #endif - #endif - (void)pLog; -#else - /* Runtime linking is disabled. */ - (void)pLog; - (void)handle; -#endif + (void)pLog; + } + #else + { + /* Runtime linking is disabled. */ + (void)pLog; + (void)handle; + } + #endif } MA_API ma_proc ma_dlsym(ma_log* pLog, ma_handle handle, const char* symbol) { -#ifndef MA_NO_RUNTIME_LINKING - ma_proc proc; + #ifndef MA_NO_RUNTIME_LINKING + { + ma_proc proc; - ma_log_postf(pLog, MA_LOG_LEVEL_DEBUG, "Loading symbol: %s\n", symbol); + ma_log_postf(pLog, MA_LOG_LEVEL_DEBUG, "Loading symbol: %s\n", symbol); -#ifdef _WIN32 - proc = (ma_proc)GetProcAddress((HMODULE)handle, symbol); -#else -#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpedantic" -#endif - proc = (ma_proc)dlsym((void*)handle, symbol); -#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) - #pragma GCC diagnostic pop -#endif -#endif + #ifdef _WIN32 + { + proc = (ma_proc)GetProcAddress((HMODULE)handle, symbol); + } + #else + { + #if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" + #endif + proc = (ma_proc)dlsym((void*)handle, symbol); + #if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) + #pragma GCC diagnostic pop + #endif + } + #endif - if (proc == NULL) { - ma_log_postf(pLog, MA_LOG_LEVEL_WARNING, "Failed to load symbol: %s\n", symbol); + if (proc == NULL) { + ma_log_postf(pLog, MA_LOG_LEVEL_WARNING, "Failed to load symbol: %s\n", symbol); + } + + (void)pLog; /* It's possible for pContext to be unused. */ + return proc; } - - (void)pLog; /* It's possible for pContext to be unused. */ - return proc; -#else - /* Runtime linking is disabled. */ - (void)pLog; - (void)handle; - (void)symbol; - return NULL; -#endif + #else + { + /* Runtime linking is disabled. */ + (void)pLog; + (void)handle; + (void)symbol; + return NULL; + } + #endif } @@ -6525,13 +7913,6 @@ DEVICE I/O ************************************************************************************************************************************************************* ************************************************************************************************************************************************************/ -/* Disable run-time linking on certain backends and platforms. */ -#ifndef MA_NO_RUNTIME_LINKING - #if defined(MA_EMSCRIPTEN) || defined(MA_ORBIS) || defined(MA_PROSPERO) - #define MA_NO_RUNTIME_LINKING - #endif -#endif - #ifdef MA_APPLE #include #endif @@ -6544,12 +7925,6 @@ DEVICE I/O #ifdef MA_POSIX #include - #include - - /* No need for dlfcn.h if we're not using runtime linking. */ - #ifndef MA_NO_RUNTIME_LINKING - #include - #endif #endif /* This must be set to at least 26. */ @@ -6804,7 +8179,7 @@ MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend) -#if defined(MA_WIN32) +#if defined(MA_WIN32) && !defined(MA_XBOX) /* WASAPI error codes. */ #define MA_AUDCLNT_E_NOT_INITIALIZED ((HRESULT)0x88890001) #define MA_AUDCLNT_E_ALREADY_INITIALIZED ((HRESULT)0x88890002) @@ -7019,6 +8394,11 @@ typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, const char* lpValueName, DWORD* lpReserved, DWORD* lpType, BYTE* lpData, DWORD* lpcbData); #endif /* MA_WIN32_DESKTOP */ +static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +/*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ +/*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ + MA_API size_t ma_strlen_WCHAR(const WCHAR* str) { size_t len = 0; @@ -7082,7 +8462,7 @@ Timing *******************************************************************************/ #if defined(MA_WIN32) && !defined(MA_POSIX) static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */ - static void ma_timer_init(ma_timer* pTimer) + static MA_INLINE void ma_timer_init(ma_timer* pTimer) { LARGE_INTEGER counter; @@ -7094,7 +8474,7 @@ Timing pTimer->counter = counter.QuadPart; } - static double ma_timer_get_time_in_seconds(ma_timer* pTimer) + static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) { LARGE_INTEGER counter; if (!QueryPerformanceCounter(&counter)) { @@ -7105,7 +8485,7 @@ Timing } #elif defined(MA_APPLE) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101200) static ma_uint64 g_ma_TimerFrequency = 0; - static void ma_timer_init(ma_timer* pTimer) + static MA_INLINE void ma_timer_init(ma_timer* pTimer) { mach_timebase_info_data_t baseTime; mach_timebase_info(&baseTime); @@ -7114,7 +8494,7 @@ Timing pTimer->counter = mach_absolute_time(); } - static double ma_timer_get_time_in_seconds(ma_timer* pTimer) + static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) { ma_uint64 newTimeCounter = mach_absolute_time(); ma_uint64 oldTimeCounter = pTimer->counter; @@ -7139,7 +8519,7 @@ Timing #define MA_CLOCK_ID CLOCK_REALTIME #endif - static void ma_timer_init(ma_timer* pTimer) + static MA_INLINE void ma_timer_init(ma_timer* pTimer) { struct timespec newTime; clock_gettime(MA_CLOCK_ID, &newTime); @@ -7147,7 +8527,7 @@ Timing pTimer->counter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec; } - static double ma_timer_get_time_in_seconds(ma_timer* pTimer) + static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) { ma_uint64 newTimeCounter; ma_uint64 oldTimeCounter; @@ -7161,7 +8541,7 @@ Timing return (newTimeCounter - oldTimeCounter) / 1000000000.0; } #else - static void ma_timer_init(ma_timer* pTimer) + static MA_INLINE void ma_timer_init(ma_timer* pTimer) { struct timeval newTime; gettimeofday(&newTime, NULL); @@ -7169,7 +8549,7 @@ Timing pTimer->counter = (newTime.tv_sec * 1000000) + newTime.tv_usec; } - static double ma_timer_get_time_in_seconds(ma_timer* pTimer) + static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) { ma_uint64 newTimeCounter; ma_uint64 oldTimeCounter; @@ -7753,14 +9133,6 @@ static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_device_state n } -#if defined(MA_WIN32) - static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - /*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ - /*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ -#endif - - MA_API ma_uint32 ma_get_format_priority_index(ma_format format) /* Lower = better. */ { @@ -8472,7 +9844,7 @@ static ma_result ma_context_init__null(ma_context* pContext, const ma_context_co WIN32 COMMON *******************************************************************************/ -#if defined(MA_WIN32) +#if defined(MA_WIN32) && !defined(MA_XBOX) #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) #define ma_CoInitializeEx(pContext, pvReserved, dwCoInit) ((pContext->win32.CoInitializeEx) ? ((MA_PFN_CoInitializeEx)pContext->win32.CoInitializeEx)(pvReserved, dwCoInit) : ((MA_PFN_CoInitialize)pContext->win32.CoInitialize)(pvReserved)) #define ma_CoUninitialize(pContext) ((MA_PFN_CoUninitialize)pContext->win32.CoUninitialize)() @@ -8487,7 +9859,7 @@ WIN32 COMMON #define ma_PropVariantClear(pContext, pvar) PropVariantClear(pvar) #endif -#if !defined(MAXULONG_PTR) && !defined(__WATCOMC__) +#if !defined(MAXULONG_PTR) && !defined(__WATCOMC__) && !defined(MA_XBOX_NXDK) typedef size_t DWORD_PTR; #endif @@ -8914,11 +10286,21 @@ typedef enum MA_AudioCategory_Other = 0 /* <-- miniaudio is only caring about Other. */ } MA_AUDIO_STREAM_CATEGORY; +typedef enum +{ + MA_AUDCLNT_STREAMOPTIONS_NONE, + MA_AUDCLNT_STREAMOPTIONS_RAW, + MA_AUDCLNT_STREAMOPTIONS_MATCH_FORMAT, + MA_AUDCLNT_STREAMOPTIONS_AMBISONICS, + MA_AUDCLNT_STREAMOPTIONS_POST_VOLUME_LOOPBACK +} MA_AUDCLNT_STREAMOPTIONS; + typedef struct { ma_uint32 cbSize; BOOL bIsOffload; MA_AUDIO_STREAM_CATEGORY eCategory; + MA_AUDCLNT_STREAMOPTIONS Options; } ma_AudioClientProperties; /* IUnknown */ @@ -10093,6 +11475,7 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device { ma_IMMDeviceEnumerator* pDeviceEnumerator; HRESULT hr; + HRESULT CoInitializeResult; MA_ASSERT(pContext != NULL); MA_ASSERT(ppMMDevice != NULL); @@ -10106,12 +11489,17 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device The community has reported that this seems to fix the crash. There are future plans to move all WASAPI operation over to a single thread to make everything safer, but in the meantime while we wait for that to come online I'm happy enough to use this hack instead. + + CoUninitialize should only be called if we successfully initialized. S_OK and S_FALSE both mean that we need to + call CoUninitialize since the internal ref count was increased. RPC_E_CHANGED_MODE means that CoInitializeEx was + called with a different COINIT value, and we don't call CoUninitialize in that case. Other errors are possible, + so we check for S_OK and S_FALSE specifically. */ - ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); + CoInitializeResult = ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); { hr = ma_CoCreateInstance(pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); - } - ma_CoUninitialize(pContext); + } + if (CoInitializeResult == S_OK || CoInitializeResult == S_FALSE) { ma_CoUninitialize(pContext); } if (FAILED(hr)) { /* <-- This is checking the call above to ma_CoCreateInstance(). */ ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.\n"); @@ -10455,7 +11843,7 @@ static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_de pActivationParams = &activationParams; /* When requesting a specific device ID we need to use a special device ID. */ - MA_COPY_MEMORY(virtualDeviceID.wasapi, MA_VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK, (wcslen(MA_VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK) + 1) * sizeof(wchar_t)); /* +1 for the null terminator. */ + MA_COPY_MEMORY(virtualDeviceID.wasapi, MA_VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK, (ma_wcslen(MA_VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK) + 1) * sizeof(wchar_t)); /* +1 for the null terminator. */ pDeviceID = &virtualDeviceID; } else { pActivationParams = NULL; /* No activation parameters required. */ @@ -15184,6 +16572,9 @@ typedef snd_pcm_channel_area_t ma_snd_pcm_channel_area_t; typedef snd_pcm_chmap_t ma_snd_pcm_chmap_t; typedef snd_pcm_state_t ma_snd_pcm_state_t; +/* snd_pcm_state_t */ +#define MA_SND_PCM_STATE_XRUN SND_PCM_STATE_XRUN + /* snd_pcm_stream_t */ #define MA_SND_PCM_STREAM_PLAYBACK SND_PCM_STREAM_PLAYBACK #define MA_SND_PCM_STREAM_CAPTURE SND_PCM_STREAM_CAPTURE @@ -15379,6 +16770,7 @@ typedef int (* ma_snd_pcm_hw_params_set_channels_minmax_proc) ( typedef int (* ma_snd_pcm_hw_params_set_rate_resample_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int val); typedef int (* ma_snd_pcm_hw_params_set_rate_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int val, int dir); typedef int (* ma_snd_pcm_hw_params_set_rate_near_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); +typedef int (* ma_snd_pcm_hw_params_set_rate_minmax_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir); typedef int (* ma_snd_pcm_hw_params_set_buffer_size_near_proc)(ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_uframes_t *val); typedef int (* ma_snd_pcm_hw_params_set_periods_near_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); typedef int (* ma_snd_pcm_hw_params_set_access_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_access_t _access); @@ -17145,8 +18537,9 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co ma_snd_pcm_hw_params_get_format_mask_proc _snd_pcm_hw_params_get_format_mask = snd_pcm_hw_params_get_format_mask; ma_snd_pcm_hw_params_set_channels_proc _snd_pcm_hw_params_set_channels = snd_pcm_hw_params_set_channels; ma_snd_pcm_hw_params_set_channels_near_proc _snd_pcm_hw_params_set_channels_near = snd_pcm_hw_params_set_channels_near; + ma_snd_pcm_hw_params_set_channels_minmax_proc _snd_pcm_hw_params_set_channels_minmax = snd_pcm_hw_params_set_channels_minmax; ma_snd_pcm_hw_params_set_rate_resample_proc _snd_pcm_hw_params_set_rate_resample = snd_pcm_hw_params_set_rate_resample; - ma_snd_pcm_hw_params_set_rate_near _snd_pcm_hw_params_set_rate = snd_pcm_hw_params_set_rate; + ma_snd_pcm_hw_params_set_rate_proc _snd_pcm_hw_params_set_rate = snd_pcm_hw_params_set_rate; ma_snd_pcm_hw_params_set_rate_near_proc _snd_pcm_hw_params_set_rate_near = snd_pcm_hw_params_set_rate_near; ma_snd_pcm_hw_params_set_rate_minmax_proc _snd_pcm_hw_params_set_rate_minmax = snd_pcm_hw_params_set_rate_minmax; ma_snd_pcm_hw_params_set_buffer_size_near_proc _snd_pcm_hw_params_set_buffer_size_near = snd_pcm_hw_params_set_buffer_size_near; @@ -17198,9 +18591,9 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co ma_snd_pcm_info_proc _snd_pcm_info = snd_pcm_info; ma_snd_pcm_info_sizeof_proc _snd_pcm_info_sizeof = snd_pcm_info_sizeof; ma_snd_pcm_info_get_name_proc _snd_pcm_info_get_name = snd_pcm_info_get_name; - ma_snd_pcm_poll_descriptors _snd_pcm_poll_descriptors = snd_pcm_poll_descriptors; - ma_snd_pcm_poll_descriptors_count _snd_pcm_poll_descriptors_count = snd_pcm_poll_descriptors_count; - ma_snd_pcm_poll_descriptors_revents _snd_pcm_poll_descriptors_revents = snd_pcm_poll_descriptors_revents; + ma_snd_pcm_poll_descriptors_proc _snd_pcm_poll_descriptors = snd_pcm_poll_descriptors; + ma_snd_pcm_poll_descriptors_count_proc _snd_pcm_poll_descriptors_count = snd_pcm_poll_descriptors_count; + ma_snd_pcm_poll_descriptors_revents_proc _snd_pcm_poll_descriptors_revents = snd_pcm_poll_descriptors_revents; ma_snd_config_update_free_global_proc _snd_config_update_free_global = snd_config_update_free_global; pContext->alsa.snd_pcm_open = (ma_proc)_snd_pcm_open; @@ -17216,6 +18609,7 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co pContext->alsa.snd_pcm_hw_params_set_rate_resample = (ma_proc)_snd_pcm_hw_params_set_rate_resample; pContext->alsa.snd_pcm_hw_params_set_rate = (ma_proc)_snd_pcm_hw_params_set_rate; pContext->alsa.snd_pcm_hw_params_set_rate_near = (ma_proc)_snd_pcm_hw_params_set_rate_near; + pContext->alsa.snd_pcm_hw_params_set_rate_minmax = (ma_proc)_snd_pcm_hw_params_set_rate_minmax; pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (ma_proc)_snd_pcm_hw_params_set_buffer_size_near; pContext->alsa.snd_pcm_hw_params_set_periods_near = (ma_proc)_snd_pcm_hw_params_set_periods_near; pContext->alsa.snd_pcm_hw_params_set_access = (ma_proc)_snd_pcm_hw_params_set_access; @@ -17941,7 +19335,7 @@ typedef void (* ma_pa_threaded_mainloop_unlock_proc) ( typedef void (* ma_pa_threaded_mainloop_wait_proc) (ma_pa_threaded_mainloop* m); typedef void (* ma_pa_threaded_mainloop_signal_proc) (ma_pa_threaded_mainloop* m, int wait_for_accept); typedef void (* ma_pa_threaded_mainloop_accept_proc) (ma_pa_threaded_mainloop* m); -typedef int (* ma_pa_threaded_mainloop_get_retval_proc) (ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_get_retval_proc) (const ma_pa_threaded_mainloop* m); typedef ma_pa_mainloop_api* (* ma_pa_threaded_mainloop_get_api_proc) (ma_pa_threaded_mainloop* m); typedef int (* ma_pa_threaded_mainloop_in_thread_proc) (ma_pa_threaded_mainloop* m); typedef void (* ma_pa_threaded_mainloop_set_name_proc) (ma_pa_threaded_mainloop* m, const char* name); @@ -17950,13 +19344,13 @@ typedef void (* ma_pa_context_unref_proc) ( typedef int (* ma_pa_context_connect_proc) (ma_pa_context* c, const char* server, ma_pa_context_flags_t flags, const ma_pa_spawn_api* api); typedef void (* ma_pa_context_disconnect_proc) (ma_pa_context* c); typedef void (* ma_pa_context_set_state_callback_proc) (ma_pa_context* c, ma_pa_context_notify_cb_t cb, void* userdata); -typedef ma_pa_context_state_t (* ma_pa_context_get_state_proc) (ma_pa_context* c); +typedef ma_pa_context_state_t (* ma_pa_context_get_state_proc) (const ma_pa_context* c); typedef ma_pa_operation* (* ma_pa_context_get_sink_info_list_proc) (ma_pa_context* c, ma_pa_sink_info_cb_t cb, void* userdata); typedef ma_pa_operation* (* ma_pa_context_get_source_info_list_proc) (ma_pa_context* c, ma_pa_source_info_cb_t cb, void* userdata); typedef ma_pa_operation* (* ma_pa_context_get_sink_info_by_name_proc) (ma_pa_context* c, const char* name, ma_pa_sink_info_cb_t cb, void* userdata); typedef ma_pa_operation* (* ma_pa_context_get_source_info_by_name_proc)(ma_pa_context* c, const char* name, ma_pa_source_info_cb_t cb, void* userdata); typedef void (* ma_pa_operation_unref_proc) (ma_pa_operation* o); -typedef ma_pa_operation_state_t (* ma_pa_operation_get_state_proc) (ma_pa_operation* o); +typedef ma_pa_operation_state_t (* ma_pa_operation_get_state_proc) (const ma_pa_operation* o); typedef ma_pa_channel_map* (* ma_pa_channel_map_init_extend_proc) (ma_pa_channel_map* m, unsigned channels, ma_pa_channel_map_def_t def); typedef int (* ma_pa_channel_map_valid_proc) (const ma_pa_channel_map* m); typedef int (* ma_pa_channel_map_compatible_proc) (const ma_pa_channel_map* m, const ma_pa_sample_spec* ss); @@ -17965,12 +19359,12 @@ typedef void (* ma_pa_stream_unref_proc) ( typedef int (* ma_pa_stream_connect_playback_proc) (ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags, const ma_pa_cvolume* volume, ma_pa_stream* sync_stream); typedef int (* ma_pa_stream_connect_record_proc) (ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags); typedef int (* ma_pa_stream_disconnect_proc) (ma_pa_stream* s); -typedef ma_pa_stream_state_t (* ma_pa_stream_get_state_proc) (ma_pa_stream* s); +typedef ma_pa_stream_state_t (* ma_pa_stream_get_state_proc) (const ma_pa_stream* s); typedef const ma_pa_sample_spec* (* ma_pa_stream_get_sample_spec_proc) (ma_pa_stream* s); typedef const ma_pa_channel_map* (* ma_pa_stream_get_channel_map_proc) (ma_pa_stream* s); typedef const ma_pa_buffer_attr* (* ma_pa_stream_get_buffer_attr_proc) (ma_pa_stream* s); typedef ma_pa_operation* (* ma_pa_stream_set_buffer_attr_proc) (ma_pa_stream* s, const ma_pa_buffer_attr* attr, ma_pa_stream_success_cb_t cb, void* userdata); -typedef const char* (* ma_pa_stream_get_device_name_proc) (ma_pa_stream* s); +typedef const char* (* ma_pa_stream_get_device_name_proc) (const ma_pa_stream* s); typedef void (* ma_pa_stream_set_write_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); typedef void (* ma_pa_stream_set_read_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); typedef void (* ma_pa_stream_set_suspended_callback_proc) (ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); @@ -17978,15 +19372,15 @@ typedef void (* ma_pa_stream_set_moved_callback_proc) ( typedef int (* ma_pa_stream_is_suspended_proc) (const ma_pa_stream* s); typedef ma_pa_operation* (* ma_pa_stream_flush_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); typedef ma_pa_operation* (* ma_pa_stream_drain_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); -typedef int (* ma_pa_stream_is_corked_proc) (ma_pa_stream* s); +typedef int (* ma_pa_stream_is_corked_proc) (const ma_pa_stream* s); typedef ma_pa_operation* (* ma_pa_stream_cork_proc) (ma_pa_stream* s, int b, ma_pa_stream_success_cb_t cb, void* userdata); typedef ma_pa_operation* (* ma_pa_stream_trigger_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); typedef int (* ma_pa_stream_begin_write_proc) (ma_pa_stream* s, void** data, size_t* nbytes); typedef int (* ma_pa_stream_write_proc) (ma_pa_stream* s, const void* data, size_t nbytes, ma_pa_free_cb_t free_cb, int64_t offset, ma_pa_seek_mode_t seek); typedef int (* ma_pa_stream_peek_proc) (ma_pa_stream* s, const void** data, size_t* nbytes); typedef int (* ma_pa_stream_drop_proc) (ma_pa_stream* s); -typedef size_t (* ma_pa_stream_writable_size_proc) (ma_pa_stream* s); -typedef size_t (* ma_pa_stream_readable_size_proc) (ma_pa_stream* s); +typedef size_t (* ma_pa_stream_writable_size_proc) (const ma_pa_stream* s); +typedef size_t (* ma_pa_stream_readable_size_proc) (const ma_pa_stream* s); typedef struct { @@ -18282,7 +19676,7 @@ static ma_result ma_init_pa_mainloop_and_pa_context__pulse(ma_context* pContext, } /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ - result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pPulseContext, pServerName, (tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pPulseContext, pServerName, (tryAutoSpawn) ? MA_PA_CONTEXT_NOFLAGS : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); if (result != MA_SUCCESS) { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); @@ -19015,7 +20409,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi const ma_pa_buffer_attr* pActualAttr = NULL; const ma_pa_channel_map* pActualChannelMap = NULL; ma_uint32 iChannel; - ma_pa_stream_flags_t streamFlags; + int streamFlags; MA_ASSERT(pDevice != NULL); MA_ZERO_OBJECT(&pDevice->pulse); @@ -19073,8 +20467,13 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ss.channels = pDescriptorCapture->channels; } + /* PulseAudio has a maximum channel count of 32. We'll get a crash if this is exceeded. */ + if (ss.channels > 32) { + ss.channels = 32; + } + /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, pConfig->pulse.channelMap); + ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, (ma_pa_channel_map_def_t)pConfig->pulse.channelMap); /* Use the requested sample rate if one was specified. */ if (pDescriptorCapture->sampleRate != 0) { @@ -19131,7 +20530,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi streamFlags |= MA_PA_STREAM_DONT_MOVE; } - error = ((ma_pa_stream_connect_record_proc)pDevice->pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); + error = ((ma_pa_stream_connect_record_proc)pDevice->pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, (ma_pa_stream_flags_t)streamFlags); if (error != MA_PA_OK) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream."); result = ma_result_from_pulse(error); @@ -19225,8 +20624,13 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ss.channels = pDescriptorPlayback->channels; } + /* PulseAudio has a maximum channel count of 32. We'll get a crash if this is exceeded. */ + if (ss.channels > 32) { + ss.channels = 32; + } + /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, pConfig->pulse.channelMap); + ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, (ma_pa_channel_map_def_t)pConfig->pulse.channelMap); /* Use the requested sample rate if one was specified. */ @@ -19288,7 +20692,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi streamFlags |= MA_PA_STREAM_DONT_MOVE; } - error = ((ma_pa_stream_connect_playback_proc)pDevice->pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); + error = ((ma_pa_stream_connect_playback_proc)pDevice->pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, (ma_pa_stream_flags_t)streamFlags, NULL, NULL); if (error != MA_PA_OK) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream."); result = ma_result_from_pulse(error); @@ -19843,6 +21247,7 @@ typedef JackProcessCallback ma_JackProcessCallback; typedef JackBufferSizeCallback ma_JackBufferSizeCallback; typedef JackShutdownCallback ma_JackShutdownCallback; #define MA_JACK_DEFAULT_AUDIO_TYPE JACK_DEFAULT_AUDIO_TYPE +#define ma_JackNullOption JackNullOption #define ma_JackNoStartServer JackNoStartServer #define ma_JackPortIsInput JackPortIsInput #define ma_JackPortIsOutput JackPortIsOutput @@ -19857,6 +21262,7 @@ typedef int (* ma_JackProcessCallback) (ma_jack_nframes_t nframes, void* arg) typedef int (* ma_JackBufferSizeCallback)(ma_jack_nframes_t nframes, void* arg); typedef void (* ma_JackShutdownCallback) (void* arg); #define MA_JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define ma_JackNullOption 0 #define ma_JackNoStartServer 1 #define ma_JackPortIsInput 1 #define ma_JackPortIsOutput 2 @@ -19897,7 +21303,7 @@ static ma_result ma_context_open_client__jack(ma_context* pContext, ma_jack_clie maxClientNameSize = ((ma_jack_client_name_size_proc)pContext->jack.jack_client_name_size)(); /* Includes null terminator. */ ma_strncpy_s(clientName, ma_min(sizeof(clientName), maxClientNameSize), (pContext->jack.pClientName != NULL) ? pContext->jack.pClientName : "miniaudio", (size_t)-1); - pClient = ((ma_jack_client_open_proc)pContext->jack.jack_client_open)(clientName, (pContext->jack.tryStartServer) ? 0 : ma_JackNoStartServer, &status, NULL); + pClient = ((ma_jack_client_open_proc)pContext->jack.jack_client_open)(clientName, (pContext->jack.tryStartServer) ? ma_JackNullOption : ma_JackNoStartServer, &status, NULL); if (pClient == NULL) { return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } @@ -25499,7 +26905,7 @@ OSS Backend #define MA_OSS_DEFAULT_DEVICE_NAME "/dev/dsp" -static int ma_open_temp_device__oss() +static int ma_open_temp_device__oss(void) { /* The OSS sample code uses "/dev/mixer" as the device for getting system properties so I'm going to do the same. */ int fd = open("/dev/mixer", O_RDONLY, 0); @@ -26339,25 +27745,30 @@ static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUs (void)error; ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); + /* When we get an error, we'll assume that the stream is in an erroneous state and needs to be restarted. From the documentation, we cannot do this from the error callback. Therefore we are going to use an event thread for the AAudio backend to do this cleanly and safely. */ - job = ma_job_init(MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE); - job.data.device.aaudio.reroute.pDevice = pDevice; - - if (pStream == pDevice->aaudio.pStreamCapture) { - job.data.device.aaudio.reroute.deviceType = ma_device_type_capture; + if (ma_atomic_bool32_get(&pDevice->aaudio.isTearingDown)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Tearing down device.\n"); } else { - job.data.device.aaudio.reroute.deviceType = ma_device_type_playback; - } - - result = ma_device_job_thread_post(&pDevice->pContext->aaudio.jobThread, &job); - if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Failed to post job for rerouting.\n"); - return; + job = ma_job_init(MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE); + job.data.device.aaudio.reroute.pDevice = pDevice; + + if (pStream == pDevice->aaudio.pStreamCapture) { + job.data.device.aaudio.reroute.deviceType = ma_device_type_capture; + } else { + job.data.device.aaudio.reroute.deviceType = ma_device_type_playback; + } + + result = ma_device_job_thread_post(&pDevice->pContext->aaudio.jobThread, &job); + if (result != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Failed to post job for rerouting.\n"); + return; + } } } @@ -26674,7 +28085,7 @@ static ma_result ma_close_streams__aaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - /* When re-routing, streams may have been closed and never re-opened. Hence the extra checks below. */ + /* When rerouting, streams may have been closed and never re-opened. Hence the extra checks below. */ if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); pDevice->aaudio.pStreamCapture = NULL; @@ -26691,6 +28102,12 @@ static ma_result ma_device_uninit__aaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); + /* + Note: Closing the streams may cause a timeout error, which would then trigger rerouting in our error callback. + We must not schedule a reroute when device is getting destroyed. + */ + ma_atomic_bool32_set(&pDevice->aaudio.isTearingDown, MA_TRUE); + /* Wait for any rerouting to finish before attempting to close the streams. */ ma_mutex_lock(&pDevice->aaudio.rerouteLock); { @@ -26698,7 +28115,7 @@ static ma_result ma_device_uninit__aaudio(ma_device* pDevice) } ma_mutex_unlock(&pDevice->aaudio.rerouteLock); - /* Destroy re-routing lock. */ + /* Destroy rerouting lock. */ ma_mutex_uninit(&pDevice->aaudio.rerouteLock); return MA_SUCCESS; @@ -26934,17 +28351,22 @@ static ma_result ma_device_stop__aaudio(ma_device* pDevice) static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type deviceType) { + const ma_int32 maxAttempts = 4; /* Reasonable retry limit. */ + ma_result result; - int32_t retries = 0; + ma_int32 iAttempt; MA_ASSERT(pDevice != NULL); - /* - TODO: Stop retrying if main thread is about to uninit device. - */ - ma_mutex_lock(&pDevice->aaudio.rerouteLock); - { -error_disconnected: + /* We got disconnected! Retry a few times, until we find a connected device! */ + iAttempt = 0; + while (iAttempt++ < maxAttempts) { + /* Device tearing down? No need to reroute! */ + if (ma_atomic_bool32_get(&pDevice->aaudio.isTearingDown)) { + result = MA_SUCCESS; /* Caller should continue as normal. */ + break; + } + /* The first thing to do is close the streams. */ ma_close_streams__aaudio(pDevice); @@ -27000,14 +28422,16 @@ error_disconnected: result = ma_device_init_streams__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to create stream after route change."); - goto done; + /* Reroute failed! */ + break; } result = ma_device_post_init(pDevice, deviceType, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to initialize device after route change."); ma_close_streams__aaudio(pDevice); - goto done; + /* Reroute failed! */ + break; } /* We'll only ever do this in response to a reroute. */ @@ -27018,26 +28442,23 @@ error_disconnected: if (pDevice->aaudio.noAutoStartAfterReroute == MA_FALSE) { result = ma_device_start__aaudio(pDevice); if (result != MA_SUCCESS) { - /* We got disconnected! Retry a few times, until we find a connected device! */ - retries += 1; - if (retries <= 3) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, retrying(%d)", retries); - goto error_disconnected; + if (iAttempt < maxAttempts) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, retrying(%d)", iAttempt); + } else { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, giving up."); } - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change."); - goto done; } } else { - ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ + ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ } } - - result = MA_SUCCESS; - } -done: - /* Re-routing done */ - ma_mutex_unlock(&pDevice->aaudio.rerouteLock); + if (result == MA_SUCCESS) { + /* Reroute successful! */ + break; + } + } + return result; } @@ -27203,7 +28624,7 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_ static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) { - ma_result result; + ma_result result = MA_SUCCESS; ma_device* pDevice; MA_ASSERT(pJob != NULL); @@ -27211,19 +28632,22 @@ static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) pDevice = (ma_device*)pJob->data.device.aaudio.reroute.pDevice; MA_ASSERT(pDevice != NULL); - /* Here is where we need to reroute the device. To do this we need to uninitialize the stream and reinitialize it. */ - result = ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); - if (result != MA_SUCCESS) { - /* - Getting here means we failed to reroute the device. The best thing I can think of here is to - just stop the device. - */ - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Stopping device due to reroute failure."); - ma_device_stop(pDevice); - return result; + ma_mutex_lock(&pDevice->aaudio.rerouteLock); + { + /* Here is where we need to reroute the device. To do this we need to uninitialize the stream and reinitialize it. */ + result = ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); + if (result != MA_SUCCESS) { + /* + Getting here means we failed to reroute the device. The best thing I can think of here is to + just stop the device. + */ + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Stopping device due to reroute failure."); + ma_device_stop(pDevice); + } } + ma_mutex_unlock(&pDevice->aaudio.rerouteLock); - return MA_SUCCESS; + return result; } #else /* Getting here means there is no AAudio backend so we need a no-op job implementation. */ @@ -29741,13 +31165,13 @@ MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceTy static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) { ma_device* pDevice = (ma_device*)pData; -#ifdef MA_WIN32 +#if defined(MA_WIN32) && !defined(MA_XBOX) HRESULT CoInitializeResult; #endif MA_ASSERT(pDevice != NULL); -#ifdef MA_WIN32 +#if defined(MA_WIN32) && !defined(MA_XBOX) CoInitializeResult = ma_CoInitializeEx(pDevice->pContext, NULL, MA_COINIT_VALUE); #endif @@ -29838,8 +31262,8 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) ma_event_signal(&pDevice->stopEvent); } -#ifdef MA_WIN32 - if (CoInitializeResult == S_OK) { +#if defined(MA_WIN32) && !defined(MA_XBOX) + if (CoInitializeResult == S_OK || CoInitializeResult == S_FALSE) { ma_CoUninitialize(pDevice->pContext); } #endif @@ -29863,67 +31287,92 @@ static ma_bool32 ma_device__is_initialized(ma_device* pDevice) static ma_result ma_context_uninit_backend_apis__win32(ma_context* pContext) { /* For some reason UWP complains when CoUninitialize() is called. I'm just not going to call it on UWP. */ -#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - if (pContext->win32.CoInitializeResult == S_OK) { - ma_CoUninitialize(pContext); + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) + { + /* TODO: Remove this once the new single threaded backend system is in place in 0.12. */ + #if !defined(MA_XBOX) + { + if (pContext->win32.CoInitializeResult == S_OK || pContext->win32.CoInitializeResult == S_FALSE) { + ma_CoUninitialize(pContext); /* TODO: Remove this once the new single threaded backend system is in place in 0.12. */ + } + } + #endif + + #if defined(MA_WIN32_DESKTOP) + ma_dlclose(ma_context_get_log(pContext), pContext->win32.hUser32DLL); + ma_dlclose(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL); + #endif + + ma_dlclose(ma_context_get_log(pContext), pContext->win32.hOle32DLL); + } + #else + { + (void)pContext; } - - #if defined(MA_WIN32_DESKTOP) - ma_dlclose(ma_context_get_log(pContext), pContext->win32.hUser32DLL); - ma_dlclose(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL); #endif - ma_dlclose(ma_context_get_log(pContext), pContext->win32.hOle32DLL); -#else - (void)pContext; -#endif - return MA_SUCCESS; } static ma_result ma_context_init_backend_apis__win32(ma_context* pContext) { -#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - #if defined(MA_WIN32_DESKTOP) - /* User32.dll */ - pContext->win32.hUser32DLL = ma_dlopen(ma_context_get_log(pContext), "user32.dll"); - if (pContext->win32.hUser32DLL == NULL) { + /* + TODO: Reassess all of this stuff and move everything to the relevant backends. For example, I think + GetForegroundWindow() and GetDesktopWindow() are only used by the DirectSound backend. + */ + #if (defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK)) && !defined(MA_XBOX) + { + #if defined(MA_WIN32_DESKTOP) + { + /* User32.dll */ + pContext->win32.hUser32DLL = ma_dlopen(ma_context_get_log(pContext), "user32.dll"); + if (pContext->win32.hUser32DLL == NULL) { + return MA_FAILED_TO_INIT_BACKEND; + } + + pContext->win32.GetForegroundWindow = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hUser32DLL, "GetForegroundWindow"); + pContext->win32.GetDesktopWindow = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hUser32DLL, "GetDesktopWindow"); + + + /* Advapi32.dll */ + pContext->win32.hAdvapi32DLL = ma_dlopen(ma_context_get_log(pContext), "advapi32.dll"); + if (pContext->win32.hAdvapi32DLL == NULL) { + return MA_FAILED_TO_INIT_BACKEND; + } + + pContext->win32.RegOpenKeyExA = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegOpenKeyExA"); + pContext->win32.RegCloseKey = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegCloseKey"); + pContext->win32.RegQueryValueExA = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegQueryValueExA"); + } + #endif + + /* Ole32.dll */ + pContext->win32.hOle32DLL = ma_dlopen(ma_context_get_log(pContext), "ole32.dll"); + if (pContext->win32.hOle32DLL == NULL) { return MA_FAILED_TO_INIT_BACKEND; } - pContext->win32.GetForegroundWindow = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hUser32DLL, "GetForegroundWindow"); - pContext->win32.GetDesktopWindow = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hUser32DLL, "GetDesktopWindow"); - - - /* Advapi32.dll */ - pContext->win32.hAdvapi32DLL = ma_dlopen(ma_context_get_log(pContext), "advapi32.dll"); - if (pContext->win32.hAdvapi32DLL == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } - - pContext->win32.RegOpenKeyExA = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegOpenKeyExA"); - pContext->win32.RegCloseKey = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegCloseKey"); - pContext->win32.RegQueryValueExA = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegQueryValueExA"); + pContext->win32.CoInitialize = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoInitialize"); + pContext->win32.CoInitializeEx = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoInitializeEx"); + pContext->win32.CoUninitialize = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoUninitialize"); + pContext->win32.CoCreateInstance = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoCreateInstance"); + pContext->win32.CoTaskMemFree = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoTaskMemFree"); + pContext->win32.PropVariantClear = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "PropVariantClear"); + pContext->win32.StringFromGUID2 = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "StringFromGUID2"); + } + #else + { + (void)pContext; /* Unused. */ + } #endif - /* Ole32.dll */ - pContext->win32.hOle32DLL = ma_dlopen(ma_context_get_log(pContext), "ole32.dll"); - if (pContext->win32.hOle32DLL == NULL) { - return MA_FAILED_TO_INIT_BACKEND; + /* TODO: Remove this once the new single threaded backend system is in place in 0.12. */ + #if !defined(MA_XBOX) + { + pContext->win32.CoInitializeResult = ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); } + #endif - pContext->win32.CoInitialize = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoInitialize"); - pContext->win32.CoInitializeEx = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoInitializeEx"); - pContext->win32.CoUninitialize = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoUninitialize"); - pContext->win32.CoCreateInstance = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoCreateInstance"); - pContext->win32.CoTaskMemFree = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "CoTaskMemFree"); - pContext->win32.PropVariantClear = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "PropVariantClear"); - pContext->win32.StringFromGUID2 = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hOle32DLL, "StringFromGUID2"); -#else - (void)pContext; /* Unused. */ -#endif - - pContext->win32.CoInitializeResult = ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); return MA_SUCCESS; } #else @@ -32521,7 +33970,7 @@ static MA_INLINE void ma_pcm_s16_to_s32__reference(void* dst, const void* src, m ma_uint64 i; for (i = 0; i < count; i += 1) { - dst_s32[i] = src_s16[i] << 16; + dst_s32[i] = (ma_int32)src_s16[i] << 16; } (void)ditherMode; @@ -44913,8 +46362,12 @@ MA_API size_t ma_channel_map_to_string(const ma_channel* pChannelMap, ma_uint32 } /* Null terminate. Don't increment the length here. */ - if (pBufferOut != NULL && bufferCap > len + 1) { - pBufferOut[len] = '\0'; + if (pBufferOut != NULL) { + if (bufferCap > len) { + pBufferOut[len] = '\0'; + } else if (bufferCap > 0) { + pBufferOut[bufferCap - 1] = '\0'; + } } return len; @@ -45125,7 +46578,7 @@ MA_API ma_result ma_rb_init_ex(size_t subbufferSizeInBytes, size_t subbufferCoun Here is where we allocate our own buffer. We always want to align this to MA_SIMD_ALIGNMENT for future SIMD optimization opportunity. To do this we need to make sure the stride is a multiple of MA_SIMD_ALIGNMENT. */ - pRB->subbufferStrideInBytes = (pRB->subbufferSizeInBytes + (MA_SIMD_ALIGNMENT-1)) & ~MA_SIMD_ALIGNMENT; + pRB->subbufferStrideInBytes = ma_align(pRB->subbufferSizeInBytes, MA_SIMD_ALIGNMENT); bufferSizeInBytes = (size_t)pRB->subbufferCount*pRB->subbufferStrideInBytes; pRB->pBuffer = ma_aligned_malloc(bufferSizeInBytes, MA_SIMD_ALIGNMENT, &pRB->allocationCallbacks); @@ -48020,7 +49473,7 @@ MA_API ma_result ma_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo } -#if !defined(MA_USE_WIN32_FILEIO) && (defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) && !defined(MA_POSIX)) +#if !defined(MA_USE_WIN32_FILEIO) && (defined(MA_WIN32) && (defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_NXDK)) && !defined(MA_NO_WIN32_FILEIO) && !defined(MA_POSIX)) #define MA_USE_WIN32_FILEIO #endif @@ -48097,25 +49550,34 @@ static ma_result ma_default_vfs_open__win32(ma_vfs* pVFS, const char* pFilePath, static ma_result ma_default_vfs_open_w__win32(ma_vfs* pVFS, const wchar_t* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) { - HANDLE hFile; - DWORD dwDesiredAccess; - DWORD dwShareMode; - DWORD dwCreationDisposition; + #if !defined(MA_XBOX_NXDK) + { + HANDLE hFile; + DWORD dwDesiredAccess; + DWORD dwShareMode; + DWORD dwCreationDisposition; - (void)pVFS; + (void)pVFS; - /* Load some Win32 symbols dynamically so we can dynamically check for the existence of SetFilePointerEx. */ - ma_win32_fileio_init(); + /* Load some Win32 symbols dynamically so we can dynamically check for the existence of SetFilePointerEx. */ + ma_win32_fileio_init(); - ma_default_vfs__get_open_settings_win32(openMode, &dwDesiredAccess, &dwShareMode, &dwCreationDisposition); + ma_default_vfs__get_open_settings_win32(openMode, &dwDesiredAccess, &dwShareMode, &dwCreationDisposition); - hFile = CreateFileW(pFilePath, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) { - return ma_result_from_GetLastError(GetLastError()); + hFile = CreateFileW(pFilePath, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return ma_result_from_GetLastError(GetLastError()); + } + + *pFile = hFile; + return MA_SUCCESS; } - - *pFile = hFile; - return MA_SUCCESS; + #else + { + /* No CreateFileW() available. */ + return MA_NOT_IMPLEMENTED; + } + #endif } static ma_result ma_default_vfs_close__win32(ma_vfs* pVFS, ma_vfs_file file) @@ -48286,19 +49748,28 @@ static ma_result ma_default_vfs_tell__win32(ma_vfs* pVFS, ma_vfs_file file, ma_i static ma_result ma_default_vfs_info__win32(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo) { - BY_HANDLE_FILE_INFORMATION fi; - BOOL result; - (void)pVFS; - result = GetFileInformationByHandle((HANDLE)file, &fi); - if (result == 0) { - return ma_result_from_GetLastError(GetLastError()); + #if !defined(MA_XBOX_NXDK) + { + BY_HANDLE_FILE_INFORMATION fi; + BOOL result; + + result = GetFileInformationByHandle((HANDLE)file, &fi); + if (result == 0) { + return ma_result_from_GetLastError(GetLastError()); + } + + pInfo->sizeInBytes = ((ma_uint64)fi.nFileSizeHigh << 32) | ((ma_uint64)fi.nFileSizeLow); + + return MA_SUCCESS; } - - pInfo->sizeInBytes = ((ma_uint64)fi.nFileSizeHigh << 32) | ((ma_uint64)fi.nFileSizeLow); - - return MA_SUCCESS; + #else + { + /* GetFileInformationByHandle() is unavailable. */ + return MA_NOT_IMPLEMENTED; + } + #endif } #else static ma_result ma_default_vfs_open__stdio(ma_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) @@ -48636,6 +50107,8 @@ static ma_result ma_default_vfs_tell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* p static ma_result ma_default_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo) { + ma_result result; + if (pInfo == NULL) { return MA_INVALID_ARGS; } @@ -48647,10 +50120,43 @@ static ma_result ma_default_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_inf } #if defined(MA_USE_WIN32_FILEIO) - return ma_default_vfs_info__win32(pVFS, file, pInfo); + result = ma_default_vfs_info__win32(pVFS, file, pInfo); #else - return ma_default_vfs_info__stdio(pVFS, file, pInfo); + result = ma_default_vfs_info__stdio(pVFS, file, pInfo); #endif + + if (result == MA_NOT_IMPLEMENTED) { + /* Not implemented. Fall back to seek/tell/seek. */ + ma_result result; + ma_int64 cursor; + ma_int64 sizeInBytes; + + result = ma_default_vfs_tell(pVFS, file, &cursor); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_default_vfs_seek(pVFS, file, 0, ma_seek_origin_end); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_default_vfs_tell(pVFS, file, &sizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pInfo->sizeInBytes = sizeInBytes; + + result = ma_default_vfs_seek(pVFS, file, cursor, ma_seek_origin_start); + if (result != MA_SUCCESS) { + return result; + } + + MA_ASSERT(result == MA_SUCCESS); + } + + return result; } @@ -48838,8 +50344,8 @@ extern "C" { #define MA_DR_WAV_STRINGIFY(x) #x #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_VERSION_MAJOR 0 -#define MA_DR_WAV_VERSION_MINOR 13 -#define MA_DR_WAV_VERSION_REVISION 18 +#define MA_DR_WAV_VERSION_MINOR 14 +#define MA_DR_WAV_VERSION_REVISION 1 #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #include #define MA_DR_WAVE_FORMAT_PCM 0x1 @@ -48855,8 +50361,9 @@ MA_API void ma_dr_wav_version(ma_uint32* pMajor, ma_uint32* pMinor, ma_uint32* p MA_API const char* ma_dr_wav_version_string(void); typedef enum { - ma_dr_wav_seek_origin_start, - ma_dr_wav_seek_origin_current + MA_DR_WAV_SEEK_SET, + MA_DR_WAV_SEEK_CUR, + MA_DR_WAV_SEEK_END } ma_dr_wav_seek_origin; typedef enum { @@ -48893,6 +50400,7 @@ MA_API ma_uint16 ma_dr_wav_fmt_get_format(const ma_dr_wav_fmt* pFMT); typedef size_t (* ma_dr_wav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); typedef size_t (* ma_dr_wav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); typedef ma_bool32 (* ma_dr_wav_seek_proc)(void* pUserData, int offset, ma_dr_wav_seek_origin origin); +typedef ma_bool32 (* ma_dr_wav_tell_proc)(void* pUserData, ma_int64* pCursor); typedef ma_uint64 (* ma_dr_wav_chunk_proc)(void* pChunkUserData, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pReadSeekUserData, const ma_dr_wav_chunk_header* pChunkHeader, ma_dr_wav_container container, const ma_dr_wav_fmt* pFMT); typedef struct { @@ -48937,6 +50445,11 @@ typedef enum ma_dr_wav_metadata_type_list_info_genre = 1 << 15, ma_dr_wav_metadata_type_list_info_album = 1 << 16, ma_dr_wav_metadata_type_list_info_tracknumber = 1 << 17, + ma_dr_wav_metadata_type_list_info_location = 1 << 18, + ma_dr_wav_metadata_type_list_info_organization = 1 << 19, + ma_dr_wav_metadata_type_list_info_keywords = 1 << 20, + ma_dr_wav_metadata_type_list_info_medium = 1 << 21, + ma_dr_wav_metadata_type_list_info_description = 1 << 22, ma_dr_wav_metadata_type_list_all_info_strings = ma_dr_wav_metadata_type_list_info_software | ma_dr_wav_metadata_type_list_info_copyright | ma_dr_wav_metadata_type_list_info_title @@ -48945,7 +50458,12 @@ typedef enum | ma_dr_wav_metadata_type_list_info_date | ma_dr_wav_metadata_type_list_info_genre | ma_dr_wav_metadata_type_list_info_album - | ma_dr_wav_metadata_type_list_info_tracknumber, + | ma_dr_wav_metadata_type_list_info_tracknumber + | ma_dr_wav_metadata_type_list_info_location + | ma_dr_wav_metadata_type_list_info_organization + | ma_dr_wav_metadata_type_list_info_keywords + | ma_dr_wav_metadata_type_list_info_medium + | ma_dr_wav_metadata_type_list_info_description, ma_dr_wav_metadata_type_list_all_adtl = ma_dr_wav_metadata_type_list_label | ma_dr_wav_metadata_type_list_note | ma_dr_wav_metadata_type_list_labelled_cue_region, @@ -48962,8 +50480,8 @@ typedef struct { ma_uint32 cuePointId; ma_uint32 type; - ma_uint32 firstSampleByteOffset; - ma_uint32 lastSampleByteOffset; + ma_uint32 firstSampleOffset; + ma_uint32 lastSampleOffset; ma_uint32 sampleFraction; ma_uint32 playCount; } ma_dr_wav_smpl_loop; @@ -48998,7 +50516,7 @@ typedef struct ma_uint8 dataChunkId[4]; ma_uint32 chunkStart; ma_uint32 blockStart; - ma_uint32 sampleByteOffset; + ma_uint32 sampleOffset; } ma_dr_wav_cue_point; typedef struct { @@ -49100,6 +50618,7 @@ typedef struct ma_dr_wav_read_proc onRead; ma_dr_wav_write_proc onWrite; ma_dr_wav_seek_proc onSeek; + ma_dr_wav_tell_proc onTell; void* pUserData; ma_allocation_callbacks allocationCallbacks; ma_dr_wav_container container; @@ -49142,9 +50661,9 @@ typedef struct ma_bool8 isUnsigned; } aiff; } ma_dr_wav; -MA_API ma_bool32 ma_dr_wav_init(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_bool32 ma_dr_wav_init_ex(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_bool32 ma_dr_wav_init_with_metadata(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_bool32 ma_dr_wav_init(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_bool32 ma_dr_wav_init_ex(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, ma_dr_wav_chunk_proc onChunk, void* pReadSeekTellUserData, void* pChunkUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_bool32 ma_dr_wav_init_with_metadata(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_bool32 ma_dr_wav_init_write(ma_dr_wav* pWav, const ma_dr_wav_data_format* pFormat, ma_dr_wav_write_proc onWrite, ma_dr_wav_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_bool32 ma_dr_wav_init_write_sequential(ma_dr_wav* pWav, const ma_dr_wav_data_format* pFormat, ma_uint64 totalSampleCount, ma_dr_wav_write_proc onWrite, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_bool32 ma_dr_wav_init_write_sequential_pcm_frames(ma_dr_wav* pWav, const ma_dr_wav_data_format* pFormat, ma_uint64 totalPCMFrameCount, ma_dr_wav_write_proc onWrite, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); @@ -49216,9 +50735,9 @@ MA_API ma_bool32 ma_dr_wav_init_memory_write(ma_dr_wav* pWav, void** ppData, siz MA_API ma_bool32 ma_dr_wav_init_memory_write_sequential(ma_dr_wav* pWav, void** ppData, size_t* pDataSize, const ma_dr_wav_data_format* pFormat, ma_uint64 totalSampleCount, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_bool32 ma_dr_wav_init_memory_write_sequential_pcm_frames(ma_dr_wav* pWav, void** ppData, size_t* pDataSize, const ma_dr_wav_data_format* pFormat, ma_uint64 totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); #ifndef MA_DR_WAV_NO_CONVERSION_API -MA_API ma_int16* ma_dr_wav_open_and_read_pcm_frames_s16(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API float* ma_dr_wav_open_and_read_pcm_frames_f32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_int32* ma_dr_wav_open_and_read_pcm_frames_s32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_int16* ma_dr_wav_open_and_read_pcm_frames_s16(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API float* ma_dr_wav_open_and_read_pcm_frames_f32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_int32* ma_dr_wav_open_and_read_pcm_frames_s32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); #ifndef MA_DR_WAV_NO_STDIO MA_API ma_int16* ma_dr_wav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); MA_API float* ma_dr_wav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks); @@ -49258,8 +50777,8 @@ extern "C" { #define MA_DR_FLAC_STRINGIFY(x) #x #define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x) #define MA_DR_FLAC_VERSION_MAJOR 0 -#define MA_DR_FLAC_VERSION_MINOR 12 -#define MA_DR_FLAC_VERSION_REVISION 43 +#define MA_DR_FLAC_VERSION_MINOR 13 +#define MA_DR_FLAC_VERSION_REVISION 1 #define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION) #include #if defined(_MSC_VER) && _MSC_VER >= 1700 @@ -49322,8 +50841,9 @@ typedef enum } ma_dr_flac_container; typedef enum { - ma_dr_flac_seek_origin_start, - ma_dr_flac_seek_origin_current + MA_DR_FLAC_SEEK_SET, + MA_DR_FLAC_SEEK_CUR, + MA_DR_FLAC_SEEK_END } ma_dr_flac_seek_origin; typedef struct { @@ -49399,6 +50919,7 @@ typedef struct } ma_dr_flac_metadata; typedef size_t (* ma_dr_flac_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); typedef ma_bool32 (* ma_dr_flac_seek_proc)(void* pUserData, int offset, ma_dr_flac_seek_origin origin); +typedef ma_bool32 (* ma_dr_flac_tell_proc)(void* pUserData, ma_int64* pCursor); typedef void (* ma_dr_flac_meta_proc)(void* pUserData, ma_dr_flac_metadata* pMetadata); typedef struct { @@ -49410,6 +50931,7 @@ typedef struct { ma_dr_flac_read_proc onRead; ma_dr_flac_seek_proc onSeek; + ma_dr_flac_tell_proc onTell; void* pUserData; size_t unalignedByteCount; ma_dr_flac_cache_t unalignedCache; @@ -49469,10 +50991,10 @@ typedef struct ma_dr_flac_bs bs; ma_uint8 pExtraData[1]; } ma_dr_flac; -MA_API ma_dr_flac* ma_dr_flac_open(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_dr_flac* ma_dr_flac_open_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_dr_flac* ma_dr_flac_open_with_metadata(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_meta_proc onMeta, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_dr_flac* ma_dr_flac_open_with_metadata_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_dr_flac* ma_dr_flac_open(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_dr_flac* ma_dr_flac_open_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_dr_flac* ma_dr_flac_open_with_metadata(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_dr_flac* ma_dr_flac_open_with_metadata_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); MA_API void ma_dr_flac_close(ma_dr_flac* pFlac); MA_API ma_uint64 ma_dr_flac_read_pcm_frames_s32(ma_dr_flac* pFlac, ma_uint64 framesToRead, ma_int32* pBufferOut); MA_API ma_uint64 ma_dr_flac_read_pcm_frames_s16(ma_dr_flac* pFlac, ma_uint64 framesToRead, ma_int16* pBufferOut); @@ -49486,9 +51008,9 @@ MA_API ma_dr_flac* ma_dr_flac_open_file_with_metadata_w(const wchar_t* pFileName #endif MA_API ma_dr_flac* ma_dr_flac_open_memory(const void* pData, size_t dataSize, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_dr_flac* ma_dr_flac_open_memory_with_metadata(const void* pData, size_t dataSize, ma_dr_flac_meta_proc onMeta, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_int32* ma_dr_flac_open_and_read_pcm_frames_s32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_int16* ma_dr_flac_open_and_read_pcm_frames_s16(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API float* ma_dr_flac_open_and_read_pcm_frames_f32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_int32* ma_dr_flac_open_and_read_pcm_frames_s32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_int16* ma_dr_flac_open_and_read_pcm_frames_s16(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API float* ma_dr_flac_open_and_read_pcm_frames_f32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); #ifndef MA_DR_FLAC_NO_STDIO MA_API ma_int32* ma_dr_flac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_int16* ma_dr_flac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, ma_uint64* totalPCMFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); @@ -49536,6 +51058,12 @@ MA_API ma_bool32 ma_dr_flac_next_cuesheet_track(ma_dr_flac_cuesheet_track_iterat #endif /* MA_NO_FLAC */ #if !defined(MA_NO_MP3) && !defined(MA_NO_DECODING) +#ifndef MA_DR_MP3_NO_SIMD + #if (defined(MA_NO_NEON) && defined(MA_ARM)) || (defined(MA_NO_SSE2) && (defined(MA_X86) || defined(MA_X64))) + #define MA_DR_MP3_NO_SIMD + #endif +#endif + /* dr_mp3_h begin */ #ifndef ma_dr_mp3_h #define ma_dr_mp3_h @@ -49545,8 +51073,8 @@ extern "C" { #define MA_DR_MP3_STRINGIFY(x) #x #define MA_DR_MP3_XSTRINGIFY(x) MA_DR_MP3_STRINGIFY(x) #define MA_DR_MP3_VERSION_MAJOR 0 -#define MA_DR_MP3_VERSION_MINOR 6 -#define MA_DR_MP3_VERSION_REVISION 40 +#define MA_DR_MP3_VERSION_MINOR 7 +#define MA_DR_MP3_VERSION_REVISION 1 #define MA_DR_MP3_VERSION_STRING MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MAJOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MINOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_REVISION) #include #define MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 @@ -49555,7 +51083,7 @@ MA_API void ma_dr_mp3_version(ma_uint32* pMajor, ma_uint32* pMinor, ma_uint32* p MA_API const char* ma_dr_mp3_version_string(void); typedef struct { - int frame_bytes, channels, hz, layer, bitrate_kbps; + int frame_bytes, channels, sample_rate, layer, bitrate_kbps; } ma_dr_mp3dec_frame_info; typedef struct { @@ -49568,8 +51096,9 @@ MA_API int ma_dr_mp3dec_decode_frame(ma_dr_mp3dec *dec, const ma_uint8 *mp3, int MA_API void ma_dr_mp3dec_f32_to_s16(const float *in, ma_int16 *out, size_t num_samples); typedef enum { - ma_dr_mp3_seek_origin_start, - ma_dr_mp3_seek_origin_current + MA_DR_MP3_SEEK_SET, + MA_DR_MP3_SEEK_CUR, + MA_DR_MP3_SEEK_END } ma_dr_mp3_seek_origin; typedef struct { @@ -49578,8 +51107,24 @@ typedef struct ma_uint16 mp3FramesToDiscard; ma_uint16 pcmFramesToDiscard; } ma_dr_mp3_seek_point; +typedef enum +{ + MA_DR_MP3_METADATA_TYPE_ID3V1, + MA_DR_MP3_METADATA_TYPE_ID3V2, + MA_DR_MP3_METADATA_TYPE_APE, + MA_DR_MP3_METADATA_TYPE_XING, + MA_DR_MP3_METADATA_TYPE_VBRI +} ma_dr_mp3_metadata_type; +typedef struct +{ + ma_dr_mp3_metadata_type type; + const void* pRawData; + size_t rawDataSize; +} ma_dr_mp3_metadata; typedef size_t (* ma_dr_mp3_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); typedef ma_bool32 (* ma_dr_mp3_seek_proc)(void* pUserData, int offset, ma_dr_mp3_seek_origin origin); +typedef ma_bool32 (* ma_dr_mp3_tell_proc)(void* pUserData, ma_int64* pCursor); +typedef void (* ma_dr_mp3_meta_proc)(void* pUserData, const ma_dr_mp3_metadata* pMetadata); typedef struct { ma_uint32 channels; @@ -49592,7 +51137,9 @@ typedef struct ma_uint32 sampleRate; ma_dr_mp3_read_proc onRead; ma_dr_mp3_seek_proc onSeek; + ma_dr_mp3_meta_proc onMeta; void* pUserData; + void* pUserDataMeta; ma_allocation_callbacks allocationCallbacks; ma_uint32 mp3FrameChannels; ma_uint32 mp3FrameSampleRate; @@ -49601,13 +51148,20 @@ typedef struct ma_uint8 pcmFrames[sizeof(float)*MA_DR_MP3_MAX_SAMPLES_PER_FRAME]; ma_uint64 currentPCMFrame; ma_uint64 streamCursor; + ma_uint64 streamLength; + ma_uint64 streamStartOffset; ma_dr_mp3_seek_point* pSeekPoints; ma_uint32 seekPointCount; + ma_uint32 delayInPCMFrames; + ma_uint32 paddingInPCMFrames; + ma_uint64 totalPCMFrameCount; + ma_bool32 isVBR; + ma_bool32 isCBR; size_t dataSize; size_t dataCapacity; size_t dataConsumed; ma_uint8* pData; - ma_bool32 atEnd : 1; + ma_bool32 atEnd; struct { const ma_uint8* pData; @@ -49615,9 +51169,12 @@ typedef struct size_t currentReadPos; } memory; } ma_dr_mp3; -MA_API ma_bool32 ma_dr_mp3_init(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_bool32 ma_dr_mp3_init(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, ma_dr_mp3_tell_proc onTell, ma_dr_mp3_meta_proc onMeta, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_bool32 ma_dr_mp3_init_memory_with_metadata(ma_dr_mp3* pMP3, const void* pData, size_t dataSize, ma_dr_mp3_meta_proc onMeta, void* pUserDataMeta, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_bool32 ma_dr_mp3_init_memory(ma_dr_mp3* pMP3, const void* pData, size_t dataSize, const ma_allocation_callbacks* pAllocationCallbacks); #ifndef MA_DR_MP3_NO_STDIO +MA_API ma_bool32 ma_dr_mp3_init_file_with_metadata(ma_dr_mp3* pMP3, const char* pFilePath, ma_dr_mp3_meta_proc onMeta, void* pUserDataMeta, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_bool32 ma_dr_mp3_init_file_with_metadata_w(ma_dr_mp3* pMP3, const wchar_t* pFilePath, ma_dr_mp3_meta_proc onMeta, void* pUserDataMeta, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_bool32 ma_dr_mp3_init_file(ma_dr_mp3* pMP3, const char* pFilePath, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_bool32 ma_dr_mp3_init_file_w(ma_dr_mp3* pMP3, const wchar_t* pFilePath, const ma_allocation_callbacks* pAllocationCallbacks); #endif @@ -49630,8 +51187,8 @@ MA_API ma_uint64 ma_dr_mp3_get_mp3_frame_count(ma_dr_mp3* pMP3); MA_API ma_bool32 ma_dr_mp3_get_mp3_and_pcm_frame_count(ma_dr_mp3* pMP3, ma_uint64* pMP3FrameCount, ma_uint64* pPCMFrameCount); MA_API ma_bool32 ma_dr_mp3_calculate_seek_points(ma_dr_mp3* pMP3, ma_uint32* pSeekPointCount, ma_dr_mp3_seek_point* pSeekPoints); MA_API ma_bool32 ma_dr_mp3_bind_seek_table(ma_dr_mp3* pMP3, ma_uint32 seekPointCount, ma_dr_mp3_seek_point* pSeekPoints); -MA_API float* ma_dr_mp3_open_and_read_pcm_frames_f32(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_int16* ma_dr_mp3_open_and_read_pcm_frames_s16(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API float* ma_dr_mp3_open_and_read_pcm_frames_f32(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, ma_dr_mp3_tell_proc onTell, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_int16* ma_dr_mp3_open_and_read_pcm_frames_s16(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, ma_dr_mp3_tell_proc onTell, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); MA_API float* ma_dr_mp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_int16* ma_dr_mp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks); #ifndef MA_DR_MP3_NO_STDIO @@ -50184,8 +51741,10 @@ static ma_bool32 ma_wav_dr_callback__seek(void* pUserData, int offset, ma_dr_wav MA_ASSERT(pWav != NULL); maSeekOrigin = ma_seek_origin_start; - if (origin == ma_dr_wav_seek_origin_current) { - maSeekOrigin = ma_seek_origin_current; + if (origin == MA_DR_WAV_SEEK_CUR) { + maSeekOrigin = ma_seek_origin_current; + } else if (origin == MA_DR_WAV_SEEK_END) { + maSeekOrigin = ma_seek_origin_end; } result = pWav->onSeek(pWav->pReadSeekTellUserData, offset, maSeekOrigin); @@ -50195,6 +51754,26 @@ static ma_bool32 ma_wav_dr_callback__seek(void* pUserData, int offset, ma_dr_wav return MA_TRUE; } + +static ma_bool32 ma_wav_dr_callback__tell(void* pUserData, ma_int64* pCursor) +{ + ma_wav* pWav = (ma_wav*)pUserData; + ma_result result; + + MA_ASSERT(pWav != NULL); + MA_ASSERT(pCursor != NULL); + + if (pWav->onTell == NULL) { + return MA_FALSE; /* Not implemented. */ + } + + result = pWav->onTell(pWav->pReadSeekTellUserData, pCursor); + if (result != MA_SUCCESS) { + return MA_FALSE; /* Failed to tell. */ + } + + return MA_TRUE; +} #endif static ma_result ma_wav_init_internal(const ma_decoding_backend_config* pConfig, ma_wav* pWav) @@ -50289,7 +51868,7 @@ MA_API ma_result ma_wav_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p { ma_bool32 wavResult; - wavResult = ma_dr_wav_init(&pWav->dr, ma_wav_dr_callback__read, ma_wav_dr_callback__seek, pWav, pAllocationCallbacks); + wavResult = ma_dr_wav_init(&pWav->dr, ma_wav_dr_callback__read, ma_wav_dr_callback__seek, ma_wav_dr_callback__tell, pWav, pAllocationCallbacks); if (wavResult != MA_TRUE) { return MA_INVALID_FILE; } @@ -50868,8 +52447,10 @@ static ma_bool32 ma_flac_dr_callback__seek(void* pUserData, int offset, ma_dr_fl MA_ASSERT(pFlac != NULL); maSeekOrigin = ma_seek_origin_start; - if (origin == ma_dr_flac_seek_origin_current) { - maSeekOrigin = ma_seek_origin_current; + if (origin == MA_DR_FLAC_SEEK_CUR) { + maSeekOrigin = ma_seek_origin_current; + } else if (origin == MA_DR_FLAC_SEEK_END) { + maSeekOrigin = ma_seek_origin_end; } result = pFlac->onSeek(pFlac->pReadSeekTellUserData, offset, maSeekOrigin); @@ -50879,6 +52460,26 @@ static ma_bool32 ma_flac_dr_callback__seek(void* pUserData, int offset, ma_dr_fl return MA_TRUE; } + +static ma_bool32 ma_flac_dr_callback__tell(void* pUserData, ma_int64* pCursor) +{ + ma_flac* pFlac = (ma_flac*)pUserData; + ma_result result; + + MA_ASSERT(pFlac != NULL); + MA_ASSERT(pCursor != NULL); + + if (pFlac->onTell == NULL) { + return MA_FALSE; /* Not implemented. */ + } + + result = pFlac->onTell(pFlac->pReadSeekTellUserData, pCursor); + if (result != MA_SUCCESS) { + return MA_FALSE; /* Failed to tell. */ + } + + return MA_TRUE; +} #endif static ma_result ma_flac_init_internal(const ma_decoding_backend_config* pConfig, ma_flac* pFlac) @@ -50930,7 +52531,7 @@ MA_API ma_result ma_flac_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_ #if !defined(MA_NO_FLAC) { - pFlac->dr = ma_dr_flac_open(ma_flac_dr_callback__read, ma_flac_dr_callback__seek, pFlac, pAllocationCallbacks); + pFlac->dr = ma_dr_flac_open(ma_flac_dr_callback__read, ma_flac_dr_callback__seek, ma_flac_dr_callback__tell, pFlac, pAllocationCallbacks); if (pFlac->dr == NULL) { return MA_INVALID_FILE; } @@ -51491,9 +53092,12 @@ static ma_bool32 ma_mp3_dr_callback__seek(void* pUserData, int offset, ma_dr_mp3 MA_ASSERT(pMP3 != NULL); - maSeekOrigin = ma_seek_origin_start; - if (origin == ma_dr_mp3_seek_origin_current) { - maSeekOrigin = ma_seek_origin_current; + if (origin == MA_DR_MP3_SEEK_SET) { + maSeekOrigin = ma_seek_origin_start; + } else if (origin == MA_DR_MP3_SEEK_END) { + maSeekOrigin = ma_seek_origin_end; + } else { + maSeekOrigin = ma_seek_origin_current; } result = pMP3->onSeek(pMP3->pReadSeekTellUserData, offset, maSeekOrigin); @@ -51503,6 +53107,21 @@ static ma_bool32 ma_mp3_dr_callback__seek(void* pUserData, int offset, ma_dr_mp3 return MA_TRUE; } + +static ma_bool32 ma_mp3_dr_callback__tell(void* pUserData, ma_int64* pCursor) +{ + ma_mp3* pMP3 = (ma_mp3*)pUserData; + ma_result result; + + MA_ASSERT(pMP3 != NULL); + + result = pMP3->onTell(pMP3->pReadSeekTellUserData, pCursor); + if (result != MA_SUCCESS) { + return MA_FALSE; + } + + return MA_TRUE; +} #endif static ma_result ma_mp3_init_internal(const ma_decoding_backend_config* pConfig, ma_mp3* pMP3) @@ -51603,7 +53222,7 @@ MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p { ma_bool32 mp3Result; - mp3Result = ma_dr_mp3_init(&pMP3->dr, ma_mp3_dr_callback__read, ma_mp3_dr_callback__seek, pMP3, pAllocationCallbacks); + mp3Result = ma_dr_mp3_init(&pMP3->dr, ma_mp3_dr_callback__read, ma_mp3_dr_callback__seek, ma_mp3_dr_callback__tell, NULL, pMP3, pAllocationCallbacks); if (mp3Result != MA_TRUE) { return MA_INVALID_FILE; } @@ -53502,14 +55121,16 @@ static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* e ext1 = extension; ext2 = ma_path_extension_w(path); -#if defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__) - return _wcsicmp(ext1, ext2) == 0; -#else - /* - I'm not aware of a wide character version of strcasecmp(). I'm therefore converting the extensions to multibyte strings and comparing those. This - isn't the most efficient way to do it, but it should work OK. - */ + #if (defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__)) && !defined(MA_XBOX_NXDK) { + return _wcsicmp(ext1, ext2) == 0; + } + #elif !defined(MA_XBOX_NXDK) && !defined(MA_DOS) + { + /* + I'm not aware of a wide character version of strcasecmp(). I'm therefore converting the extensions to multibyte strings and comparing those. This + isn't the most efficient way to do it, but it should work OK. + */ char ext1MB[4096]; char ext2MB[4096]; const wchar_t* pext1 = ext1; @@ -53529,7 +55150,13 @@ static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* e return strcasecmp(ext1MB, ext2MB) == 0; } -#endif + #else + { + /* Getting here means we don't have a way to do a case-sensitive comparison for wide strings. Fall back to a simple case-sensitive comparison. */ + /* TODO: Implement our own wchar_t-to-char conversion routine and then use the char* version for comparing. */ + return ma_wcscmp(ext1, ext2) == 0; + } + #endif } #endif /* MA_HAS_PATH_API */ @@ -54624,10 +56251,18 @@ static ma_bool32 ma_encoder__internal_on_seek_wav(void* pUserData, int offset, m { ma_encoder* pEncoder = (ma_encoder*)pUserData; ma_result result; + ma_seek_origin maSeekOrigin; MA_ASSERT(pEncoder != NULL); - result = pEncoder->onSeek(pEncoder, offset, (origin == ma_dr_wav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + maSeekOrigin = ma_seek_origin_start; + if (origin == MA_DR_WAV_SEEK_CUR) { + maSeekOrigin = ma_seek_origin_current; + } else if (origin == MA_DR_WAV_SEEK_END) { + maSeekOrigin = ma_seek_origin_end; + } + + result = pEncoder->onSeek(pEncoder, offset, maSeekOrigin); if (result != MA_SUCCESS) { return MA_FALSE; } else { @@ -56149,7 +57784,7 @@ static MA_INLINE ma_uint32 ma_hash_getblock(const ma_uint32* blocks, int i) ma_uint32 block; /* Try silencing a sanitization warning about unaligned access by doing a memcpy() instead of assignment. */ - MA_COPY_MEMORY(&block, ma_offset_ptr(blocks, i * sizeof(block)), sizeof(block)); + MA_COPY_MEMORY(&block, ma_offset_ptr(blocks, i * (int) sizeof(block)), sizeof(block)); if (ma_is_little_endian()) { return block; @@ -56225,7 +57860,7 @@ static ma_uint32 ma_hash_string_32(const char* str) static ma_uint32 ma_hash_string_w_32(const wchar_t* str) { - return ma_hash_32(str, (int)wcslen(str) * sizeof(*str), MA_DEFAULT_HASH_SEED); + return ma_hash_32(str, (int)ma_wcslen(str) * sizeof(*str), MA_DEFAULT_HASH_SEED); } @@ -56385,6 +58020,7 @@ static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_ return ma_resource_manager_data_buffer_node_find_min(pDataBufferNode->pChildHi); } +#if 0 /* Currently unused, but might make use of this later. */ static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_predecessor(ma_resource_manager_data_buffer_node* pDataBufferNode) { MA_ASSERT(pDataBufferNode != NULL); @@ -56392,6 +58028,7 @@ static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_ return ma_resource_manager_data_buffer_node_find_max(pDataBufferNode->pChildLo); } +#endif static ma_result ma_resource_manager_data_buffer_node_remove(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) { @@ -57514,16 +59151,19 @@ static ma_result ma_resource_manager_data_buffer_node_acquire_critical_section(m /* Failed to post job. Probably ran out of memory. */ ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); - /* - Fences were acquired before posting the job, but since the job was not able to - be posted, we need to make sure we release them so nothing gets stuck waiting. - */ - if (pInitFence != NULL) { ma_fence_release(pInitFence); } - if (pDoneFence != NULL) { ma_fence_release(pDoneFence); } - if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { ma_resource_manager_inline_notification_uninit(pInitNotification); } else { + /* + Fences were acquired before posting the job, but since the job was not able to + be posted, we need to make sure we release them so nothing gets stuck waiting. + + In the WAIT_INIT case, these will have already been released in ma_job_process() + so we should only release fences in this branch. + */ + if (pInitFence != NULL) { ma_fence_release(pInitFence); } + if (pDoneFence != NULL) { ma_fence_release(pDoneFence); } + /* These will have been freed by the job thread, but with WAIT_INIT they will already have happened since the job has already been handled. */ ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); @@ -65179,7 +66819,7 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con } if (pConfig->loopPointBegInPCMFrames != 0 || pConfig->loopPointEndInPCMFrames != ~((ma_uint64)0)) { - ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + ma_data_source_set_loop_point_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); } ma_sound_set_looping(pSound, pConfig->isLooping || ((pConfig->flags & MA_SOUND_FLAG_LOOPING) != 0)); @@ -65241,6 +66881,7 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, pSound->pResourceManagerDataSource); if (result != MA_SUCCESS) { + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); goto done; } @@ -66046,7 +67687,12 @@ MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound) MA_API ma_uint64 ma_sound_get_time_in_milliseconds(const ma_sound* pSound) { - return ma_sound_get_time_in_pcm_frames(pSound) * 1000 / ma_engine_get_sample_rate(ma_sound_get_engine(pSound)); + ma_uint32 sampleRate = ma_engine_get_sample_rate(ma_sound_get_engine(pSound)); + if (sampleRate == 0) { + return 0; /* Prevent a division by zero. */ + } + + return ma_sound_get_time_in_pcm_frames(pSound) * 1000 / sampleRate; } MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping) @@ -66130,7 +67776,7 @@ MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeco return ma_sound_seek_to_pcm_frame(pSound, frameIndex); } -MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +MA_API ma_result ma_sound_get_data_format(const ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { if (pSound == NULL) { return MA_INVALID_ARGS; @@ -66163,7 +67809,7 @@ MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, } } -MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor) +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(const ma_sound* pSound, ma_uint64* pCursor) { ma_uint64 seekTarget; @@ -66185,7 +67831,7 @@ MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* } } -MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength) +MA_API ma_result ma_sound_get_length_in_pcm_frames(const ma_sound* pSound, ma_uint64* pLength) { if (pSound == NULL) { return MA_INVALID_ARGS; @@ -66199,7 +67845,7 @@ MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* return ma_data_source_get_length_in_pcm_frames(pSound->pDataSource, pLength); } -MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor) +MA_API ma_result ma_sound_get_cursor_in_seconds(const ma_sound* pSound, float* pCursor) { ma_result result; ma_uint64 cursorInPCMFrames; @@ -66225,7 +67871,7 @@ MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor return MA_SUCCESS; } -MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength) +MA_API ma_result ma_sound_get_length_in_seconds(const ma_sound* pSound, float* pLength) { if (pSound == NULL) { return MA_INVALID_ARGS; @@ -67044,12 +68690,12 @@ MA_PRIVATE ma_bool32 ma_dr_wav__seek_forward(ma_dr_wav_seek_proc onSeek, ma_uint ma_uint64 bytesRemainingToSeek = offset; while (bytesRemainingToSeek > 0) { if (bytesRemainingToSeek > 0x7FFFFFFF) { - if (!onSeek(pUserData, 0x7FFFFFFF, ma_dr_wav_seek_origin_current)) { + if (!onSeek(pUserData, 0x7FFFFFFF, MA_DR_WAV_SEEK_CUR)) { return MA_FALSE; } bytesRemainingToSeek -= 0x7FFFFFFF; } else { - if (!onSeek(pUserData, (int)bytesRemainingToSeek, ma_dr_wav_seek_origin_current)) { + if (!onSeek(pUserData, (int)bytesRemainingToSeek, MA_DR_WAV_SEEK_CUR)) { return MA_FALSE; } bytesRemainingToSeek = 0; @@ -67060,17 +68706,17 @@ MA_PRIVATE ma_bool32 ma_dr_wav__seek_forward(ma_dr_wav_seek_proc onSeek, ma_uint MA_PRIVATE ma_bool32 ma_dr_wav__seek_from_start(ma_dr_wav_seek_proc onSeek, ma_uint64 offset, void* pUserData) { if (offset <= 0x7FFFFFFF) { - return onSeek(pUserData, (int)offset, ma_dr_wav_seek_origin_start); + return onSeek(pUserData, (int)offset, MA_DR_WAV_SEEK_SET); } - if (!onSeek(pUserData, 0x7FFFFFFF, ma_dr_wav_seek_origin_start)) { + if (!onSeek(pUserData, 0x7FFFFFFF, MA_DR_WAV_SEEK_SET)) { return MA_FALSE; } offset -= 0x7FFFFFFF; for (;;) { if (offset <= 0x7FFFFFFF) { - return onSeek(pUserData, (int)offset, ma_dr_wav_seek_origin_current); + return onSeek(pUserData, (int)offset, MA_DR_WAV_SEEK_CUR); } - if (!onSeek(pUserData, 0x7FFFFFFF, ma_dr_wav_seek_origin_current)) { + if (!onSeek(pUserData, 0x7FFFFFFF, MA_DR_WAV_SEEK_CUR)) { return MA_FALSE; } offset -= 0x7FFFFFFF; @@ -67093,7 +68739,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav__on_seek(ma_dr_wav_seek_proc onSeek, void* pUserD if (!onSeek(pUserData, offset, origin)) { return MA_FALSE; } - if (origin == ma_dr_wav_seek_origin_start) { + if (origin == MA_DR_WAV_SEEK_SET) { *pCursor = offset; } else { *pCursor += offset; @@ -67212,12 +68858,12 @@ MA_PRIVATE ma_uint64 ma_dr_wav__read_smpl_to_metadata_obj(ma_dr_wav__metadata_pa ma_uint8 smplLoopData[MA_DR_WAV_SMPL_LOOP_BYTES]; bytesJustRead = ma_dr_wav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); if (bytesJustRead == sizeof(smplLoopData)) { - pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = ma_dr_wav_bytes_to_u32(smplLoopData + 0); - pMetadata->data.smpl.pLoops[iSampleLoop].type = ma_dr_wav_bytes_to_u32(smplLoopData + 4); - pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = ma_dr_wav_bytes_to_u32(smplLoopData + 8); - pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = ma_dr_wav_bytes_to_u32(smplLoopData + 12); - pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = ma_dr_wav_bytes_to_u32(smplLoopData + 16); - pMetadata->data.smpl.pLoops[iSampleLoop].playCount = ma_dr_wav_bytes_to_u32(smplLoopData + 20); + pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = ma_dr_wav_bytes_to_u32(smplLoopData + 0); + pMetadata->data.smpl.pLoops[iSampleLoop].type = ma_dr_wav_bytes_to_u32(smplLoopData + 4); + pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleOffset = ma_dr_wav_bytes_to_u32(smplLoopData + 8); + pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleOffset = ma_dr_wav_bytes_to_u32(smplLoopData + 12); + pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = ma_dr_wav_bytes_to_u32(smplLoopData + 16); + pMetadata->data.smpl.pLoops[iSampleLoop].playCount = ma_dr_wav_bytes_to_u32(smplLoopData + 20); } else { break; } @@ -67261,7 +68907,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav__read_cue_to_metadata_obj(ma_dr_wav__metadata_par pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = ma_dr_wav_bytes_to_u32(cuePointData + 12); pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = ma_dr_wav_bytes_to_u32(cuePointData + 16); - pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = ma_dr_wav_bytes_to_u32(cuePointData + 20); + pMetadata->data.cue.pCuePoints[iCuePoint].sampleOffset = ma_dr_wav_bytes_to_u32(cuePointData + 20); } else { break; } @@ -67601,7 +69247,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav__metadata_process_chunk(ma_dr_wav__metadata_parse if (pParser->stage == ma_dr_wav__metadata_parser_stage_count) { ma_uint8 buffer[4]; size_t bytesJustRead; - if (!pParser->onSeek(pParser->pReadSeekUserData, 28, ma_dr_wav_seek_origin_current)) { + if (!pParser->onSeek(pParser->pReadSeekUserData, 28, MA_DR_WAV_SEEK_CUR)) { return bytesRead; } bytesRead += 28; @@ -67696,7 +69342,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav__metadata_process_chunk(ma_dr_wav__metadata_parse return bytesRead; } allocSizeNeeded += ma_dr_wav__strlen(buffer) + 1; - allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - MA_DR_WAV_BEXT_BYTES; + allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - MA_DR_WAV_BEXT_BYTES + 1; ma_dr_wav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); pParser->metadataCount += 1; } else { @@ -67779,6 +69425,16 @@ MA_PRIVATE ma_uint64 ma_dr_wav__metadata_process_chunk(ma_dr_wav__metadata_parse subchunkBytesRead = ma_dr_wav__metadata_process_info_text_chunk(pParser, subchunkDataSize, ma_dr_wav_metadata_type_list_info_album); } else if (ma_dr_wav__chunk_matches(allowedMetadataTypes, subchunkId, ma_dr_wav_metadata_type_list_info_tracknumber, "ITRK")) { subchunkBytesRead = ma_dr_wav__metadata_process_info_text_chunk(pParser, subchunkDataSize, ma_dr_wav_metadata_type_list_info_tracknumber); + } else if (ma_dr_wav__chunk_matches(allowedMetadataTypes, subchunkId, ma_dr_wav_metadata_type_list_info_location, "IARL")) { + subchunkBytesRead = ma_dr_wav__metadata_process_info_text_chunk(pParser, subchunkDataSize, ma_dr_wav_metadata_type_list_info_location); + } else if (ma_dr_wav__chunk_matches(allowedMetadataTypes, subchunkId, ma_dr_wav_metadata_type_list_info_organization, "ICMS")) { + subchunkBytesRead = ma_dr_wav__metadata_process_info_text_chunk(pParser, subchunkDataSize, ma_dr_wav_metadata_type_list_info_organization); + } else if (ma_dr_wav__chunk_matches(allowedMetadataTypes, subchunkId, ma_dr_wav_metadata_type_list_info_keywords, "IKEY")) { + subchunkBytesRead = ma_dr_wav__metadata_process_info_text_chunk(pParser, subchunkDataSize, ma_dr_wav_metadata_type_list_info_keywords); + } else if (ma_dr_wav__chunk_matches(allowedMetadataTypes, subchunkId, ma_dr_wav_metadata_type_list_info_medium, "IMED")) { + subchunkBytesRead = ma_dr_wav__metadata_process_info_text_chunk(pParser, subchunkDataSize, ma_dr_wav_metadata_type_list_info_medium); + } else if (ma_dr_wav__chunk_matches(allowedMetadataTypes, subchunkId, ma_dr_wav_metadata_type_list_info_description, "ISBJ")) { + subchunkBytesRead = ma_dr_wav__metadata_process_info_text_chunk(pParser, subchunkDataSize, ma_dr_wav_metadata_type_list_info_description); } else if ((allowedMetadataTypes & ma_dr_wav_metadata_type_unknown) != 0) { subchunkBytesRead = ma_dr_wav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); } @@ -67786,13 +69442,13 @@ MA_PRIVATE ma_uint64 ma_dr_wav__metadata_process_chunk(ma_dr_wav__metadata_parse MA_DR_WAV_ASSERT(subchunkBytesRead <= subchunkDataSize); if (subchunkBytesRead < subchunkDataSize) { ma_uint64 bytesToSeek = subchunkDataSize - subchunkBytesRead; - if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, ma_dr_wav_seek_origin_current)) { + if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, MA_DR_WAV_SEEK_CUR)) { break; } bytesRead += bytesToSeek; } if ((subchunkDataSize % 2) == 1) { - if (!pParser->onSeek(pParser->pReadSeekUserData, 1, ma_dr_wav_seek_origin_current)) { + if (!pParser->onSeek(pParser->pReadSeekUserData, 1, MA_DR_WAV_SEEK_CUR)) { break; } bytesRead += 1; @@ -67829,7 +69485,7 @@ MA_API ma_uint16 ma_dr_wav_fmt_get_format(const ma_dr_wav_fmt* pFMT) return ma_dr_wav_bytes_to_u16(pFMT->subFormat); } } -MA_PRIVATE ma_bool32 ma_dr_wav_preinit(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pReadSeekUserData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_PRIVATE ma_bool32 ma_dr_wav_preinit(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pReadSeekTellUserData, const ma_allocation_callbacks* pAllocationCallbacks) { if (pWav == NULL || onRead == NULL || onSeek == NULL) { return MA_FALSE; @@ -67837,7 +69493,8 @@ MA_PRIVATE ma_bool32 ma_dr_wav_preinit(ma_dr_wav* pWav, ma_dr_wav_read_proc onRe MA_DR_WAV_ZERO_MEMORY(pWav, sizeof(*pWav)); pWav->onRead = onRead; pWav->onSeek = onSeek; - pWav->pUserData = pReadSeekUserData; + pWav->onTell = onTell; + pWav->pUserData = pReadSeekTellUserData; pWav->allocationCallbacks = ma_dr_wav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { return MA_FALSE; @@ -68051,14 +69708,14 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p fmt.channelMask = ma_dr_wav_bytes_to_u32_ex(fmtext + 2, pWav->container); ma_dr_wav_bytes_to_guid(fmtext + 6, fmt.subFormat); } else { - if (pWav->onSeek(pWav->pUserData, fmt.extendedSize, ma_dr_wav_seek_origin_current) == MA_FALSE) { + if (pWav->onSeek(pWav->pUserData, fmt.extendedSize, MA_DR_WAV_SEEK_CUR) == MA_FALSE) { return MA_FALSE; } } cursor += fmt.extendedSize; bytesReadSoFar += fmt.extendedSize; } - if (pWav->onSeek(pWav->pUserData, (int)(header.sizeInBytes - bytesReadSoFar), ma_dr_wav_seek_origin_current) == MA_FALSE) { + if (pWav->onSeek(pWav->pUserData, (int)(header.sizeInBytes - bytesReadSoFar), MA_DR_WAV_SEEK_CUR) == MA_FALSE) { return MA_FALSE; } cursor += (header.sizeInBytes - bytesReadSoFar); @@ -68209,15 +69866,26 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p return MA_FALSE; } offset = ma_dr_wav_bytes_to_u32_ex(offsetAndBlockSizeData + 0, pWav->container); - if (ma_dr_wav__seek_forward(pWav->onSeek, offset, pWav->pUserData) == MA_FALSE) { - return MA_FALSE; - } - cursor += offset; - pWav->dataChunkDataPos = cursor; + pWav->dataChunkDataPos = cursor + offset; dataChunkSize = chunkSize; - if (sequential || !isProcessingMetadata) { - break; + if (dataChunkSize > offset) { + dataChunkSize -= offset; } else { + dataChunkSize = 0; + } + if (sequential) { + if (foundChunk_fmt) { + if (ma_dr_wav__seek_forward(pWav->onSeek, offset, pWav->pUserData) == MA_FALSE) { + return MA_FALSE; + } + cursor += offset; + break; + } else { + return MA_FALSE; + } + } else { + chunkSize += header.paddingSize; + chunkSize -= sizeof(offsetAndBlockSizeData); if (ma_dr_wav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == MA_FALSE) { break; } @@ -68281,6 +69949,17 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p pWav->pMetadata = metadataParser.pMetadata; pWav->metadataCount = metadataParser.metadataCount; } + if (pWav->onTell != NULL && pWav->onSeek != NULL) { + if (pWav->onSeek(pWav->pUserData, 0, MA_DR_WAV_SEEK_END) == MA_TRUE) { + ma_int64 fileSize; + if (pWav->onTell(pWav->pUserData, &fileSize)) { + if (dataChunkSize + pWav->dataChunkDataPos > (ma_uint64)fileSize) { + dataChunkSize = (ma_uint64)fileSize - pWav->dataChunkDataPos; + } + } + } else { + } + } if (dataChunkSize == 0xFFFFFFFF && (pWav->container == ma_dr_wav_container_riff || pWav->container == ma_dr_wav_container_rifx) && pWav->isSequentialWrite == MA_FALSE) { dataChunkSize = 0; for (;;) { @@ -68300,8 +69979,14 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p pWav->sampleRate = fmt.sampleRate; pWav->channels = fmt.channels; pWav->bitsPerSample = fmt.bitsPerSample; - pWav->bytesRemaining = dataChunkSize; pWav->translatedFormatTag = translatedFormatTag; + if (!ma_dr_wav__is_compressed_format_tag(translatedFormatTag)) { + ma_uint32 bytesPerFrame = ma_dr_wav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame > 0) { + dataChunkSize -= (dataChunkSize % bytesPerFrame); + } + } + pWav->bytesRemaining = dataChunkSize; pWav->dataChunkDataSize = dataChunkSize; if (sampleCountFromFactChunk != 0) { pWav->totalPCMFrameCount = sampleCountFromFactChunk; @@ -68356,20 +70041,20 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p #endif return MA_TRUE; } -MA_API ma_bool32 ma_dr_wav_init(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_bool32 ma_dr_wav_init(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_dr_wav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks); + return ma_dr_wav_init_ex(pWav, onRead, onSeek, onTell, NULL, pUserData, NULL, 0, pAllocationCallbacks); } -MA_API ma_bool32 ma_dr_wav_init_ex(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_bool32 ma_dr_wav_init_ex(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, ma_dr_wav_chunk_proc onChunk, void* pReadSeekTellUserData, void* pChunkUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks) { - if (!ma_dr_wav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) { + if (!ma_dr_wav_preinit(pWav, onRead, onSeek, onTell, pReadSeekTellUserData, pAllocationCallbacks)) { return MA_FALSE; } return ma_dr_wav_init__internal(pWav, onChunk, pChunkUserData, flags); } -MA_API ma_bool32 ma_dr_wav_init_with_metadata(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_bool32 ma_dr_wav_init_with_metadata(ma_dr_wav* pWav, ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks) { - if (!ma_dr_wav_preinit(pWav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!ma_dr_wav_preinit(pWav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return MA_FALSE; } return ma_dr_wav_init__internal(pWav, NULL, NULL, flags | MA_DR_WAV_WITH_METADATA); @@ -68531,8 +70216,8 @@ MA_PRIVATE size_t ma_dr_wav__write_or_count_metadata(ma_dr_wav* pWav, ma_dr_wav_ for (iLoop = 0; iLoop < pMetadata->data.smpl.sampleLoopCount; ++iLoop) { bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].cuePointId); bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].type); - bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleByteOffset); - bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleByteOffset); + bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleOffset); + bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleOffset); bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].sampleFraction); bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].playCount); } @@ -68566,7 +70251,7 @@ MA_PRIVATE size_t ma_dr_wav__write_or_count_metadata(ma_dr_wav* pWav, ma_dr_wav_ bytesWritten += ma_dr_wav__write_or_count(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId, 4); bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart); bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].blockStart); - bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset); + bytesWritten += ma_dr_wav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleOffset); } } break; case ma_dr_wav_metadata_type_acid: @@ -68652,15 +70337,20 @@ MA_PRIVATE size_t ma_dr_wav__write_or_count_metadata(ma_dr_wav* pWav, ma_dr_wav_ if (pMetadata->type & ma_dr_wav_metadata_type_list_all_info_strings) { const char* pID = NULL; switch (pMetadata->type) { - case ma_dr_wav_metadata_type_list_info_software: pID = "ISFT"; break; - case ma_dr_wav_metadata_type_list_info_copyright: pID = "ICOP"; break; - case ma_dr_wav_metadata_type_list_info_title: pID = "INAM"; break; - case ma_dr_wav_metadata_type_list_info_artist: pID = "IART"; break; - case ma_dr_wav_metadata_type_list_info_comment: pID = "ICMT"; break; - case ma_dr_wav_metadata_type_list_info_date: pID = "ICRD"; break; - case ma_dr_wav_metadata_type_list_info_genre: pID = "IGNR"; break; - case ma_dr_wav_metadata_type_list_info_album: pID = "IPRD"; break; - case ma_dr_wav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; + case ma_dr_wav_metadata_type_list_info_software: pID = "ISFT"; break; + case ma_dr_wav_metadata_type_list_info_copyright: pID = "ICOP"; break; + case ma_dr_wav_metadata_type_list_info_title: pID = "INAM"; break; + case ma_dr_wav_metadata_type_list_info_artist: pID = "IART"; break; + case ma_dr_wav_metadata_type_list_info_comment: pID = "ICMT"; break; + case ma_dr_wav_metadata_type_list_info_date: pID = "ICRD"; break; + case ma_dr_wav_metadata_type_list_info_genre: pID = "IGNR"; break; + case ma_dr_wav_metadata_type_list_info_album: pID = "IPRD"; break; + case ma_dr_wav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; + case ma_dr_wav_metadata_type_list_info_location: pID = "IARL"; break; + case ma_dr_wav_metadata_type_list_info_organization: pID = "ICMS"; break; + case ma_dr_wav_metadata_type_list_info_keywords: pID = "IKEY"; break; + case ma_dr_wav_metadata_type_list_info_medium: pID = "IMED"; break; + case ma_dr_wav_metadata_type_list_info_description: pID = "ISBJ"; break; default: break; } MA_DR_WAV_ASSERT(pID != NULL); @@ -68875,7 +70565,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init_write__internal(ma_dr_wav* pWav, const ma_dr } pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; if (pFormat->container == ma_dr_wav_container_riff) { - ma_uint32 chunkSizeRIFF = 28 + (ma_uint32)initialDataChunkSize; + ma_uint32 chunkSizeRIFF = 36 + (ma_uint32)initialDataChunkSize; runningPos += ma_dr_wav__write(pWav, "RIFF", 4); runningPos += ma_dr_wav__write_u32ne_to_le(pWav, chunkSizeRIFF); runningPos += ma_dr_wav__write(pWav, "WAVE", 4); @@ -68998,7 +70688,31 @@ MA_PRIVATE size_t ma_dr_wav__on_write_stdio(void* pUserData, const void* pData, } MA_PRIVATE ma_bool32 ma_dr_wav__on_seek_stdio(void* pUserData, int offset, ma_dr_wav_seek_origin origin) { - return fseek((FILE*)pUserData, offset, (origin == ma_dr_wav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + int whence = SEEK_SET; + if (origin == MA_DR_WAV_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == MA_DR_WAV_SEEK_END) { + whence = SEEK_END; + } + return fseek((FILE*)pUserData, offset, whence) == 0; +} +MA_PRIVATE ma_bool32 ma_dr_wav__on_tell_stdio(void* pUserData, ma_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + ma_int64 result; + MA_DR_WAV_ASSERT(pFileStdio != NULL); + MA_DR_WAV_ASSERT(pCursor != NULL); +#if defined(_WIN32) && !defined(NXDK) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + *pCursor = result; + return MA_TRUE; } MA_API ma_bool32 ma_dr_wav_init_file(ma_dr_wav* pWav, const char* filename, const ma_allocation_callbacks* pAllocationCallbacks) { @@ -69007,7 +70721,7 @@ MA_API ma_bool32 ma_dr_wav_init_file(ma_dr_wav* pWav, const char* filename, cons MA_PRIVATE ma_bool32 ma_dr_wav_init_file__internal_FILE(ma_dr_wav* pWav, FILE* pFile, ma_dr_wav_chunk_proc onChunk, void* pChunkUserData, ma_uint32 flags, const ma_allocation_callbacks* pAllocationCallbacks) { ma_bool32 result; - result = ma_dr_wav_preinit(pWav, ma_dr_wav__on_read_stdio, ma_dr_wav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = ma_dr_wav_preinit(pWav, ma_dr_wav__on_read_stdio, ma_dr_wav__on_seek_stdio, ma_dr_wav__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (result != MA_TRUE) { fclose(pFile); return result; @@ -69144,25 +70858,27 @@ MA_PRIVATE size_t ma_dr_wav__on_read_memory(void* pUserData, void* pBufferOut, s MA_PRIVATE ma_bool32 ma_dr_wav__on_seek_memory(void* pUserData, int offset, ma_dr_wav_seek_origin origin) { ma_dr_wav* pWav = (ma_dr_wav*)pUserData; + ma_int64 newCursor; MA_DR_WAV_ASSERT(pWav != NULL); - if (origin == ma_dr_wav_seek_origin_current) { - if (offset > 0) { - if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) { - return MA_FALSE; - } - } else { - if (pWav->memoryStream.currentReadPos < (size_t)-offset) { - return MA_FALSE; - } - } - pWav->memoryStream.currentReadPos += offset; + newCursor = pWav->memoryStream.currentReadPos; + if (origin == MA_DR_WAV_SEEK_SET) { + newCursor = 0; + } else if (origin == MA_DR_WAV_SEEK_CUR) { + newCursor = (ma_int64)pWav->memoryStream.currentReadPos; + } else if (origin == MA_DR_WAV_SEEK_END) { + newCursor = (ma_int64)pWav->memoryStream.dataSize; } else { - if ((ma_uint32)offset <= pWav->memoryStream.dataSize) { - pWav->memoryStream.currentReadPos = offset; - } else { - return MA_FALSE; - } + MA_DR_WAV_ASSERT(!"Invalid seek origin"); + return MA_FALSE; } + newCursor += offset; + if (newCursor < 0) { + return MA_FALSE; + } + if ((size_t)newCursor > pWav->memoryStream.dataSize) { + return MA_FALSE; + } + pWav->memoryStream.currentReadPos = (size_t)newCursor; return MA_TRUE; } MA_PRIVATE size_t ma_dr_wav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) @@ -69196,25 +70912,35 @@ MA_PRIVATE size_t ma_dr_wav__on_write_memory(void* pUserData, const void* pDataI MA_PRIVATE ma_bool32 ma_dr_wav__on_seek_memory_write(void* pUserData, int offset, ma_dr_wav_seek_origin origin) { ma_dr_wav* pWav = (ma_dr_wav*)pUserData; + ma_int64 newCursor; MA_DR_WAV_ASSERT(pWav != NULL); - if (origin == ma_dr_wav_seek_origin_current) { - if (offset > 0) { - if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) { - offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos); - } - } else { - if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) { - offset = -(int)pWav->memoryStreamWrite.currentWritePos; - } - } - pWav->memoryStreamWrite.currentWritePos += offset; + newCursor = pWav->memoryStreamWrite.currentWritePos; + if (origin == MA_DR_WAV_SEEK_SET) { + newCursor = 0; + } else if (origin == MA_DR_WAV_SEEK_CUR) { + newCursor = (ma_int64)pWav->memoryStreamWrite.currentWritePos; + } else if (origin == MA_DR_WAV_SEEK_END) { + newCursor = (ma_int64)pWav->memoryStreamWrite.dataSize; } else { - if ((ma_uint32)offset <= pWav->memoryStreamWrite.dataSize) { - pWav->memoryStreamWrite.currentWritePos = offset; - } else { - pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; - } + MA_DR_WAV_ASSERT(!"Invalid seek origin"); + return MA_INVALID_ARGS; } + newCursor += offset; + if (newCursor < 0) { + return MA_FALSE; + } + if ((size_t)newCursor > pWav->memoryStreamWrite.dataSize) { + return MA_FALSE; + } + pWav->memoryStreamWrite.currentWritePos = (size_t)newCursor; + return MA_TRUE; +} +MA_PRIVATE ma_bool32 ma_dr_wav__on_tell_memory(void* pUserData, ma_int64* pCursor) +{ + ma_dr_wav* pWav = (ma_dr_wav*)pUserData; + MA_DR_WAV_ASSERT(pWav != NULL); + MA_DR_WAV_ASSERT(pCursor != NULL); + *pCursor = (ma_int64)pWav->memoryStream.currentReadPos; return MA_TRUE; } MA_API ma_bool32 ma_dr_wav_init_memory(ma_dr_wav* pWav, const void* data, size_t dataSize, const ma_allocation_callbacks* pAllocationCallbacks) @@ -69226,7 +70952,7 @@ MA_API ma_bool32 ma_dr_wav_init_memory_ex(ma_dr_wav* pWav, const void* data, siz if (data == NULL || dataSize == 0) { return MA_FALSE; } - if (!ma_dr_wav_preinit(pWav, ma_dr_wav__on_read_memory, ma_dr_wav__on_seek_memory, pWav, pAllocationCallbacks)) { + if (!ma_dr_wav_preinit(pWav, ma_dr_wav__on_read_memory, ma_dr_wav__on_seek_memory, ma_dr_wav__on_tell_memory, pWav, pAllocationCallbacks)) { return MA_FALSE; } pWav->memoryStream.data = (const ma_uint8*)data; @@ -69239,7 +70965,7 @@ MA_API ma_bool32 ma_dr_wav_init_memory_with_metadata(ma_dr_wav* pWav, const void if (data == NULL || dataSize == 0) { return MA_FALSE; } - if (!ma_dr_wav_preinit(pWav, ma_dr_wav__on_read_memory, ma_dr_wav__on_seek_memory, pWav, pAllocationCallbacks)) { + if (!ma_dr_wav_preinit(pWav, ma_dr_wav__on_read_memory, ma_dr_wav__on_seek_memory, ma_dr_wav__on_tell_memory, pWav, pAllocationCallbacks)) { return MA_FALSE; } pWav->memoryStream.data = (const ma_uint8*)data; @@ -69298,30 +71024,30 @@ MA_API ma_result ma_dr_wav_uninit(ma_dr_wav* pWav) } if (pWav->onSeek && !pWav->isSequentialWrite) { if (pWav->container == ma_dr_wav_container_riff) { - if (pWav->onSeek(pWav->pUserData, 4, ma_dr_wav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, 4, MA_DR_WAV_SEEK_SET)) { ma_uint32 riffChunkSize = ma_dr_wav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); ma_dr_wav__write_u32ne_to_le(pWav, riffChunkSize); } - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, ma_dr_wav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, MA_DR_WAV_SEEK_SET)) { ma_uint32 dataChunkSize = ma_dr_wav__data_chunk_size_riff(pWav->dataChunkDataSize); ma_dr_wav__write_u32ne_to_le(pWav, dataChunkSize); } } else if (pWav->container == ma_dr_wav_container_w64) { - if (pWav->onSeek(pWav->pUserData, 16, ma_dr_wav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, 16, MA_DR_WAV_SEEK_SET)) { ma_uint64 riffChunkSize = ma_dr_wav__riff_chunk_size_w64(pWav->dataChunkDataSize); ma_dr_wav__write_u64ne_to_le(pWav, riffChunkSize); } - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, ma_dr_wav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, MA_DR_WAV_SEEK_SET)) { ma_uint64 dataChunkSize = ma_dr_wav__data_chunk_size_w64(pWav->dataChunkDataSize); ma_dr_wav__write_u64ne_to_le(pWav, dataChunkSize); } } else if (pWav->container == ma_dr_wav_container_rf64) { int ds64BodyPos = 12 + 8; - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, ma_dr_wav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, MA_DR_WAV_SEEK_SET)) { ma_uint64 riffChunkSize = ma_dr_wav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); ma_dr_wav__write_u64ne_to_le(pWav, riffChunkSize); } - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, ma_dr_wav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, MA_DR_WAV_SEEK_SET)) { ma_uint64 dataChunkSize = ma_dr_wav__data_chunk_size_rf64(pWav->dataChunkDataSize); ma_dr_wav__write_u64ne_to_le(pWav, dataChunkSize); } @@ -69368,7 +71094,7 @@ MA_API size_t ma_dr_wav_read_raw(ma_dr_wav* pWav, size_t bytesToRead, void* pBuf if (bytesToSeek > 0x7FFFFFFF) { bytesToSeek = 0x7FFFFFFF; } - if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, ma_dr_wav_seek_origin_current) == MA_FALSE) { + if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, MA_DR_WAV_SEEK_CUR) == MA_FALSE) { break; } bytesRead += bytesToSeek; @@ -69467,7 +71193,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_seek_to_first_pcm_frame(ma_dr_wav* pWav) if (pWav->onWrite != NULL) { return MA_FALSE; } - if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, ma_dr_wav_seek_origin_start)) { + if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, MA_DR_WAV_SEEK_SET)) { return MA_FALSE; } if (ma_dr_wav__is_compressed_format_tag(pWav->translatedFormatTag)) { @@ -69548,7 +71274,7 @@ MA_API ma_bool32 ma_dr_wav_seek_to_pcm_frame(ma_dr_wav* pWav, ma_uint64 targetFr } while (offset > 0) { int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); - if (!pWav->onSeek(pWav->pUserData, offset32, ma_dr_wav_seek_origin_current)) { + if (!pWav->onSeek(pWav->pUserData, offset32, MA_DR_WAV_SEEK_CUR)) { return MA_FALSE; } pWav->readCursorInPCMFrames += offset32 / bytesPerFrame; @@ -69674,12 +71400,12 @@ MA_API ma_uint64 ma_dr_wav_write_pcm_frames(ma_dr_wav* pWav, ma_uint64 framesToW MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_uint64 framesToRead, ma_int16* pBufferOut) { ma_uint64 totalFramesRead = 0; - static ma_int32 adaptationTable[] = { + static const ma_int32 adaptationTable[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; - static ma_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; - static ma_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + static const ma_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static const ma_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; MA_DR_WAV_ASSERT(pWav != NULL); MA_DR_WAV_ASSERT(framesToRead > 0); while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { @@ -69812,11 +71538,11 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__ima(ma_dr_wav* pWav, ma_uint { ma_uint64 totalFramesRead = 0; ma_uint32 iChannel; - static ma_int32 indexTable[16] = { + static const ma_int32 indexTable[16] = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 }; - static ma_int32 stepTable[89] = { + static const ma_int32 stepTable[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, @@ -69839,7 +71565,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__ima(ma_dr_wav* pWav, ma_uint } pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); if (header[2] >= ma_dr_wav_countof(stepTable)) { - pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, ma_dr_wav_seek_origin_current); + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, MA_DR_WAV_SEEK_CUR); pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; } @@ -69854,7 +71580,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__ima(ma_dr_wav* pWav, ma_uint } pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); if (header[2] >= ma_dr_wav_countof(stepTable) || header[6] >= ma_dr_wav_countof(stepTable)) { - pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, ma_dr_wav_seek_origin_current); + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, MA_DR_WAV_SEEK_CUR); pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; } @@ -69929,7 +71655,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__ima(ma_dr_wav* pWav, ma_uint return totalFramesRead; } #ifndef MA_DR_WAV_NO_CONVERSION_API -static unsigned short g_ma_dr_wavAlawTable[256] = { +static const unsigned short ma_dr_wav_gAlawTable[256] = { 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, @@ -69947,7 +71673,7 @@ static unsigned short g_ma_dr_wavAlawTable[256] = { 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 }; -static unsigned short g_ma_dr_wavMulawTable[256] = { +static const unsigned short ma_dr_wav_gMulawTable[256] = { 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, @@ -69967,11 +71693,11 @@ static unsigned short g_ma_dr_wavMulawTable[256] = { }; static MA_INLINE ma_int16 ma_dr_wav__alaw_to_s16(ma_uint8 sampleIn) { - return (short)g_ma_dr_wavAlawTable[sampleIn]; + return (short)ma_dr_wav_gAlawTable[sampleIn]; } static MA_INLINE ma_int16 ma_dr_wav__mulaw_to_s16(ma_uint8 sampleIn) { - return (short)g_ma_dr_wavMulawTable[sampleIn]; + return (short)ma_dr_wav_gMulawTable[sampleIn]; } MA_PRIVATE void ma_dr_wav__pcm_to_s16(ma_int16* pOut, const ma_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { @@ -71130,7 +72856,7 @@ MA_PRIVATE ma_int32* ma_dr_wav__read_pcm_frames_and_close_s32(ma_dr_wav* pWav, u } return pSampleData; } -MA_API ma_int16* ma_dr_wav_open_and_read_pcm_frames_s16(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_int16* ma_dr_wav_open_and_read_pcm_frames_s16(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_wav wav; if (channelsOut) { @@ -71142,12 +72868,12 @@ MA_API ma_int16* ma_dr_wav_open_and_read_pcm_frames_s16(ma_dr_wav_read_proc onRe if (totalFrameCountOut) { *totalFrameCountOut = 0; } - if (!ma_dr_wav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!ma_dr_wav_init(&wav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return NULL; } return ma_dr_wav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } -MA_API float* ma_dr_wav_open_and_read_pcm_frames_f32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API float* ma_dr_wav_open_and_read_pcm_frames_f32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_wav wav; if (channelsOut) { @@ -71159,12 +72885,12 @@ MA_API float* ma_dr_wav_open_and_read_pcm_frames_f32(ma_dr_wav_read_proc onRead, if (totalFrameCountOut) { *totalFrameCountOut = 0; } - if (!ma_dr_wav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!ma_dr_wav_init(&wav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return NULL; } return ma_dr_wav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } -MA_API ma_int32* ma_dr_wav_open_and_read_pcm_frames_s32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_int32* ma_dr_wav_open_and_read_pcm_frames_s32(ma_dr_wav_read_proc onRead, ma_dr_wav_seek_proc onSeek, ma_dr_wav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_wav wav; if (channelsOut) { @@ -71176,7 +72902,7 @@ MA_API ma_int32* ma_dr_wav_open_and_read_pcm_frames_s32(ma_dr_wav_read_proc onRe if (totalFrameCountOut) { *totalFrameCountOut = 0; } - if (!ma_dr_wav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!ma_dr_wav_init(&wav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return NULL; } return ma_dr_wav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); @@ -72611,23 +74337,23 @@ static ma_bool32 ma_dr_flac__seek_to_byte(ma_dr_flac_bs* bs, ma_uint64 offsetFro MA_DR_FLAC_ASSERT(offsetFromStart > 0); if (offsetFromStart > 0x7FFFFFFF) { ma_uint64 bytesRemaining = offsetFromStart; - if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, ma_dr_flac_seek_origin_start)) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, MA_DR_FLAC_SEEK_SET)) { return MA_FALSE; } bytesRemaining -= 0x7FFFFFFF; while (bytesRemaining > 0x7FFFFFFF) { - if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, ma_dr_flac_seek_origin_current)) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } bytesRemaining -= 0x7FFFFFFF; } if (bytesRemaining > 0) { - if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, ma_dr_flac_seek_origin_current)) { + if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } } } else { - if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, ma_dr_flac_seek_origin_start)) { + if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, MA_DR_FLAC_SEEK_SET)) { return MA_FALSE; } } @@ -75105,6 +76831,7 @@ typedef struct { ma_dr_flac_read_proc onRead; ma_dr_flac_seek_proc onSeek; + ma_dr_flac_tell_proc onTell; ma_dr_flac_meta_proc onMeta; ma_dr_flac_container container; void* pUserData; @@ -75233,11 +76960,12 @@ static void ma_dr_flac__free_from_callbacks(void* p, const ma_allocation_callbac pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } } -static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_meta_proc onMeta, void* pUserData, void* pUserDataMD, ma_uint64* pFirstFramePos, ma_uint64* pSeektablePos, ma_uint32* pSeekpointCount, ma_allocation_callbacks* pAllocationCallbacks) +static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, void* pUserData, void* pUserDataMD, ma_uint64* pFirstFramePos, ma_uint64* pSeektablePos, ma_uint32* pSeekpointCount, ma_allocation_callbacks* pAllocationCallbacks) { ma_uint64 runningFilePos = 42; ma_uint64 seektablePos = 0; ma_uint32 seektableSize = 0; + (void)onTell; for (;;) { ma_dr_flac_metadata metadata; ma_uint8 isLastBlock = 0; @@ -75495,7 +77223,7 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea { if (onMeta) { metadata.data.padding.unused = 0; - if (!onSeek(pUserData, blockSize, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, MA_DR_FLAC_SEEK_CUR)) { isLastBlock = MA_TRUE; } else { onMeta(pUserDataMD, &metadata); @@ -75505,7 +77233,7 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea case MA_DR_FLAC_METADATA_BLOCK_TYPE_INVALID: { if (onMeta) { - if (!onSeek(pUserData, blockSize, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, MA_DR_FLAC_SEEK_CUR)) { isLastBlock = MA_TRUE; } } @@ -75529,7 +77257,7 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea } break; } if (onMeta == NULL && blockSize > 0) { - if (!onSeek(pUserData, blockSize, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, MA_DR_FLAC_SEEK_CUR)) { isLastBlock = MA_TRUE; } } @@ -75793,6 +77521,7 @@ typedef struct { ma_dr_flac_read_proc onRead; ma_dr_flac_seek_proc onSeek; + ma_dr_flac_tell_proc onTell; void* pUserData; ma_uint64 currentBytePos; ma_uint64 firstBytePos; @@ -75811,29 +77540,29 @@ static size_t ma_dr_flac_oggbs__read_physical(ma_dr_flac_oggbs* oggbs, void* buf } static ma_bool32 ma_dr_flac_oggbs__seek_physical(ma_dr_flac_oggbs* oggbs, ma_uint64 offset, ma_dr_flac_seek_origin origin) { - if (origin == ma_dr_flac_seek_origin_start) { + if (origin == MA_DR_FLAC_SEEK_SET) { if (offset <= 0x7FFFFFFF) { - if (!oggbs->onSeek(oggbs->pUserData, (int)offset, ma_dr_flac_seek_origin_start)) { + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, MA_DR_FLAC_SEEK_SET)) { return MA_FALSE; } oggbs->currentBytePos = offset; return MA_TRUE; } else { - if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, ma_dr_flac_seek_origin_start)) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, MA_DR_FLAC_SEEK_SET)) { return MA_FALSE; } oggbs->currentBytePos = offset; - return ma_dr_flac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, ma_dr_flac_seek_origin_current); + return ma_dr_flac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, MA_DR_FLAC_SEEK_CUR); } } else { while (offset > 0x7FFFFFFF) { - if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, ma_dr_flac_seek_origin_current)) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } oggbs->currentBytePos += 0x7FFFFFFF; offset -= 0x7FFFFFFF; } - if (!oggbs->onSeek(oggbs->pUserData, (int)offset, ma_dr_flac_seek_origin_current)) { + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } oggbs->currentBytePos += offset; @@ -75859,7 +77588,7 @@ static ma_bool32 ma_dr_flac_oggbs__goto_next_page(ma_dr_flac_oggbs* oggbs, ma_dr continue; } if (header.serialNumber != oggbs->serialNumber) { - if (pageBodySize > 0 && !ma_dr_flac_oggbs__seek_physical(oggbs, pageBodySize, ma_dr_flac_seek_origin_current)) { + if (pageBodySize > 0 && !ma_dr_flac_oggbs__seek_physical(oggbs, pageBodySize, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } continue; @@ -75921,7 +77650,7 @@ static ma_bool32 ma_dr_flac_oggbs__seek_to_next_packet(ma_dr_flac_oggbs* oggbs) } bytesToEndOfPacketOrPage += segmentSize; } - ma_dr_flac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, ma_dr_flac_seek_origin_current); + ma_dr_flac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, MA_DR_FLAC_SEEK_CUR); oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; if (atEndOfPage) { if (!ma_dr_flac_oggbs__goto_next_page(oggbs)) { @@ -75974,36 +77703,44 @@ static ma_bool32 ma_dr_flac__on_seek_ogg(void* pUserData, int offset, ma_dr_flac int bytesSeeked = 0; MA_DR_FLAC_ASSERT(oggbs != NULL); MA_DR_FLAC_ASSERT(offset >= 0); - if (origin == ma_dr_flac_seek_origin_start) { - if (!ma_dr_flac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, ma_dr_flac_seek_origin_start)) { + if (origin == MA_DR_FLAC_SEEK_SET) { + if (!ma_dr_flac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, MA_DR_FLAC_SEEK_SET)) { return MA_FALSE; } if (!ma_dr_flac_oggbs__goto_next_page(oggbs, ma_dr_flac_ogg_fail_on_crc_mismatch)) { return MA_FALSE; } - return ma_dr_flac__on_seek_ogg(pUserData, offset, ma_dr_flac_seek_origin_current); - } - MA_DR_FLAC_ASSERT(origin == ma_dr_flac_seek_origin_current); - while (bytesSeeked < offset) { - int bytesRemainingToSeek = offset - bytesSeeked; - MA_DR_FLAC_ASSERT(bytesRemainingToSeek >= 0); - if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { - bytesSeeked += bytesRemainingToSeek; - (void)bytesSeeked; - oggbs->bytesRemainingInPage -= bytesRemainingToSeek; - break; - } - if (oggbs->bytesRemainingInPage > 0) { - bytesSeeked += (int)oggbs->bytesRemainingInPage; - oggbs->bytesRemainingInPage = 0; - } - MA_DR_FLAC_ASSERT(bytesRemainingToSeek > 0); - if (!ma_dr_flac_oggbs__goto_next_page(oggbs, ma_dr_flac_ogg_fail_on_crc_mismatch)) { - return MA_FALSE; + return ma_dr_flac__on_seek_ogg(pUserData, offset, MA_DR_FLAC_SEEK_CUR); + } else if (origin == MA_DR_FLAC_SEEK_CUR) { + while (bytesSeeked < offset) { + int bytesRemainingToSeek = offset - bytesSeeked; + MA_DR_FLAC_ASSERT(bytesRemainingToSeek >= 0); + if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { + bytesSeeked += bytesRemainingToSeek; + (void)bytesSeeked; + oggbs->bytesRemainingInPage -= bytesRemainingToSeek; + break; + } + if (oggbs->bytesRemainingInPage > 0) { + bytesSeeked += (int)oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } + MA_DR_FLAC_ASSERT(bytesRemainingToSeek > 0); + if (!ma_dr_flac_oggbs__goto_next_page(oggbs, ma_dr_flac_ogg_fail_on_crc_mismatch)) { + return MA_FALSE; + } } + } else if (origin == MA_DR_FLAC_SEEK_END) { + return MA_FALSE; } return MA_TRUE; } +static ma_bool32 ma_dr_flac__on_tell_ogg(void* pUserData, ma_int64* pCursor) +{ + (void)pUserData; + (void)pCursor; + return MA_FALSE; +} static ma_bool32 ma_dr_flac_ogg__seek_to_pcm_frame(ma_dr_flac* pFlac, ma_uint64 pcmFrameIndex) { ma_dr_flac_oggbs* oggbs = (ma_dr_flac_oggbs*)pFlac->_oggbs; @@ -76020,7 +77757,7 @@ static ma_bool32 ma_dr_flac_ogg__seek_to_pcm_frame(ma_dr_flac* pFlac, ma_uint64 runningGranulePosition = 0; for (;;) { if (!ma_dr_flac_oggbs__goto_next_page(oggbs, ma_dr_flac_ogg_recover_on_crc_mismatch)) { - ma_dr_flac_oggbs__seek_physical(oggbs, originalBytePos, ma_dr_flac_seek_origin_start); + ma_dr_flac_oggbs__seek_physical(oggbs, originalBytePos, MA_DR_FLAC_SEEK_SET); return MA_FALSE; } runningFrameBytePos = oggbs->currentBytePos - ma_dr_flac_ogg__get_page_header_size(&oggbs->currentPageHeader) - oggbs->pageDataSize; @@ -76039,7 +77776,7 @@ static ma_bool32 ma_dr_flac_ogg__seek_to_pcm_frame(ma_dr_flac* pFlac, ma_uint64 } } } - if (!ma_dr_flac_oggbs__seek_physical(oggbs, runningFrameBytePos, ma_dr_flac_seek_origin_start)) { + if (!ma_dr_flac_oggbs__seek_physical(oggbs, runningFrameBytePos, MA_DR_FLAC_SEEK_SET)) { return MA_FALSE; } if (!ma_dr_flac_oggbs__goto_next_page(oggbs, ma_dr_flac_ogg_recover_on_crc_mismatch)) { @@ -76134,7 +77871,7 @@ static ma_bool32 ma_dr_flac__init_private__ogg(ma_dr_flac_init_info* pInit, ma_d if (mappingVersion[0] != 1) { return MA_FALSE; } - if (!onSeek(pUserData, 2, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, 2, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } if (onRead(pUserData, sig, 4) != 4) { @@ -76179,17 +77916,17 @@ static ma_bool32 ma_dr_flac__init_private__ogg(ma_dr_flac_init_info* pInit, ma_d return MA_FALSE; } } else { - if (!onSeek(pUserData, bytesRemainingInPage, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, bytesRemainingInPage, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } } } else { - if (!onSeek(pUserData, bytesRemainingInPage, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, bytesRemainingInPage, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } } } else { - if (!onSeek(pUserData, pageBodySize, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, pageBodySize, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } } @@ -76203,7 +77940,7 @@ static ma_bool32 ma_dr_flac__init_private__ogg(ma_dr_flac_init_info* pInit, ma_d return MA_TRUE; } #endif -static ma_bool32 ma_dr_flac__init_private(ma_dr_flac_init_info* pInit, ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, void* pUserDataMD) +static ma_bool32 ma_dr_flac__init_private(ma_dr_flac_init_info* pInit, ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, void* pUserDataMD) { ma_bool32 relaxed; ma_uint8 id[4]; @@ -76213,12 +77950,14 @@ static ma_bool32 ma_dr_flac__init_private(ma_dr_flac_init_info* pInit, ma_dr_fla MA_DR_FLAC_ZERO_MEMORY(pInit, sizeof(*pInit)); pInit->onRead = onRead; pInit->onSeek = onSeek; + pInit->onTell = onTell; pInit->onMeta = onMeta; pInit->container = container; pInit->pUserData = pUserData; pInit->pUserDataMD = pUserDataMD; pInit->bs.onRead = onRead; pInit->bs.onSeek = onSeek; + pInit->bs.onTell = onTell; pInit->bs.pUserData = pUserData; ma_dr_flac__reset_cache(&pInit->bs); relaxed = container != ma_dr_flac_container_unknown; @@ -76241,7 +77980,7 @@ static ma_bool32 ma_dr_flac__init_private(ma_dr_flac_init_info* pInit, ma_dr_fla if (flags & 0x10) { headerSize += 10; } - if (!onSeek(pUserData, headerSize, ma_dr_flac_seek_origin_current)) { + if (!onSeek(pUserData, headerSize, MA_DR_FLAC_SEEK_CUR)) { return MA_FALSE; } pInit->runningFilePos += headerSize; @@ -76284,7 +78023,7 @@ static void ma_dr_flac__init_from_info(ma_dr_flac* pFlac, const ma_dr_flac_init_ pFlac->totalPCMFrameCount = pInit->totalPCMFrameCount; pFlac->container = pInit->container; } -static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, void* pUserDataMD, const ma_allocation_callbacks* pAllocationCallbacks) +static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, void* pUserDataMD, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_flac_init_info init; ma_uint32 allocationSize; @@ -76299,7 +78038,7 @@ static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc on ma_allocation_callbacks allocationCallbacks; ma_dr_flac* pFlac; ma_dr_flac__init_cpu_caps(); - if (!ma_dr_flac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { + if (!ma_dr_flac__init_private(&init, onRead, onSeek, onTell, onMeta, container, pUserData, pUserDataMD)) { return NULL; } if (pAllocationCallbacks != NULL) { @@ -76332,6 +78071,7 @@ static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc on MA_DR_FLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs)); pOggbs->onRead = onRead; pOggbs->onSeek = onSeek; + pOggbs->onTell = onTell; pOggbs->pUserData = pUserData; pOggbs->currentBytePos = init.oggFirstBytePos; pOggbs->firstBytePos = init.oggFirstBytePos; @@ -76346,15 +78086,17 @@ static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc on if (init.hasMetadataBlocks) { ma_dr_flac_read_proc onReadOverride = onRead; ma_dr_flac_seek_proc onSeekOverride = onSeek; + ma_dr_flac_tell_proc onTellOverride = onTell; void* pUserDataOverride = pUserData; #ifndef MA_DR_FLAC_NO_OGG if (init.container == ma_dr_flac_container_ogg) { onReadOverride = ma_dr_flac__on_read_ogg; onSeekOverride = ma_dr_flac__on_seek_ogg; + onTellOverride = ma_dr_flac__on_tell_ogg; pUserDataOverride = (void*)pOggbs; } #endif - if (!ma_dr_flac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { + if (!ma_dr_flac__read_and_decode_metadata(onReadOverride, onSeekOverride, onTellOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { #ifndef MA_DR_FLAC_NO_OGG ma_dr_flac__free_from_callbacks(pOggbs, &allocationCallbacks); #endif @@ -76380,6 +78122,7 @@ static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc on pOggbs = NULL; pFlac->bs.onRead = ma_dr_flac__on_read_ogg; pFlac->bs.onSeek = ma_dr_flac__on_seek_ogg; + pFlac->bs.onTell = ma_dr_flac__on_tell_ogg; pFlac->bs.pUserData = (void*)pInternalOggbs; pFlac->_oggbs = (void*)pInternalOggbs; } @@ -76399,7 +78142,7 @@ static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc on pFlac->pSeekpoints = (ma_dr_flac_seekpoint*)((ma_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); MA_DR_FLAC_ASSERT(pFlac->bs.onSeek != NULL); MA_DR_FLAC_ASSERT(pFlac->bs.onRead != NULL); - if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, ma_dr_flac_seek_origin_start)) { + if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, MA_DR_FLAC_SEEK_SET)) { ma_uint32 iSeekpoint; for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) { if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints + iSeekpoint, MA_DR_FLAC_SEEKPOINT_SIZE_IN_BYTES) == MA_DR_FLAC_SEEKPOINT_SIZE_IN_BYTES) { @@ -76412,7 +78155,7 @@ static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc on break; } } - if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, ma_dr_flac_seek_origin_start)) { + if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, MA_DR_FLAC_SEEK_SET)) { ma_dr_flac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } @@ -76455,8 +78198,31 @@ static size_t ma_dr_flac__on_read_stdio(void* pUserData, void* bufferOut, size_t } static ma_bool32 ma_dr_flac__on_seek_stdio(void* pUserData, int offset, ma_dr_flac_seek_origin origin) { - MA_DR_FLAC_ASSERT(offset >= 0); - return fseek((FILE*)pUserData, offset, (origin == ma_dr_flac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + int whence = SEEK_SET; + if (origin == MA_DR_FLAC_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == MA_DR_FLAC_SEEK_END) { + whence = SEEK_END; + } + return fseek((FILE*)pUserData, offset, whence) == 0; +} +static ma_bool32 ma_dr_flac__on_tell_stdio(void* pUserData, ma_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + ma_int64 result; + MA_DR_FLAC_ASSERT(pFileStdio != NULL); + MA_DR_FLAC_ASSERT(pCursor != NULL); +#if defined(_WIN32) && !defined(NXDK) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + *pCursor = result; + return MA_TRUE; } MA_API ma_dr_flac* ma_dr_flac_open_file(const char* pFileName, const ma_allocation_callbacks* pAllocationCallbacks) { @@ -76465,7 +78231,7 @@ MA_API ma_dr_flac* ma_dr_flac_open_file(const char* pFileName, const ma_allocati if (ma_fopen(&pFile, pFileName, "rb") != MA_SUCCESS) { return NULL; } - pFlac = ma_dr_flac_open(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + pFlac = ma_dr_flac_open(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, ma_dr_flac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; @@ -76480,7 +78246,7 @@ MA_API ma_dr_flac* ma_dr_flac_open_file_w(const wchar_t* pFileName, const ma_all if (ma_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != MA_SUCCESS) { return NULL; } - pFlac = ma_dr_flac_open(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + pFlac = ma_dr_flac_open(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, ma_dr_flac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; @@ -76495,7 +78261,7 @@ MA_API ma_dr_flac* ma_dr_flac_open_file_with_metadata(const char* pFileName, ma_ if (ma_fopen(&pFile, pFileName, "rb") != MA_SUCCESS) { return NULL; } - pFlac = ma_dr_flac_open_with_metadata_private(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, onMeta, ma_dr_flac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + pFlac = ma_dr_flac_open_with_metadata_private(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, ma_dr_flac__on_tell_stdio, onMeta, ma_dr_flac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; @@ -76510,7 +78276,7 @@ MA_API ma_dr_flac* ma_dr_flac_open_file_with_metadata_w(const wchar_t* pFileName if (ma_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != MA_SUCCESS) { return NULL; } - pFlac = ma_dr_flac_open_with_metadata_private(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, onMeta, ma_dr_flac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + pFlac = ma_dr_flac_open_with_metadata_private(ma_dr_flac__on_read_stdio, ma_dr_flac__on_seek_stdio, ma_dr_flac__on_tell_stdio, onMeta, ma_dr_flac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; @@ -76538,24 +78304,35 @@ static size_t ma_dr_flac__on_read_memory(void* pUserData, void* bufferOut, size_ static ma_bool32 ma_dr_flac__on_seek_memory(void* pUserData, int offset, ma_dr_flac_seek_origin origin) { ma_dr_flac__memory_stream* memoryStream = (ma_dr_flac__memory_stream*)pUserData; + ma_int64 newCursor; MA_DR_FLAC_ASSERT(memoryStream != NULL); - MA_DR_FLAC_ASSERT(offset >= 0); - if (offset > (ma_int64)memoryStream->dataSize) { + newCursor = memoryStream->currentReadPos; + if (origin == MA_DR_FLAC_SEEK_SET) { + newCursor = 0; + } else if (origin == MA_DR_FLAC_SEEK_CUR) { + newCursor = (ma_int64)memoryStream->currentReadPos; + } else if (origin == MA_DR_FLAC_SEEK_END) { + newCursor = (ma_int64)memoryStream->dataSize; + } else { + MA_DR_FLAC_ASSERT(!"Invalid seek origin"); return MA_FALSE; } - if (origin == ma_dr_flac_seek_origin_current) { - if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { - memoryStream->currentReadPos += offset; - } else { - return MA_FALSE; - } - } else { - if ((ma_uint32)offset <= memoryStream->dataSize) { - memoryStream->currentReadPos = offset; - } else { - return MA_FALSE; - } + newCursor += offset; + if (newCursor < 0) { + return MA_FALSE; } + if ((size_t)newCursor > memoryStream->dataSize) { + return MA_FALSE; + } + memoryStream->currentReadPos = (size_t)newCursor; + return MA_TRUE; +} +static ma_bool32 ma_dr_flac__on_tell_memory(void* pUserData, ma_int64* pCursor) +{ + ma_dr_flac__memory_stream* memoryStream = (ma_dr_flac__memory_stream*)pUserData; + MA_DR_FLAC_ASSERT(memoryStream != NULL); + MA_DR_FLAC_ASSERT(pCursor != NULL); + *pCursor = (ma_int64)memoryStream->currentReadPos; return MA_TRUE; } MA_API ma_dr_flac* ma_dr_flac_open_memory(const void* pData, size_t dataSize, const ma_allocation_callbacks* pAllocationCallbacks) @@ -76565,7 +78342,7 @@ MA_API ma_dr_flac* ma_dr_flac_open_memory(const void* pData, size_t dataSize, co memoryStream.data = (const ma_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = ma_dr_flac_open(ma_dr_flac__on_read_memory, ma_dr_flac__on_seek_memory, &memoryStream, pAllocationCallbacks); + pFlac = ma_dr_flac_open(ma_dr_flac__on_read_memory, ma_dr_flac__on_seek_memory, ma_dr_flac__on_tell_memory, &memoryStream, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -76590,7 +78367,7 @@ MA_API ma_dr_flac* ma_dr_flac_open_memory_with_metadata(const void* pData, size_ memoryStream.data = (const ma_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = ma_dr_flac_open_with_metadata_private(ma_dr_flac__on_read_memory, ma_dr_flac__on_seek_memory, onMeta, ma_dr_flac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); + pFlac = ma_dr_flac_open_with_metadata_private(ma_dr_flac__on_read_memory, ma_dr_flac__on_seek_memory, ma_dr_flac__on_tell_memory, onMeta, ma_dr_flac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -76608,21 +78385,21 @@ MA_API ma_dr_flac* ma_dr_flac_open_memory_with_metadata(const void* pData, size_ } return pFlac; } -MA_API ma_dr_flac* ma_dr_flac_open(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_dr_flac* ma_dr_flac_open(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_dr_flac_open_with_metadata_private(onRead, onSeek, NULL, ma_dr_flac_container_unknown, pUserData, pUserData, pAllocationCallbacks); + return ma_dr_flac_open_with_metadata_private(onRead, onSeek, onTell, NULL, ma_dr_flac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -MA_API ma_dr_flac* ma_dr_flac_open_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_dr_flac* ma_dr_flac_open_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_dr_flac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData, pAllocationCallbacks); + return ma_dr_flac_open_with_metadata_private(onRead, onSeek, onTell, NULL, container, pUserData, pUserData, pAllocationCallbacks); } -MA_API ma_dr_flac* ma_dr_flac_open_with_metadata(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_meta_proc onMeta, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_dr_flac* ma_dr_flac_open_with_metadata(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_dr_flac_open_with_metadata_private(onRead, onSeek, onMeta, ma_dr_flac_container_unknown, pUserData, pUserData, pAllocationCallbacks); + return ma_dr_flac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, ma_dr_flac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -MA_API ma_dr_flac* ma_dr_flac_open_with_metadata_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_dr_flac* ma_dr_flac_open_with_metadata_relaxed(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, ma_dr_flac_container container, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_dr_flac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData, pAllocationCallbacks); + return ma_dr_flac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, container, pUserData, pUserData, pAllocationCallbacks); } MA_API void ma_dr_flac_close(ma_dr_flac* pFlac) { @@ -78915,7 +80692,7 @@ on_error: MA_DR_FLAC_DEFINE_FULL_READ_AND_CLOSE(s32, ma_int32) MA_DR_FLAC_DEFINE_FULL_READ_AND_CLOSE(s16, ma_int16) MA_DR_FLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) -MA_API ma_int32* ma_dr_flac_open_and_read_pcm_frames_s32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalPCMFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_int32* ma_dr_flac_open_and_read_pcm_frames_s32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalPCMFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_flac* pFlac; if (channelsOut) { @@ -78927,13 +80704,13 @@ MA_API ma_int32* ma_dr_flac_open_and_read_pcm_frames_s32(ma_dr_flac_read_proc on if (totalPCMFrameCountOut) { *totalPCMFrameCountOut = 0; } - pFlac = ma_dr_flac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = ma_dr_flac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return ma_dr_flac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -MA_API ma_int16* ma_dr_flac_open_and_read_pcm_frames_s16(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalPCMFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_int16* ma_dr_flac_open_and_read_pcm_frames_s16(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalPCMFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_flac* pFlac; if (channelsOut) { @@ -78945,13 +80722,13 @@ MA_API ma_int16* ma_dr_flac_open_and_read_pcm_frames_s16(ma_dr_flac_read_proc on if (totalPCMFrameCountOut) { *totalPCMFrameCountOut = 0; } - pFlac = ma_dr_flac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = ma_dr_flac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return ma_dr_flac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -MA_API float* ma_dr_flac_open_and_read_pcm_frames_f32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalPCMFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API float* ma_dr_flac_open_and_read_pcm_frames_f32(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, ma_uint64* totalPCMFrameCountOut, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_flac* pFlac; if (channelsOut) { @@ -78963,7 +80740,7 @@ MA_API float* ma_dr_flac_open_and_read_pcm_frames_f32(ma_dr_flac_read_proc onRea if (totalPCMFrameCountOut) { *totalPCMFrameCountOut = 0; } - pFlac = ma_dr_flac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = ma_dr_flac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -79767,6 +81544,10 @@ static float ma_dr_mp3_L3_ldexp_q2(float y, int exp_q2) } while ((exp_q2 -= e) > 0); return y; } +#if (defined(__GNUC__) && (__GNUC__ >= 14)) && !defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif static void ma_dr_mp3_L3_decode_scalefactors(const ma_uint8 *hdr, ma_uint8 *ist_pos, ma_dr_mp3_bs *bs, const ma_dr_mp3_L3_gr_info *gr, float *scf, int ch) { static const ma_uint8 g_scf_partitions[3][28] = { @@ -79825,7 +81606,10 @@ static void ma_dr_mp3_L3_decode_scalefactors(const ma_uint8 *hdr, ma_uint8 *ist_ scf[i] = ma_dr_mp3_L3_ldexp_q2(gain, iscf[i] << scf_shift); } } -static const float g_ma_dr_mp3_pow43[129 + 16] = { +#if (defined(__GNUC__) && (__GNUC__ >= 14)) && !defined(__clang__) + #pragma GCC diagnostic pop +#endif +static const float ma_dr_mp3_g_pow43[129 + 16] = { 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f }; @@ -79835,7 +81619,7 @@ static float ma_dr_mp3_L3_pow_43(int x) int sign, mult = 256; if (x < 129) { - return g_ma_dr_mp3_pow43[16 + x]; + return ma_dr_mp3_g_pow43[16 + x]; } if (x < 1024) { @@ -79844,7 +81628,7 @@ static float ma_dr_mp3_L3_pow_43(int x) } sign = 2*x & 64; frac = (float)((x & 63) - sign) / ((x & ~63) + sign); - return g_ma_dr_mp3_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; + return ma_dr_mp3_g_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; } static void ma_dr_mp3_L3_huffman(float *dst, ma_dr_mp3_bs *bs, const ma_dr_mp3_L3_gr_info *gr_info, const float *scf, int layer3gr_limit) { @@ -79914,7 +81698,7 @@ static void ma_dr_mp3_L3_huffman(float *dst, ma_dr_mp3_bs *bs, const ma_dr_mp3_L *dst = one*ma_dr_mp3_L3_pow_43(lsb)*((ma_int32)bs_cache < 0 ? -1: 1); } else { - *dst = g_ma_dr_mp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + *dst = ma_dr_mp3_g_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; } MA_DR_MP3_FLUSH_BITS(lsb ? 1 : 0); } @@ -79942,7 +81726,7 @@ static void ma_dr_mp3_L3_huffman(float *dst, ma_dr_mp3_bs *bs, const ma_dr_mp3_L for (j = 0; j < 2; j++, dst++, leaf >>= 4) { int lsb = leaf & 0x0F; - *dst = g_ma_dr_mp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + *dst = ma_dr_mp3_g_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; MA_DR_MP3_FLUSH_BITS(lsb ? 1 : 0); } MA_DR_MP3_CHECK_BITS; @@ -80773,7 +82557,7 @@ MA_API int ma_dr_mp3dec_decode_frame(ma_dr_mp3dec *dec, const ma_uint8 *mp3, int MA_DR_MP3_COPY_MEMORY(dec->header, hdr, MA_DR_MP3_HDR_SIZE); info->frame_bytes = i + frame_size; info->channels = MA_DR_MP3_HDR_IS_MONO(hdr) ? 1 : 2; - info->hz = ma_dr_mp3_hdr_sample_rate_hz(hdr); + info->sample_rate = ma_dr_mp3_hdr_sample_rate_hz(hdr); info->layer = 4 - MA_DR_MP3_HDR_GET_LAYER(hdr); info->bitrate_kbps = ma_dr_mp3_hdr_bitrate_kbps(hdr); ma_dr_mp3_bs_init(bs_frame, hdr + MA_DR_MP3_HDR_SIZE, frame_size - MA_DR_MP3_HDR_SIZE); @@ -80996,19 +82780,41 @@ static ma_allocation_callbacks ma_dr_mp3_copy_allocation_callbacks_or_defaults(c } static size_t ma_dr_mp3__on_read(ma_dr_mp3* pMP3, void* pBufferOut, size_t bytesToRead) { - size_t bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); + size_t bytesRead; + MA_DR_MP3_ASSERT(pMP3 != NULL); + MA_DR_MP3_ASSERT(pMP3->onRead != NULL); + if (bytesToRead == 0) { + return 0; + } + bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); pMP3->streamCursor += bytesRead; return bytesRead; } +static size_t ma_dr_mp3__on_read_clamped(ma_dr_mp3* pMP3, void* pBufferOut, size_t bytesToRead) +{ + MA_DR_MP3_ASSERT(pMP3 != NULL); + MA_DR_MP3_ASSERT(pMP3->onRead != NULL); + if (pMP3->streamLength == MA_UINT64_MAX) { + return ma_dr_mp3__on_read(pMP3, pBufferOut, bytesToRead); + } else { + ma_uint64 bytesRemaining; + bytesRemaining = (pMP3->streamLength - pMP3->streamCursor); + if (bytesToRead > bytesRemaining) { + bytesToRead = (size_t)bytesRemaining; + } + return ma_dr_mp3__on_read(pMP3, pBufferOut, bytesToRead); + } +} static ma_bool32 ma_dr_mp3__on_seek(ma_dr_mp3* pMP3, int offset, ma_dr_mp3_seek_origin origin) { MA_DR_MP3_ASSERT(offset >= 0); + MA_DR_MP3_ASSERT(origin == MA_DR_MP3_SEEK_SET || origin == MA_DR_MP3_SEEK_CUR); if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) { return MA_FALSE; } - if (origin == ma_dr_mp3_seek_origin_start) { + if (origin == MA_DR_MP3_SEEK_SET) { pMP3->streamCursor = (ma_uint64)offset; - } else { + } else{ pMP3->streamCursor += offset; } return MA_TRUE; @@ -81018,18 +82824,18 @@ static ma_bool32 ma_dr_mp3__on_seek_64(ma_dr_mp3* pMP3, ma_uint64 offset, ma_dr_ if (offset <= 0x7FFFFFFF) { return ma_dr_mp3__on_seek(pMP3, (int)offset, origin); } - if (!ma_dr_mp3__on_seek(pMP3, 0x7FFFFFFF, ma_dr_mp3_seek_origin_start)) { + if (!ma_dr_mp3__on_seek(pMP3, 0x7FFFFFFF, MA_DR_MP3_SEEK_SET)) { return MA_FALSE; } offset -= 0x7FFFFFFF; while (offset > 0) { if (offset <= 0x7FFFFFFF) { - if (!ma_dr_mp3__on_seek(pMP3, (int)offset, ma_dr_mp3_seek_origin_current)) { + if (!ma_dr_mp3__on_seek(pMP3, (int)offset, MA_DR_MP3_SEEK_CUR)) { return MA_FALSE; } offset = 0; } else { - if (!ma_dr_mp3__on_seek(pMP3, 0x7FFFFFFF, ma_dr_mp3_seek_origin_current)) { + if (!ma_dr_mp3__on_seek(pMP3, 0x7FFFFFFF, MA_DR_MP3_SEEK_CUR)) { return MA_FALSE; } offset -= 0x7FFFFFFF; @@ -81037,7 +82843,18 @@ static ma_bool32 ma_dr_mp3__on_seek_64(ma_dr_mp3* pMP3, ma_uint64 offset, ma_dr_ } return MA_TRUE; } -static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_dr_mp3d_sample_t* pPCMFrames) +static void ma_dr_mp3__on_meta(ma_dr_mp3* pMP3, ma_dr_mp3_metadata_type type, const void* pRawData, size_t rawDataSize) +{ + if (pMP3->onMeta) { + ma_dr_mp3_metadata metadata; + MA_DR_MP3_ZERO_OBJECT(&metadata); + metadata.type = type; + metadata.pRawData = pRawData; + metadata.rawDataSize = rawDataSize; + pMP3->onMeta(pMP3->pUserDataMeta, &metadata); + } +} +static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_dr_mp3d_sample_t* pPCMFrames, ma_dr_mp3dec_frame_info* pMP3FrameInfo, const ma_uint8** ppMP3FrameData) { ma_uint32 pcmFramesRead = 0; MA_DR_MP3_ASSERT(pMP3 != NULL); @@ -81064,7 +82881,7 @@ static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_d pMP3->pData = pNewData; pMP3->dataCapacity = newDataCap; } - bytesRead = ma_dr_mp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + bytesRead = ma_dr_mp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { if (pMP3->dataSize == 0) { pMP3->atEnd = MA_TRUE; @@ -81083,16 +82900,20 @@ static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_d return 0; } pcmFramesRead = ma_dr_mp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); - if (info.frame_bytes > 0) { - pMP3->dataConsumed += (size_t)info.frame_bytes; - pMP3->dataSize -= (size_t)info.frame_bytes; - } + pMP3->dataConsumed += (size_t)info.frame_bytes; + pMP3->dataSize -= (size_t)info.frame_bytes; if (pcmFramesRead > 0) { pcmFramesRead = ma_dr_mp3_hdr_frame_samples(pMP3->decoder.header); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; - pMP3->mp3FrameSampleRate = info.hz; + pMP3->mp3FrameSampleRate = info.sample_rate; + if (pMP3FrameInfo != NULL) { + *pMP3FrameInfo = info; + } + if (ppMP3FrameData != NULL) { + *ppMP3FrameData = pMP3->pData + pMP3->dataConsumed - (size_t)info.frame_bytes; + } break; } else if (info.frame_bytes == 0) { size_t bytesRead; @@ -81109,7 +82930,7 @@ static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_d pMP3->pData = pNewData; pMP3->dataCapacity = newDataCap; } - bytesRead = ma_dr_mp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + bytesRead = ma_dr_mp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { pMP3->atEnd = MA_TRUE; return 0; @@ -81119,7 +82940,7 @@ static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_d }; return pcmFramesRead; } -static ma_uint32 ma_dr_mp3_decode_next_frame_ex__memory(ma_dr_mp3* pMP3, ma_dr_mp3d_sample_t* pPCMFrames) +static ma_uint32 ma_dr_mp3_decode_next_frame_ex__memory(ma_dr_mp3* pMP3, ma_dr_mp3d_sample_t* pPCMFrames, ma_dr_mp3dec_frame_info* pMP3FrameInfo, const ma_uint8** ppMP3FrameData) { ma_uint32 pcmFramesRead = 0; ma_dr_mp3dec_frame_info info; @@ -81135,36 +82956,44 @@ static ma_uint32 ma_dr_mp3_decode_next_frame_ex__memory(ma_dr_mp3* pMP3, ma_dr_m pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; - pMP3->mp3FrameSampleRate = info.hz; + pMP3->mp3FrameSampleRate = info.sample_rate; + if (pMP3FrameInfo != NULL) { + *pMP3FrameInfo = info; + } + if (ppMP3FrameData != NULL) { + *ppMP3FrameData = pMP3->memory.pData + pMP3->memory.currentReadPos; + } break; } else if (info.frame_bytes > 0) { pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + pMP3->streamCursor += (size_t)info.frame_bytes; } else { break; } } pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + pMP3->streamCursor += (size_t)info.frame_bytes; return pcmFramesRead; } -static ma_uint32 ma_dr_mp3_decode_next_frame_ex(ma_dr_mp3* pMP3, ma_dr_mp3d_sample_t* pPCMFrames) +static ma_uint32 ma_dr_mp3_decode_next_frame_ex(ma_dr_mp3* pMP3, ma_dr_mp3d_sample_t* pPCMFrames, ma_dr_mp3dec_frame_info* pMP3FrameInfo, const ma_uint8** ppMP3FrameData) { if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) { - return ma_dr_mp3_decode_next_frame_ex__memory(pMP3, pPCMFrames); + return ma_dr_mp3_decode_next_frame_ex__memory(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData); } else { - return ma_dr_mp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames); + return ma_dr_mp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData); } } static ma_uint32 ma_dr_mp3_decode_next_frame(ma_dr_mp3* pMP3) { MA_DR_MP3_ASSERT(pMP3 != NULL); - return ma_dr_mp3_decode_next_frame_ex(pMP3, (ma_dr_mp3d_sample_t*)pMP3->pcmFrames); + return ma_dr_mp3_decode_next_frame_ex(pMP3, (ma_dr_mp3d_sample_t*)pMP3->pcmFrames, NULL, NULL); } #if 0 static ma_uint32 ma_dr_mp3_seek_next_frame(ma_dr_mp3* pMP3) { ma_uint32 pcmFrameCount; MA_DR_MP3_ASSERT(pMP3 != NULL); - pcmFrameCount = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL); + pcmFrameCount = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFrameCount == 0) { return 0; } @@ -81174,33 +83003,249 @@ static ma_uint32 ma_dr_mp3_seek_next_frame(ma_dr_mp3* pMP3) return pcmFrameCount; } #endif -static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) +static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, ma_dr_mp3_tell_proc onTell, ma_dr_mp3_meta_proc onMeta, void* pUserData, void* pUserDataMeta, const ma_allocation_callbacks* pAllocationCallbacks) { + ma_dr_mp3dec_frame_info firstFrameInfo; + const ma_uint8* pFirstFrameData; + ma_uint32 firstFramePCMFrameCount; + ma_uint32 detectedMP3FrameCount = 0xFFFFFFFF; MA_DR_MP3_ASSERT(pMP3 != NULL); MA_DR_MP3_ASSERT(onRead != NULL); ma_dr_mp3dec_init(&pMP3->decoder); pMP3->onRead = onRead; pMP3->onSeek = onSeek; + pMP3->onMeta = onMeta; pMP3->pUserData = pUserData; + pMP3->pUserDataMeta = pUserDataMeta; pMP3->allocationCallbacks = ma_dr_mp3_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); if (pMP3->allocationCallbacks.onFree == NULL || (pMP3->allocationCallbacks.onMalloc == NULL && pMP3->allocationCallbacks.onRealloc == NULL)) { return MA_FALSE; } - if (ma_dr_mp3_decode_next_frame(pMP3) == 0) { + pMP3->streamCursor = 0; + pMP3->streamLength = MA_UINT64_MAX; + pMP3->streamStartOffset = 0; + pMP3->delayInPCMFrames = 0; + pMP3->paddingInPCMFrames = 0; + pMP3->totalPCMFrameCount = MA_UINT64_MAX; + #if 1 + if (onSeek != NULL && onTell != NULL) { + if (onSeek(pUserData, 0, MA_DR_MP3_SEEK_END)) { + ma_int64 streamLen; + int streamEndOffset = 0; + if (onTell(pUserData, &streamLen)) { + if (streamLen > 128) { + char id3[3]; + if (onSeek(pUserData, streamEndOffset - 128, MA_DR_MP3_SEEK_END)) { + if (onRead(pUserData, id3, 3) == 3 && id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G') { + streamEndOffset -= 128; + streamLen -= 128; + if (onMeta != NULL) { + ma_uint8 tag[128]; + tag[0] = 'T'; tag[1] = 'A'; tag[2] = 'G'; + if (onRead(pUserData, tag + 3, 125) == 125) { + ma_dr_mp3__on_meta(pMP3, MA_DR_MP3_METADATA_TYPE_ID3V1, tag, 128); + } + } + } else { + } + } else { + } + } else { + } + if (streamLen > 32) { + char ape[32]; + if (onSeek(pUserData, streamEndOffset - 32, MA_DR_MP3_SEEK_END)) { + if (onRead(pUserData, ape, 32) == 32 && ape[0] == 'A' && ape[1] == 'P' && ape[2] == 'E' && ape[3] == 'T' && ape[4] == 'A' && ape[5] == 'G' && ape[6] == 'E' && ape[7] == 'X') { + ma_uint32 tagSize = + ((ma_uint32)ape[24] << 0) | + ((ma_uint32)ape[25] << 8) | + ((ma_uint32)ape[26] << 16) | + ((ma_uint32)ape[27] << 24); + streamEndOffset -= 32 + tagSize; + streamLen -= 32 + tagSize; + if (onMeta != NULL) { + if (onSeek(pUserData, streamEndOffset, MA_DR_MP3_SEEK_END)) { + size_t apeTagSize = (size_t)tagSize + 32; + ma_uint8* pTagData = (ma_uint8*)ma_dr_mp3_malloc(apeTagSize, pAllocationCallbacks); + if (pTagData != NULL) { + if (onRead(pUserData, pTagData, apeTagSize) == apeTagSize) { + ma_dr_mp3__on_meta(pMP3, MA_DR_MP3_METADATA_TYPE_APE, pTagData, apeTagSize); + } + ma_dr_mp3_free(pTagData, pAllocationCallbacks); + } + } + } + } + } + } else { + } + if (!onSeek(pUserData, 0, MA_DR_MP3_SEEK_SET)) { + return MA_FALSE; + } + pMP3->streamLength = (ma_uint64)streamLen; + if (pMP3->memory.pData != NULL) { + pMP3->memory.dataSize = (size_t)pMP3->streamLength; + } + } else { + if (!onSeek(pUserData, 0, MA_DR_MP3_SEEK_SET)) { + return MA_FALSE; + } + } + } else { + } + } else { + } + #endif + #if 1 + { + char header[10]; + if (onRead(pUserData, header, 10) == 10) { + if (header[0] == 'I' && header[1] == 'D' && header[2] == '3') { + ma_uint32 tagSize = + (((ma_uint32)header[6] & 0x7F) << 21) | + (((ma_uint32)header[7] & 0x7F) << 14) | + (((ma_uint32)header[8] & 0x7F) << 7) | + (((ma_uint32)header[9] & 0x7F) << 0); + if (header[5] & 0x10) { + tagSize += 10; + } + if (onMeta != NULL) { + size_t tagSizeWithHeader = 10 + tagSize; + ma_uint8* pTagData = (ma_uint8*)ma_dr_mp3_malloc(tagSizeWithHeader, pAllocationCallbacks); + if (pTagData != NULL) { + MA_DR_MP3_COPY_MEMORY(pTagData, header, 10); + if (onRead(pUserData, pTagData + 10, tagSize) == tagSize) { + ma_dr_mp3__on_meta(pMP3, MA_DR_MP3_METADATA_TYPE_ID3V2, pTagData, tagSizeWithHeader); + } + ma_dr_mp3_free(pTagData, pAllocationCallbacks); + } + } else { + if (onSeek != NULL) { + if (!onSeek(pUserData, tagSize, MA_DR_MP3_SEEK_CUR)) { + return MA_FALSE; + } + } else { + char discard[1024]; + while (tagSize > 0) { + size_t bytesToRead = tagSize; + if (bytesToRead > sizeof(discard)) { + bytesToRead = sizeof(discard); + } + if (onRead(pUserData, discard, bytesToRead) != bytesToRead) { + return MA_FALSE; + } + tagSize -= (ma_uint32)bytesToRead; + } + } + } + pMP3->streamStartOffset += 10 + tagSize; + pMP3->streamCursor = pMP3->streamStartOffset; + } else { + if (onSeek != NULL) { + if (!onSeek(pUserData, 0, MA_DR_MP3_SEEK_SET)) { + return MA_FALSE; + } + } else { + } + } + } else { + return MA_FALSE; + } + } + #endif + firstFramePCMFrameCount = ma_dr_mp3_decode_next_frame_ex(pMP3, (ma_dr_mp3d_sample_t*)pMP3->pcmFrames, &firstFrameInfo, &pFirstFrameData); + if (firstFramePCMFrameCount > 0) { + MA_DR_MP3_ASSERT(pFirstFrameData != NULL); + #if 1 + MA_DR_MP3_ASSERT(firstFrameInfo.frame_bytes > 0); + { + ma_dr_mp3_bs bs; + ma_dr_mp3_L3_gr_info grInfo[4]; + const ma_uint8* pTagData = pFirstFrameData; + ma_dr_mp3_bs_init(&bs, pFirstFrameData + MA_DR_MP3_HDR_SIZE, firstFrameInfo.frame_bytes - MA_DR_MP3_HDR_SIZE); + if (MA_DR_MP3_HDR_IS_CRC(pFirstFrameData)) { + ma_dr_mp3_bs_get_bits(&bs, 16); + } + if (ma_dr_mp3_L3_read_side_info(&bs, grInfo, pFirstFrameData) >= 0) { + ma_bool32 isXing = MA_FALSE; + ma_bool32 isInfo = MA_FALSE; + const ma_uint8* pTagDataBeg; + pTagDataBeg = pFirstFrameData + MA_DR_MP3_HDR_SIZE + (bs.pos/8); + pTagData = pTagDataBeg; + isXing = (pTagData[0] == 'X' && pTagData[1] == 'i' && pTagData[2] == 'n' && pTagData[3] == 'g'); + isInfo = (pTagData[0] == 'I' && pTagData[1] == 'n' && pTagData[2] == 'f' && pTagData[3] == 'o'); + if (isXing || isInfo) { + ma_uint32 bytes = 0; + ma_uint32 flags = pTagData[7]; + pTagData += 8; + if (flags & 0x01) { + detectedMP3FrameCount = (ma_uint32)pTagData[0] << 24 | (ma_uint32)pTagData[1] << 16 | (ma_uint32)pTagData[2] << 8 | (ma_uint32)pTagData[3]; + pTagData += 4; + } + if (flags & 0x02) { + bytes = (ma_uint32)pTagData[0] << 24 | (ma_uint32)pTagData[1] << 16 | (ma_uint32)pTagData[2] << 8 | (ma_uint32)pTagData[3]; + (void)bytes; + pTagData += 4; + } + if (flags & 0x04) { + pTagData += 100; + } + if (flags & 0x08) { + pTagData += 4; + } + if (pTagData[0]) { + pTagData += 21; + if (pTagData - pFirstFrameData + 14 < firstFrameInfo.frame_bytes) { + int delayInPCMFrames; + int paddingInPCMFrames; + delayInPCMFrames = (( (ma_uint32)pTagData[0] << 4) | ((ma_uint32)pTagData[1] >> 4)) + (528 + 1); + paddingInPCMFrames = ((((ma_uint32)pTagData[1] & 0xF) << 8) | ((ma_uint32)pTagData[2] )) - (528 + 1); + if (paddingInPCMFrames < 0) { + paddingInPCMFrames = 0; + } + pMP3->delayInPCMFrames = (ma_uint32)delayInPCMFrames; + pMP3->paddingInPCMFrames = (ma_uint32)paddingInPCMFrames; + } + } + if (isXing) { + pMP3->isVBR = MA_TRUE; + } else if (isInfo) { + pMP3->isCBR = MA_TRUE; + } + if (onMeta != NULL) { + ma_dr_mp3_metadata_type metadataType = isXing ? MA_DR_MP3_METADATA_TYPE_XING : MA_DR_MP3_METADATA_TYPE_VBRI; + size_t tagDataSize; + tagDataSize = (size_t)firstFrameInfo.frame_bytes; + tagDataSize -= (size_t)(pTagDataBeg - pFirstFrameData); + ma_dr_mp3__on_meta(pMP3, metadataType, pTagDataBeg, tagDataSize); + } + pMP3->pcmFramesRemainingInMP3Frame = 0; + pMP3->streamStartOffset += (ma_uint32)(firstFrameInfo.frame_bytes); + pMP3->streamCursor = pMP3->streamStartOffset; + ma_dr_mp3dec_init(&pMP3->decoder); + } + } else { + } + } + #endif + } else { ma_dr_mp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); return MA_FALSE; } + if (detectedMP3FrameCount != 0xFFFFFFFF) { + pMP3->totalPCMFrameCount = detectedMP3FrameCount * firstFramePCMFrameCount; + } pMP3->channels = pMP3->mp3FrameChannels; pMP3->sampleRate = pMP3->mp3FrameSampleRate; return MA_TRUE; } -MA_API ma_bool32 ma_dr_mp3_init(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_bool32 ma_dr_mp3_init(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, ma_dr_mp3_tell_proc onTell, ma_dr_mp3_meta_proc onMeta, void* pUserData, const ma_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL || onRead == NULL) { return MA_FALSE; } MA_DR_MP3_ZERO_OBJECT(pMP3); - return ma_dr_mp3_init_internal(pMP3, onRead, onSeek, pUserData, pAllocationCallbacks); + return ma_dr_mp3_init_internal(pMP3, onRead, onSeek, onTell, onMeta, pUserData, pUserData, pAllocationCallbacks); } static size_t ma_dr_mp3__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) { @@ -81221,29 +83266,40 @@ static size_t ma_dr_mp3__on_read_memory(void* pUserData, void* pBufferOut, size_ static ma_bool32 ma_dr_mp3__on_seek_memory(void* pUserData, int byteOffset, ma_dr_mp3_seek_origin origin) { ma_dr_mp3* pMP3 = (ma_dr_mp3*)pUserData; + ma_int64 newCursor; MA_DR_MP3_ASSERT(pMP3 != NULL); - if (origin == ma_dr_mp3_seek_origin_current) { - if (byteOffset > 0) { - if (pMP3->memory.currentReadPos + byteOffset > pMP3->memory.dataSize) { - byteOffset = (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos); - } - } else { - if (pMP3->memory.currentReadPos < (size_t)-byteOffset) { - byteOffset = -(int)pMP3->memory.currentReadPos; - } - } - pMP3->memory.currentReadPos += byteOffset; + newCursor = pMP3->memory.currentReadPos; + if (origin == MA_DR_MP3_SEEK_SET) { + newCursor = 0; + } else if (origin == MA_DR_MP3_SEEK_CUR) { + newCursor = (ma_int64)pMP3->memory.currentReadPos; + } else if (origin == MA_DR_MP3_SEEK_END) { + newCursor = (ma_int64)pMP3->memory.dataSize; } else { - if ((ma_uint32)byteOffset <= pMP3->memory.dataSize) { - pMP3->memory.currentReadPos = byteOffset; - } else { - pMP3->memory.currentReadPos = pMP3->memory.dataSize; - } + MA_DR_MP3_ASSERT(!"Invalid seek origin"); + return MA_FALSE; } + newCursor += byteOffset; + if (newCursor < 0) { + return MA_FALSE; + } + if ((size_t)newCursor > pMP3->memory.dataSize) { + return MA_FALSE; + } + pMP3->memory.currentReadPos = (size_t)newCursor; return MA_TRUE; } -MA_API ma_bool32 ma_dr_mp3_init_memory(ma_dr_mp3* pMP3, const void* pData, size_t dataSize, const ma_allocation_callbacks* pAllocationCallbacks) +static ma_bool32 ma_dr_mp3__on_tell_memory(void* pUserData, ma_int64* pCursor) { + ma_dr_mp3* pMP3 = (ma_dr_mp3*)pUserData; + MA_DR_MP3_ASSERT(pMP3 != NULL); + MA_DR_MP3_ASSERT(pCursor != NULL); + *pCursor = (ma_int64)pMP3->memory.currentReadPos; + return MA_TRUE; +} +MA_API ma_bool32 ma_dr_mp3_init_memory_with_metadata(ma_dr_mp3* pMP3, const void* pData, size_t dataSize, ma_dr_mp3_meta_proc onMeta, void* pUserDataMeta, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_bool32 result; if (pMP3 == NULL) { return MA_FALSE; } @@ -81254,7 +83310,21 @@ MA_API ma_bool32 ma_dr_mp3_init_memory(ma_dr_mp3* pMP3, const void* pData, size_ pMP3->memory.pData = (const ma_uint8*)pData; pMP3->memory.dataSize = dataSize; pMP3->memory.currentReadPos = 0; - return ma_dr_mp3_init_internal(pMP3, ma_dr_mp3__on_read_memory, ma_dr_mp3__on_seek_memory, pMP3, pAllocationCallbacks); + result = ma_dr_mp3_init_internal(pMP3, ma_dr_mp3__on_read_memory, ma_dr_mp3__on_seek_memory, ma_dr_mp3__on_tell_memory, onMeta, pMP3, pUserDataMeta, pAllocationCallbacks); + if (result == MA_FALSE) { + return MA_FALSE; + } + if (pMP3->streamLength <= (ma_uint64)MA_SIZE_MAX) { + pMP3->memory.dataSize = (size_t)pMP3->streamLength; + } + if (pMP3->streamStartOffset > (ma_uint64)MA_SIZE_MAX) { + return MA_FALSE; + } + return MA_TRUE; +} +MA_API ma_bool32 ma_dr_mp3_init_memory(ma_dr_mp3* pMP3, const void* pData, size_t dataSize, const ma_allocation_callbacks* pAllocationCallbacks) +{ + return ma_dr_mp3_init_memory_with_metadata(pMP3, pData, dataSize, NULL, NULL, pAllocationCallbacks); } #ifndef MA_DR_MP3_NO_STDIO #include @@ -81265,36 +83335,76 @@ static size_t ma_dr_mp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t } static ma_bool32 ma_dr_mp3__on_seek_stdio(void* pUserData, int offset, ma_dr_mp3_seek_origin origin) { - return fseek((FILE*)pUserData, offset, (origin == ma_dr_mp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + int whence = SEEK_SET; + if (origin == MA_DR_MP3_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == MA_DR_MP3_SEEK_END) { + whence = SEEK_END; + } + return fseek((FILE*)pUserData, offset, whence) == 0; } -MA_API ma_bool32 ma_dr_mp3_init_file(ma_dr_mp3* pMP3, const char* pFilePath, const ma_allocation_callbacks* pAllocationCallbacks) +static ma_bool32 ma_dr_mp3__on_tell_stdio(void* pUserData, ma_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + ma_int64 result; + MA_DR_MP3_ASSERT(pFileStdio != NULL); + MA_DR_MP3_ASSERT(pCursor != NULL); +#if defined(_WIN32) && !defined(NXDK) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + *pCursor = result; + return MA_TRUE; +} +MA_API ma_bool32 ma_dr_mp3_init_file_with_metadata(ma_dr_mp3* pMP3, const char* pFilePath, ma_dr_mp3_meta_proc onMeta, void* pUserDataMeta, const ma_allocation_callbacks* pAllocationCallbacks) { ma_bool32 result; FILE* pFile; + if (pMP3 == NULL) { + return MA_FALSE; + } + MA_DR_MP3_ZERO_OBJECT(pMP3); if (ma_fopen(&pFile, pFilePath, "rb") != MA_SUCCESS) { return MA_FALSE; } - result = ma_dr_mp3_init(pMP3, ma_dr_mp3__on_read_stdio, ma_dr_mp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = ma_dr_mp3_init_internal(pMP3, ma_dr_mp3__on_read_stdio, ma_dr_mp3__on_seek_stdio, ma_dr_mp3__on_tell_stdio, onMeta, (void*)pFile, pUserDataMeta, pAllocationCallbacks); if (result != MA_TRUE) { fclose(pFile); return result; } return MA_TRUE; } -MA_API ma_bool32 ma_dr_mp3_init_file_w(ma_dr_mp3* pMP3, const wchar_t* pFilePath, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_bool32 ma_dr_mp3_init_file_with_metadata_w(ma_dr_mp3* pMP3, const wchar_t* pFilePath, ma_dr_mp3_meta_proc onMeta, void* pUserDataMeta, const ma_allocation_callbacks* pAllocationCallbacks) { ma_bool32 result; FILE* pFile; + if (pMP3 == NULL) { + return MA_FALSE; + } + MA_DR_MP3_ZERO_OBJECT(pMP3); if (ma_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != MA_SUCCESS) { return MA_FALSE; } - result = ma_dr_mp3_init(pMP3, ma_dr_mp3__on_read_stdio, ma_dr_mp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = ma_dr_mp3_init_internal(pMP3, ma_dr_mp3__on_read_stdio, ma_dr_mp3__on_seek_stdio, ma_dr_mp3__on_tell_stdio, onMeta, (void*)pFile, pUserDataMeta, pAllocationCallbacks); if (result != MA_TRUE) { fclose(pFile); return result; } return MA_TRUE; } +MA_API ma_bool32 ma_dr_mp3_init_file(ma_dr_mp3* pMP3, const char* pFilePath, const ma_allocation_callbacks* pAllocationCallbacks) +{ + return ma_dr_mp3_init_file_with_metadata(pMP3, pFilePath, NULL, NULL, pAllocationCallbacks); +} +MA_API ma_bool32 ma_dr_mp3_init_file_w(ma_dr_mp3* pMP3, const wchar_t* pFilePath, const ma_allocation_callbacks* pAllocationCallbacks) +{ + return ma_dr_mp3_init_file_with_metadata_w(pMP3, pFilePath, NULL, NULL, pAllocationCallbacks); +} #endif MA_API void ma_dr_mp3_uninit(ma_dr_mp3* pMP3) { @@ -81364,17 +83474,38 @@ static ma_uint64 ma_dr_mp3_read_pcm_frames_raw(ma_dr_mp3* pMP3, ma_uint64 frames MA_DR_MP3_ASSERT(pMP3 != NULL); MA_DR_MP3_ASSERT(pMP3->onRead != NULL); while (framesToRead > 0) { - ma_uint32 framesToConsume = (ma_uint32)MA_DR_MP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); + ma_uint32 framesToConsume; + if (pMP3->currentPCMFrame < pMP3->delayInPCMFrames) { + ma_uint32 framesToSkip = (ma_uint32)MA_DR_MP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, pMP3->delayInPCMFrames - pMP3->currentPCMFrame); + pMP3->currentPCMFrame += framesToSkip; + pMP3->pcmFramesConsumedInMP3Frame += framesToSkip; + pMP3->pcmFramesRemainingInMP3Frame -= framesToSkip; + } + framesToConsume = (ma_uint32)MA_DR_MP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); + if (pMP3->totalPCMFrameCount != MA_UINT64_MAX && pMP3->totalPCMFrameCount > pMP3->paddingInPCMFrames) { + if (pMP3->currentPCMFrame < (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames)) { + ma_uint64 framesRemainigToPadding = (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames) - pMP3->currentPCMFrame; + if (framesToConsume > framesRemainigToPadding) { + framesToConsume = (ma_uint32)framesRemainigToPadding; + } + } else { + break; + } + } if (pBufferOut != NULL) { - #if defined(MA_DR_MP3_FLOAT_OUTPUT) - float* pFramesOutF32 = (float*)MA_DR_MP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); - float* pFramesInF32 = (float*)MA_DR_MP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); - MA_DR_MP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); - #else - ma_int16* pFramesOutS16 = (ma_int16*)MA_DR_MP3_OFFSET_PTR(pBufferOut, sizeof(ma_int16) * totalFramesRead * pMP3->channels); - ma_int16* pFramesInS16 = (ma_int16*)MA_DR_MP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(ma_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); - MA_DR_MP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(ma_int16) * framesToConsume * pMP3->channels); - #endif + #if defined(MA_DR_MP3_FLOAT_OUTPUT) + { + float* pFramesOutF32 = (float*)MA_DR_MP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); + float* pFramesInF32 = (float*)MA_DR_MP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + MA_DR_MP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); + } + #else + { + ma_int16* pFramesOutS16 = (ma_int16*)MA_DR_MP3_OFFSET_PTR(pBufferOut, sizeof(ma_int16) * totalFramesRead * pMP3->channels); + ma_int16* pFramesInS16 = (ma_int16*)MA_DR_MP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(ma_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + MA_DR_MP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(ma_int16) * framesToConsume * pMP3->channels); + } + #endif } pMP3->currentPCMFrame += framesToConsume; pMP3->pcmFramesConsumedInMP3Frame += framesToConsume; @@ -81384,6 +83515,9 @@ static ma_uint64 ma_dr_mp3_read_pcm_frames_raw(ma_dr_mp3* pMP3, ma_uint64 frames if (framesToRead == 0) { break; } + if (pMP3->totalPCMFrameCount != MA_UINT64_MAX && pMP3->totalPCMFrameCount > pMP3->paddingInPCMFrames && pMP3->currentPCMFrame >= (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames)) { + break; + } MA_DR_MP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 0); if (ma_dr_mp3_decode_next_frame(pMP3) == 0) { break; @@ -81463,7 +83597,7 @@ static ma_bool32 ma_dr_mp3_seek_to_start_of_stream(ma_dr_mp3* pMP3) { MA_DR_MP3_ASSERT(pMP3 != NULL); MA_DR_MP3_ASSERT(pMP3->onSeek != NULL); - if (!ma_dr_mp3__on_seek(pMP3, 0, ma_dr_mp3_seek_origin_start)) { + if (!ma_dr_mp3__on_seek_64(pMP3, pMP3->streamStartOffset, MA_DR_MP3_SEEK_SET)) { return MA_FALSE; } ma_dr_mp3_reset(pMP3); @@ -81529,7 +83663,7 @@ static ma_bool32 ma_dr_mp3_seek_to_pcm_frame__seek_table(ma_dr_mp3* pMP3, ma_uin seekPoint.mp3FramesToDiscard = 0; seekPoint.pcmFramesToDiscard = 0; } - if (!ma_dr_mp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, ma_dr_mp3_seek_origin_start)) { + if (!ma_dr_mp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, MA_DR_MP3_SEEK_SET)) { return MA_FALSE; } ma_dr_mp3_reset(pMP3); @@ -81540,7 +83674,7 @@ static ma_bool32 ma_dr_mp3_seek_to_pcm_frame__seek_table(ma_dr_mp3* pMP3, ma_uin if (iMP3Frame == seekPoint.mp3FramesToDiscard-1) { pPCMFrames = (ma_dr_mp3d_sample_t*)pMP3->pcmFrames; } - pcmFramesRead = ma_dr_mp3_decode_next_frame_ex(pMP3, pPCMFrames); + pcmFramesRead = ma_dr_mp3_decode_next_frame_ex(pMP3, pPCMFrames, NULL, NULL); if (pcmFramesRead == 0) { return MA_FALSE; } @@ -81582,7 +83716,7 @@ MA_API ma_bool32 ma_dr_mp3_get_mp3_and_pcm_frame_count(ma_dr_mp3* pMP3, ma_uint6 totalMP3FrameCount = 0; for (;;) { ma_uint32 pcmFramesInCurrentMP3Frame; - pcmFramesInCurrentMP3Frame = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3Frame = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3Frame == 0) { break; } @@ -81606,10 +83740,26 @@ MA_API ma_bool32 ma_dr_mp3_get_mp3_and_pcm_frame_count(ma_dr_mp3* pMP3, ma_uint6 MA_API ma_uint64 ma_dr_mp3_get_pcm_frame_count(ma_dr_mp3* pMP3) { ma_uint64 totalPCMFrameCount; - if (!ma_dr_mp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { + if (pMP3 == NULL) { return 0; } - return totalPCMFrameCount; + if (pMP3->totalPCMFrameCount != MA_UINT64_MAX) { + totalPCMFrameCount = pMP3->totalPCMFrameCount; + if (totalPCMFrameCount >= pMP3->delayInPCMFrames) { + totalPCMFrameCount -= pMP3->delayInPCMFrames; + } else { + } + if (totalPCMFrameCount >= pMP3->paddingInPCMFrames) { + totalPCMFrameCount -= pMP3->paddingInPCMFrames; + } else { + } + return totalPCMFrameCount; + } else { + if (!ma_dr_mp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { + return 0; + } + return totalPCMFrameCount; + } } MA_API ma_uint64 ma_dr_mp3_get_mp3_frame_count(ma_dr_mp3* pMP3) { @@ -81679,7 +83829,7 @@ MA_API ma_bool32 ma_dr_mp3_calculate_seek_points(ma_dr_mp3* pMP3, ma_uint32* pSe MA_DR_MP3_ASSERT(pMP3->streamCursor >= pMP3->dataSize); mp3FrameInfo[iMP3Frame].bytePos = pMP3->streamCursor - pMP3->dataSize; mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; - pcmFramesInCurrentMP3FrameIn = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3FrameIn = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { return MA_FALSE; } @@ -81703,7 +83853,7 @@ MA_API ma_bool32 ma_dr_mp3_calculate_seek_points(ma_dr_mp3* pMP3, ma_uint32* pSe } mp3FrameInfo[MA_DR_MP3_COUNTOF(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; mp3FrameInfo[MA_DR_MP3_COUNTOF(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; - pcmFramesInCurrentMP3FrameIn = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3FrameIn = ma_dr_mp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; @@ -81841,18 +83991,18 @@ static ma_int16* ma_dr_mp3__full_read_and_close_s16(ma_dr_mp3* pMP3, ma_dr_mp3_c } return pFrames; } -MA_API float* ma_dr_mp3_open_and_read_pcm_frames_f32(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API float* ma_dr_mp3_open_and_read_pcm_frames_f32(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, ma_dr_mp3_tell_proc onTell, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_mp3 mp3; - if (!ma_dr_mp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!ma_dr_mp3_init(&mp3, onRead, onSeek, onTell, NULL, pUserData, pAllocationCallbacks)) { return NULL; } return ma_dr_mp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -MA_API ma_int16* ma_dr_mp3_open_and_read_pcm_frames_s16(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_int16* ma_dr_mp3_open_and_read_pcm_frames_s16(ma_dr_mp3_read_proc onRead, ma_dr_mp3_seek_proc onSeek, ma_dr_mp3_tell_proc onTell, void* pUserData, ma_dr_mp3_config* pConfig, ma_uint64* pTotalFrameCount, const ma_allocation_callbacks* pAllocationCallbacks) { ma_dr_mp3 mp3; - if (!ma_dr_mp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!ma_dr_mp3_init(&mp3, onRead, onSeek, onTell, NULL, pUserData, pAllocationCallbacks)) { return NULL; } return ma_dr_mp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); diff --git a/extras/miniaudio_split/miniaudio.h b/extras/miniaudio_split/miniaudio.h index 595ea18a..24c613f3 100644 --- a/extras/miniaudio_split/miniaudio.h +++ b/extras/miniaudio_split/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.22 - 2025-02-24 +miniaudio - v0.11.23 - 2025-09-11 David Reid - mackron@gmail.com @@ -20,7 +20,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 11 -#define MA_VERSION_REVISION 22 +#define MA_VERSION_REVISION 23 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -127,6 +127,8 @@ typedef ma_uint16 wchar_t; #define MA_SIZE_MAX 0xFFFFFFFF /* When SIZE_MAX is not defined by the standard library just default to the maximum 32-bit unsigned integer. */ #endif +#define MA_UINT64_MAX (((ma_uint64)0xFFFFFFFF << 32) | (ma_uint64)0xFFFFFFFF) /* Weird shifting syntax is for VC6 compatibility. */ + /* Platform/backend detection. */ #if defined(_WIN32) || defined(__COSMOPOLITAN__) @@ -135,29 +137,55 @@ typedef ma_uint16 wchar_t; #define MA_WIN32_UWP #elif defined(WINAPI_FAMILY) && (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES) #define MA_WIN32_GDK + #elif defined(NXDK) + #define MA_WIN32_NXDK #else #define MA_WIN32_DESKTOP #endif + + /* The original Xbox. */ + #if defined(NXDK) /* <-- Add other Xbox compiler toolchains here, and then add a toolchain-specific define in case we need to discriminate between them later. */ + #define MA_XBOX + + #if defined(NXDK) + #define MA_XBOX_NXDK + #endif + #endif #endif -#if !defined(_WIN32) /* If it's not Win32, assume POSIX. */ +#if defined(__MSDOS__) || defined(MSDOS) || defined(_MSDOS) || defined(__DOS__) + #define MA_DOS + + /* No threading allowed on DOS. */ + #ifndef MA_NO_THREADING + #define MA_NO_THREADING + #endif + + /* No runtime linking allowed on DOS. */ + #ifndef MA_NO_RUNTIME_LINKING + #define MA_NO_RUNTIME_LINKING + #endif +#endif +#if !defined(MA_WIN32) && !defined(MA_DOS) /* If it's not Win32, assume POSIX. */ #define MA_POSIX - /* - Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented. - You can use this to avoid including pthread.h in the header section. The downside is that it - results in some fixed sized structures being declared for the various types that are used in - miniaudio. The risk here is that these types might be too small for a given platform. This - risk is yours to take and no support will be offered if you enable this option. - */ - #ifndef MA_NO_PTHREAD_IN_HEADER - #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ - typedef pthread_t ma_pthread_t; - typedef pthread_mutex_t ma_pthread_mutex_t; - typedef pthread_cond_t ma_pthread_cond_t; - #else - typedef ma_uintptr ma_pthread_t; - typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t; - typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t; + #if !defined(MA_NO_THREADING) + /* + Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented. + You can use this to avoid including pthread.h in the header section. The downside is that it + results in some fixed sized structures being declared for the various types that are used in + miniaudio. The risk here is that these types might be too small for a given platform. This + risk is yours to take and no support will be offered if you enable this option. + */ + #ifndef MA_NO_PTHREAD_IN_HEADER + #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ + typedef pthread_t ma_pthread_t; + typedef pthread_mutex_t ma_pthread_mutex_t; + typedef pthread_cond_t ma_pthread_cond_t; + #else + typedef ma_uintptr ma_pthread_t; + typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t; + typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t; + #endif #endif #if defined(__unix__) @@ -184,8 +212,11 @@ typedef ma_uint16 wchar_t; #if defined(__PROSPERO__) #define MA_PROSPERO #endif - #if defined(__NX__) - #define MA_NX + #if defined(__3DS__) + #define MA_3DS + #endif + #if defined(__SWITCH__) || defined(__NX__) + #define MA_SWITCH #endif #if defined(__BEOS__) || defined(__HAIKU__) #define MA_BEOS @@ -195,12 +226,13 @@ typedef ma_uint16 wchar_t; #endif #endif -#if defined(__has_c_attribute) - #if __has_c_attribute(fallthrough) - #define MA_FALLTHROUGH [[fallthrough]] - #endif +#if !defined(MA_FALLTHROUGH) && defined(__cplusplus) && __cplusplus >= 201703L + #define MA_FALLTHROUGH [[fallthrough]] #endif -#if !defined(MA_FALLTHROUGH) && defined(__has_attribute) && (defined(__clang__) || defined(__GNUC__)) +#if !defined(MA_FALLTHROUGH) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L + #define MA_FALLTHROUGH [[fallthrough]] +#endif +#if !defined(MA_FALLTHROUGH) && defined(__has_attribute) #if __has_attribute(fallthrough) #define MA_FALLTHROUGH __attribute__((fallthrough)) #endif @@ -237,7 +269,7 @@ typedef ma_uint16 wchar_t; #define MA_NO_INLINE __attribute__((noinline)) #else #define MA_INLINE MA_GNUC_INLINE_HINT - #define MA_NO_INLINE __attribute__((noinline)) + #define MA_NO_INLINE #endif #elif defined(__WATCOMC__) #define MA_INLINE __inline @@ -620,7 +652,7 @@ typedef struct typedef struct { - ma_int32 state; + ma_uint32 state; } ma_lcg; @@ -2839,7 +2871,7 @@ This section contains the APIs for device playback and capture. Here is where yo ************************************************************************************************************************************************************/ #ifndef MA_NO_DEVICE_IO /* Some backends are only supported on certain platforms. */ -#if defined(MA_WIN32) +#if defined(MA_WIN32) && !defined(MA_XBOX) #define MA_SUPPORT_WASAPI #if defined(MA_WIN32_DESKTOP) /* DirectSound and WinMM backends are only supported on desktops. */ @@ -3696,6 +3728,7 @@ struct ma_context ma_proc snd_pcm_hw_params_set_rate_resample; ma_proc snd_pcm_hw_params_set_rate; ma_proc snd_pcm_hw_params_set_rate_near; + ma_proc snd_pcm_hw_params_set_rate_minmax; ma_proc snd_pcm_hw_params_set_buffer_size_near; ma_proc snd_pcm_hw_params_set_periods_near; ma_proc snd_pcm_hw_params_set_access; @@ -4256,6 +4289,7 @@ struct ma_device /*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamCapture; ma_mutex rerouteLock; + ma_atomic_bool32 isTearingDown; ma_aaudio_usage usage; ma_aaudio_content_type contentType; ma_aaudio_input_preset inputPreset; @@ -7525,7 +7559,7 @@ typedef struct ma_log* pLog; /* When set to NULL, will use the context's log. */ ma_uint32 listenerCount; /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ ma_uint32 channels; /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ - ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native channel count of the device. */ + ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native sample rate of the device. */ ma_uint32 periodSizeInFrames; /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ ma_uint32 periodSizeInMilliseconds; /* Used if periodSizeInFrames is unset. */ ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ @@ -7689,11 +7723,11 @@ MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound); MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds); /* Abstraction to ma_sound_seek_to_pcm_frame() */ -MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); -MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); -MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); -MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor); -MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength); +MA_API ma_result ma_sound_get_data_format(const ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(const ma_sound* pSound, ma_uint64* pCursor); +MA_API ma_result ma_sound_get_length_in_pcm_frames(const ma_sound* pSound, ma_uint64* pLength); +MA_API ma_result ma_sound_get_cursor_in_seconds(const ma_sound* pSound, float* pCursor); +MA_API ma_result ma_sound_get_length_in_seconds(const ma_sound* pSound, float* pLength); MA_API ma_result ma_sound_set_end_callback(ma_sound* pSound, ma_sound_end_proc callback, void* pUserData); MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup); diff --git a/miniaudio.h b/miniaudio.h index 0d30de9f..9457a339 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.23 - TBD +miniaudio - v0.11.23 - 2025-09-11 David Reid - mackron@gmail.com @@ -14,7 +14,7 @@ GitHub: https://github.com/mackron/miniaudio =============== To use miniaudio, just include "miniaudio.h" like any other header and add "miniaudio.c" to your source tree. If you don't want to add it to your source tree you can compile and link to it like -any other library. Note that ABI compatiblity is not guaranteed between versions, even with bug +any other library. Note that ABI compatibility is not guaranteed between versions, even with bug fix releases, so take care if compiling as a shared object. miniaudio includes both low level and high level APIs. The low level API is good for those who want @@ -295,7 +295,7 @@ The engine encapsulates both the resource manager and the node graph to create a use high level API. The resource manager and node graph APIs are covered in more later sections of this manual. -The code below shows how you can initialize an engine using it's default configuration. +The code below shows how you can initialize an engine using its default configuration. ```c ma_result result; @@ -383,7 +383,7 @@ Sounds are not started by default. Start a sound with `ma_sound_start()` and sto `ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use `ma_sound_seek_to_pcm_frame(&sound, 0)` to seek back to the start of a sound. By default, starting and stopping sounds happens immediately, but sometimes it might be convenient to schedule the sound -the be started and/or stopped at a specific time. This can be done with the following functions: +to be started and/or stopped at a specific time. This can be done with the following functions: ```c ma_sound_set_start_time_in_pcm_frames() @@ -529,7 +529,7 @@ you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link wi The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. You cannot use `-std=c*` compiler flags, nor `-ansi`. -You can enable the use of AudioWorkets by defining `MA_ENABLE_AUDIO_WORKLETS` and then compiling +You can enable the use of AudioWorklets by defining `MA_ENABLE_AUDIO_WORKLETS` and then compiling with the following options: -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY @@ -872,7 +872,7 @@ read data within a certain range of the underlying data. To do this you can use This is useful if you have a sound bank where many sounds are stored in the same file and you want the data source to only play one of those sub-sounds. Note that once the range is set, everything -that takes a position, such as cursors and loop points, should always be relatvie to the start of +that takes a position, such as cursors and loop points, should always be relative to the start of the range. When the range is set, any previously defined loop point will be reset. Custom loop points can also be used with data sources. By default, data sources will loop after @@ -880,7 +880,7 @@ they reach the end of the data source, but if you need to loop at a specific loc the following: ```c - result = ma_data_set_loop_point_in_pcm_frames(pDataSource, loopBegInFrames, loopEndInFrames); + result = ma_data_source_set_loop_point_in_pcm_frames(pDataSource, loopBegInFrames, loopEndInFrames); if (result != MA_SUCCESS) { return result; // Failed to set the loop point. } @@ -46897,7 +46897,7 @@ static MA_INLINE void ma_pcm_s16_to_s32__reference(void* dst, const void* src, m ma_uint64 i; for (i = 0; i < count; i += 1) { - dst_s32[i] = src_s16[i] << 16; + dst_s32[i] = (ma_int32)src_s16[i] << 16; } (void)ditherMode; @@ -63224,7 +63224,7 @@ extern "C" { #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_VERSION_MAJOR 0 #define MA_DR_WAV_VERSION_MINOR 14 -#define MA_DR_WAV_VERSION_REVISION 0 +#define MA_DR_WAV_VERSION_REVISION 1 #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #include #define MA_DR_WAVE_FORMAT_PCM 0x1 @@ -63657,7 +63657,7 @@ extern "C" { #define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x) #define MA_DR_FLAC_VERSION_MAJOR 0 #define MA_DR_FLAC_VERSION_MINOR 13 -#define MA_DR_FLAC_VERSION_REVISION 0 +#define MA_DR_FLAC_VERSION_REVISION 1 #define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION) #include #if defined(_MSC_VER) && _MSC_VER >= 1700 @@ -95593,6 +95593,7 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on pMP3->pcmFramesRemainingInMP3Frame = 0; pMP3->streamStartOffset += (ma_uint32)(firstFrameInfo.frame_bytes); pMP3->streamCursor = pMP3->streamStartOffset; + ma_dr_mp3dec_init(&pMP3->decoder); } } else { } diff --git a/tools/madoc/madoc.c b/tools/madoc/madoc.c new file mode 100644 index 00000000..bc95db8b --- /dev/null +++ b/tools/madoc/madoc.c @@ -0,0 +1,2018 @@ +/* +This is very rough and built specifically for miniaudio. Don't get clever and try using this for +your own project because it will most likely not work. +*/ + +#include "../../../fs/fs.c" + +#define C89STR_IMPLEMENTATION +#include "../../../c89str/c89str.h" + +/* +This runs in two phases. The first phase generates a webplate-compatible template site. The second phase +uses webplate to actually generate the actual website. +*/ +#include "../../../webplate/source/libwebplate.c" + +/* The output directory, relative to the tools/build/_bin directory. */ +#define WEBSITE_DOCS_FOLDER "website/docs" +#define WEBSITE_DOCS_MANUAL_FOLDER WEBSITE_DOCS_FOLDER"/manual" +#define WEBSITE_DOCS_EXAMPLES_FOLDER WEBSITE_DOCS_FOLDER"/examples" +#define WEBSITE_DOCS_API_FOLDER WEBSITE_DOCS_FOLDER"/api" + +#define EXAMPLES_FOLDER "examples" + + + +c89str load_file(const char* pFilePath) +{ + fs_result resultFS; + size_t fileSize; + char* pFileData; + c89str str; + + resultFS = fs_file_open_and_read(NULL, pFilePath, FS_FORMAT_TEXT, (void**)&pFileData, &fileSize); + if (resultFS != FS_SUCCESS) { + printf("Failed to open %s: %s.\n", pFilePath, fs_result_to_string(resultFS)); + return NULL; + } + + /* We have the file data, but now we need to convert it to a dynamic string so we can manipulate it later. */ + str = c89str_newn(NULL, pFileData, fileSize); + fs_free(pFileData, NULL); + + return str; +} + +int save_file(const char* pFilePath, c89str src) +{ + fs_result resultFS; + + resultFS = fs_file_open_and_write(NULL, pFilePath, src, c89str_len(src)); + if (resultFS != FS_SUCCESS) { + printf("Failed to save %s: %s.\n", pFilePath, fs_result_to_string(resultFS)); + return (int)resultFS; + } + + return 0; +} + + +c89str convert_tabs_to_spaces(c89str str) +{ + /* Assume a tab is 4 spaces. */ + return c89str_replace_all(str, NULL, "\t", 1, " ", 4); +} + + +c89str strip_code_block_comments(c89str str) +{ + size_t runningOffset = 0; + + if (str == NULL) { + return str; + } + + /* + Block comments are not recursive which slightly simplifies our algorithm. The algorithm we're using here is not + based on speed and can be made much more efficient. Note that this will stip blocks that contain an opening or + closing block within a string constant. The proper way to do this is to use a proper C parser, but that is more + than what we need right now. + */ + for (;;) { + size_t openingOffset; + size_t closingOffset; + + /* Opening. */ + if (c89str_find(str + runningOffset, "/*", &openingOffset) != C89STR_SUCCESS) { + break; /* We're done. */ + } + openingOffset += runningOffset; /* Normalize the opening offset. */ + + /* Closing. */ + if (c89str_find(str + openingOffset, "*/", &closingOffset) != C89STR_SUCCESS) { + break; /* We're done. */ + } + closingOffset += openingOffset; /* Normalize the closing offset. */ + closingOffset += 2; /* Make sure to include the closing block itself. */ + + /* We now have enough information to delete the comment block. */ + str = c89str_replace(str, NULL, openingOffset, (closingOffset - openingOffset), "", 0); + if (str == NULL) { + return str; + } + + /* We're done with this block so we can now move on to the next. */ + runningOffset = openingOffset; + } + + return str; +} + +c89str strip_code_line_comments(c89str str) +{ + if (str == NULL) { + return str; + } + + /* TODO: Implement me. */ + return str; +} + +c89str strip_code_comments(c89str str) +{ + return strip_code_line_comments(strip_code_block_comments(str)); +} + + + +c89str strip_empty_lines(c89str str) +{ + const char* pRunningStr; + c89str newstr = NULL; + size_t lineLen; + size_t nextLineOffset; + + if (str == NULL) { + return str; + } + + pRunningStr = str; + + /* We are going to fully recreate the string line by line. We can allocate enough space from the start. */ + newstr = c89str_new_with_cap(NULL, c89str_len(str)); + if (newstr == NULL) { + return str; + } + + while (pRunningStr[0] != '\0') { + nextLineOffset = c89str_utf8_find_next_line(pRunningStr, (size_t)-1, &lineLen); + + if (c89str_is_null_or_whitespace(pRunningStr, lineLen) == C89STR_FALSE) { + newstr = c89str_catn(newstr, NULL, pRunningStr, nextLineOffset); + } + + if (nextLineOffset == c89str_npos) { + break; + } else { + pRunningStr += nextLineOffset; + } + } + + if (newstr == NULL) { + return str; + } + + c89str_delete(str, NULL); + return newstr; +} + +c89str strip_trailing_whitespace(c89str str) +{ + const char* pRunningStr; + c89str newstr = NULL; + + if (str == NULL) { + return str; + } + + pRunningStr = str; + + /* We are going to fully recreate the string line by line. We can allocate enough space from the start. */ + newstr = c89str_new_with_cap(NULL, c89str_len(str)); + if (newstr == NULL) { + return str; + } + + while (pRunningStr[0] != '\0') { + size_t lineLen; + size_t nextLineOffset; + + nextLineOffset = c89str_utf8_find_next_line(pRunningStr, (size_t)-1, &lineLen); + newstr = c89str_catn(newstr, NULL, pRunningStr, c89str_utf8_rtrim_offset(pRunningStr, lineLen)); + + /* Insert the new line character. */ + if (nextLineOffset != c89str_npos) { + newstr = c89str_catn(newstr, NULL, pRunningStr + lineLen, nextLineOffset - lineLen); + } else { + newstr = c89str_catn(newstr, NULL, pRunningStr + lineLen, lineLen); + } + + if (nextLineOffset == c89str_npos) { + break; + } else { + pRunningStr += nextLineOffset; + } + } + + if (newstr == NULL) { + return str; + } + + c89str_delete(str, NULL); + return newstr; +} + +c89str strip_whitespace(c89str str) +{ + return strip_trailing_whitespace(strip_empty_lines(str)); +} + +c89str minify_code(c89str str) +{ + return convert_tabs_to_spaces(strip_whitespace(strip_code_comments(str))); +} + + + + +static int path_remove_extension(char* dst, size_t dstSizeInBytes, const char* src) +{ + const char* ext; + + if (src == NULL) { + if (dst != NULL && dstSizeInBytes > 0) { + dst[0] = '\0'; + } + + return -1; + } + + ext = fs_path_extension(src, FS_NULL_TERMINATED); + if (ext == NULL || ext[0] == '\0') { + /* No extension. */ + if (dst == src) { + return (ext - dst); + } else { + size_t len = strlen(src); + fs_strncpy_s(dst, dstSizeInBytes, src, len); + + return (int)len; + } + } else { + /* Have extension. */ + size_t dstLen = (size_t)(ext - src - 1); /* -1 for the period. */ + + if (dst == src) { + dst[dstLen] = '\0'; + } else { + fs_strncpy_s(dst, dstSizeInBytes, src, dstLen); + } + + return dstLen; + } +} + +static int path_append_extension(char* dst, size_t dstSizeInBytes, const char* base, const char* extension) +{ + fs_result result = FS_SUCCESS; + size_t baseLength; + size_t extLength; + size_t dstLen; + + if (base == NULL) { + base = ""; + } + + if (extension == NULL) { + extension = ""; + } + + if (extension[0] == '\0') { + if (dst != NULL) { + if (dst != base) { + fs_strcpy_s(dst, dstSizeInBytes, base); + } + } + + return (int)strlen(base); + } + + + baseLength = strlen(base); + extLength = strlen(extension); + dstLen = baseLength + 1 + extLength; + + if (dst != NULL) { + if (dstLen+1 <= dstSizeInBytes) { + if (dst != base) { + fs_strcpy_s(dst + 0, dstSizeInBytes - 0, base); + } + fs_strcpy_s(dst + baseLength, dstSizeInBytes - baseLength, "."); + fs_strcpy_s(dst + baseLength + 1, dstSizeInBytes - baseLength - 1, extension); + } + } + + return (int)dstLen; +} + + +static fs_result fs_rmdir_content(const char* pDirectory) +{ + /* We'll use an iterator for this. */ + fs_result result; + fs_iterator* pIterator; + + if (pDirectory == NULL) { + return FS_INVALID_ARGS; + } + + pIterator = fs_first(NULL, pDirectory, FS_READ); + while (pIterator != NULL) { + char* pFilePath; + int filePathLen; + + /* Get the length first. */ + filePathLen = fs_path_append(NULL, 0, pDirectory, FS_NULL_TERMINATED, pIterator->pName, pIterator->nameLen); + if (filePathLen > 0) { + pFilePath = (char*)fs_malloc((size_t)filePathLen + 1, NULL); /* +1 for null terminator. */ + if (pFilePath != NULL) { + fs_path_append(pFilePath, (size_t)filePathLen + 1, pDirectory, FS_NULL_TERMINATED, pIterator->pName, pIterator->nameLen); + + if (pIterator->info.directory) { + if (pIterator->pName[0] == '.' && pIterator->pName[1] == '\0') { + /* "." - ignore. */ + } else if (pIterator->pName[0] == '.' && pIterator->pName[1] == '.' && pIterator->pName[2] == '\0') { + /* ".." - ignore. */ + } else { + fs_rmdir_content(pFilePath); + + } + } + + fs_remove(NULL, pFilePath, 0); + } else { + return FS_OUT_OF_MEMORY; + } + } else { + /* Failed to retrieve the file path length. */ + } + + pIterator = fs_next(pIterator); + } + + fs_free_iterator(pIterator); + + return FS_SUCCESS; +} + + + +typedef struct +{ + c89str name; + c89str code; +} doc_example; + +typedef enum +{ + doc_category_home, + doc_category_manual, + doc_category_examples, + doc_category_api +} doc_category; + +typedef enum +{ + doc_token_type_paragraph, + doc_token_type_code, + doc_token_type_table, + doc_token_type_header, + doc_token_type_list_item +} doc_token_type; + +typedef enum +{ + doc_lang_none, + doc_lang_c +} doc_lang; + +typedef struct +{ + const char* pText; + size_t textLen; /* Set to (size_t)-1 for null terminated. */ + size_t textOff; /* The cursor. */ + doc_token_type token; + const char* pTokenStr; + size_t tokenLen; + size_t headerLevel; + size_t indentation; /* Useful for knowing how to offset code. Code tags can be indented and we want to know how deep the base level of indentation is. */ + size_t prevLineOff; + size_t prevLineLen; + doc_lang codeLang; + int isLastListItem; + size_t listItemCounter; +} doc_lexer; + +int doc_lexer_init(const char* pText, size_t textLen, doc_lexer* pLexer) +{ + if (pText == NULL || pLexer == NULL) { + return EINVAL; + } + + C89STR_ZERO_OBJECT(pLexer); + + if (textLen == (size_t)-1) { + textLen = c89str_strlen(pText); + } + + pLexer->pText = pText; + pLexer->textLen = textLen; + pLexer->textOff = 0; + + return 0; +} + +static int doc_lexer_is_header_underline(const char* pText, size_t textLen, char ch) +{ + size_t i; + + if (textLen == 0) { + return 0; + } + + for (i = 0; i < textLen; i += 1) { + if (pText[i] == '\0') { + break; + } + + if (pText[i] != ch) { + return 0; + } + } + + /* Getting here means it's an underline. */ + return 1; +} + +static size_t doc_lexer_get_header_underline_level(const char* pText, size_t textLen) +{ + if (doc_lexer_is_header_underline(pText, textLen, '-')) { + return 2; + } + + if (doc_lexer_is_header_underline(pText, textLen, '=')) { + return 1; + } + + return 0; +} + +int doc_lexer_next(doc_lexer* pLexer) +{ + //int result; + const char* txt; + size_t off; + size_t len; + + if (pLexer == NULL) { + return EINVAL; + } + + txt = pLexer->pText; + off = pLexer->textOff; /* Moves forward. */ + len = pLexer->textLen; /* Constant. */ + + /* We run line-by-line. */ + for (;;) { + size_t thisLineLen; + size_t nextLineBeg; + size_t indentation; + size_t headerLevel; + size_t location; /* When searching for a token on a line. */ + + if (txt[off] == '\0' || len == off) { + /* We're done. The last paragraph needs to be added, if any. */ + if (off > pLexer->textOff) { + pLexer->token = doc_token_type_paragraph; + pLexer->pTokenStr = txt + pLexer->textOff; + pLexer->tokenLen = off - pLexer->textOff; + pLexer->textOff = off; + return 0; + } else { + return ENOMEM; /* We're done. */ + } + } + + /* Grab the next line. */ + nextLineBeg = c89str_utf8_find_next_line(txt + off, len - off, &thisLineLen); + + /* Check if we have a header underline. If so, we want to end the paragraph, if any, and then return. */ + headerLevel = doc_lexer_get_header_underline_level(txt + off, thisLineLen); + if (headerLevel > 0) { + if (pLexer->textOff < pLexer->prevLineOff) { + /* We have a paragraph to post. */ + pLexer->token = doc_token_type_paragraph; + pLexer->pTokenStr = txt + pLexer->textOff; + pLexer->tokenLen = pLexer->prevLineOff - pLexer->textOff; + pLexer->textOff = off; + } else { + /* We don't have a paragraph. Just post the header itself. */ + pLexer->token = doc_token_type_header; + pLexer->pTokenStr = txt + pLexer->prevLineOff; + pLexer->tokenLen = pLexer->prevLineLen; + pLexer->textOff = off + nextLineBeg; + pLexer->headerLevel = headerLevel; + } + + return 0; + } + + /* Getting here means it's not a header. */ + indentation = c89str_utf8_ltrim_offset(txt + off, len - off); + + if (c89str_findn(txt + off, thisLineLen, "```", (size_t)-1, &location) == C89STR_SUCCESS) { + /* The beginning of a code block. If there's a pending paragraph that needs to be posted. */ + if (pLexer->textOff < off) { + /* We have a paragraph to post. */ + pLexer->token = doc_token_type_paragraph; + pLexer->pTokenStr = txt + pLexer->textOff; + pLexer->tokenLen = off - pLexer->textOff; + pLexer->textOff = off; + } else { + /* We don't have a paragraph. We can now parse the code block. */ + size_t tokenOff = off; + size_t tokenLen = 0; + doc_lang lang = doc_lang_none; + if (txt[off + location + 3] == 'c') { + lang = doc_lang_c; + } + + nextLineBeg = c89str_utf8_find_next_line(txt + off, len - off, &thisLineLen); + tokenOff = off + nextLineBeg; + tokenLen = 0; + + off += nextLineBeg; + + /* We now need to keep looping over each line until we find the closing the code block. */ + for (;;) { + if (txt[off] == '\0' || off == len) { + break; + } + + nextLineBeg = c89str_utf8_find_next_line(txt + off, len - off, &thisLineLen); + + if (c89str_findn(txt + off, thisLineLen, "```", (size_t)-1, &location) == FS_SUCCESS) { + /* We found the end of the code block. */ + pLexer->token = doc_token_type_code; + pLexer->pTokenStr = txt + tokenOff; + pLexer->tokenLen = tokenLen; + pLexer->textOff = off + nextLineBeg; + pLexer->indentation = indentation; + pLexer->codeLang = lang; + break; + } else { + + } + + off += nextLineBeg; + tokenLen += nextLineBeg; + } + } + + return 0; + } + + /* Getting here means it's not a header nor a code block. It might be a table. */ + if (c89str_findn(txt + off, thisLineLen, "+--", (size_t)-1, &location) == FS_SUCCESS) { + /* The beginning of a table. The end of the table is the end of the last line that starts with a "+" or "|". */ + if (pLexer->textOff < off) { + /* We have a paragraph to post. */ + pLexer->token = doc_token_type_paragraph; + pLexer->pTokenStr = txt + pLexer->textOff; + pLexer->tokenLen = off - pLexer->textOff; + pLexer->textOff = off; + } else { + /* We don't have a paragraph. We can now parse the table block. */ + size_t tokenOff = off; + size_t tokenLen = 0; + + for (;;) { + if (txt[off] == '\0' || off == len) { + break; + } + + nextLineBeg = c89str_utf8_find_next_line(txt + off, len - off, &thisLineLen); + + if (nextLineBeg < 4 || (txt[off + indentation] != '+' && txt[off + indentation] != '|')) { + /* We found the end of the table. The end of the table is the end of this line. */ + pLexer->token = doc_token_type_table; + pLexer->pTokenStr = txt + tokenOff; + pLexer->tokenLen = tokenLen; + pLexer->textOff = off + nextLineBeg; + pLexer->indentation = indentation; + break; + } else { + /* We're still looking at the table. */ + } + + off += nextLineBeg; + tokenLen += nextLineBeg; + } + } + + return 0; + } + + + /* Check if it's a bullet point. */ + if (txt[off + indentation] == '-' || txt[off + indentation] == '*') { + if (pLexer->textOff < off) { + /* There's a pending paragraph to post. */ + pLexer->token = doc_token_type_paragraph; + pLexer->pTokenStr = txt + pLexer->textOff; + pLexer->tokenLen = off - pLexer->textOff; + pLexer->textOff = off; + } else { + /* A bullet point can cover multiple lines. The bullet point ends when a line starts with another bullet point or is empty. */ + size_t tokenOff = off + indentation + 2; + size_t tokenLen = 0; + //int isLineBlank = 0; + int isNextLineBlank = 0; + + off += indentation + 2; + + /* Keep going until we find the start of a new bullet point or an empty line. */ + for (;;) { + int foundEndOfBulletPoint = 0; + + if (txt[off] == '\0' || off == len) { + break; /* At end. */ + } + + /* Go to the next line. */ + nextLineBeg = c89str_utf8_find_next_line(txt + off, len - off, &thisLineLen); + + /* Determine whether or not the next line is blank. */ + isNextLineBlank = c89str_is_null_or_whitespace(txt + off + nextLineBeg, c89str_utf8_find_next_line(txt + off + nextLineBeg, len - off - nextLineBeg, NULL)); + if (isNextLineBlank) { + foundEndOfBulletPoint = 1; + } else { + /* The next line is not blank so now check if it starts with a bullet point. */ + size_t lineContentOff = off + nextLineBeg + c89str_utf8_ltrim_offset(txt + off + nextLineBeg, len - off - nextLineBeg); + if (txt[lineContentOff] == '-' || txt[lineContentOff] == '*' || txt[lineContentOff] == '\0') { + foundEndOfBulletPoint = 1; + } + } + + if (foundEndOfBulletPoint) { + /* We found the end of the bullet point. Update the lexer and return. */ + pLexer->token = doc_token_type_list_item; + pLexer->pTokenStr = txt + tokenOff; + pLexer->tokenLen = tokenLen + thisLineLen; + pLexer->textOff = off + nextLineBeg; + pLexer->indentation = indentation; + + /* Reset the list item counter if necessary. */ + if (pLexer->isLastListItem) { + pLexer->listItemCounter = 0; + } + + pLexer->listItemCounter += 1; + pLexer->isLastListItem = isNextLineBlank; + + return 0; + } else { + /* We didn't find the end of the bullet point. Move forward. */ + off += indentation + nextLineBeg; + tokenLen += indentation + nextLineBeg; + } + } + } + + return 0; + } + + + /* Not a header underline, code block or table. Assume a paragraph. If we have an empty line it means we can terminate our paragraph. */ + if (c89str_is_null_or_whitespace(txt + off, thisLineLen)) { + if (pLexer->textOff < off) { + /* We have a paragraph to post. */ + pLexer->token = doc_token_type_paragraph; + pLexer->pTokenStr = txt + pLexer->textOff; + pLexer->tokenLen = off - pLexer->textOff; + pLexer->textOff = off; + + if (!c89str_is_null_or_whitespace(pLexer->pTokenStr, pLexer->tokenLen)) { + return 0; + } + } + } + + + /* We need to keep track of the previous line for the purpose of header underlines. */ + pLexer->prevLineOff = off; + pLexer->prevLineLen = thisLineLen; + + /* We're done. We can move to the next line now. */ + off += nextLineBeg; + } + + /*return 0;*/ +} + + +typedef struct +{ + c89str title; + c89str bookmark; + size_t level; +} manual_section; + +typedef struct +{ + struct + { + c89str raw; + size_t sectionCount; + manual_section sections[256]; + } manual; + + struct + { + size_t count; + doc_example examples[256]; + } examples; +} docstate; + +c89str extract_manual_section_title(const char* pStr, size_t len, size_t* pLevel) +{ + if (pLevel != NULL) { + *pLevel = 0; + } + + /* We want to remove the numbers because we have limited horizontal space. */ + while (pStr[0] != '\0' && len > 0) { + if ((pStr[0] >= '0' && pStr[0] <= '9') || pStr[0] == '.') { + pStr += 1; + len -= 1; + + if (pStr[0] == '.' && pLevel != NULL) { + *pLevel += 1; + } + + continue; + } else { + break; + } + } + + return c89str_trim(c89str_newn(NULL, pStr, len), NULL); +} + +c89str extract_manual_section_bookmark(const char* pStr, size_t len) +{ + return c89str_replace_all(extract_manual_section_title(pStr, len, NULL), NULL, " ", 1, "-", 0); +} + +int load_manual(c89str miniaudio, docstate* pState) +{ + int result; + c89str_lexer lexer; + int commentCounter; + + result = c89str_lexer_init(&lexer, miniaudio, c89str_len(miniaudio)); + if (result != 0) { + return result; + } + + /* The manual will be in the second comment block. The first comment block is the project summary. */ + commentCounter = 0; + for (;;) { + result = c89str_lexer_next(&lexer); + if (result != 0) { + return result; + } + + if (lexer.token == c89str_token_type_comment) { + commentCounter += 1; + } + + if (commentCounter == 2) { + /* We've found the comment block with the content of the programming manual. We just need to remove the enveloping comment tokens. */ + c89str_lexer_transform_token(&lexer, &pState->manual.raw, NULL); + if (pState->manual.raw == NULL) { + return ENOMEM; + } else { + break; + } + } + } + + /* We need to extract all the sections of the manual for the purpose of the navigation panel. */ + { + doc_lexer docLexer; + + result = doc_lexer_init(pState->manual.raw, c89str_len(pState->manual.raw), &docLexer); + if (result != 0) { + return ENOMEM; + } + + for (;;) { + result = doc_lexer_next(&docLexer); + if (result != 0) { + break; /* We're done. */ + } + + if (docLexer.token == doc_token_type_header) { + manual_section section; + + section.title = extract_manual_section_title(docLexer.pTokenStr, docLexer.tokenLen, §ion.level); + section.bookmark = extract_manual_section_bookmark(docLexer.pTokenStr, docLexer.tokenLen); + + pState->manual.sections[pState->manual.sectionCount] = section; + pState->manual.sectionCount += 1; + } + } + } + + return 0; +} + +int load_examples(docstate* pState) +{ + fs_result resultFS; + fs_iterator* pIterator; + + /* Extract all examples. */ + pIterator = fs_first(NULL, EXAMPLES_FOLDER, FS_READ); + while (pIterator != NULL) { + if (pIterator->info.size > 0) { /* Ignore any empty files. Sometimes I'll put placeholder files in the examples folder to keep track of ideas for examples. */ + /* We need to get the contents of the file. */ + doc_example example; + char* pFileData; + char filePath[4096]; + + fs_path_append(filePath, sizeof(filePath), EXAMPLES_FOLDER, FS_NULL_TERMINATED, pIterator->pName, pIterator->nameLen); + + /* Name. We don't care about the whole file path - just the name part of it. */ + example.name = c89str_newn(NULL, pIterator->pName, pIterator->nameLen); + + /* File content. */ + resultFS = fs_file_open_and_read(NULL, filePath, FS_FORMAT_TEXT, (void**)&pFileData, NULL); + if (resultFS != FS_SUCCESS) { + printf("Failed to load example: %s\n", pIterator->pName); + return -1; + } + + example.code = c89str_new(NULL, pFileData); + fs_free(pFileData, NULL); + + pState->examples.examples[pState->examples.count] = example; + pState->examples.count += 1; + } + + pIterator = fs_next(pIterator); + } + + fs_free_iterator(pIterator); + + return 0; +} + +int load_api(c89str miniaudio, docstate* pState) +{ + (void)miniaudio; + (void)pState; + return 0; +} + + +int load(docstate* pState) +{ + int result; + c89str miniaudio; + + C89STR_ZERO_OBJECT(pState); + + /* Data is extracted from miniaudio.h, so we'll need to get that loaded as a start. */ + miniaudio = load_file("miniaudio.h"); + if (miniaudio == NULL) { + printf("Failed to load miniaudio.h"); + return -1; + } + + result = load_manual(miniaudio, pState); + if (result != 0) { + printf("Failed to load manual."); + return result; + } + + result = load_examples(pState); + if (result != 0) { + printf("Failed to load examples."); + return result; + } + + result = load_api(miniaudio, pState); + if (result != 0) { + printf("Failed to load API."); + return result; + } + + /* We're done. */ + c89str_delete(miniaudio, NULL); + return 0; +} + +c89str raw_to_html(c89str raw); + +c89str transform_inline_code(c89str html) +{ + size_t offset = 0; + + /* + To do this we just need to find the next grave character. Then the content between that and the following grave needs to be wrapped in a tag + with a monospace font. + */ + for (;;) { + size_t loc1; + size_t loc2; + c89str inner; + c89str replacement; + + if (c89str_find(html + offset, "`", &loc1) != C89STR_SUCCESS) { + break; /* No more occurances. */ + } + + if (c89str_find(html + offset + loc1 + 1, "`", &loc2) != C89STR_SUCCESS) { + break; /* No more occurances. */ + } + loc2 += loc1 + 1; /* Make the closing grave offset relative to "offset" like loc1. */ + + /* We need a copy of the section inside the graves just in case the replacement operation needs to reallocate the pointer. */ + inner = c89str_newn(NULL, html + offset + loc1 + 1, loc2 - loc1 - 1); + if (inner == NULL) { + return NULL; /* Out of memory. */ + } + + replacement = c89str_newf(NULL, "%s", inner); + c89str_delete(inner, NULL); + + if (replacement == NULL) { + return NULL; + } + + /* We now have what we need to replace the segment. */ + html = c89str_replace(html, NULL, offset + loc1, loc2 - loc1 + 1, replacement, c89str_len(replacement)); + if (html == NULL) { + c89str_delete(replacement, NULL); + return NULL; /* Out of memory. */ + } + + offset += loc1 + c89str_len(replacement); + c89str_delete(replacement, NULL); + } + + return html; +} + +c89str transform_urls_by_protocol(c89str html, const char* pProtocol) +{ + size_t offset = 0; + size_t protocolLen; + + protocolLen = c89str_strlen(pProtocol); + if (protocolLen == 0) { + return html; + } + + for (;;) { + size_t loc1; + size_t loc2; + c89str url; + c89str replacement; + + if (c89str_find(html + offset, pProtocol, &loc1) != C89STR_SUCCESS) { + break; /* No more occurances. */ + } + + loc2 = c89str_find_next_whitespace(html + offset + loc1, (size_t)-1, NULL); + if (loc2 != c89str_npos) { + loc2 += loc1; + } else { + loc2 = c89str_len(html) - offset; + } + + /* We need a copy of the section inside the graves just in case the replacement operation needs to reallocate the pointer. */ + url = c89str_trim(c89str_newn(NULL, html + offset + loc1, loc2 - loc1), NULL); + if (url == NULL) { + return NULL; /* Out of memory. */ + } + + /* We're going to remove any trailing symbols. */ + size_t len = c89str_len(url); + while (url[len-1] == '.' || url[len-1] == ')' || url[len-1] == '(' || url[len-1] == ';') { + len -= 1; + } + + url = c89str_setn(url, NULL, url, len); /* Should never fail since we're just shrinking the string. */ + loc2 = loc1 + len; + + replacement = c89str_newf(NULL, "%s", url, url); + c89str_delete(url, NULL); + + if (replacement == NULL) { + return NULL; + } + + /* We now have what we need to replace the segment. */ + html = c89str_replace(html, NULL, offset + loc1, loc2 - loc1, replacement, c89str_len(replacement)); + if (html == NULL) { + c89str_delete(replacement, NULL); + return NULL; /* Out of memory. */ + } + + offset += loc1 + c89str_len(replacement); + c89str_delete(replacement, NULL); + } + + return html; +} + +c89str transform_urls(c89str html) +{ + /* This is similar to inline code segments, except out opening token is "http://" or "https://" and ends with */ + html = transform_urls_by_protocol(html, "https://"); + html = transform_urls_by_protocol(html, "http://"); + return html; +} + +c89str escape_html(const char* pHTML, size_t len) +{ + c89str html = c89str_newn(NULL, pHTML, len); + + /* Slow, but it's simple and it works. */ + html = c89str_replace_all(html, NULL, "&", (size_t)-1, "&", (size_t)-1); /* <-- This must come first to ensure it doesn't replace the "&" symbols used in the escapes before it. */ + html = c89str_replace_all(html, NULL, "<", (size_t)-1, "<", (size_t)-1); + html = c89str_replace_all(html, NULL, ">", (size_t)-1, ">", (size_t)-1); + html = c89str_replace_all(html, NULL, "\"", (size_t)-1, """, (size_t)-1); + html = c89str_replace_all(html, NULL, "\'", (size_t)-1, "'", (size_t)-1); + + /* We want to keep
tags unescaped. This is not a good way to do this, but it works well enough for now since we won't in practice have this string in our documentation. */ + html = c89str_replace_all(html, NULL, "<br>", (size_t)-1, "
", (size_t)-1); + + /* Content inside `` tags need to be formatted as code. */ + html = transform_inline_code(html); + + /* URLs need to be transformed. */ + html = transform_urls(html); + + return html; + +} + +c89str raw_to_html_p(const char* pText, size_t textLen) +{ + c89str p = NULL; + + /* Don't emit anything if the paragraph is empty. */ + if (c89str_is_null_or_whitespace(pText, textLen)) { + return c89str_new(NULL, ""); + } + + p = c89str_cat(p, NULL, "

\n"); + { + p = c89str_cat(p, NULL, escape_html(pText, textLen)); + } + p = c89str_cat(p, NULL, "

\n"); + + return p; +} + +const char* g_CKeywords[] = { + "auto", + "break", + "case", + "char", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extern", + "float", + "for", + "goto", + "if", + "inline", + "int", + "long", + "register", + "restrict", + "return", + "short", + "signed", + "sizeof", + "static", + "struct", + "switch", + "typedef", + "union", + "unsigned", + "void", + "volatile", + "while", +}; + +/* This is temporary until we get a basic C parser working and we can dynamically extract our custom types. */ +const char* g_CustomTypes[] = { + "ma_uint8", + "ma_int8", + "ma_uint16", + "ma_int16", + "ma_uint32", + "ma_int32", + "ma_uint64", + "ma_int64", + "ma_bool8", + "ma_bool32", + "ma_mutex", + "ma_semaphore", + "ma_event", + "ma_thread", + "ma_result", + "ma_context_config", + "ma_context", + "ma_device_config", + "ma_device", + "ma_device_info", + "ma_decoder_config", + "ma_decoder", + "ma_encoder_config", + "ma_encoder", + "ma_waveform_config", + "ma_waveform", + "ma_noise_config", + "ma_noise", + "ma_audio_buffer_config", + "ma_audio_buffer", + "ma_data_source", + "ma_pcm_rb", + "ma_rb", + "ma_channel_converter_config", + "ma_channel_converter", + "ma_resampler_config", + "ma_resampler", + "ma_data_converter_config", + "ma_data_converter", + "ma_biquad_config", + "ma_biquad", + "ma_lpf_config", + "ma_lpf", +}; + +#define C_COMMENT_COLOR "#009900" +#define C_STRING_COLOR "#cc3300" +#define C_KEYWORD_COLOR "#0033ff" +#define C_PREPROCESSOR_COLOR "#666666" +#define C_CUSTOM_TYPE_COLOR "#0099cc" + +c89str_bool32 list_contains_string(const char** ppList, size_t listCount, const char* pText, size_t textLen) +{ + size_t i = 0; + for (i = 0; i < listCount; i += 1) { + if (strncmp(ppList[i], pText, textLen) == 0) { + return C89STR_TRUE; + } + } + + return C89STR_FALSE; +} + +c89str_bool32 is_c_keyword(const char* pText, size_t textLen) +{ + return list_contains_string(g_CKeywords, C89STR_COUNTOF(g_CKeywords), pText, textLen); +} + +c89str_bool32 is_custom_type(const char* pText, size_t textLen) +{ + return list_contains_string(g_CustomTypes, C89STR_COUNTOF(g_CustomTypes), pText, textLen); +} + +c89str html_highlight(const char* pText, size_t textLen, const char* pColor) +{ + c89str html = NULL; + + html = c89str_catf(html, NULL, "", pColor); + html = c89str_cat (html, NULL, escape_html(pText, textLen)); + html = c89str_cat (html, NULL, ""); + + return html; +} + +c89str raw_to_html_code(const char* pText, size_t textLen, size_t indentation, doc_lang lang) +{ + c89str code = NULL; + + if (textLen == (size_t)-1) { + textLen = c89str_strlen(pText); + } + + if (lang == doc_lang_none) { + code = c89str_cat(code, NULL, "
\n");
+    } else {
+        code = c89str_cat(code, NULL, "
\n");
+    }
+    {
+        /* We need to do a quick pre-processing of the string to strip the indentation. Then we need to iterate over each token and reconstruct the string. */
+        int result;
+        c89str text = NULL;
+        const char* pRunningText = pText;
+        c89str_lexer lexer;
+
+        while (pRunningText < pText + textLen) {
+            size_t thisLineLen;
+            size_t nextLineBeg = c89str_utf8_find_next_line(pRunningText, textLen - (pRunningText - pText), &thisLineLen);
+        
+            if (nextLineBeg > indentation) {
+                text = c89str_catn(text, NULL, pRunningText + indentation, nextLineBeg - indentation);
+            } else {
+                text = c89str_catn(text, NULL, pRunningText, nextLineBeg);
+            }
+
+            if (nextLineBeg == thisLineLen) {
+                break;
+            }
+
+            pRunningText += nextLineBeg;
+        }
+
+        /* The code should be stripped of it's indentation so now we need to iterate over each token and construct a html string. */
+        result = c89str_lexer_init(&lexer, text, c89str_len(text));
+        if (result == 0) {
+            while (c89str_lexer_next(&lexer) == 0) {
+                if (lexer.token == c89str_token_type_comment) {
+                    code = c89str_cat(code, NULL, html_highlight(lexer.pTokenStr, lexer.tokenLen, C_COMMENT_COLOR));
+                } else if (lexer.token == c89str_token_type_string_double || lexer.token == c89str_token_type_string_single) {
+                    code = c89str_cat(code, NULL, html_highlight(lexer.pTokenStr, lexer.tokenLen, C_STRING_COLOR));
+                } else {
+                    /* Special case if we're handling a pre-processor keyword. We want to get the next identifier and highlight the entire segment appropriately. */
+                    if (lexer.token == '#') {
+                        const char* pSegmentStr = lexer.pTokenStr;
+                        while (c89str_lexer_next(&lexer) == 0) {
+                            if (lexer.token == c89str_token_type_eof) {
+                                break;
+                            }
+
+                            if (lexer.token == c89str_token_type_identifier) {
+                                code = c89str_cat(code, NULL, html_highlight(pSegmentStr, (lexer.pTokenStr - pSegmentStr) + lexer.tokenLen, C_PREPROCESSOR_COLOR));
+
+                                if (strncmp(lexer.pTokenStr, "include", lexer.tokenLen) == 0) {
+                                    while (c89str_lexer_next(&lexer) == 0 && lexer.token == c89str_token_type_whitespace) {
+                                        code = c89str_cat(code, NULL, escape_html(lexer.pTokenStr, lexer.tokenLen));
+                                    }
+
+                                    /* If the token is "<" we will want to make sure we highlight it as a string. */
+                                    if (lexer.token == c89str_token_type_string_double || lexer.token == c89str_token_type_string_single) {
+                                        code = c89str_cat(code, NULL, html_highlight(lexer.pTokenStr, lexer.tokenLen, C_STRING_COLOR));
+                                    } else {
+                                        if (lexer.token == '<') {
+                                            /* We're highligting an #include. */
+                                            const char* pIncludeStr = lexer.pTokenStr;
+                                            while (c89str_lexer_next(&lexer) == 0 && lexer.token != '>') {
+                                                if (lexer.token == c89str_token_type_eof) {
+                                                    break;
+                                                }
+                                            }
+
+                                            code = c89str_cat(code, NULL, html_highlight(pIncludeStr, (lexer.pTokenStr - pIncludeStr) + lexer.tokenLen, C_STRING_COLOR));
+                                        }
+                                    }
+                                }
+
+                                break;
+                            }
+                        }
+                    } else {
+                        if (lang == doc_lang_c) {
+                            if (is_c_keyword(lexer.pTokenStr, lexer.tokenLen)) {
+                                code = c89str_cat(code, NULL, html_highlight(lexer.pTokenStr, lexer.tokenLen, C_KEYWORD_COLOR));
+                            } else if (is_custom_type(lexer.pTokenStr, lexer.tokenLen)) {
+                                code = c89str_cat(code, NULL, html_highlight(lexer.pTokenStr, lexer.tokenLen, C_CUSTOM_TYPE_COLOR));
+                            } else {
+                                code = c89str_cat(code, NULL, escape_html(lexer.pTokenStr, lexer.tokenLen));
+                            }
+                        } else {
+                            code = c89str_cat(code, NULL, escape_html(lexer.pTokenStr, lexer.tokenLen));
+                        }
+                    }
+                }
+            }
+        }
+    }
+    code = c89str_cat(code, NULL, "
"); + + return code; +} + + +/* We never have huge tables in our documentation. */ +#define MAX_TABLE_COLUMNS 16 +#define MAX_TABLE_ROWS 128 + +typedef struct +{ + size_t cellCount; + c89str cells[MAX_TABLE_COLUMNS]; +} table_row; + +typedef struct +{ + size_t colCount; + size_t rowCount; + table_row rows[MAX_TABLE_ROWS]; /* First row is the header. */ +} table; + +void merge_table_row(table_row* pTarget, const table_row* pSource) +{ + size_t cellCount; + size_t iCell; + + /* This assumes the cell count is the same. */ + cellCount = pTarget->cellCount; + + for (iCell = 0; iCell < cellCount; iCell += 1) { + pTarget->cells[iCell] = c89str_catf(pTarget->cells[iCell], NULL, "\n%s", pSource->cells[iCell]); + } +} + +table_row parse_table_row(c89str line) +{ + table_row row; + const char* pRunningStr = line; + + C89STR_ZERO_OBJECT(&row); + + /* All we're doing is splitting on '|'. */ + pRunningStr += c89str_utf8_ltrim_offset(line, c89str_len(line)) + 1; /* Skip the initial '|' character. */ + + for (;;) { + size_t loc; + + if (c89str_find(pRunningStr, "|", &loc) != FS_SUCCESS) { + break; + } + + row.cells[row.cellCount] = c89str_trim(c89str_newn(NULL, pRunningStr, loc), NULL); + row.cellCount += 1; + + pRunningStr += loc + 1; /* Plus one for the pipe character. */ + } + + return row; +} + +table parse_table(const char* pText, size_t textLen) +{ + table t; + const char* pRunningText = pText; + size_t thisLineLen; + size_t nextLineBeg; + + C89STR_ZERO_OBJECT(&t); + + /* We just ignore the first row which should be starting with "+". */ + pRunningText += c89str_utf8_find_next_line(pRunningText, textLen, NULL); + + /* + There's two ways to generate define the rows in a table. The first is just one line equals one row. The other uses a separator to define the rows. To distinguish + between the two we just need to count how many separators there are in the table. If there is 3, we can use the one line technique. + */ + while (pRunningText < pText + textLen) { + c89str line; + size_t loff; + + nextLineBeg = c89str_utf8_find_next_line(pRunningText, textLen - (pRunningText - pText), &thisLineLen); + + line = c89str_newn(NULL, pRunningText, nextLineBeg); /* Intentionally using nextLineBeg instead of thisLineEnd so we can capture the new-line character. */ + loff = c89str_utf8_ltrim_offset(line, c89str_len(line)); + + if (line[loff] == '|') { + table_row row = parse_table_row(line); + + if (c89str_is_null_or_whitespace(row.cells[0], (size_t)-1)) { + /* There's nothing in the first cell so we're just going to merge the rows. */ + merge_table_row(&t.rows[t.rowCount], &row); + } else { + /* It's a new row. Anything in the current row needs to be committed, and then the new row started. */ + if (!c89str_is_null_or_whitespace(t.rows[t.rowCount].cells[0], (size_t)-1)) { + t.rowCount += 1; /* Commit the existing row if there's anything there at the moment. */ + } + t.rows[t.rowCount] = row; + } + } else if (line[loff] == '+') { + t.rowCount += 1; /* Commit the row. */ + } + + pRunningText += nextLineBeg; + } + + /* The column count can be set to the cell count of the first row. */ + if (t.rowCount > 0) { + t.colCount = t.rows[0].cellCount; + } + + return t; +} + +c89str raw_to_html_table(const char* pText, size_t textLen, size_t indentation) +{ + c89str html = NULL; + + (void)indentation; + + html = c89str_cat(html, NULL, "
"); + { + /* We're doing to generate the table in two passes. The first is going to extract the contents of the table, the second will generate the HTML. */ + table t; + size_t iRow; + size_t iCol; + + /* First step is to parse the table. */ + t = parse_table(pText, textLen); + + /* Now that we have the table we can generate the HTML. */ + for (iRow = 0; iRow < t.rowCount; iRow += 1) { + html = c89str_cat(html, NULL, "\n"); + { + for (iCol = 0; iCol < t.colCount; iCol += 1) { + html = c89str_catf(html, NULL, "", (iRow == 0) ? "h" : "d"); + html = c89str_cat(html, NULL, raw_to_html(t.rows[iRow].cells[iCol])); + html = c89str_catf(html, NULL, "\n", (iRow == 0) ? "h" : "d"); + } + } + html = c89str_cat(html, NULL, "\n"); + } + } + html = c89str_cat(html, NULL, "
"); + + return html; +} + +c89str raw_to_html_header(const char* pText, size_t textLen, size_t headerLevel) +{ + c89str header = NULL; + + header = c89str_catf(header, NULL, "", (int)headerLevel, extract_manual_section_bookmark(pText, textLen)); + { + header = c89str_catn(header, NULL, pText, textLen); + } + header = c89str_catf(header, NULL, "\n", (int)headerLevel); + + return header; +} + +c89str raw_to_html_list_item(const char* pText, size_t textLen, size_t itemCounter, int isLastItem) +{ + c89str html = NULL; + + if (itemCounter == 1) { + html = c89str_cat(html, NULL, "\n"); + } + + return html; +} + +c89str raw_to_html(c89str raw) +{ + int result; + c89str html = NULL; + doc_lexer lexer; + + result = doc_lexer_init(raw, c89str_len(raw), &lexer); + if (result != 0) { + return NULL; + } + + for (;;) { + result = doc_lexer_next(&lexer); + if (result != 0) { + break; /* We're done. */ + } + + if (lexer.token == doc_token_type_header) { + html = c89str_cat(html, NULL, raw_to_html_header(lexer.pTokenStr, lexer.tokenLen, lexer.headerLevel)); + } else if (lexer.token == doc_token_type_code) { + html = c89str_cat(html, NULL, raw_to_html_code(lexer.pTokenStr, lexer.tokenLen, lexer.indentation, lexer.codeLang)); + } else if (lexer.token == doc_token_type_table) { + html = c89str_cat(html, NULL, raw_to_html_table(lexer.pTokenStr, lexer.tokenLen, lexer.indentation)); + } else if (lexer.token == doc_token_type_list_item) { + html = c89str_cat(html, NULL, raw_to_html_list_item(lexer.pTokenStr, lexer.tokenLen, lexer.listItemCounter, lexer.isLastListItem)); + } else { + html = c89str_cat(html, NULL, raw_to_html_p(lexer.pTokenStr, lexer.tokenLen)); + } + } + + return html; +} + +c89str example_name_to_display(c89str name) +{ + char* pRunningStr; + c89str display = NULL; + char temp[256]; + path_remove_extension(temp, sizeof(temp), name); + + display = c89str_new(NULL, temp); + display = c89str_replace_all(display, NULL, "_", (size_t)-1, " ", (size_t)-1); + + /* We need to capitalize the first character of each word. */ + if (display[0] >= 'a' && display[0] <= 'z') { + display[0] -= 32; + } + + /* For each remaining word. */ + pRunningStr = display; + for (;;) { + size_t next = c89str_find_next_whitespace(pRunningStr, (size_t)-1, NULL); + if (next == c89str_npos) { + break; /* We're done. */ + } + + if (pRunningStr[next+1] >= 'a' && pRunningStr[next+1] <= 'z') { + pRunningStr[next+1] -= 32; + } + + pRunningStr += next + 1; + } + + return display; +} + +c89str example_name_to_html_file_name(c89str name) +{ + char html[4096]; + + path_remove_extension(html, sizeof(html), name); + path_append_extension(html, sizeof(html), html, "html"); + + return c89str_new(NULL, html); +} + +c89str extract_example_summary_from_comment(c89str comment) +{ + int result; + doc_lexer lexer; + + result = doc_lexer_init(comment, c89str_len(comment), &lexer); + if (result != 0) { + return c89str_new(NULL, ""); /* Failed to initialize lexer. */ + } + + /* All we need to do is extract the first paragraph. */ + result = doc_lexer_next(&lexer); + if (result != 0) { + return c89str_new(NULL, ""); + } + + return c89str_newn(NULL, lexer.pTokenStr, lexer.tokenLen); +} + +c89str extract_example_summary(c89str code) +{ + int result; + c89str_lexer lexer; + c89str comment; + c89str summary; + + /* The summary is the first paragraph of the top section of the code which will be in a block comment. */ + result = c89str_lexer_init(&lexer, code, c89str_len(code)); + if (result != 0) { + return c89str_new(NULL, ""); /* Failed. */ + } + + /* + We have the C lexer ready to go. We need to get the first block comment. To do this we just exclude whitespace and new lines and take the first + token. If it's a comment, that'll be where we draw the summary from. Otherwise we'll just return an empty string. + */ + lexer.options.skipNewlines = C89STR_TRUE; + lexer.options.skipWhitespace = C89STR_TRUE; + + result = c89str_lexer_next(&lexer); + if (result != 0 || lexer.token != c89str_token_type_comment) { + return c89str_new(NULL, ""); /* Failed to retrieve the first token. */ + } + + /* We now want to format the comment in preparation for running it through the documentation lexer. */ + result = c89str_lexer_transform_token(&lexer, &comment, NULL); + if (result != 0) { + return c89str_new(NULL, ""); /* Failed to transform comment. */ + } + + /* Trim the comment to ensure all leading whitespace and new line characters are excluded. */ + comment = c89str_trim(comment, NULL); + + /* We now have enough information to extract the summary from the comment. */ + summary = c89str_trim(extract_example_summary_from_comment(comment), NULL); + c89str_delete(comment, NULL); + + return summary; +} + +c89str navigation_to_html(docstate* pState, doc_category category, const char* pEntityName) +{ + c89str html = NULL; + + (void)pState; + + if (category == doc_category_home) { + html = c89str_cat(html, NULL, "Documentation Home"); + } else { + html = c89str_cat(html, NULL, "Documentation Home"); + } + + if (category == doc_category_manual) { + size_t iSection; + + html = c89str_cat(html, NULL, "Programming Manual"); + + for (iSection = 0; iSection < pState->manual.sectionCount; iSection += 1) { + if (pState->manual.sections[iSection].level == 1) { + html = c89str_catf(html, NULL, "%s", pState->manual.sections[iSection].bookmark, (int)pState->manual.sections[iSection].level, pState->manual.sections[iSection].title); + } + } + } else { + html = c89str_cat(html, NULL, "Programming Manual"); + } + + if (category == doc_category_examples) { + size_t iExample; + + html = c89str_catf(html, NULL, "Examples", (pEntityName == NULL) ? "doc-navigation-active" : ""); + + for (iExample = 0; iExample < pState->examples.count; iExample += 1) { + c89str_bool32 isActive = C89STR_FALSE; + if (pEntityName != NULL && strcmp(pEntityName, pState->examples.examples[iExample].name) == 0) { + isActive = C89STR_TRUE; + } + + html = c89str_catf(html, NULL, "%s", example_name_to_html_file_name(pState->examples.examples[iExample].name), (int)1, (isActive) ? "doc-navigation-active" : "", example_name_to_display(pState->examples.examples[iExample].name)); + } + } else { + html = c89str_cat(html, NULL, "Examples"); + } + + if (category == doc_category_api) { + html = c89str_cat(html, NULL, "API Reference"); + } else { + html = c89str_cat(html, NULL, "API Reference"); + } + + return html; +} + +c89str generate_html_generic(docstate* pState, const char* pNavitationHTML, const char* pBodyHTML) +{ + c89str html = NULL; + + (void)pState; + + html = c89str_cat(html, NULL, "{{ miniaudio-header }}\n"); + { + html = c89str_cat(html, NULL, "\n"); + { + /* Navigation panel. */ + html = c89str_cat(html, NULL, ""); + + /* Body. */ + html = c89str_cat(html, NULL, ""); + } + html = c89str_cat(html, NULL, "\n
\n"); + { + html = c89str_cat(html, NULL, pNavitationHTML); + } + html = c89str_cat(html, NULL, "
\n"); + { + html = c89str_cat(html, NULL, pBodyHTML); + } + html = c89str_cat(html, NULL, "
"); + } + html = c89str_cat(html, NULL, "\n{{ miniaudio-footer }}"); + + return html; +} + + +static const char* g_HTMLBannerImage = "
"; + +c89str generate_home_index_html(docstate* pState) +{ + c89str html = NULL; + + (void)pState; + + html = c89str_cat(html, NULL, "
"); + { + html = c89str_cat(html, NULL, g_HTMLBannerImage); + html = c89str_cat(html, NULL, "
Documentation
"); + html = c89str_cat(html, NULL, "
"); + { + html = c89str_catf(html, NULL, "Programming Manual - "); + html = c89str_catf(html, NULL, "Examples - "); + html = c89str_catf(html, NULL, "API Reference - "); + html = c89str_catf(html, NULL, "Source Code"); + } + html = c89str_cat(html, NULL, "
"); + } + html = c89str_cat(html, NULL, "
"); + + return html; +} + +c89str generate_home_html(docstate* pState) +{ + return generate_html_generic(pState, navigation_to_html(pState, doc_category_home, NULL), generate_home_index_html(pState)); +} + + +c89str generate_manual_html(docstate* pState) +{ + c89str html = NULL; + + html = c89str_cat(html, NULL, "
"); + { + html = c89str_cat(html, NULL, g_HTMLBannerImage); + html = c89str_cat(html, NULL, "
Programming Manual
"); + html = c89str_cat(html, NULL, "
"); + { + html = c89str_catf(html, NULL, "Examples - "); + html = c89str_catf(html, NULL, "API Reference - "); + html = c89str_catf(html, NULL, "Source Code"); + } + html = c89str_cat(html, NULL, "
"); + } + html = c89str_cat(html, NULL, "
\n"); + + html = c89str_cat(html, NULL, raw_to_html(pState->manual.raw)); + + return generate_html_generic(pState, navigation_to_html(pState, doc_category_manual, NULL), html); +} + + +c89str generate_example_index_body(docstate* pState) +{ + c89str html = NULL; + + html = c89str_cat(html, NULL, "
"); + { + html = c89str_cat(html, NULL, g_HTMLBannerImage); + html = c89str_cat(html, NULL, "
Examples
"); + html = c89str_cat(html, NULL, "
"); + { + html = c89str_catf(html, NULL, "Programming Manual - "); + html = c89str_catf(html, NULL, "API Reference - "); + html = c89str_catf(html, NULL, "Source Code"); + } + html = c89str_cat(html, NULL, "
"); + } + html = c89str_cat(html, NULL, "
\n"); + + /* At the moment this is just a list of examples. */ + html = c89str_cat(html, NULL, ""); + { + size_t iExample; + for (iExample = 0; iExample < pState->examples.count; iExample += 1) { + html = c89str_cat(html, NULL, ""); + { + /* Name and URL. */ + html = c89str_cat(html, NULL, ""); + + /* Summary. */ + html = c89str_cat(html, NULL, ""); + } + html = c89str_cat(html, NULL, ""); + } + } + html = c89str_cat(html, NULL, "
"); + { + html = c89str_catf(html, NULL, "%s", example_name_to_html_file_name(pState->examples.examples[iExample].name), example_name_to_display(pState->examples.examples[iExample].name)); + } + html = c89str_cat(html, NULL, ""); + { + html = c89str_cat(html, NULL, extract_example_summary(pState->examples.examples[iExample].code)); // example_name_to_display(pState->examples.examples[iExample].name)); /* TODO: Change this to the summary. */ + } + html = c89str_cat(html, NULL, "
"); + + return html; +} + +c89str generate_example_index_html(docstate* pState) +{ + return generate_html_generic(pState, navigation_to_html(pState, doc_category_examples, NULL), generate_example_index_body(pState)); +} + +c89str generate_example_html_body(docstate* pState, const doc_example* pExample) +{ + c89str html = NULL; + c89str_lexer lexer; + const char* pCodeStart = pExample->code; + + (void)pState; + + html = c89str_catf(html, NULL, "

%s

", example_name_to_display(pExample->name)); + { + /* We use a C lexer to extract the top section which is in a comment. We then convert this to HTML. */ + int result = c89str_lexer_init(&lexer, pExample->code, c89str_len(pExample->code)); + if (result == 0) { + c89str comment; + + lexer.options.skipWhitespace = C89STR_TRUE; + lexer.options.skipNewlines = C89STR_TRUE; + + result = c89str_lexer_next(&lexer); + if (result == 0 && lexer.token == c89str_token_type_comment) { + result = c89str_lexer_transform_token(&lexer, &comment, NULL); + if (result == 0) { + comment = c89str_trim(comment, NULL); + html = c89str_cat(html, NULL, raw_to_html(comment)); + c89str_delete(comment, NULL); + + pCodeStart = lexer.pText + lexer.textOff; + } + } + } + } + html = c89str_cat(html, NULL, raw_to_html_code(pCodeStart + c89str_utf8_ltrim_offset(pCodeStart, (size_t)-1), (size_t)-1, 0, doc_lang_c)); + + return html; +} + +c89str generate_example_html(docstate* pState, const doc_example* pExample) +{ + c89str body = NULL; + c89str html = NULL; + + body = generate_example_html_body(pState, pExample); + + html = generate_html_generic(pState, navigation_to_html(pState, doc_category_examples, pExample->name), body); + c89str_delete(body, NULL); + + return html; +} + +int generate_examples(docstate* pState) +{ + int result; + size_t iExample; + + /* Examples home page. This is basically just a list of examples. */ + result = save_file(WEBSITE_DOCS_EXAMPLES_FOLDER"/index.html", generate_example_index_html(pState)); + if (result != 0) { + return result; /* Failed to output the examples index page. */ + } + + for (iExample = 0; iExample < pState->examples.count; iExample += 1) { + /* The file path is the same as the file name, only with .html as the extension. */ + char filePath[4096]; + + fs_path_append (filePath, sizeof(filePath), WEBSITE_DOCS_EXAMPLES_FOLDER, FS_NULL_TERMINATED, pState->examples.examples[iExample].name, FS_NULL_TERMINATED); + path_remove_extension(filePath, sizeof(filePath), filePath); + path_append_extension(filePath, sizeof(filePath), filePath, "html"); + + result = save_file(filePath, generate_example_html(pState, &pState->examples.examples[iExample])); + if (result != 0) { + return result; /* Failed to save the file. */ + } + } + + return 0; +} + + +c89str generate_api_index_body(docstate* pState) +{ + c89str html = NULL; + + (void)pState; + + html = c89str_cat(html, NULL, "
"); + { + html = c89str_cat(html, NULL, g_HTMLBannerImage); + html = c89str_cat(html, NULL, "
API Reference
"); + html = c89str_cat(html, NULL, "
"); + { + html = c89str_catf(html, NULL, "Programming Manual - "); + html = c89str_catf(html, NULL, "Examples - "); + html = c89str_catf(html, NULL, "Source Code"); + } + html = c89str_cat(html, NULL, "
"); + } + html = c89str_cat(html, NULL, "
\n"); + + html = c89str_cat(html, NULL, "Coming soon..."); + + return html; +} + +c89str generate_api_index_html(docstate* pState) +{ + return generate_html_generic(pState, navigation_to_html(pState, doc_category_api, NULL), generate_api_index_body(pState)); +} + +int generate_api(docstate* pState) +{ + int result; + + /* API home page. This is basically just a list of examples. */ + result = save_file(WEBSITE_DOCS_API_FOLDER"/index.html", generate_api_index_html(pState)); + if (result != 0) { + return result; /* Failed to output the examples index page. */ + } + + /* TODO: Generate API files. */ + + return 0; +} + + +int generate(docstate* pState) +{ + int result; + + + fs_rmdir_content(WEBSITE_DOCS_FOLDER); + + + /* Home */ + result = save_file(WEBSITE_DOCS_FOLDER"/index.html", generate_home_html(pState)); + if (result != 0) { + return result; + } + + + /* Manual */ + result = save_file(WEBSITE_DOCS_MANUAL_FOLDER"/index.html", generate_manual_html(pState)); + if (result != 0) { + return result; + } + + + /* Examples */ + result = generate_examples(pState); + if (result != 0) { + return result; + } + + + /* API */ + result = generate_api(pState); + if (result != 0) { + return result; + } + + + /* We're done. */ + return 0; +} + + +int main(int argc, char** argv) +{ + int result; + docstate state; + + (void)argc; + (void)argv; + + result = load(&state); + if (result != 0) { + return result; + } + + /* + NOTE: + + There's a weird permission error going on with my NAS. For now, you need to generate the website separately, + and then generate the output via webplate as a separate step. In addition, clearing the output directory with + WEBPLATE_FLAG_CLEAR_OUTDIR will sometimes fail. To address this, manually delete the output directory and run + webplate again. + */ + #if 1 + { + result = generate(&state); + if (result != 0) { + return result; + } + } + #endif + + /* Generate the final website with webplate. */ + if (webplate_process("website", "../miniaud.io", WEBPLATE_FLAG_CLEAR_OUTDIR | 0) != WEBPLATE_SUCCESS) { + printf("Failed to generate website via webplate."); + } + + return 0; +}