rhino/demo/c/dr_libs/old/dr_fs.h
Go to the documentation of this file.
1 // Public Domain. See "unlicense" statement at the end of this file.
2 //
3 // Includes code from miniz.c which can be found here: https://github.com/richgel999/miniz
4 
5 // NOTE: dr_fs is very early in development and should be considered unstable. Expect many APIs to change.
6 
7 // ABOUT
8 //
9 // dr_fs is a simple library which abstracts file IO to allow one to open files from both the native file
10 // system and archive/package files such as Zip files using a common API.
11 //
12 // This file includes code from miniz.c which has been stripped down to include only what's needed to support
13 // Zip files at basic level. Every public API has been namespaced with "drfs_" to avoid naming conflicts.
14 //
15 // Some noteworthy features:
16 // - Supports verbose absolute paths to avoid ambiguity. For example you can specify a path
17 // such as "my/package.zip/file.txt"
18 // - Supports shortened, transparent paths by automatically scanning for supported archives. The
19 // path "my/package.zip/file.txt" can be shortened to "my/file.txt", for example. This does not
20 // work for absolute paths, however. See notes below.
21 // - Fully recursive. A path such as "pack1.zip/pack2.zip/file.txt" should work just fine.
22 // - Easily supports custom package formats without the need to modify the original source code.
23 // Look at drfs_register_archive_backend() and the implementation of Zip archives for an
24 // example.
25 // - No dependencies except for the C standard library.
26 //
27 // Limitations:
28 // - When a file contained within a Zip file is opened, the entire uncompressed data is loaded
29 // onto the heap. Keep this in mind when working with large files.
30 // - Zip, PAK and Wavefront MTL archives are read-only at the moment.
31 // - dr_fs is not fully thread-safe. See notes below.
32 // - Asynchronous IO is not supported.
33 //
34 //
35 //
36 // USAGE
37 //
38 // This is a single-file library. To use it, do something like the following in one .c file.
39 // #define DR_FS_IMPLEMENTATION
40 // #include "dr_fs.h"
41 //
42 // You can then #include dr_fs.h in other parts of the program as you would with any other header file.
43 //
44 // Example:
45 // // Create a context.
46 // drfs_context* pVFS = drfs_create_context();
47 // if (pVFS == NULL) {
48 // // There was an error creating the context.
49 // }
50 //
51 // // Add your base directories for loading from relative paths. If you do not specify at
52 // // least one base directory you will need to load from absolute paths.
53 // drfs_add_base_directory(pVFS, "C:/Users/Admin");
54 // drfs_add_base_directory(pVFS, "C:/My/Folder");
55 //
56 // ...
57 //
58 // // Open a file. A relative path was specified which means it will first check it against
59 // // "C:/Users/Admin". If it can't be found it will then check against "C:/My/Folder".
60 // drfs_file* pFile;
61 // drfs_result result = drfs_open(pVFS, "my/file.txt", DRFS_READ, &pFile);
62 // if (result != drfs_success) {
63 // // There was an error loading the file. It probably doesn't exist.
64 // }
65 //
66 // result = drfs_read(pFile, buffer, bufferSize, NULL);
67 // if (result != drfs_success) {
68 // // There was an error reading the file.
69 // }
70 //
71 // drfs_close(pFile);
72 //
73 // ...
74 //
75 // // Shutdown.
76 // drfs_delete_context(pVFS);
77 //
78 //
79 //
80 // OPTIONS
81 //
82 // To use these options, just place the appropriate #define's in the .c file before including this file.
83 //
84 // #define DR_FS_NO_ZIP
85 // Disable built-in support for Zip files.
86 //
87 // #define DR_FS_NO_PAK
88 // Disable support for Quake 2 PAK files.
89 //
90 // #define DR_FS_NO_MTL
91 // Disable support for Wavefront MTL files.
92 //
93 //
94 //
95 // THREAD SAFETY
96 //
97 // dr_fs is not fully thread safe. Known unsafe functionality includes:
98 // - Opening a file while adding or removing base directories and backends
99 // - Closing a file while doing anything on that file object
100 // - drfs_open() will malloc() the drfs_file object, and drfs_close() will free() it with no garbage collection
101 // nor reference counting.
102 //
103 // The issues mentioned above should not be an issue for the vast majority of cases. Base directories and backends
104 // will typically be registered once during initialization when the context is created, and it's unlikely an
105 // application will want to close a file while simultaneously trying to use it on another thread without doing it's
106 // own synchronization anyway.
107 //
108 // Thread-safety has not been completely ignored either. It is possible to read, write and seek on multiple threads.
109 // In this case it is a simple matter of first-in first-served. Also, APIs are in place to allow an application to
110 // do it's own synchronization. An application can use drfs_lock() and drfs_unlock() to lock and unlock a file using
111 // simple mutal exclusion. Inside the lock/unlock pair the application can then use the "_nolock" variation of the
112 // relevant APIs:
113 // - drfs_read_nolock()
114 // - drfs_write_nolock()
115 // - drfs_seek_nolock()
116 // - drfs_tell_nolock()
117 // - drfs_size_nolock()
118 //
119 // Opening two files that share the same archive should work fine across multiple threads. For example, if you have
120 // an archive called MyArchive.zip and then open two files within that archive, you can do work with each of those
121 // files independently on separate threads. This functionality depends on the implementation of the relevant backend,
122 // however.
123 //
124 // When implementing a backend, it is important to keep synchronization in mind when reading data from the host
125 // archive file. To help with this, use the drfs_lock() and drfs_unlock() combined with the "_nolock" variations
126 // of the APIs listed above.
127 //
128 //
129 //
130 // QUICK NOTES
131 //
132 // - The library works by using the notion of an "archive" to create an abstraction around the file system.
133 // - Conceptually, and archive is just a grouping of files and folders. An archive can be a directory on the native
134 // file system or an actual archive file such as a .zip file.
135 // - When iterating over files and folder, the order is undefined. Do not assume alphabetical.
136 // - When a path includes the name of the package file, such as "my/package.zip/file.txt" (note how the .zip file is
137 // included in the path), it is referred to as a verbose path.
138 // - When specifying an absolute path, it is assumed to be verbose. When specifying a relative path, it does not need
139 // to be verbose, in which case the library will try to search for it. A path such as "my/package.zip/file.txt" is
140 // equivalent to "my/file.txt".
141 // - Archive backends are selected based on their extension.
142 // - Archive backends cannot currently share the same extension. For example, many package file formats use the .pak
143 // extension, however only one backend can use that .pak extension.
144 // - For safety, if you want to overwrite a file you must explicitly call drfs_open() with the DRFS_TRUNCATE flag.
145 // - Platforms other than Windows do not use buffering. That is, they use read() and write() instead of fread() and
146 // fwrite().
147 // - On Linux platforms, if you are having issues with opening files larger than 2GB, make sure this file is the first
148 // file included in the .c file. This ensures the _LARGEFILE64_SOURCE macro is defined before any other header file
149 // as required for the use of 64-bit variants of the POSIX APIs.
150 // - Base paths must be absolute and verbose.
151 //
152 //
153 //
154 // TODO:
155 //
156 // - Test result code consistency.
157 // - Document performance issues.
158 // - Consider making it so persistent constant strings (such as base paths) use dynamically allocated strings rather
159 // than fixed sized arrays of DRFS_MAX_PATH.
160 // - Replace the miniz reading functionality with a custom one:
161 // - There is a sort-of-bug where miniz does not correctly enumerate directories in a zip file that was created with
162 // the "Send to -> Compressed (zipped) folder" functionality in Windows Explorer. This is more of a thing with
163 // Windows Explorer more than anything, but it'd be nice if it would Just Work.
164 // - miniz does not support streamed reading yet. Instead, one must decompress the entire file onto the heap which
165 // is a bit untidy and doesn't work well with very large files.
166 // - ZIP64 is not supported.
167 
168 #ifndef dr_fs_h
169 #define dr_fs_h
170 
171 // These need to be defined before including any headers, but we don't want to expose it to the public header.
172 #if defined(DR_FS_IMPLEMENTATION) && !defined(_WIN32)
173 #ifndef _LARGEFILE64_SOURCE
174 #define _LARGEFILE64_SOURCE
175 #endif
176 #ifndef _FILE_OFFSET_BITS
177 #define _FILE_OFFSET_BITS 64
178 #endif
179 #endif
180 
181 #include <stddef.h>
182 
183 #ifndef DR_SIZED_TYPES_DEFINED
184 #define DR_SIZED_TYPES_DEFINED
185 #if defined(_MSC_VER) && _MSC_VER < 1600
186 typedef signed char dr_int8;
187 typedef unsigned char dr_uint8;
188 typedef signed short dr_int16;
189 typedef unsigned short dr_uint16;
190 typedef signed int dr_int32;
191 typedef unsigned int dr_uint32;
192 typedef signed __int64 dr_int64;
193 typedef unsigned __int64 dr_uint64;
194 #else
195 #include <stdint.h>
196 typedef int8_t dr_int8;
197 typedef uint8_t dr_uint8;
198 typedef int16_t dr_int16;
199 typedef uint16_t dr_uint16;
200 typedef int32_t dr_int32;
201 typedef uint32_t dr_uint32;
202 typedef int64_t dr_int64;
203 typedef uint64_t dr_uint64;
204 #endif
207 #define DR_TRUE 1
208 #define DR_FALSE 0
209 #endif
210 
211 #ifdef __cplusplus
212 extern "C" {
213 #endif
214 
215 
216 // The maximum length of a path in bytes, including the null terminator. If a path exceeds this amount, it will be set to an empty
217 // string. When this is changed the source file will need to be recompiled. Most of the time leaving this at 256 is fine, but it's
218 // not a problem to increase the size if you are encountering issues. Note that increasing this value will increase memory usage
219 // on both the heap and the stack.
220 #ifndef DRFS_MAX_PATH
221 //#define DRFS_MAX_PATH 256
222 #define DRFS_MAX_PATH 1024
223 //#define DRFS_MAX_PATH 4096
224 #endif
225 
226 #define DRFS_READ (1 << 0)
227 #define DRFS_WRITE (1 << 1)
228 #define DRFS_EXISTING (1 << 2)
229 #define DRFS_TRUNCATE (1 << 3)
230 #define DRFS_CREATE_DIRS (1 << 4) // Creates the directory structure if required.
231 
232 #define DRFS_FILE_ATTRIBUTE_DIRECTORY 0x00000001
233 #define DRFS_FILE_ATTRIBUTE_READONLY 0x00000002
234 
235 // Result codes.
236 typedef enum
237 {
240  drfs_invalid_args = -2, // Bad input arguments like a file path string is equal to NULL or whatnot.
247  drfs_not_in_write_directory = -9, // A write operation is required, but the given path is not within the write directory or it's sub-directories.
255 } drfs_result;
256 
257 // The allowable seeking origins.
258 typedef enum
259 {
264 
265 
266 typedef void* drfs_handle;
267 
268 typedef struct drfs_context drfs_context;
269 typedef struct drfs_archive drfs_archive;
270 typedef struct drfs_file drfs_file;
273 
274 typedef dr_bool32 (* drfs_is_valid_extension_proc)(const char* extension);
275 typedef drfs_result (* drfs_open_archive_proc) (drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut);
276 typedef void (* drfs_close_archive_proc) (drfs_handle archive);
277 typedef drfs_result (* drfs_get_file_info_proc) (drfs_handle archive, const char* relativePath, drfs_file_info* fi);
278 typedef drfs_handle (* drfs_begin_iteration_proc) (drfs_handle archive, const char* relativePath);
279 typedef void (* drfs_end_iteration_proc) (drfs_handle archive, drfs_handle iterator);
281 typedef drfs_result (* drfs_delete_file_proc) (drfs_handle archive, const char* relativePath);
282 typedef drfs_result (* drfs_create_directory_proc) (drfs_handle archive, const char* relativePath);
283 typedef drfs_result (* drfs_move_file_proc) (drfs_handle archive, const char* relativePathOld, const char* relativePathNew);
284 typedef drfs_result (* drfs_copy_file_proc) (drfs_handle archive, const char* relativePathSrc, const char* relativePathDst, dr_bool32 failIfExists);
285 typedef drfs_result (* drfs_open_file_proc) (drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut);
286 typedef void (* drfs_close_file_proc) (drfs_handle archive, drfs_handle file);
287 typedef drfs_result (* drfs_read_file_proc) (drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut);
288 typedef drfs_result (* drfs_write_file_proc) (drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut);
289 typedef drfs_result (* drfs_seek_file_proc) (drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin);
292 typedef void (* drfs_flush_file_proc) (drfs_handle archive, drfs_handle file);
293 
294 typedef struct
295 {
296  drfs_is_valid_extension_proc is_valid_extension;
297  drfs_open_archive_proc open_archive;
298  drfs_close_archive_proc close_archive;
299  drfs_get_file_info_proc get_file_info;
300  drfs_begin_iteration_proc begin_iteration;
301  drfs_end_iteration_proc end_iteration;
302  drfs_next_iteration_proc next_iteration;
303  drfs_delete_file_proc delete_file;
304  drfs_create_directory_proc create_directory;
305  drfs_move_file_proc move_file;
306  drfs_copy_file_proc copy_file;
307  drfs_open_file_proc open_file;
308  drfs_close_file_proc close_file;
311  drfs_seek_file_proc seek_file;
312  drfs_tell_file_proc tell_file;
313  drfs_file_size_proc file_size;
314  drfs_flush_file_proc flush_file;
316 
317 struct drfs_file_info
318 {
319  // The absolute path of the file.
321 
322  // The size of the file, in bytes.
324 
325  // The time the file was last modified.
327 
328  // File attributes.
329  unsigned int attributes;
330 };
331 
332 struct drfs_iterator
333 {
334  // A pointer to the archive that contains the folder being iterated.
336 
337  // A pointer to the iterator's internal handle that was returned with begin_iteration().
339 
340  // The file info.
342 };
343 
344 
345 typedef struct
346 {
347  drfs_archive_callbacks* pBuffer;
348  unsigned int count;
350 
351 typedef struct
352 {
353  char absolutePath[DRFS_MAX_PATH];
354 } drfs_basepath;
355 
356 typedef struct
357 {
358  drfs_basepath* pBuffer;
359  unsigned int capacity;
360  unsigned int count;
361 } drfs_basedirs;
362 
363 struct drfs_context
364 {
365  // The list of archive callbacks which are used for loading non-native archives. This does not include the native callbacks.
367 
368  // The list of base directories.
370 
371  // The write base directory.
373 
374  // Keeps track of whether or not write directory guard is enabled.
376 };
377 
378 
379 // Initializes a pre-allocated context.
381 
382 // Uninitializes a context.
384 
385 
386 // Creates an empty context.
388 
389 // Deletes the given context.
390 //
391 // This does not close any files or archives - it is up to the application to ensure those are tidied up.
392 void drfs_delete_context(drfs_context* pContext);
393 
394 
395 // Registers an archive back-end.
397 
398 
399 // Inserts a base directory at a specific priority position.
400 //
401 // A lower value index means a higher priority. This must be in the range of [0, drfs_get_base_directory_count()].
402 void drfs_insert_base_directory(drfs_context* pContext, const char* absolutePath, unsigned int index);
403 
404 // Adds a base directory to the end of the list.
405 //
406 // The further down the list the base directory, the lower priority is will receive. This adds it to the end which
407 // means it it given a lower priority to those that are already in the list. Use drfs_insert_base_directory() to
408 // insert the base directory at a specific position.
409 //
410 // Base directories must be an absolute path to a real directory.
411 void drfs_add_base_directory(drfs_context* pContext, const char* absolutePath);
412 
413 // Removes the given base directory.
414 void drfs_remove_base_directory(drfs_context* pContext, const char* absolutePath);
415 
416 // Removes the directory at the given index.
417 //
418 // If you need to remove every base directory, use drfs_remove_all_base_directories() since that is more efficient.
419 void drfs_remove_base_directory_by_index(drfs_context* pContext, unsigned int index);
420 
421 // Removes every base directory from the given context.
423 
424 // Retrieves the number of base directories attached to the given context.
425 unsigned int drfs_get_base_directory_count(drfs_context* pContext);
426 
427 // Retrieves the base directory at the given index.
428 const char* drfs_get_base_directory_by_index(drfs_context* pContext, unsigned int index);
429 
430 
431 // Sets the base directory for write operations (including delete).
432 //
433 // When doing a write operation using a relative path, the full path will be resolved using this directory as the base.
434 //
435 // If the base write directory is not set, and absolute path must be used for all write operations.
436 //
437 // If the write directory guard is enabled, all write operations that are attempted at a higher level than this directory
438 // will fail.
439 void drfs_set_base_write_directory(drfs_context* pContext, const char* absolutePath);
440 
441 // Retrieves the base write directory.
442 dr_bool32 drfs_get_base_write_directory(drfs_context* pContext, char* absolutePathOut, unsigned int absolutePathOutSize);
443 
444 // Enables the write directory guard.
446 
447 // Disables the write directory guard.
449 
450 // Determines whether or not the base directory guard is enabled.
452 
453 
454 // Opens an archive at the given path.
455 //
456 // If the given path points to a directory on the native file system an archive will be created at that
457 // directory. If the path points to an archive file such as a .zip file, dr_fs will hold a handle to
458 // that file until the archive is closed with drfs_close_archive(). Keep this in mind if you are keeping
459 // many archives open at a time on platforms that limit the number of files one can have open at any given
460 // time.
461 //
462 // The given path must be either absolute, or relative to one of the base directories.
463 //
464 // The path can be nested, such as "C:/my_zip_file.zip/my_inner_zip_file.zip".
465 drfs_result drfs_open_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_archive** ppArchiveOut);
466 
467 // Opens the archive that owns the given file.
468 //
469 // This is different to drfs_open_archive() in that it can accept non-archive files. It will open the
470 // archive that directly owns the file. In addition, it will output the path of the file, relative to the
471 // archive.
472 //
473 // If the given file is an archive itself, the archive that owns that archive will be opened. If the file
474 // is a file on the native file system, the returned archive will represent the folder it's directly
475 // contained in.
476 drfs_result drfs_open_owner_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut);
477 
478 // Closes the given archive.
479 void drfs_close_archive(drfs_archive* pArchive);
480 
481 // Opens a file relative to the given archive.
482 drfs_result drfs_open_file_from_archive(drfs_archive* pArchive, const char* relativePath, unsigned int accessMode, drfs_file** ppFileOut);
483 
484 
485 
486 // Opens a file.
487 //
488 // When opening the file in write mode, the write pointer will always be sitting at the start of the file.
489 drfs_result drfs_open(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_file** ppFileOut);
490 
491 // Closes the given file.
492 void drfs_close(drfs_file* pFile);
493 
494 // Reads data from the given file.
495 //
496 // Returns DR_TRUE if successful; DR_FALSE otherwise. If the value output to <pBytesReadOut> is less than <bytesToRead> it means the file is at the end.
497 //
498 // Do not use the return value to check if the end of the file has been reached. Instead, compare <bytesRead> to the value returned to <pBytesReadOut>.
499 drfs_result drfs_read(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut);
500 
501 // Writes data to the given file.
502 drfs_result drfs_write(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut);
503 
504 // Seeks the file pointer by the given number of bytes, relative to the specified origin.
505 drfs_result drfs_seek(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin);
506 
507 // Retrieves the current position of the file pointer.
509 
510 // Retrieves the size of the given file.
512 
513 // Flushes the given file.
514 void drfs_flush(drfs_file* pFile);
515 
516 
517 // Locks the given file for simple mutal exclusion.
518 //
519 // If DR_FALSE is returned it means there was an error and the operation should be aborted.
521 
522 // Unlocks the given file for simple mutal exclusion.
523 void drfs_unlock(drfs_file* pFile);
524 
525 // Unlocked drfs_read() - should only be called inside a drfs_lock()/drfs_unlock() pair.
526 drfs_result drfs_read_nolock(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut);
527 
528 // Unlocked drfs_write() - should only be called inside a drfs_lock()/drfs_unlock() pair.
529 drfs_result drfs_write_nolock(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut);
530 
531 // Unlocked drfs_seek() - should only be called inside a drfs_lock()/drfs_unlock() pair.
532 drfs_result drfs_seek_nolock(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin);
533 
534 // Unlocked drfs_tell() - should only be called inside a drfs_lock()/drfs_unlock() pair.
536 
537 // Unlocked drfs_size() - should only be called inside a drfs_lock()/drfs_unlock() pair.
539 
540 
541 // Retrieves information about the file at the given path.
542 //
543 // <fi> is allowed to be null, in which case the call is equivalent to simply checking if the file exists.
544 drfs_result drfs_get_file_info(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_file_info* fi);
545 
546 
547 // Creates an iterator for iterating over the files and folders in the given directory.
548 dr_bool32 drfs_begin(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_iterator* pIteratorOut);
549 
550 // Goes to the next file or folder based on the given iterator.
551 dr_bool32 drfs_next(drfs_context* pContext, drfs_iterator* pIterator);
552 
553 // Closes the given iterator.
554 //
555 // This is not needed if drfs_next_iteration() returns DR_FALSE naturally. If iteration is terminated early, however, this
556 // needs to be called on the iterator to ensure internal resources are freed.
557 void drfs_end(drfs_context* pContext, drfs_iterator* pIterator);
558 
559 
560 // Deletes the file at the given path.
561 //
562 // The path must be a absolute, or relative to the write directory.
563 drfs_result drfs_delete_file(drfs_context* pContext, const char* path);
564 
565 // Creates a directory.
566 //
567 // The path must be a absolute, or relative to the write directory.
568 drfs_result drfs_create_directory(drfs_context* pContext, const char* path);
569 
570 // Moves or renames the given file.
571 //
572 // The path must be a absolute, or relative to the write directory. This will fail if:
573 // - the file already exists
574 // - the old and new paths are across different archives
575 // - the archive containing both the old and new paths is read-only
576 // - the destinations directory structure does not exist
577 //
578 // Consider using drfs_copy_file() for more flexibility with moving files to a different location.
579 drfs_result drfs_move_file(drfs_context* pContext, const char* pathOld, const char* pathNew);
580 
581 // Copies a file.
582 //
583 // The destination path must be a absolute, or relative to the write directory.
584 drfs_result drfs_copy_file(drfs_context* pContext, const char* srcPath, const char* dstPath, dr_bool32 failIfExists);
585 
586 
587 // Determines whether or not the given path refers to an archive file.
588 //
589 // This does not validate that the archive file exists or is valid. This will also return DR_FALSE if the path refers
590 // to a folder on the normal file system.
591 //
592 // Use drfs_open_archive() to check that the archive file actually exists.
593 dr_bool32 drfs_is_archive_path(drfs_context* pContext, const char* path);
594 
595 
596 
598 //
599 // High Level API
600 //
602 
603 // Free's memory that was allocated internally by dr_fs. This is used when dr_fs allocates memory via a high-level helper API
604 // and the application is done with that memory.
605 void drfs_free(void* p);
606 
607 // Finds the absolute, verbose path of the given path.
608 drfs_result drfs_find_absolute_path(drfs_context* pContext, const char* relativePath, char* absolutePathOut, size_t absolutePathOutSize);
609 
610 // Finds the absolute, verbose path of the given path, using the given path as the higest priority base path.
611 drfs_result drfs_find_absolute_path_explicit_base(drfs_context* pContext, const char* relativePath, const char* highestPriorityBasePath, char* absolutePathOut, size_t absolutePathOutSize);
612 
613 // Helper function for determining whether or not the given path refers to a base directory.
614 dr_bool32 drfs_is_base_directory(drfs_context* pContext, const char* baseDir);
615 
616 // Helper function for writing a string.
617 drfs_result drfs_write_string(drfs_file* pFile, const char* str);
618 
619 // Helper function for writing a string, and then inserting a new line right after it.
620 //
621 // The new line character is "\n" and NOT "\r\n".
622 drfs_result drfs_write_line(drfs_file* pFile, const char* str);
623 
624 
625 // Helper function for opening a binary file and retrieving it's data in one go.
626 //
627 // Free the returned pointer with drfs_free()
628 void* drfs_open_and_read_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut);
629 
630 // Helper function for opening a text file and retrieving it's data in one go.
631 //
632 // Free the returned pointer with drfs_free().
633 //
634 // The returned string is null terminated. The size returned by pSizeInBytesOut does not include the null terminator.
635 char* drfs_open_and_read_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut);
636 
637 // Helper function for opening a file, writing the given data, and then closing it. This deletes the contents of the existing file, if any.
638 drfs_result drfs_open_and_write_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, const void* pData, size_t dataSize);
639 
640 // Helper function for opening a file, writing the given textual data, and then closing it. This deletes the contents of the existing file, if any.
641 drfs_result drfs_open_and_write_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, const char* pTextData);
642 
643 
644 // Helper function for determining whether or not the given path refers to an existing file or directory.
645 dr_bool32 drfs_exists(drfs_context* pContext, const char* absoluteOrRelativePath);
646 
647 // Determines if the given path refers to an existing file (not a directory).
648 //
649 // This will return DR_FALSE for directories. Use drfs_exists() to check for either a file or directory.
650 dr_bool32 drfs_is_existing_file(drfs_context* pContext, const char* absoluteOrRelativePath);
651 
652 // Determines if the given path refers to an existing directory.
653 dr_bool32 drfs_is_existing_directory(drfs_context* pContext, const char* absoluteOrRelativePath);
654 
655 // Same as drfs_create_directory(), except creates the entire directory structure recursively.
656 drfs_result drfs_create_directory_recursive(drfs_context* pContext, const char* path);
657 
658 // Determines whether or not the given file is at the end.
659 //
660 // This is just a high-level helper function equivalent to drfs_tell(pFile) == drfs_size(pFile).
662 
663 
664 
665 #ifdef __cplusplus
666 }
667 #endif
668 
669 #endif //dr_fs_h
670 
671 
672 
674 //
675 // IMPLEMENTATION
676 //
678 
679 #ifdef DR_FS_IMPLEMENTATION
680 #include <stdlib.h>
681 #include <string.h>
682 #include <assert.h>
683 
684 #ifdef _WIN32
685 #include <windows.h>
686 #else
687 #include <strings.h>
688 #include <errno.h>
689 #include <pthread.h>
690 #endif
691 
692 // Whether or not the file owns the archive object it's part of.
693 #define DR_FS_OWNS_PARENT_ARCHIVE 0x00000001
694 
695 
696 static int drfs__strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
697 {
698 #ifdef _MSC_VER
699  return strcpy_s(dst, dstSizeInBytes, src);
700 #else
701  if (dst == 0) {
702  return EINVAL;
703  }
704  if (dstSizeInBytes == 0) {
705  return ERANGE;
706  }
707  if (src == 0) {
708  dst[0] = '\0';
709  return EINVAL;
710  }
711 
712  size_t i;
713  for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) {
714  dst[i] = src[i];
715  }
716 
717  if (i < dstSizeInBytes) {
718  dst[i] = '\0';
719  return 0;
720  }
721 
722  dst[0] = '\0';
723  return ERANGE;
724 #endif
725 }
726 
727 static int drfs__strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
728 {
729 #ifdef _MSC_VER
730  return strncpy_s(dst, dstSizeInBytes, src, count);
731 #else
732  if (dst == 0) {
733  return EINVAL;
734  }
735  if (dstSizeInBytes == 0) {
736  return EINVAL;
737  }
738  if (src == 0) {
739  dst[0] = '\0';
740  return EINVAL;
741  }
742 
743  size_t maxcount = count;
744  if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE
745  maxcount = dstSizeInBytes - 1;
746  }
747 
748  size_t i;
749  for (i = 0; i < maxcount && src[i] != '\0'; ++i) {
750  dst[i] = src[i];
751  }
752 
753  if (src[i] == '\0' || i == count || count == ((size_t)-1)) {
754  dst[i] = '\0';
755  return 0;
756  }
757 
758  dst[0] = '\0';
759  return ERANGE;
760 #endif
761 }
762 
763 static int drfs__strcat_s(char* dst, size_t dstSizeInBytes, const char* src)
764 {
765 #ifdef _MSC_VER
766  return strcat_s(dst, dstSizeInBytes, src);
767 #else
768  if (dst == 0) {
769  return EINVAL;
770  }
771  if (dstSizeInBytes == 0) {
772  return ERANGE;
773  }
774  if (src == 0) {
775  dst[0] = '\0';
776  return EINVAL;
777  }
778 
779  char* dstorig = dst;
780 
781  while (dstSizeInBytes > 0 && dst[0] != '\0') {
782  dst += 1;
783  dstSizeInBytes -= 1;
784  }
785 
786  if (dstSizeInBytes == 0) {
787  return EINVAL; // Unterminated.
788  }
789 
790 
791  while (dstSizeInBytes > 0 && src[0] != '\0') {
792  *dst++ = *src++;
793  dstSizeInBytes -= 1;
794  }
795 
796  if (dstSizeInBytes > 0) {
797  dst[0] = '\0';
798  } else {
799  dstorig[0] = '\0';
800  return ERANGE;
801  }
802 
803  return 0;
804 #endif
805 }
806 
807 static int drfs__stricmp(const char* string1, const char* string2)
808 {
809 #ifdef _MSC_VER
810  return _stricmp(string1, string2);
811 #else
812  return strcasecmp(string1, string2);
813 #endif
814 }
815 
816 
817 static dr_bool32 drfs_basedirs_init(drfs_basedirs* pBasePaths)
818 {
819  if (pBasePaths == NULL) {
820  return DR_FALSE;
821  }
822 
823  pBasePaths->pBuffer = 0;
824  pBasePaths->capacity = 0;
825  pBasePaths->count = 0;
826 
827  return DR_TRUE;
828 }
829 
830 static void drfs_basedirs_uninit(drfs_basedirs* pBasePaths)
831 {
832  if (pBasePaths == NULL) {
833  return;
834  }
835 
836  free(pBasePaths->pBuffer);
837 }
838 
839 static dr_bool32 drfs_basedirs_inflateandinsert(drfs_basedirs* pBasePaths, const char* absolutePath, unsigned int index)
840 {
841  if (pBasePaths == NULL) {
842  return DR_FALSE;
843  }
844 
845  unsigned int newBufferSize = (pBasePaths->capacity == 0) ? 2 : pBasePaths->capacity*2;
846 
847  drfs_basepath* pOldBuffer = pBasePaths->pBuffer;
848  drfs_basepath* pNewBuffer = (drfs_basepath*)malloc(newBufferSize * sizeof(drfs_basepath));
849  if (pNewBuffer == NULL) {
850  return DR_FALSE;
851  }
852 
853  for (unsigned int iDst = 0; iDst < index; ++iDst) {
854  memcpy(pNewBuffer + iDst, pOldBuffer + iDst, sizeof(drfs_basepath));
855  }
856 
857  drfs__strcpy_s((pNewBuffer + index)->absolutePath, DRFS_MAX_PATH, absolutePath);
858 
859  for (unsigned int iDst = index; iDst < pBasePaths->count; ++iDst) {
860  memcpy(pNewBuffer + iDst + 1, pOldBuffer + iDst, sizeof(drfs_basepath));
861  }
862 
863 
864  pBasePaths->pBuffer = pNewBuffer;
865  pBasePaths->capacity = newBufferSize;
866  pBasePaths->count += 1;
867 
868  free(pOldBuffer);
869  return DR_TRUE;
870 }
871 
872 static dr_bool32 drfs_basedirs_movedown1slot(drfs_basedirs* pBasePaths, unsigned int index)
873 {
874  if (pBasePaths == NULL || pBasePaths->count >= pBasePaths->capacity) {
875  return DR_FALSE;
876  }
877 
878  for (unsigned int iDst = pBasePaths->count; iDst > index; --iDst) {
879  memcpy(pBasePaths->pBuffer + iDst, pBasePaths->pBuffer + iDst - 1, sizeof(drfs_basepath));
880  }
881 
882  return DR_TRUE;
883 }
884 
885 static dr_bool32 drfs_basedirs_insert(drfs_basedirs* pBasePaths, const char* absolutePath, unsigned int index)
886 {
887  if (pBasePaths == NULL || index > pBasePaths->count) {
888  return DR_FALSE;
889  }
890 
891  if (pBasePaths->count == pBasePaths->capacity) {
892  return drfs_basedirs_inflateandinsert(pBasePaths, absolutePath, index);
893  } else {
894  if (!drfs_basedirs_movedown1slot(pBasePaths, index)) {
895  return DR_FALSE;
896  }
897 
898  drfs__strcpy_s((pBasePaths->pBuffer + index)->absolutePath, DRFS_MAX_PATH, absolutePath);
899  pBasePaths->count += 1;
900 
901  return DR_TRUE;
902  }
903 }
904 
905 static dr_bool32 drfs_basedirs_remove(drfs_basedirs* pBasePaths, unsigned int index)
906 {
907  if (pBasePaths == NULL || index >= pBasePaths->count) {
908  return DR_FALSE;
909  }
910 
911  assert(pBasePaths->count > 0);
912 
913  for (unsigned int iDst = index; iDst < pBasePaths->count - 1; ++iDst) {
914  memcpy(pBasePaths->pBuffer + iDst, pBasePaths->pBuffer + iDst + 1, sizeof(drfs_basepath));
915  }
916 
917  pBasePaths->count -= 1;
918 
919  return DR_TRUE;
920 }
921 
922 static void drfs_basedirs_clear(drfs_basedirs* pBasePaths)
923 {
924  if (pBasePaths == NULL) {
925  return;
926  }
927 
928  drfs_basedirs_uninit(pBasePaths);
929  drfs_basedirs_init(pBasePaths);
930 }
931 
932 
933 
934 static dr_bool32 drfs_callbacklist_init(drfs_callbacklist* pList)
935 {
936  if (pList == NULL) {
937  return DR_FALSE;
938  }
939 
940  pList->pBuffer = 0;
941  pList->count = 0;
942 
943  return DR_TRUE;
944 }
945 
946 static void drfs_callbacklist_uninit(drfs_callbacklist* pList)
947 {
948  if (pList == NULL) {
949  return;
950  }
951 
952  free(pList->pBuffer);
953 }
954 
955 static dr_bool32 drfs_callbacklist_inflate(drfs_callbacklist* pList)
956 {
957  if (pList == NULL) {
958  return DR_FALSE;
959  }
960 
961  drfs_archive_callbacks* pOldBuffer = pList->pBuffer;
962  drfs_archive_callbacks* pNewBuffer = (drfs_archive_callbacks*)malloc((pList->count + 1) * sizeof(drfs_archive_callbacks));
963  if (pNewBuffer == NULL) {
964  return DR_FALSE;
965  }
966 
967  for (unsigned int iDst = 0; iDst < pList->count; ++iDst) {
968  memcpy(pNewBuffer + iDst, pOldBuffer + iDst, sizeof(drfs_archive_callbacks));
969  }
970 
971  pList->pBuffer = pNewBuffer;
972 
973  free(pOldBuffer);
974  return DR_TRUE;
975 }
976 
977 static dr_bool32 drfs_callbacklist_pushback(drfs_callbacklist* pList, drfs_archive_callbacks callbacks)
978 {
979  if (pList == NULL) {
980  return DR_FALSE;
981  }
982 
983  if (!drfs_callbacklist_inflate(pList)) {
984  return DR_FALSE;
985  }
986 
987  pList->pBuffer[pList->count] = callbacks;
988  pList->count += 1;
989 
990  return DR_TRUE;
991 }
992 
993 
994 struct drfs_archive
995 {
996  // A pointer to the context that owns this archive.
997  drfs_context* pContext;
998 
999  // A pointer to the archive that contains this archive. This can be null in which case it is the top level archive (which is always native).
1000  drfs_archive* pParentArchive;
1001 
1002  // A pointer to the file containing the data of the archive file.
1003  drfs_file* pFile;
1004 
1005  // The internal handle that was returned when the archive was opened by the archive definition.
1006  drfs_handle internalArchiveHandle;
1007 
1008  // Flags. Can be a combination of the following:
1009  // DR_FS_OWNS_PARENT_ARCHIVE
1010  int flags;
1011 
1012  // The callbacks to use when working with on the archive. This contains all of the functions for opening files, reading
1013  // files, etc.
1014  drfs_archive_callbacks callbacks;
1015 
1016  // The absolute, verbose path of the archive. For native archives, this will be the name of the folder on the native file
1017  // system. For non-native archives (zip, etc.) this is the the path of the archive file.
1018  char absolutePath[DRFS_MAX_PATH]; // Change this to char absolutePath[1] and have it sized exactly as needed.
1019 };
1020 
1021 struct drfs_file
1022 {
1023  // A pointer to the archive that contains the file. This should never be null. Retrieve a pointer to the contex from this
1024  // by doing pArchive->pContext. The file containing the archive's raw data can be retrieved with pArchive->pFile.
1025  drfs_archive* pArchive;
1026 
1027  // The internal file handle for use by the archive that owns it.
1028  drfs_handle internalFileHandle;
1029 
1030 
1031  // Flags. Can be a combination of the following:
1032  // DR_FS_OWNS_PARENT_ARCHIVE
1033  int flags;
1034 
1035 
1036  // The critical section for locking and unlocking files.
1037 #ifdef _WIN32
1038 #ifdef DR_FS_WIN32_USE_EVENT_MUTEX
1039  HANDLE lock;
1040 #else
1041  CRITICAL_SECTION lock;
1042 #endif
1043 #else
1044  pthread_mutex_t lock;
1045 #endif
1046 };
1047 
1048 
1050 //
1051 // Functionality below is taken straight from dr_path, but namespaced as "drfs" to avoid naming conflicts.
1052 
1053 // Structure representing a section of a path.
1054 typedef struct
1055 {
1056  size_t offset;
1057  size_t length;
1058 } drfs_drpath_segment;
1059 
1060 // Structure used for iterating over a path while at the same time providing useful and easy-to-use information about the iteration.
1061 typedef struct drfs_drpath_iterator
1062 {
1063  const char* path;
1064  drfs_drpath_segment segment;
1065 } drfs_drpath_iterator;
1066 
1067 static dr_bool32 drfs_drpath_next(drfs_drpath_iterator* i)
1068 {
1069  if (i == NULL || i->path == NULL) {
1070  return DR_FALSE;
1071  }
1072 
1073  i->segment.offset = i->segment.offset + i->segment.length;
1074  i->segment.length = 0;
1075 
1076  while (i->path[i->segment.offset] != '\0' && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\')) {
1077  i->segment.offset += 1;
1078  }
1079 
1080  if (i->path[i->segment.offset] == '\0') {
1081  return DR_FALSE;
1082  }
1083 
1084 
1085  while (i->path[i->segment.offset + i->segment.length] != '\0' && (i->path[i->segment.offset + i->segment.length] != '/' && i->path[i->segment.offset + i->segment.length] != '\\')) {
1086  i->segment.length += 1;
1087  }
1088 
1089  return DR_TRUE;
1090 }
1091 
1092 static dr_bool32 drfs_drpath_prev(drfs_drpath_iterator* i)
1093 {
1094  if (i == NULL || i->path == NULL || i->segment.offset == 0) {
1095  return DR_FALSE;
1096  }
1097 
1098  i->segment.length = 0;
1099 
1100  do
1101  {
1102  i->segment.offset -= 1;
1103  } while (i->segment.offset > 0 && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\'));
1104 
1105  if (i->segment.offset == 0) {
1106  if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') {
1107  i->segment.length = 0;
1108  return DR_TRUE;
1109  }
1110 
1111  return DR_FALSE;
1112  }
1113 
1114 
1115  size_t offsetEnd = i->segment.offset + 1;
1116  while (i->segment.offset > 0 && (i->path[i->segment.offset] != '/' && i->path[i->segment.offset] != '\\')) {
1117  i->segment.offset -= 1;
1118  }
1119 
1120  if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') {
1121  i->segment.offset += 1;
1122  }
1123 
1124 
1125  i->segment.length = offsetEnd - i->segment.offset;
1126 
1127  return DR_TRUE;
1128 }
1129 
1130 static dr_bool32 drfs_drpath_first(const char* path, drfs_drpath_iterator* i)
1131 {
1132  if (i == 0) return DR_FALSE;
1133  i->path = path;
1134  i->segment.offset = 0;
1135  i->segment.length = 0;
1136 
1137  if (path == 0 || path[0] == '\0') {
1138  return DR_FALSE;
1139  }
1140 
1141  while (i->path[i->segment.length] != '\0' && (i->path[i->segment.length] != '/' && i->path[i->segment.length] != '\\')) {
1142  i->segment.length += 1;
1143  }
1144 
1145  return DR_TRUE;
1146 }
1147 
1148 static dr_bool32 drfs_drpath_last(const char* path, drfs_drpath_iterator* i)
1149 {
1150  if (i == 0) return DR_FALSE;
1151  i->path = path;
1152  i->segment.offset = 0;
1153  i->segment.length = 0;
1154 
1155  if (path == 0 || path[0] == '\0') {
1156  return DR_FALSE;
1157  }
1158 
1159  i->path = path;
1160  i->segment.offset = strlen(path);
1161  i->segment.length = 0;
1162 
1163  return drfs_drpath_prev(i);
1164 }
1165 
1166 static dr_bool32 drfs_drpath_segments_equal(const char* s0Path, const drfs_drpath_segment s0, const char* s1Path, const drfs_drpath_segment s1)
1167 {
1168  if (s0Path == NULL || s1Path == NULL) {
1169  return DR_FALSE;
1170  }
1171 
1172  if (s0.length != s1.length) {
1173  return DR_FALSE;
1174  }
1175 
1176  return strncmp(s0Path + s0.offset, s1Path + s1.offset, s0.length) == 0;
1177 }
1178 
1179 static dr_bool32 drfs_drpath_iterators_equal(const drfs_drpath_iterator i0, const drfs_drpath_iterator i1)
1180 {
1181  return drfs_drpath_segments_equal(i0.path, i0.segment, i1.path, i1.segment);
1182 }
1183 
1184 static dr_bool32 drfs_drpath_is_linux_style_root_segment(const drfs_drpath_iterator i)
1185 {
1186  if (i.path == NULL) {
1187  return DR_FALSE;
1188  }
1189 
1190  if (i.segment.offset == 0 && i.segment.length == 0) {
1191  return DR_TRUE; // "/" style root.
1192  }
1193 
1194  return DR_FALSE;
1195 }
1196 
1197 static dr_bool32 drfs_drpath_is_win32_style_root_segment(const drfs_drpath_iterator i)
1198 {
1199  if (i.path == NULL) {
1200  return DR_FALSE;
1201  }
1202 
1203  if (i.segment.offset == 0 && i.segment.length == 2) {
1204  if (((i.path[0] >= 'a' && i.path[0] <= 'z') || (i.path[0] >= 'A' && i.path[0] <= 'Z')) && i.path[1] == ':') {
1205  return DR_TRUE;
1206  }
1207  }
1208 
1209  return DR_FALSE;
1210 }
1211 
1212 static dr_bool32 drfs_drpath_is_root_segment(const drfs_drpath_iterator i)
1213 {
1214  return drfs_drpath_is_linux_style_root_segment(i) || drfs_drpath_is_win32_style_root_segment(i);
1215 }
1216 
1217 static dr_bool32 drfs_drpath_append_iterator(char* base, size_t baseBufferSizeInBytes, drfs_drpath_iterator i)
1218 {
1219  if (base == NULL) {
1220  return DR_FALSE;
1221  }
1222 
1223  size_t path1Length = strlen(base);
1224  size_t path2Length = i.segment.length;
1225 
1226  if (path1Length >= baseBufferSizeInBytes) {
1227  return DR_FALSE;
1228  }
1229 
1230  if (drfs_drpath_is_linux_style_root_segment(i)) {
1231  if (baseBufferSizeInBytes > 1) {
1232  base[0] = '/';
1233  base[1] = '\0';
1234  return DR_TRUE;
1235  }
1236  }
1237 
1238 
1239  // Slash.
1240  if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') {
1241  base[path1Length] = '/';
1242  path1Length += 1;
1243  }
1244 
1245  // Other part.
1246  if (path1Length + path2Length >= baseBufferSizeInBytes) {
1247  path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator.
1248  }
1249 
1250  drfs__strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, i.path + i.segment.offset, path2Length);
1251 
1252  return DR_TRUE;
1253 }
1254 
1255 static dr_bool32 drfs_drpath_is_descendant(const char* descendantAbsolutePath, const char* parentAbsolutePath)
1256 {
1257  drfs_drpath_iterator iChild;
1258  if (!drfs_drpath_first(descendantAbsolutePath, &iChild)) {
1259  return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant.
1260  }
1261 
1262  drfs_drpath_iterator iParent;
1263  if (drfs_drpath_first(parentAbsolutePath, &iParent))
1264  {
1265  do
1266  {
1267  // If the segment is different, the paths are different and thus it is not a descendant.
1268  if (!drfs_drpath_iterators_equal(iParent, iChild)) {
1269  return DR_FALSE;
1270  }
1271 
1272  if (!drfs_drpath_next(&iChild)) {
1273  return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant.
1274  }
1275 
1276  } while (drfs_drpath_next(&iParent));
1277  }
1278 
1279  return DR_TRUE;
1280 }
1281 
1282 static dr_bool32 drfs_drpath_is_child(const char* childAbsolutePath, const char* parentAbsolutePath)
1283 {
1284  drfs_drpath_iterator iChild;
1285  if (!drfs_drpath_first(childAbsolutePath, &iChild)) {
1286  return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant.
1287  }
1288 
1289  drfs_drpath_iterator iParent;
1290  if (drfs_drpath_first(parentAbsolutePath, &iParent))
1291  {
1292  do
1293  {
1294  // If the segment is different, the paths are different and thus it is not a descendant.
1295  if (!drfs_drpath_iterators_equal(iParent, iChild)) {
1296  return DR_FALSE;
1297  }
1298 
1299  if (!drfs_drpath_next(&iChild)) {
1300  return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant.
1301  }
1302 
1303  } while (drfs_drpath_next(&iParent));
1304  }
1305 
1306  // At this point we have finished iteration of the parent, which should be shorter one. We now do one more iterations of
1307  // the child to ensure it is indeed a direct child and not a deeper descendant.
1308  return !drfs_drpath_next(&iChild);
1309 }
1310 
1311 static void drfs_drpath_base_path(char* path)
1312 {
1313  if (path == NULL) {
1314  return;
1315  }
1316 
1317  char* baseend = path;
1318 
1319  // We just loop through the path until we find the last slash.
1320  while (path[0] != '\0') {
1321  if (path[0] == '/' || path[0] == '\\') {
1322  baseend = path;
1323  }
1324 
1325  path += 1;
1326  }
1327 
1328 
1329  // Now we just loop backwards until we hit the first non-slash.
1330  while (baseend > path) {
1331  if (baseend[0] != '/' && baseend[0] != '\\') {
1332  break;
1333  }
1334 
1335  baseend -= 1;
1336  }
1337 
1338 
1339  // We just put a null terminator on the end.
1340  baseend[0] = '\0';
1341 }
1342 
1343 static void drfs_drpath_copy_base_path(const char* path, char* baseOut, size_t baseSizeInBytes)
1344 {
1345  if (path == NULL || baseOut == NULL || baseSizeInBytes == 0) {
1346  return;
1347  }
1348 
1349  drfs__strcpy_s(baseOut, baseSizeInBytes, path);
1350  drfs_drpath_base_path(baseOut);
1351 }
1352 
1353 static const char* drfs_drpath_file_name(const char* path)
1354 {
1355  if (path == NULL) {
1356  return NULL;
1357  }
1358 
1359  const char* fileName = path;
1360 
1361  // We just loop through the path until we find the last slash.
1362  while (path[0] != '\0') {
1363  if (path[0] == '/' || path[0] == '\\') {
1364  fileName = path;
1365  }
1366 
1367  path += 1;
1368  }
1369 
1370  // At this point the file name is sitting on a slash, so just move forward.
1371  while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) {
1372  fileName += 1;
1373  }
1374 
1375  return fileName;
1376 }
1377 
1378 static const char* drfs_drpath_extension(const char* path)
1379 {
1380  if (path == NULL) {
1381  return NULL;
1382  }
1383 
1384  const char* extension = drfs_drpath_file_name(path);
1385  const char* lastoccurance = 0;
1386 
1387  // Just find the last '.' and return.
1388  while (extension[0] != '\0')
1389  {
1390  extension += 1;
1391 
1392  if (extension[0] == '.') {
1393  extension += 1;
1394  lastoccurance = extension;
1395  }
1396  }
1397 
1398  return (lastoccurance != 0) ? lastoccurance : extension;
1399 }
1400 
1401 static dr_bool32 drfs_drpath_equal(const char* path1, const char* path2)
1402 {
1403  if (path1 == NULL || path2 == NULL) {
1404  return DR_FALSE;
1405  }
1406 
1407  if (path1 == path2 || (path1[0] == '\0' && path2[0] == '\0')) {
1408  return DR_TRUE; // Two empty paths are treated as the same.
1409  }
1410 
1411  drfs_drpath_iterator iPath1;
1412  drfs_drpath_iterator iPath2;
1413  if (drfs_drpath_first(path1, &iPath1) && drfs_drpath_first(path2, &iPath2))
1414  {
1415  dr_bool32 isPath1Valid;
1416  dr_bool32 isPath2Valid;
1417 
1418  do
1419  {
1420  if (!drfs_drpath_iterators_equal(iPath1, iPath2)) {
1421  return DR_FALSE;
1422  }
1423 
1424  isPath1Valid = drfs_drpath_next(&iPath1);
1425  isPath2Valid = drfs_drpath_next(&iPath2);
1426 
1427  } while (isPath1Valid && isPath2Valid);
1428 
1429  // At this point either iPath1 and/or iPath2 have finished iterating. If both of them are at the end, the two paths are equal.
1430  return isPath1Valid == isPath2Valid && iPath1.path[iPath1.segment.offset] == '\0' && iPath2.path[iPath2.segment.offset] == '\0';
1431  }
1432 
1433  return DR_FALSE;
1434 }
1435 
1436 static dr_bool32 drfs_drpath_is_relative(const char* path)
1437 {
1438  if (path == NULL) {
1439  return DR_FALSE;
1440  }
1441 
1442  drfs_drpath_iterator seg;
1443  if (drfs_drpath_first(path, &seg)) {
1444  return !drfs_drpath_is_root_segment(seg);
1445  }
1446 
1447  // We'll get here if the path is empty. We consider this to be a relative path.
1448  return DR_TRUE;
1449 }
1450 
1451 static dr_bool32 drfs_drpath_is_absolute(const char* path)
1452 {
1453  return !drfs_drpath_is_relative(path);
1454 }
1455 
1456 static dr_bool32 drfs_drpath_append(char* base, size_t baseBufferSizeInBytes, const char* other)
1457 {
1458  if (base == NULL || other == NULL) {
1459  return DR_FALSE;
1460  }
1461 
1462  size_t path1Length = strlen(base);
1463  size_t path2Length = strlen(other);
1464 
1465  if (path1Length >= baseBufferSizeInBytes) {
1466  return DR_FALSE;
1467  }
1468 
1469 
1470  // Slash.
1471  if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') {
1472  base[path1Length] = '/';
1473  path1Length += 1;
1474  }
1475 
1476  // Other part.
1477  if (path1Length + path2Length >= baseBufferSizeInBytes) {
1478  path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator.
1479  }
1480 
1481  drfs__strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, other, path2Length);
1482 
1483  return DR_TRUE;
1484 }
1485 
1486 static dr_bool32 drfs_drpath_copy_and_append(char* dst, size_t dstSizeInBytes, const char* base, const char* other)
1487 {
1488  if (dst == NULL || dstSizeInBytes == 0) {
1489  return DR_FALSE;
1490  }
1491 
1492  drfs__strcpy_s(dst, dstSizeInBytes, base);
1493  return drfs_drpath_append(dst, dstSizeInBytes, other);
1494 }
1495 
1496 // This function recursively cleans a path which is defined as a chain of iterators. This function works backwards, which means at the time of calling this
1497 // function, each iterator in the chain should be placed at the end of the path.
1498 //
1499 // This does not write the null terminator, nor a leading slash for absolute paths.
1500 static size_t _drfs_drpath_clean_trywrite(drfs_drpath_iterator* iterators, unsigned int iteratorCount, char* pathOut, size_t pathOutSizeInBytes, unsigned int ignoreCounter)
1501 {
1502  if (iteratorCount == 0) {
1503  return 0;
1504  }
1505 
1506  drfs_drpath_iterator isegment = iterators[iteratorCount - 1];
1507 
1508 
1509  // If this segment is a ".", we ignore it. If it is a ".." we ignore it and increment "ignoreCount".
1510  int ignoreThisSegment = ignoreCounter > 0 && isegment.segment.length > 0;
1511 
1512  if (isegment.segment.length == 1 && isegment.path[isegment.segment.offset] == '.')
1513  {
1514  // "."
1515  ignoreThisSegment = 1;
1516  }
1517  else
1518  {
1519  if (isegment.segment.length == 2 && isegment.path[isegment.segment.offset] == '.' && isegment.path[isegment.segment.offset + 1] == '.')
1520  {
1521  // ".."
1522  ignoreThisSegment = 1;
1523  ignoreCounter += 1;
1524  }
1525  else
1526  {
1527  // It's a regular segment, so decrement the ignore counter.
1528  if (ignoreCounter > 0) {
1529  ignoreCounter -= 1;
1530  }
1531  }
1532  }
1533 
1534 
1535  // The previous segment needs to be written before we can write this one.
1536  size_t bytesWritten = 0;
1537 
1538  drfs_drpath_iterator prev = isegment;
1539  if (!drfs_drpath_prev(&prev))
1540  {
1541  if (iteratorCount > 1)
1542  {
1543  iteratorCount -= 1;
1544  prev = iterators[iteratorCount - 1];
1545  }
1546  else
1547  {
1548  prev.path = NULL;
1549  prev.segment.offset = 0;
1550  prev.segment.length = 0;
1551  }
1552  }
1553 
1554  if (prev.segment.length > 0)
1555  {
1556  iterators[iteratorCount - 1] = prev;
1557  bytesWritten = _drfs_drpath_clean_trywrite(iterators, iteratorCount, pathOut, pathOutSizeInBytes, ignoreCounter);
1558  }
1559 
1560 
1561  if (!ignoreThisSegment)
1562  {
1563  if (pathOutSizeInBytes > 0)
1564  {
1565  pathOut += bytesWritten;
1566  pathOutSizeInBytes -= bytesWritten;
1567 
1568  if (bytesWritten > 0)
1569  {
1570  pathOut[0] = '/';
1571  bytesWritten += 1;
1572 
1573  pathOut += 1;
1574  pathOutSizeInBytes -= 1;
1575  }
1576 
1577  if (pathOutSizeInBytes >= isegment.segment.length)
1578  {
1579  drfs__strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, isegment.segment.length);
1580  bytesWritten += isegment.segment.length;
1581  }
1582  else
1583  {
1584  drfs__strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, pathOutSizeInBytes);
1585  bytesWritten += pathOutSizeInBytes;
1586  }
1587  }
1588  }
1589 
1590  return bytesWritten;
1591 }
1592 
1593 static size_t drfs_drpath_append_and_clean(char* dst, size_t dstSizeInBytes, const char* base, const char* other)
1594 {
1595  if (base == NULL || other == NULL) {
1596  return 0;
1597  }
1598 
1599  drfs_drpath_iterator last[2];
1600  dr_bool32 isPathEmpty0 = !drfs_drpath_last(base, last + 0);
1601  dr_bool32 isPathEmpty1 = !drfs_drpath_last(other, last + 1);
1602 
1603  if (isPathEmpty0 && isPathEmpty1) {
1604  return 0; // Both input strings are empty.
1605  }
1606 
1607  size_t bytesWritten = 0;
1608  if (base[0] == '/') {
1609  if (dst != NULL && dstSizeInBytes > 1) {
1610  dst[0] = '/';
1611  bytesWritten = 1;
1612  }
1613  }
1614 
1615  bytesWritten += _drfs_drpath_clean_trywrite(last, 2, dst + bytesWritten, dstSizeInBytes - bytesWritten - 1, 0); // -1 to ensure there is enough room for a null terminator later on.
1616  if (dstSizeInBytes > bytesWritten) {
1617  dst[bytesWritten] = '\0';
1618  }
1619 
1620  return bytesWritten + 1;
1621 }
1622 
1623 
1625 
1626 // Recursively creates the given directory structure on the native file system.
1627 static dr_bool32 drfs_mkdir_recursive_native(const char* absolutePath);
1628 
1629 // Determines whether or not the given path is valid for writing based on the base write path and whether or not
1630 // the write guard is enabled.
1631 static dr_bool32 drfs_validate_write_path(drfs_context* pContext, const char* absoluteOrRelativePath, char* absolutePathOut, unsigned int absolutePathOutSize)
1632 {
1633  // If the path is relative, we need to convert to absolute. Then, if the write directory guard is enabled, we need to check that it's a descendant of the base path.
1634  if (drfs_drpath_is_relative(absoluteOrRelativePath)) {
1635  if (drfs_drpath_append_and_clean(absolutePathOut, absolutePathOutSize, pContext->writeBaseDirectory, absoluteOrRelativePath)) {
1636  absoluteOrRelativePath = absolutePathOut;
1637  } else {
1638  return DR_FALSE;
1639  }
1640  } else {
1641  if (drfs__strcpy_s(absolutePathOut, absolutePathOutSize, absoluteOrRelativePath) != 0) {
1642  return DR_FALSE;
1643  }
1644  }
1645 
1646  // If you trigger this assert it means you're trying to open a file for writing with a relative path but haven't yet
1647  // set the write directory. Either set the write directory with drfs_set_base_write_directory(), or use an absolute
1648  // path to open the file.
1649  assert(drfs_drpath_is_absolute(absoluteOrRelativePath));
1650  if (!drfs_drpath_is_absolute(absoluteOrRelativePath)) {
1651  return DR_FALSE;
1652  }
1653 
1654 
1655  if (drfs_is_write_directory_guard_enabled(pContext)) {
1656  if (drfs_drpath_is_descendant(absoluteOrRelativePath, pContext->writeBaseDirectory)) {
1657  return DR_TRUE;
1658  } else {
1659  return DR_FALSE;
1660  }
1661  } else {
1662  return DR_TRUE;
1663  }
1664 }
1665 
1666 // A simple helper function for determining the access mode to use for an archive file based on the access mode
1667 // of a file within that archive.
1668 static unsigned int drfs_archive_access_mode(unsigned int fileAccessMode)
1669 {
1670  return (fileAccessMode == DRFS_READ) ? DRFS_READ : DRFS_READ | DRFS_WRITE | DRFS_EXISTING;
1671 }
1672 
1673 
1674 
1676 
1677 // The functions in this section implement a common abstraction for working with files on the native file system. When adding
1678 // support for a new platform you'll probably want to either implement a platform-specific back-end or force stdio.
1679 
1680 #if defined(_WIN32)
1681 #define DR_FS_USE_WIN32
1682 #else
1683 #define DR_FS_USE_STDIO
1684 #endif
1685 
1686 // Low-level function for opening a file on the native file system.
1687 //
1688 // This will fail if attempting to open a file that's inside an archive such as a zip file. It can only open
1689 // files that are sitting on the native file system.
1690 //
1691 // The given file path must be absolute.
1692 static drfs_result drfs_open_native_file(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut);
1693 
1694 // Closes the given native file.
1695 static void drfs_close_native_file(drfs_handle file);
1696 
1697 // Determines whether or not the given path refers to a directory on the native file system.
1698 static dr_bool32 drfs_is_native_directory(const char* absolutePath);
1699 
1700 // Determines whether or not the given path refers to a file on the native file system. This will return DR_FALSE for directories.
1701 static dr_bool32 drfs_is_native_file(const char* absolutePath);
1702 
1703 // Deletes a native file.
1704 static drfs_result drfs_delete_native_file(const char* absolutePath);
1705 
1706 // Creates a directory on the native file system.
1707 static drfs_result drfs_mkdir_native(const char* absolutePath);
1708 
1709 // Moves or renames a native file. Fails if the target already exists.
1710 static drfs_result drfs_move_native_file(const char* absolutePathOld, const char* absolutePathNew);
1711 
1712 // Creates a copy of a native file.
1713 static drfs_result drfs_copy_native_file(const char* absolutePathSrc, const char* absolutePathDst, dr_bool32 failIfExists);
1714 
1715 // Reads data from the given native file.
1716 static drfs_result drfs_read_native_file(drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut);
1717 
1718 // Writes data to the given native file.
1719 static drfs_result drfs_write_native_file(drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut);
1720 
1721 // Seeks the given native file.
1722 static drfs_result drfs_seek_native_file(drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin);
1723 
1724 // Retrieves the read/write pointer of the given native file.
1725 static dr_uint64 drfs_tell_native_file(drfs_handle file);
1726 
1727 // Retrieves the size of the given native file.
1728 static dr_uint64 drfs_get_native_file_size(drfs_handle file);
1729 
1730 // Flushes the given native file.
1731 static void drfs_flush_native_file(drfs_handle file);
1732 
1733 // Retrieves information about the file OR DIRECTORY at the given path on the native file system.
1734 //
1735 // <fi> is allowed to be null, in which case the call is equivalent to simply checking if the file or directory exists.
1736 static drfs_result drfs_get_native_file_info(const char* absolutePath, drfs_file_info* fi);
1737 
1738 // Creates an iterator for iterating over the native files in the given directory.
1739 //
1740 // The iterator will be deleted with drfs_end_native_iteration().
1741 static drfs_handle drfs_begin_native_iteration(const char* absolutePath);
1742 
1743 // Deletes the iterator that was returned with drfs_end_native_iteration().
1744 static void drfs_end_native_iteration(drfs_handle iterator);
1745 
1746 // Retrieves information about the next native file based on the given iterator.
1747 static dr_bool32 drfs_next_native_iteration(drfs_handle iterator, drfs_file_info* fi);
1748 
1749 
1750 
1751 #ifdef DR_FS_USE_WIN32
1752 static drfs_result drfs__GetLastError_to_result()
1753 {
1754  switch (GetLastError())
1755  {
1756  case ERROR_SUCCESS: return drfs_success;
1757  case ERROR_FILE_NOT_FOUND: return drfs_does_not_exist;
1758  case ERROR_PATH_NOT_FOUND: return drfs_does_not_exist;
1759  case ERROR_TOO_MANY_OPEN_FILES: return drfs_too_many_open_files;
1760  case ERROR_ACCESS_DENIED: return drfs_permission_denied;
1761  case ERROR_NOT_ENOUGH_MEMORY: return drfs_out_of_memory;
1762  case ERROR_DISK_FULL: return drfs_no_space;
1763  case ERROR_HANDLE_EOF: return drfs_at_end_of_file;
1764  case ERROR_NEGATIVE_SEEK: return drfs_negative_seek;
1765  default: return drfs_unknown_error;
1766  }
1767 }
1768 
1769 static drfs_result drfs_open_native_file(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut)
1770 {
1771  assert(absolutePath != NULL);
1772  assert(pHandleOut != NULL);
1773 
1774  DWORD dwDesiredAccess = 0;
1775  DWORD dwShareMode = 0;
1776  DWORD dwCreationDisposition = OPEN_EXISTING;
1777 
1778  if ((accessMode & DRFS_READ) != 0) {
1779  dwDesiredAccess |= FILE_GENERIC_READ;
1780  dwShareMode |= FILE_SHARE_READ;
1781  }
1782 
1783  if ((accessMode & DRFS_WRITE) != 0) {
1784  dwDesiredAccess |= FILE_GENERIC_WRITE;
1785 
1786  if ((accessMode & DRFS_EXISTING) != 0) {
1787  if ((accessMode & DRFS_TRUNCATE) != 0) {
1788  dwCreationDisposition = TRUNCATE_EXISTING;
1789  } else {
1790  dwCreationDisposition = OPEN_EXISTING;
1791  }
1792  } else {
1793  if ((accessMode & DRFS_TRUNCATE) != 0) {
1794  dwCreationDisposition = CREATE_ALWAYS;
1795  } else {
1796  dwCreationDisposition = OPEN_ALWAYS;
1797  }
1798  }
1799  }
1800 
1801 
1802  HANDLE hFile = CreateFileA(absolutePath, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
1803  if (hFile == INVALID_HANDLE_VALUE)
1804  {
1805  // We failed to create the file, however it may be because we are trying to create the file and the directory structure does not exist yet. In this
1806  // case we want to try creating the directory structure and try again.
1807  if ((accessMode & DRFS_WRITE) != 0 && (accessMode & DRFS_CREATE_DIRS) != 0)
1808  {
1809  char dirAbsolutePath[DRFS_MAX_PATH];
1810  drfs_drpath_copy_base_path(absolutePath, dirAbsolutePath, sizeof(dirAbsolutePath));
1811 
1812  if (!drfs_is_native_directory(dirAbsolutePath) && drfs_mkdir_recursive_native(dirAbsolutePath)) {
1813  hFile = CreateFileA(absolutePath, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
1814  }
1815  }
1816  }
1817 
1818  if (hFile != INVALID_HANDLE_VALUE) {
1819  *pHandleOut = (drfs_handle)hFile;
1820  return drfs_success;
1821  }
1822 
1823  return drfs__GetLastError_to_result();
1824 }
1825 
1826 static void drfs_close_native_file(drfs_handle file)
1827 {
1828  CloseHandle((HANDLE)file);
1829 }
1830 
1831 static dr_bool32 drfs_is_native_directory(const char* absolutePath)
1832 {
1833  DWORD attributes = GetFileAttributesA(absolutePath);
1834  return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
1835 }
1836 
1837 static dr_bool32 drfs_is_native_file(const char* absolutePath)
1838 {
1839  DWORD attributes = GetFileAttributesA(absolutePath);
1840  return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
1841 }
1842 
1843 static drfs_result drfs_delete_native_file(const char* absolutePath)
1844 {
1845  BOOL wasSuccessful;
1846 
1847  DWORD attributes = GetFileAttributesA(absolutePath);
1848  if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
1849  wasSuccessful = DeleteFileA(absolutePath);
1850  } else {
1851  wasSuccessful = RemoveDirectoryA(absolutePath);
1852  }
1853 
1854  if (wasSuccessful) {
1855  return drfs_success;
1856  }
1857 
1858  return drfs__GetLastError_to_result();
1859 }
1860 
1861 static drfs_result drfs_mkdir_native(const char* absolutePath)
1862 {
1863  BOOL wasSuccessful = CreateDirectoryA(absolutePath, NULL);
1864  if (wasSuccessful) {
1865  return drfs_success;
1866  }
1867 
1868  return drfs__GetLastError_to_result();
1869 }
1870 
1871 static drfs_result drfs_move_native_file(const char* absolutePathOld, const char* absolutePathNew)
1872 {
1873  BOOL wasSuccessful = MoveFileExA(absolutePathOld, absolutePathNew, 0);
1874  if (wasSuccessful) {
1875  return drfs_success;
1876  }
1877 
1878  return drfs__GetLastError_to_result();
1879 }
1880 
1881 static drfs_result drfs_copy_native_file(const char* absolutePathSrc, const char* absolutePathDst, dr_bool32 failIfExists)
1882 {
1883  BOOL wasSuccessful = CopyFileA(absolutePathSrc, absolutePathDst, failIfExists);
1884  if (wasSuccessful) {
1885  return drfs_success;
1886  }
1887 
1888  return drfs__GetLastError_to_result();
1889 }
1890 
1891 static drfs_result drfs_read_native_file(drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
1892 {
1893  // Unfortunately Win32 expects a DWORD for the number of bytes to read, however we accept size_t. We need to loop to ensure
1894  // we handle data > 4GB correctly.
1895 
1896  size_t totalBytesRead = 0;
1897 
1898  char* pDst = (char*)pDataOut;
1899  dr_uint64 bytesRemaining = bytesToRead;
1900  while (bytesRemaining > 0)
1901  {
1902  DWORD bytesToProcess;
1903  if (bytesRemaining > UINT_MAX) {
1904  bytesToProcess = UINT_MAX;
1905  } else {
1906  bytesToProcess = (DWORD)bytesRemaining;
1907  }
1908 
1909 
1910  DWORD bytesRead;
1911  BOOL wasSuccessful = ReadFile((HANDLE)file, pDst, bytesToProcess, &bytesRead, NULL);
1912  if (!wasSuccessful || bytesRead != bytesToProcess)
1913  {
1914  // We failed to read the chunk.
1915  totalBytesRead += bytesRead;
1916 
1917  if (pBytesReadOut) {
1918  *pBytesReadOut = totalBytesRead;
1919  }
1920 
1921  return drfs__GetLastError_to_result();
1922  }
1923 
1924  pDst += bytesRead;
1925  bytesRemaining -= bytesRead;
1926  totalBytesRead += bytesRead;
1927  }
1928 
1929 
1930  if (pBytesReadOut != NULL) {
1931  *pBytesReadOut = totalBytesRead;
1932  }
1933 
1934  return drfs_success;
1935 }
1936 
1937 static drfs_result drfs_write_native_file(drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
1938 {
1939  // Unfortunately Win32 expects a DWORD for the number of bytes to write, however we accept size_t. We need to loop to ensure
1940  // we handle data > 4GB correctly.
1941 
1942  size_t totalBytesWritten = 0;
1943  const char* pSrc = (const char*)pData;
1944 
1945  size_t bytesRemaining = bytesToWrite;
1946  while (bytesRemaining > 0)
1947  {
1948  DWORD bytesToProcess;
1949  if (bytesRemaining > UINT_MAX) {
1950  bytesToProcess = UINT_MAX;
1951  } else {
1952  bytesToProcess = (DWORD)bytesRemaining;
1953  }
1954 
1955  DWORD bytesWritten;
1956  BOOL wasSuccessful = WriteFile((HANDLE)file, pSrc, bytesToProcess, &bytesWritten, NULL);
1957  if (!wasSuccessful || bytesWritten != bytesToProcess)
1958  {
1959  // We failed to write the chunk.
1960  totalBytesWritten += bytesWritten;
1961 
1962  if (pBytesWrittenOut) {
1963  *pBytesWrittenOut = totalBytesWritten;
1964  }
1965 
1966  return drfs__GetLastError_to_result();
1967  }
1968 
1969  pSrc += bytesWritten;
1970  bytesRemaining -= bytesWritten;
1971  totalBytesWritten += bytesWritten;
1972  }
1973 
1974  if (pBytesWrittenOut) {
1975  *pBytesWrittenOut = totalBytesWritten;
1976  }
1977 
1978  return drfs_success;;
1979 }
1980 
1981 static drfs_result drfs_seek_native_file(drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin)
1982 {
1983  LARGE_INTEGER lNewFilePointer;
1984  LARGE_INTEGER lDistanceToMove;
1985  lDistanceToMove.QuadPart = bytesToSeek;
1986 
1987  DWORD dwMoveMethod = FILE_CURRENT;
1988  if (origin == drfs_origin_start) {
1989  dwMoveMethod = FILE_BEGIN;
1990  } else if (origin == drfs_origin_end) {
1991  dwMoveMethod = FILE_END;
1992  }
1993 
1994  BOOL wasSuccessful = SetFilePointerEx((HANDLE)file, lDistanceToMove, &lNewFilePointer, dwMoveMethod);
1995  if (!wasSuccessful) {
1996  return drfs__GetLastError_to_result();
1997  }
1998 
1999  return drfs_success;
2000 }
2001 
2002 static dr_uint64 drfs_tell_native_file(drfs_handle file)
2003 {
2004  LARGE_INTEGER lNewFilePointer;
2005  LARGE_INTEGER lDistanceToMove;
2006  lDistanceToMove.QuadPart = 0;
2007 
2008  if (SetFilePointerEx((HANDLE)file, lDistanceToMove, &lNewFilePointer, FILE_CURRENT)) {
2009  return (dr_uint64)lNewFilePointer.QuadPart;
2010  }
2011 
2012  return 0;
2013 }
2014 
2015 static dr_uint64 drfs_get_native_file_size(drfs_handle file)
2016 {
2017  LARGE_INTEGER fileSize;
2018  if (GetFileSizeEx((HANDLE)file, &fileSize)) {
2019  return (dr_uint64)fileSize.QuadPart;
2020  }
2021 
2022  return 0;
2023 }
2024 
2025 static void drfs_flush_native_file(drfs_handle file)
2026 {
2027  FlushFileBuffers((HANDLE)file);
2028 }
2029 
2030 static drfs_result drfs_get_native_file_info(const char* absolutePath, drfs_file_info* fi)
2031 {
2032  assert(absolutePath != NULL);
2033 
2034  // <fi> is allowed to be null, in which case the call is equivalent to simply checking if the file exists.
2035  if (fi == NULL) {
2036  if (GetFileAttributesA(absolutePath) != INVALID_FILE_ATTRIBUTES) {
2037  return drfs_success;
2038  }
2039 
2040  return drfs_does_not_exist;
2041  }
2042 
2043  WIN32_FILE_ATTRIBUTE_DATA fad;
2044  if (GetFileAttributesExA(absolutePath, GetFileExInfoStandard, &fad))
2045  {
2046  ULARGE_INTEGER liSize;
2047  liSize.LowPart = fad.nFileSizeLow;
2048  liSize.HighPart = fad.nFileSizeHigh;
2049 
2050  ULARGE_INTEGER liTime;
2051  liTime.LowPart = fad.ftLastWriteTime.dwLowDateTime;
2052  liTime.HighPart = fad.ftLastWriteTime.dwHighDateTime;
2053 
2054  strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), absolutePath);
2055  fi->sizeInBytes = liSize.QuadPart;
2056  fi->lastModifiedTime = liTime.QuadPart;
2057 
2058  fi->attributes = 0;
2059  if ((fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
2061  }
2062  if ((fad.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {
2064  }
2065 
2066  return drfs_success;
2067  }
2068 
2069  return drfs__GetLastError_to_result();
2070 }
2071 
2072 typedef struct
2073 {
2074  HANDLE hFind;
2075  WIN32_FIND_DATAA ffd;
2076  char directoryPath[1];
2077 } drfs_iterator_win32;
2078 
2079 static drfs_handle drfs_begin_native_iteration(const char* absolutePath)
2080 {
2081  assert(drfs_drpath_is_absolute(absolutePath));
2082 
2083  char searchQuery[DRFS_MAX_PATH];
2084  if (strcpy_s(searchQuery, sizeof(searchQuery), absolutePath) != 0) {
2085  return NULL;
2086  }
2087 
2088  unsigned int searchQueryLength = (unsigned int)strlen(searchQuery);
2089  if (searchQueryLength >= DRFS_MAX_PATH - 3) {
2090  return NULL; // Path is too long.
2091  }
2092 
2093  searchQuery[searchQueryLength + 0] = '\\';
2094  searchQuery[searchQueryLength + 1] = '*';
2095  searchQuery[searchQueryLength + 2] = '\0';
2096 
2097  drfs_iterator_win32* pIterator = (drfs_iterator_win32*)malloc(sizeof(*pIterator) + searchQueryLength);
2098  if (pIterator == NULL) {
2099  return NULL;
2100  }
2101 
2102  ZeroMemory(pIterator, sizeof(*pIterator));
2103 
2104  pIterator->hFind = FindFirstFileA(searchQuery, &pIterator->ffd);
2105  if (pIterator->hFind == INVALID_HANDLE_VALUE) {
2106  free(pIterator);
2107  return NULL; // Failed to begin search.
2108  }
2109 
2110  strcpy_s(pIterator->directoryPath, searchQueryLength + 1, absolutePath); // This won't fail in practice.
2111  return (drfs_handle)pIterator;
2112 }
2113 
2114 static void drfs_end_native_iteration(drfs_handle iterator)
2115 {
2116  drfs_iterator_win32* pIterator = (drfs_iterator_win32*)iterator;
2117  assert(pIterator != NULL);
2118 
2119  if (pIterator->hFind) {
2120  FindClose(pIterator->hFind);
2121  }
2122 
2123  free(pIterator);
2124 }
2125 
2126 static dr_bool32 drfs_next_native_iteration(drfs_handle iterator, drfs_file_info* fi)
2127 {
2128  drfs_iterator_win32* pIterator = (drfs_iterator_win32*)iterator;
2129  assert(pIterator != NULL);
2130 
2131  if (pIterator->hFind != INVALID_HANDLE_VALUE && pIterator->hFind != NULL)
2132  {
2133  // Skip past "." and ".." directories.
2134  while (strcmp(pIterator->ffd.cFileName, ".") == 0 || strcmp(pIterator->ffd.cFileName, "..") == 0) {
2135  if (FindNextFileA(pIterator->hFind, &pIterator->ffd) == 0) {
2136  return DR_FALSE;
2137  }
2138  }
2139 
2140  if (fi != NULL)
2141  {
2142  // The absolute path actually needs to be set to the relative path. The higher level APIs are the once responsible for translating
2143  // it back to an absolute path.
2144  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), pIterator->ffd.cFileName);
2145 
2146  ULARGE_INTEGER liSize;
2147  liSize.LowPart = pIterator->ffd.nFileSizeLow;
2148  liSize.HighPart = pIterator->ffd.nFileSizeHigh;
2149  fi->sizeInBytes = liSize.QuadPart;
2150 
2151  ULARGE_INTEGER liTime;
2152  liTime.LowPart = pIterator->ffd.ftLastWriteTime.dwLowDateTime;
2153  liTime.HighPart = pIterator->ffd.ftLastWriteTime.dwHighDateTime;
2154  fi->lastModifiedTime = liTime.QuadPart;
2155 
2156  fi->attributes = 0;
2157  if ((pIterator->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
2159  }
2160  if ((pIterator->ffd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {
2162  }
2163  }
2164 
2165  if (!FindNextFileA(pIterator->hFind, &pIterator->ffd)) {
2166  FindClose(pIterator->hFind);
2167  pIterator->hFind = NULL;
2168  }
2169 
2170  return DR_TRUE;
2171  }
2172 
2173  return DR_FALSE;
2174 }
2175 #endif //DR_FS_USE_WIN32
2176 
2177 
2178 #ifdef DR_FS_USE_STDIO
2179 #include <stdio.h>
2180 #include <sys/stat.h>
2181 #include <sys/types.h>
2182 #include <fcntl.h>
2183 #include <dirent.h>
2184 #include <unistd.h>
2185 #include <limits.h>
2186 
2187 #ifdef __linux__
2188 #include <sys/sendfile.h>
2189 #endif
2190 
2191 #define DRFS_HANDLE_TO_FD(file) ((int)((size_t)file) - 1)
2192 #define DRFS_FD_TO_HANDLE(fd) ((drfs_handle)(size_t)(fd + 1))
2193 
2194 static drfs_result drfs__errno_to_result()
2195 {
2196  switch (errno)
2197  {
2198  case EACCES: return drfs_permission_denied;
2199  case EEXIST: return drfs_already_exists;
2200  case EISDIR: return drfs_permission_denied;
2201  case EMFILE: return drfs_too_many_open_files;
2202  case ENFILE: return drfs_too_many_open_files;
2203  case ENAMETOOLONG: return drfs_path_too_long;
2204  case ENOENT: return drfs_does_not_exist;
2205  case ENOMEM: return drfs_out_of_memory;
2206  case ENOSPC: return drfs_no_space;
2207  case ENOTDIR: return drfs_not_directory;
2208  case EOVERFLOW: return drfs_too_large;
2209  case EROFS: return drfs_permission_denied;
2210  case ETXTBSY: return drfs_permission_denied;
2211  case EBADF: return drfs_invalid_args;
2212  case EINVAL: return drfs_invalid_args;
2213  default: return drfs_unknown_error;
2214  }
2215 }
2216 
2217 static int drfs__open_fd(const char* absolutePath, int flags)
2218 {
2219  return open64(absolutePath, flags, 0666);
2220 }
2221 
2222 static int drfs__stat64(const char* filename, struct stat64* st)
2223 {
2224  return stat64(filename, st);
2225 }
2226 
2227 static int drfs__fstat64(int fd, struct stat64* st)
2228 {
2229  return fstat64(fd, st);
2230 }
2231 
2232 static int drfs__mode_is_dir(int mode)
2233 {
2234  return S_ISDIR(mode);
2235 }
2236 
2237 static int drfs__mode_has_write_permission(int mode)
2238 {
2239  return (mode & S_IWUSR) != 0;
2240 }
2241 
2242 
2243 static drfs_result drfs_open_native_file(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut)
2244 {
2245  assert(absolutePath != NULL);
2246  assert(pHandleOut != NULL);
2247 
2248  *pHandleOut = NULL;
2249 
2250  int flags = 0;
2251  if ((accessMode & DRFS_READ) != 0) {
2252  if ((accessMode & DRFS_WRITE) != 0) {
2253  flags = O_RDWR;
2254  } else {
2255  flags = O_RDONLY;
2256  }
2257  } else {
2258  if ((accessMode & DRFS_WRITE) != 0) {
2259  flags = O_WRONLY;
2260  } else {
2261  return drfs_permission_denied; // Neither read nor write mode was specified.
2262  }
2263  }
2264 
2265  if ((accessMode & DRFS_TRUNCATE) != 0) {
2266  flags |= O_TRUNC;
2267  }
2268 
2269  if ((accessMode & DRFS_EXISTING) == 0) {
2270  flags |= O_CREAT;
2271  }
2272 
2273  int fd = drfs__open_fd(absolutePath, flags);
2274  if (fd == -1)
2275  {
2276  // We failed to open the file, however it could be because the directory structure is not in place. We need to check
2277  // the access mode flags for DRFS_CREATE_DIRS and try creating the directory structure.
2278  if ((accessMode & DRFS_WRITE) != 0 && (accessMode & DRFS_CREATE_DIRS) != 0)
2279  {
2280  char dirAbsolutePath[DRFS_MAX_PATH];
2281  drfs_drpath_copy_base_path(absolutePath, dirAbsolutePath, sizeof(dirAbsolutePath));
2282 
2283  if (!drfs_is_native_directory(dirAbsolutePath) && drfs_mkdir_recursive_native(dirAbsolutePath)) {
2284  fd = drfs__open_fd(absolutePath, flags);
2285  }
2286  }
2287  }
2288 
2289  if (fd == -1) {
2290  return drfs__errno_to_result();
2291  }
2292 
2293  *pHandleOut = DRFS_FD_TO_HANDLE(fd);
2294  return drfs_success;
2295 }
2296 
2297 static void drfs_close_native_file(drfs_handle file)
2298 {
2299  close(DRFS_HANDLE_TO_FD(file));
2300 }
2301 
2302 static dr_bool32 drfs_is_native_directory(const char* absolutePath)
2303 {
2304  struct stat64 info;
2305  if (drfs__stat64(absolutePath, &info) != 0) {
2306  return DR_FALSE; // Likely the folder doesn't exist.
2307  }
2308 
2309  return drfs__mode_is_dir(info.st_mode); // Only return DR_TRUE if it's a directory. Return DR_FALSE if it's a file.
2310 }
2311 
2312 static dr_bool32 drfs_is_native_file(const char* absolutePath)
2313 {
2314  struct stat64 info;
2315  if (drfs__stat64(absolutePath, &info) != 0) {
2316  return DR_FALSE; // Likely the folder doesn't exist.
2317  }
2318 
2319  return !drfs__mode_is_dir(info.st_mode); // Only return DR_TRUE if it's a file. Return DR_FALSE if it's a directory.
2320 }
2321 
2322 static drfs_result drfs_delete_native_file(const char* absolutePath)
2323 {
2324  if (remove(absolutePath) == 0) {
2325  return drfs_success;
2326  }
2327 
2328  return drfs__errno_to_result();
2329 }
2330 
2331 static drfs_result drfs_move_native_file(const char* absolutePathOld, const char* absolutePathNew)
2332 {
2333  if (rename(absolutePathOld, absolutePathNew) == 0) {
2334  return drfs_success;
2335  }
2336 
2337  return drfs__errno_to_result();
2338 }
2339 
2340 static drfs_result drfs_mkdir_native(const char* absolutePath)
2341 {
2342  if (mkdir(absolutePath, 0777) == 0) {
2343  return drfs_success;
2344  }
2345 
2346  return drfs__errno_to_result();
2347 }
2348 
2349 static drfs_result drfs_copy_native_file(const char* absolutePathSrc, const char* absolutePathDst, dr_bool32 failIfExists)
2350 {
2351  if (drfs_drpath_equal(absolutePathSrc, absolutePathDst)) {
2352  if (!failIfExists) {
2353  return drfs_success;
2354  } else {
2355  return drfs_already_exists;
2356  }
2357  }
2358 
2359  int fdSrc = drfs__open_fd(absolutePathSrc, O_RDONLY);
2360  if (fdSrc == -1) {
2361  return drfs__errno_to_result();
2362  }
2363 
2364  int fdDst = drfs__open_fd(absolutePathDst, O_WRONLY | O_TRUNC | O_CREAT | ((failIfExists) ? O_EXCL : 0));
2365  if (fdDst == -1) {
2366  close(fdSrc);
2367  return drfs__errno_to_result();
2368  }
2369 
2370  drfs_result result = drfs_success;
2371 
2372  struct stat64 info;
2373  if (drfs__fstat64(fdSrc, &info) == 0)
2374  {
2375 #ifdef __linux__
2376  ssize_t bytesRemaining = info.st_size;
2377  while (bytesRemaining > 0) {
2378  ssize_t bytesCopied = sendfile(fdDst, fdSrc, NULL, bytesRemaining);
2379  if (bytesCopied == -1) {
2380  result = drfs__errno_to_result();
2381  break;
2382  }
2383 
2384  bytesRemaining -= bytesCopied;
2385  }
2386 #else
2387  char buffer[BUFSIZ];
2388  int bytesRead;
2389  while ((bytesRead = read(fdSrc, buffer, sizeof(buffer))) > 0) {
2390  if (write(fdDst, buffer, bytesRead) != bytesRead) {
2391  result = drfs__errno_to_result();
2392  break;
2393  }
2394  }
2395 #endif
2396  }
2397  else
2398  {
2399  result = drfs__errno_to_result();
2400  }
2401 
2402  close(fdDst);
2403  close(fdSrc);
2404 
2405  // Permissions.
2406  chmod(absolutePathDst, info.st_mode & 07777);
2407 
2408  return result;
2409 }
2410 
2411 static drfs_result drfs_read_native_file(drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
2412 {
2413  // The documentation for read() states that if the number of bytes being read (bytesToRead) is larger than SSIZE_MAX,
2414  // the result is unspecified. We'll make things a bit more robust by explicitly checking for this and handling it.
2415  char* pDataOut8 = (char*)pDataOut;
2416  drfs_result result = drfs_success;
2417 
2418  size_t totalBytesRead = 0;
2419  while (bytesToRead > 0)
2420  {
2421  ssize_t bytesRead = read(DRFS_HANDLE_TO_FD(file), pDataOut8 + totalBytesRead, (bytesToRead > SSIZE_MAX) ? SSIZE_MAX : bytesToRead);
2422  if (bytesRead == -1) {
2423  result = drfs__errno_to_result();
2424  break;
2425  }
2426 
2427  if (bytesRead == 0) {
2428  break; // Reached the end.
2429  }
2430 
2431  totalBytesRead += bytesRead;
2432  bytesToRead -= bytesRead;
2433  }
2434 
2435  if (pBytesReadOut) {
2436  *pBytesReadOut = totalBytesRead;
2437  }
2438 
2439  return result;
2440 }
2441 
2442 static drfs_result drfs_write_native_file(drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
2443 {
2444  // We want to handle writes in the same way as we do reads due to the return valid being signed.
2445  const char* pDataIn8 = (const char*)pData;
2446  drfs_result result = drfs_success;
2447 
2448  size_t totalBytesWritten = 0;
2449  while (bytesToWrite > 0)
2450  {
2451  ssize_t bytesWritten = write(DRFS_HANDLE_TO_FD(file), pDataIn8 + totalBytesWritten, (bytesToWrite > SSIZE_MAX) ? SSIZE_MAX : bytesToWrite);
2452  if (bytesWritten == -1) {
2453  result = drfs__errno_to_result();
2454  break;
2455  }
2456 
2457  if (bytesWritten == 0) {
2458  result = drfs__errno_to_result();
2459  break;
2460  }
2461 
2462  totalBytesWritten += bytesWritten;
2463  bytesToWrite -= bytesWritten;
2464  }
2465 
2466  if (pBytesWrittenOut != NULL) {
2467  *pBytesWrittenOut = totalBytesWritten;
2468  }
2469 
2470  return result;
2471 }
2472 
2473 static drfs_result drfs_seek_native_file(drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin)
2474 {
2475  int stdioOrigin = SEEK_CUR;
2476  if (origin == drfs_origin_start) {
2477  stdioOrigin = SEEK_SET;
2478  } else if (origin == drfs_origin_end) {
2479  stdioOrigin = SEEK_END;
2480  }
2481 
2482  if (lseek64(DRFS_HANDLE_TO_FD(file), bytesToSeek, stdioOrigin) == -1) {
2483  return drfs__errno_to_result();
2484  }
2485 
2486  return drfs_success;
2487 }
2488 
2489 static dr_uint64 drfs_tell_native_file(drfs_handle file)
2490 {
2491  return lseek64(DRFS_HANDLE_TO_FD(file), 0, SEEK_CUR);
2492 }
2493 
2494 static dr_uint64 drfs_get_native_file_size(drfs_handle file)
2495 {
2496  struct stat64 info;
2497  if (drfs__fstat64(DRFS_HANDLE_TO_FD(file), &info) != 0) {
2498  return 0;
2499  }
2500 
2501  return info.st_size;
2502 }
2503 
2504 static void drfs_flush_native_file(drfs_handle file)
2505 {
2506  // The posix implementation does not require flushing.
2507  (void)file;
2508 }
2509 
2510 static drfs_result drfs_get_native_file_info(const char* absolutePath, drfs_file_info* fi)
2511 {
2512  assert(absolutePath != NULL);
2513 
2514  struct stat64 info;
2515  if (stat64(absolutePath, &info) != 0) {
2516  return drfs__errno_to_result();
2517  }
2518 
2519  // <fi> is allowed to be null, in which case the call is equivalent to simply checking if the file exists.
2520  if (fi != NULL)
2521  {
2522  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), absolutePath);
2523  fi->sizeInBytes = info.st_size;
2524  fi->lastModifiedTime = info.st_mtime;
2525 
2526  fi->attributes = 0;
2527  if (drfs__mode_is_dir(info.st_mode)) {
2529  }
2530  if (drfs__mode_has_write_permission(info.st_mode) == DR_FALSE) {
2532  }
2533  }
2534 
2535  return drfs_success;
2536 }
2537 
2538 typedef struct
2539 {
2540  DIR* dir;
2541  char directoryPath[1];
2542 } drfs_iterator_posix;
2543 
2544 static drfs_handle drfs_begin_native_iteration(const char* absolutePath)
2545 {
2546  DIR* dir = opendir(absolutePath);
2547  if (dir == NULL) {
2548  return NULL;
2549  }
2550 
2551  drfs_iterator_posix* pIterator = (drfs_iterator_posix*)malloc(sizeof(drfs_iterator_posix) + strlen(absolutePath));
2552  if (pIterator == NULL) {
2553  return NULL;
2554  }
2555 
2556  pIterator->dir = dir;
2557  drfs__strcpy_s(pIterator->directoryPath, strlen(absolutePath) + 1, absolutePath);
2558 
2559  return pIterator;
2560 }
2561 
2562 static void drfs_end_native_iteration(drfs_handle iterator)
2563 {
2564  drfs_iterator_posix* pIterator = (drfs_iterator_posix*)iterator;
2565  if (pIterator == NULL) {
2566  return;
2567  }
2568 
2569  closedir(pIterator->dir);
2570  free(pIterator);
2571 }
2572 
2573 static dr_bool32 drfs_next_native_iteration(drfs_handle iterator, drfs_file_info* fi)
2574 {
2575  drfs_iterator_posix* pIterator = (drfs_iterator_posix*)iterator;
2576  if (pIterator == NULL || pIterator->dir == NULL) {
2577  return DR_FALSE;
2578  }
2579 
2580  struct dirent* info = readdir(pIterator->dir);
2581  if (info == NULL) {
2582  return DR_FALSE;
2583  }
2584 
2585  // Skip over "." and ".." folders.
2586  while (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) {
2587  info = readdir(pIterator->dir);
2588  if (info == NULL) {
2589  return DR_FALSE;
2590  }
2591  }
2592 
2593  char fileAbsolutePath[DRFS_MAX_PATH];
2594  drfs_drpath_copy_and_append(fileAbsolutePath, sizeof(fileAbsolutePath), pIterator->directoryPath, info->d_name);
2595 
2596  if (drfs_get_native_file_info(fileAbsolutePath, fi)) {
2597  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), info->d_name);
2598  }
2599 
2600  return DR_TRUE;
2601 }
2602 #endif //DR_FS_USE_STDIO
2603 
2604 
2605 static dr_bool32 drfs_mkdir_recursive_native(const char* absolutePath)
2606 {
2607  char runningPath[DRFS_MAX_PATH];
2608  runningPath[0] = '\0';
2609 
2610  drfs_drpath_iterator iPathSeg;
2611  if (!drfs_drpath_first(absolutePath, &iPathSeg)) {
2612  return DR_FALSE;
2613  }
2614 
2615  // Never check the first segment because we can assume it always exists - it will always be the drive root.
2616  if (drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg))
2617  {
2618  // Loop over every directory until we find one that does not exist.
2619  while (drfs_drpath_next(&iPathSeg))
2620  {
2621  if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) {
2622  return DR_FALSE;
2623  }
2624 
2625  if (!drfs_is_native_directory(runningPath)) {
2626  if (drfs_mkdir_native(runningPath) != drfs_success) {
2627  return DR_FALSE;
2628  }
2629 
2630  break;
2631  }
2632  }
2633 
2634 
2635  // At this point all we need to do is create the remaining directories - we can assert that the directory does not exist
2636  // rather than actually checking it which should be a bit more efficient.
2637  while (drfs_drpath_next(&iPathSeg))
2638  {
2639  if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) {
2640  return DR_FALSE;
2641  }
2642 
2643  assert(!drfs_is_native_directory(runningPath));
2644 
2645  if (drfs_mkdir_native(runningPath) != drfs_success) {
2646  return DR_FALSE;
2647  }
2648  }
2649 
2650  return DR_TRUE;
2651  }
2652 
2653  return DR_FALSE;
2654 }
2655 
2656 
2657 
2659 
2660 typedef struct
2661 {
2662  // The access mode.
2663  unsigned int accessMode;
2664 
2665  // The absolute path of the directory.
2666  char absolutePath[1];
2667 
2668 } drfs_archive_native;
2669 
2670 static drfs_result drfs_open_archive__native(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut)
2671 {
2672  (void)pArchiveFile;
2673  (void)accessMode;
2674 
2675  // This function should never be called for native archives. Native archives are a special case because they are just directories
2676  // on the file system rather than actual files, and as such they are handled just slightly differently. This function is only
2677  // included here for this assert so we can ensure we don't incorrectly call this function.
2678  assert(DR_FALSE);
2679 
2680  *pHandleOut = NULL;
2681  return drfs_unknown_error;
2682 }
2683 
2684 static drfs_result drfs_open_archive__native__special(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut)
2685 {
2686  assert(absolutePath != NULL); // There is no notion of a file for native archives (which are just directories on the file system).
2687  assert(pHandleOut != NULL);
2688 
2689  *pHandleOut = NULL;
2690 
2691  size_t absolutePathLen = strlen(absolutePath);
2692 
2693  drfs_archive_native* pNativeArchive = (drfs_archive_native*)malloc(sizeof(*pNativeArchive) + absolutePathLen + 1);
2694  if (pNativeArchive == NULL) {
2695  return drfs_out_of_memory;
2696  }
2697 
2698  drfs__strcpy_s(pNativeArchive->absolutePath, absolutePathLen + 1, absolutePath);
2699  pNativeArchive->accessMode = accessMode;
2700 
2701  *pHandleOut = (drfs_handle)pNativeArchive;
2702  return drfs_success;
2703 }
2704 
2705 static void drfs_close_archive__native(drfs_handle archive)
2706 {
2707  // Nothing to do except free the object.
2708  free(archive);
2709 }
2710 
2711 static drfs_result drfs_get_file_info__native(drfs_handle archive, const char* relativePath, drfs_file_info* fi)
2712 {
2713  drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive;
2714  assert(pNativeArchive != NULL);
2715 
2716  char absolutePath[DRFS_MAX_PATH];
2717  if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) {
2718  return drfs_path_too_long;
2719  }
2720 
2721  if (fi != NULL) {
2722  memset(fi, 0, sizeof(*fi));
2723  }
2724 
2725  return drfs_get_native_file_info(absolutePath, fi);
2726 }
2727 
2728 static drfs_handle drfs_begin_iteration__native(drfs_handle archive, const char* relativePath)
2729 {
2730  drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive;
2731  assert(pNativeArchive != NULL);
2732 
2733  char absolutePath[DRFS_MAX_PATH];
2734  if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) {
2735  return NULL;
2736  }
2737 
2738  return drfs_begin_native_iteration(absolutePath);
2739 }
2740 
2741 static void drfs_end_iteration__native(drfs_handle archive, drfs_handle iterator)
2742 {
2743  (void)archive;
2744  assert(archive != NULL);
2745  assert(iterator != NULL);
2746 
2747  drfs_end_native_iteration(iterator);
2748 }
2749 
2750 static dr_bool32 drfs_next_iteration__native(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi)
2751 {
2752  (void)archive;
2753  assert(archive != NULL);
2754  assert(iterator != NULL);
2755 
2756  return drfs_next_native_iteration(iterator, fi);
2757 }
2758 
2759 static drfs_result drfs_delete_file__native(drfs_handle archive, const char* relativePath)
2760 {
2761  assert(relativePath != NULL);
2762 
2763  drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive;
2764  assert(pNativeArchive != NULL);
2765 
2766  char absolutePath[DRFS_MAX_PATH];
2767  if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) {
2768  return drfs_path_too_long;
2769  }
2770 
2771  return drfs_delete_native_file(absolutePath);
2772 }
2773 
2774 static drfs_result drfs_move_file__native(drfs_handle archive, const char* relativePathOld, const char* relativePathNew)
2775 {
2776  assert(relativePathOld != NULL);
2777  assert(relativePathNew != NULL);
2778 
2779  drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive;
2780  assert(pNativeArchive != NULL);
2781 
2782  char absolutePathOld[DRFS_MAX_PATH];
2783  if (!drfs_drpath_copy_and_append(absolutePathOld, sizeof(absolutePathOld), pNativeArchive->absolutePath, relativePathOld)) {
2784  return drfs_path_too_long;
2785  }
2786 
2787  char absolutePathNew[DRFS_MAX_PATH];
2788  if (!drfs_drpath_copy_and_append(absolutePathNew, sizeof(absolutePathNew), pNativeArchive->absolutePath, relativePathNew)) {
2789  return drfs_path_too_long;
2790  }
2791 
2792  return drfs_move_native_file(absolutePathOld, absolutePathNew);
2793 }
2794 
2795 static drfs_result drfs_create_directory__native(drfs_handle archive, const char* relativePath)
2796 {
2797  assert(relativePath != NULL);
2798 
2799  drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive;
2800  assert(pNativeArchive != NULL);
2801 
2802  char absolutePath[DRFS_MAX_PATH];
2803  if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) {
2804  return drfs_path_too_long;
2805  }
2806 
2807  return drfs_mkdir_native(absolutePath);
2808 }
2809 
2810 static drfs_result drfs_copy_file__native(drfs_handle archive, const char* relativePathSrc, const char* relativePathDst, dr_bool32 failIfExists)
2811 {
2812  assert(relativePathSrc != NULL);
2813  assert(relativePathDst != NULL);
2814 
2815  drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive;
2816  assert(pNativeArchive != NULL);
2817 
2818  char absolutePathSrc[DRFS_MAX_PATH];
2819  if (!drfs_drpath_copy_and_append(absolutePathSrc, sizeof(absolutePathSrc), pNativeArchive->absolutePath, relativePathSrc)) {
2820  return drfs_path_too_long;
2821  }
2822 
2823  char absolutePathDst[DRFS_MAX_PATH];
2824  if (!drfs_drpath_copy_and_append(absolutePathDst, sizeof(absolutePathDst), pNativeArchive->absolutePath, relativePathDst)) {
2825  return drfs_path_too_long;
2826  }
2827 
2828  return drfs_copy_native_file(absolutePathSrc, absolutePathDst, failIfExists);
2829 }
2830 
2831 static drfs_result drfs_open_file__native(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut)
2832 {
2833  assert(archive != NULL);
2834  assert(relativePath != NULL);
2835 
2836  drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive;
2837  assert(pNativeArchive != NULL);
2838 
2839  char absolutePath[DRFS_MAX_PATH];
2840  if (drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) {
2841  return drfs_open_native_file(absolutePath, accessMode, pHandleOut);
2842  }
2843 
2844  return drfs_path_too_long;
2845 }
2846 
2847 static void drfs_close_file__native(drfs_handle archive, drfs_handle file)
2848 {
2849  (void)archive;
2850  assert(archive != NULL);
2851  assert(file != NULL);
2852 
2853  drfs_close_native_file(file);
2854 }
2855 
2856 static drfs_result drfs_read_file__native(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
2857 {
2858  (void)archive;
2859  assert(archive != NULL);
2860  assert(file != NULL);
2861 
2862  return drfs_read_native_file(file, pDataOut, bytesToRead, pBytesReadOut);
2863 }
2864 
2865 static drfs_result drfs_write_file__native(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
2866 {
2867  (void)archive;
2868  assert(archive != NULL);
2869  assert(file != NULL);
2870 
2871  return drfs_write_native_file(file, pData, bytesToWrite, pBytesWrittenOut);
2872 }
2873 
2874 static drfs_result drfs_seek_file__native(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin)
2875 {
2876  (void)archive;
2877  assert(archive != NULL);
2878  assert(file != NULL);
2879 
2880  return drfs_seek_native_file(file, bytesToSeek, origin);
2881 }
2882 
2883 static dr_uint64 drfs_tell_file__native(drfs_handle archive, drfs_handle file)
2884 {
2885  (void)archive;
2886  assert(archive != NULL);
2887  assert(file != NULL);
2888 
2889  return drfs_tell_native_file(file);
2890 }
2891 
2892 static dr_uint64 drfs_file_size__native(drfs_handle archive, drfs_handle file)
2893 {
2894  (void)archive;
2895  assert(archive != NULL);
2896  assert(file != NULL);
2897 
2898  return drfs_get_native_file_size(file);
2899 }
2900 
2901 static void drfs_flush__native(drfs_handle archive, drfs_handle file)
2902 {
2903  (void)archive;
2904  assert(archive != NULL);
2905  assert(file != NULL);
2906 
2907  drfs_flush_native_file(file);
2908 }
2909 
2910 
2911 // Finds the back-end callbacks by the given extension.
2912 static dr_bool32 drfs_find_backend_by_extension(drfs_context* pContext, const char* extension, drfs_archive_callbacks* pCallbacksOut)
2913 {
2914  if (pContext == NULL || extension == NULL || extension[0] == '\0') {
2915  return DR_FALSE;
2916  }
2917 
2918  for (unsigned int i = 0; i < pContext->archiveCallbacks.count; ++i)
2919  {
2920  if (pContext->archiveCallbacks.pBuffer[i].is_valid_extension) {
2921  if (pContext->archiveCallbacks.pBuffer[i].is_valid_extension(extension)) {
2922  if (pCallbacksOut) {
2923  *pCallbacksOut = pContext->archiveCallbacks.pBuffer[i];
2924  }
2925  return DR_TRUE;
2926  }
2927  }
2928  }
2929 
2930  return DR_FALSE;
2931 }
2932 
2933 // Recursively claims ownership of parent archives so that when the child archive is deleted, so are it's parents.
2934 static void drfs_recursively_claim_ownership_or_parent_archive(drfs_archive* pArchive)
2935 {
2936  if (pArchive == NULL) {
2937  return;
2938  }
2939 
2940  pArchive->flags |= DR_FS_OWNS_PARENT_ARCHIVE;
2941  drfs_recursively_claim_ownership_or_parent_archive(pArchive->pParentArchive);
2942 }
2943 
2944 // Opens a native archive.
2945 static drfs_result drfs_open_native_archive(drfs_context* pContext, const char* absolutePath, unsigned int accessMode, drfs_archive** ppArchive)
2946 {
2947  assert(pContext != NULL);
2948  assert(ppArchive != NULL);
2949  assert(absolutePath != NULL);
2950 
2951  *ppArchive = NULL;
2952 
2953  drfs_handle internalArchiveHandle;
2954  drfs_result result = drfs_open_archive__native__special(absolutePath, accessMode, &internalArchiveHandle);
2955  if (result != drfs_success) {
2956  return result;
2957  }
2958 
2959  drfs_archive* pArchive = (drfs_archive*)malloc(sizeof(*pArchive));
2960  if (pArchive == NULL) {
2961  drfs_close_archive__native(internalArchiveHandle);
2962  return drfs_out_of_memory;
2963  }
2964 
2965  pArchive->pContext = pContext;
2966  pArchive->pParentArchive = NULL;
2967  pArchive->pFile = NULL;
2968  pArchive->internalArchiveHandle = internalArchiveHandle;
2969  pArchive->flags = 0;
2970  pArchive->callbacks.is_valid_extension = NULL;
2971  pArchive->callbacks.open_archive = drfs_open_archive__native;
2972  pArchive->callbacks.close_archive = drfs_close_archive__native;
2973  pArchive->callbacks.get_file_info = drfs_get_file_info__native;
2974  pArchive->callbacks.begin_iteration = drfs_begin_iteration__native;
2975  pArchive->callbacks.end_iteration = drfs_end_iteration__native;
2976  pArchive->callbacks.next_iteration = drfs_next_iteration__native;
2977  pArchive->callbacks.delete_file = drfs_delete_file__native;
2978  pArchive->callbacks.create_directory = drfs_create_directory__native;
2979  pArchive->callbacks.move_file = drfs_move_file__native;
2980  pArchive->callbacks.copy_file = drfs_copy_file__native;
2981  pArchive->callbacks.open_file = drfs_open_file__native;
2982  pArchive->callbacks.close_file = drfs_close_file__native;
2983  pArchive->callbacks.read_file = drfs_read_file__native;
2984  pArchive->callbacks.write_file = drfs_write_file__native;
2985  pArchive->callbacks.seek_file = drfs_seek_file__native;
2986  pArchive->callbacks.tell_file = drfs_tell_file__native;
2987  pArchive->callbacks.file_size = drfs_file_size__native;
2988  pArchive->callbacks.flush_file = drfs_flush__native;
2989  drfs__strcpy_s(pArchive->absolutePath, sizeof(pArchive->absolutePath), absolutePath);
2990 
2991  *ppArchive = pArchive;
2992  return drfs_success;
2993 }
2994 
2995 // Opens an archive from a file and callbacks.
2996 static drfs_result drfs_open_non_native_archive(drfs_archive* pParentArchive, drfs_file* pArchiveFile, drfs_archive_callbacks* pBackEndCallbacks, const char* relativePath, unsigned int accessMode, drfs_archive** ppArchiveOut)
2997 {
2998  assert(pParentArchive != NULL);
2999  assert(pArchiveFile != NULL);
3000  assert(pBackEndCallbacks != NULL);
3001  assert(ppArchiveOut != NULL);
3002 
3003  *ppArchiveOut = NULL;
3004 
3005  if (pBackEndCallbacks->open_archive == NULL) {
3006  return drfs_no_backend;
3007  }
3008 
3009  drfs_handle internalArchiveHandle;
3010  drfs_result result = pBackEndCallbacks->open_archive(pArchiveFile, accessMode, &internalArchiveHandle);
3011  if (result != drfs_success) {
3012  return result;
3013  }
3014 
3015  drfs_archive* pArchive = (drfs_archive*)malloc(sizeof(*pArchive));
3016  if (pArchive == NULL) {
3017  pBackEndCallbacks->close_archive(internalArchiveHandle);
3018  return drfs_out_of_memory;
3019  }
3020 
3021  pArchive->pContext = pParentArchive->pContext;
3022  pArchive->pParentArchive = pParentArchive;
3023  pArchive->pFile = pArchiveFile;
3024  pArchive->internalArchiveHandle = internalArchiveHandle;
3025  pArchive->flags = 0;
3026  pArchive->callbacks = *pBackEndCallbacks;
3027  drfs_drpath_copy_and_append(pArchive->absolutePath, sizeof(pArchive->absolutePath), pParentArchive->absolutePath, relativePath);
3028 
3029  *ppArchiveOut = pArchive;
3030  return drfs_success;
3031 }
3032 
3033 // Attempts to open an archive from another archive.
3034 static drfs_result drfs_open_non_native_archive_from_path(drfs_archive* pParentArchive, const char* relativePath, unsigned int accessMode, drfs_archive** ppArchiveOut)
3035 {
3036  assert(pParentArchive != NULL);
3037  assert(ppArchiveOut != NULL);
3038  assert(relativePath != NULL);
3039 
3040  *ppArchiveOut = NULL;
3041 
3042  drfs_archive_callbacks backendCallbacks;
3043  if (!drfs_find_backend_by_extension(pParentArchive->pContext, drfs_drpath_extension(relativePath), &backendCallbacks)) {
3044  return drfs_no_backend;
3045  }
3046 
3047  drfs_file* pArchiveFile;
3048  drfs_result result = drfs_open_file_from_archive(pParentArchive, relativePath, accessMode, &pArchiveFile);
3049  if (result != drfs_success) {
3050  return result;
3051  }
3052 
3053  drfs_archive* pArchive;
3054  result = drfs_open_non_native_archive(pParentArchive, pArchiveFile, &backendCallbacks, relativePath, accessMode, &pArchive);
3055  if (pArchive == NULL) {
3056  return result;
3057  }
3058 
3059  *ppArchiveOut = pArchive;
3060  return drfs_success;
3061 }
3062 
3063 
3064 // Recursively opens the archive that owns the file at the given verbose path.
3065 static drfs_result drfs_open_owner_archive_recursively_from_verbose_path(drfs_archive* pParentArchive, const char* relativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut)
3066 {
3067  assert(pParentArchive != NULL);
3068  assert(relativePath != NULL);
3069  assert(ppArchiveOut != NULL);
3070 
3071  *ppArchiveOut = NULL;
3072 
3073  if (pParentArchive->callbacks.get_file_info == NULL) {
3074  return drfs_no_backend;
3075  }
3076 
3077  drfs_file_info fi;
3078  if (pParentArchive->callbacks.get_file_info(pParentArchive->internalArchiveHandle, relativePath, &fi) == drfs_success)
3079  {
3080  // The file is in this archive.
3081  drfs__strcpy_s(relativePathOut, relativePathOutSize, relativePath);
3082  *ppArchiveOut = pParentArchive;
3083  return drfs_success;
3084  }
3085  else
3086  {
3087  char runningPath[DRFS_MAX_PATH];
3088  runningPath[0] = '\0';
3089 
3090  drfs_drpath_iterator segment;
3091  if (drfs_drpath_first(relativePath, &segment))
3092  {
3093  do
3094  {
3095  if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), segment)) {
3096  return drfs_path_too_long;
3097  }
3098 
3099  if (pParentArchive->callbacks.get_file_info(pParentArchive->internalArchiveHandle, runningPath, &fi) == drfs_success)
3100  {
3101  if ((fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) == 0)
3102  {
3103  // The running path points to an actual file. It could be a sub-archive.
3104  drfs_archive_callbacks backendCallbacks;
3105  if (drfs_find_backend_by_extension(pParentArchive->pContext, drfs_drpath_extension(runningPath), &backendCallbacks))
3106  {
3107  drfs_file* pNextArchiveFile;
3108  drfs_open_file_from_archive(pParentArchive, runningPath, accessMode, &pNextArchiveFile);
3109  if (pNextArchiveFile == NULL) {
3110  break; // Failed to open the archive file.
3111  }
3112 
3113  drfs_archive* pNextArchive;
3114  drfs_open_non_native_archive(pParentArchive, pNextArchiveFile, &backendCallbacks, runningPath, accessMode, &pNextArchive);
3115  if (pNextArchive == NULL) {
3116  drfs_close(pNextArchiveFile);
3117  break;
3118  }
3119 
3120  // At this point we should have an archive. We now need to call this function recursively if there are any segments left.
3121  drfs_drpath_iterator nextsegment = segment;
3122  if (drfs_drpath_next(&nextsegment))
3123  {
3124  drfs_archive* pOwnerArchive;
3125  drfs_open_owner_archive_recursively_from_verbose_path(pNextArchive, nextsegment.path + nextsegment.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive);
3126  if (pOwnerArchive == NULL) {
3127  drfs_close_archive(pNextArchive);
3128  break;
3129  }
3130 
3131  *ppArchiveOut = pOwnerArchive;
3132  return drfs_success;
3133  }
3134  else
3135  {
3136  // We reached the end of the path. If we get here it means the file doesn't exist, because otherwise we would have caught it in the check right at the top.
3137  drfs_close_archive(pNextArchive);
3138  break;
3139  }
3140  }
3141  }
3142  }
3143  } while (drfs_drpath_next(&segment));
3144  }
3145 
3146  // The running path is not part of this archive.
3147  // NOTE: Is this the correct return value we should be using? Should we be returning an error and setting the output archive to NULL?
3148  drfs__strcpy_s(relativePathOut, relativePathOutSize, relativePath);
3149  *ppArchiveOut = pParentArchive;
3150  return drfs_does_not_exist;
3151  }
3152 }
3153 
3154 // Opens the archive that owns the file at the given verbose path.
3155 static drfs_result drfs_open_owner_archive_from_absolute_path(drfs_context* pContext, const char* absolutePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut)
3156 {
3157  assert(pContext != NULL);
3158  assert(absolutePath != NULL);
3159  assert(ppArchiveOut != NULL);
3160 
3161  *ppArchiveOut = NULL;
3162 
3163  // We are currently assuming absolute path's are verbose. This means we don't need to do any searching on the file system. We just
3164  // move through the path and look for a segment with an extension matching one of the registered back-ends.
3165 
3166  char runningPath[DRFS_MAX_PATH];
3167  runningPath[0] = '\0';
3168 
3169  if (absolutePath[0] == '/') {
3170  runningPath[0] = '/';
3171  runningPath[1] = '\0';
3172  }
3173 
3174  drfs_drpath_iterator segment;
3175  if (drfs_drpath_first(absolutePath, &segment))
3176  {
3177  do
3178  {
3179  if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), segment)) {
3180  return drfs_path_too_long;
3181  }
3182 
3183  if (!drfs_is_native_directory(runningPath))
3184  {
3185  char dirAbsolutePath[DRFS_MAX_PATH];
3186  drfs_drpath_copy_base_path(runningPath, dirAbsolutePath, sizeof(dirAbsolutePath));
3187 
3188  drfs_archive* pNativeArchive;
3189  drfs_result result = drfs_open_native_archive(pContext, dirAbsolutePath, accessMode, &pNativeArchive);
3190  if (result != drfs_success) {
3191  return result; // Failed to open the native archive.
3192  }
3193 
3194 
3195  // The running path is not a native directory. It could be an archive file.
3196  drfs_archive_callbacks backendCallbacks;
3197  if (drfs_find_backend_by_extension(pContext, drfs_drpath_extension(runningPath), &backendCallbacks))
3198  {
3199  // The running path refers to an archive file. We need to try opening the archive here. If this fails, we
3200  // need to return NULL.
3201  drfs_archive* pArchive;
3202  result = drfs_open_owner_archive_recursively_from_verbose_path(pNativeArchive, segment.path + segment.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pArchive);
3203  if (pArchive == NULL) {
3204  drfs_close_archive(pNativeArchive);
3205  return result;
3206  }
3207 
3208  *ppArchiveOut = pArchive;
3209  return drfs_success;
3210  }
3211  else
3212  {
3213  drfs__strcpy_s(relativePathOut, relativePathOutSize, segment.path + segment.segment.offset);
3214  *ppArchiveOut = pNativeArchive;
3215  return drfs_success;
3216  }
3217  }
3218 
3219  } while (drfs_drpath_next(&segment));
3220  }
3221 
3222  return drfs_does_not_exist;
3223 }
3224 
3225 // Recursively opens the archive that owns the file specified by the given relative path.
3226 static drfs_result drfs_open_owner_archive_recursively_from_relative_path(drfs_archive* pParentArchive, const char* rootSearchPath, const char* relativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut)
3227 {
3228  assert(pParentArchive != NULL);
3229  assert(relativePath != NULL);
3230  assert(ppArchiveOut != NULL);
3231 
3232  *ppArchiveOut = NULL;
3233 
3234  if (pParentArchive->callbacks.get_file_info == NULL) {
3235  return drfs_no_backend;
3236  }
3237 
3238  // Always try the direct route by checking if the file exists within the archive first.
3239  drfs_file_info fi;
3240  if (pParentArchive->callbacks.get_file_info(pParentArchive->internalArchiveHandle, relativePath, &fi) == drfs_success)
3241  {
3242  // The file is in this archive.
3243  drfs__strcpy_s(relativePathOut, relativePathOutSize, relativePath);
3244  *ppArchiveOut = pParentArchive;
3245  return drfs_success;
3246  }
3247  else
3248  {
3249  // The file does not exist within this archive. We need to search the parent archive recursively. We never search above
3250  // the path specified by rootSearchPath.
3251  char runningPath[DRFS_MAX_PATH];
3252  drfs__strcpy_s(runningPath, sizeof(runningPath), rootSearchPath);
3253 
3254  // Part of rootSearchPath and relativePath will be overlapping. We want to begin searching at the non-overlapping section.
3255  drfs_drpath_iterator pathSeg0;
3256  drfs_drpath_iterator pathSeg1;
3257  dr_bool32 isSeg0Valid = drfs_drpath_first(rootSearchPath, &pathSeg0);
3258  dr_bool32 isSeg1Valid = drfs_drpath_first(relativePath, &pathSeg1);
3259  while (isSeg0Valid && isSeg1Valid) {
3260  isSeg0Valid = drfs_drpath_next(&pathSeg0);
3261  isSeg1Valid = drfs_drpath_next(&pathSeg1);
3262  }
3263 
3264  relativePath = pathSeg1.path + pathSeg1.segment.offset;
3265 
3266  // Searching works as such:
3267  // For each segment in "relativePath"
3268  // If segment is archive then
3269  // Open and search archive
3270  // Else
3271  // Search each archive file in this directory
3272  // Endif
3273  drfs_drpath_iterator pathseg;
3274  if (drfs_drpath_first(relativePath, &pathseg))
3275  {
3276  do
3277  {
3278  char runningPathBase[DRFS_MAX_PATH];
3279  drfs__strcpy_s(runningPathBase, sizeof(runningPathBase), runningPath);
3280 
3281  if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), pathseg)) {
3282  return drfs_path_too_long;
3283  }
3284 
3285  drfs_archive* pNextArchive;
3286  drfs_open_non_native_archive_from_path(pParentArchive, runningPath, accessMode, &pNextArchive);
3287  if (pNextArchive != NULL)
3288  {
3289  // It's an archive segment. We need to check this archive recursively, starting from the next segment.
3290  drfs_drpath_iterator nextseg = pathseg;
3291  if (!drfs_drpath_next(&nextseg)) {
3292  // Should never actually get here, but if we do it means we've reached the end of the path. Assume the file could not be found.
3293  drfs_close_archive(pNextArchive);
3294  return drfs_unknown_error;
3295  }
3296 
3297  drfs_archive* pOwnerArchive;
3298  drfs_open_owner_archive_recursively_from_relative_path(pNextArchive, "", nextseg.path + nextseg.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive);
3299  if (pOwnerArchive == NULL) {
3300  drfs_close_archive(pNextArchive);
3301  return drfs_does_not_exist;
3302  }
3303 
3304  *ppArchiveOut = pOwnerArchive;
3305  return drfs_success;
3306  }
3307  else
3308  {
3309  // It's not an archive segment. We need to search.
3310  if (pParentArchive->callbacks.begin_iteration == NULL || pParentArchive->callbacks.next_iteration == NULL || pParentArchive->callbacks.end_iteration == NULL) {
3311  return drfs_no_backend;
3312  }
3313 
3314  drfs_handle iterator = pParentArchive->callbacks.begin_iteration(pParentArchive->internalArchiveHandle, runningPathBase);
3315  if (iterator == NULL) {
3316  return drfs_no_backend;
3317  }
3318 
3319  while (pParentArchive->callbacks.next_iteration(pParentArchive->internalArchiveHandle, iterator, &fi))
3320  {
3321  if ((fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) == 0)
3322  {
3323  // It's a file which means it could be an archive. Note that fi.absolutePath is actually relative to the parent archive.
3324  drfs_open_non_native_archive_from_path(pParentArchive, fi.absolutePath, accessMode, &pNextArchive);
3325  if (pNextArchive != NULL)
3326  {
3327  // It's an archive, so check it.
3328  drfs_archive* pOwnerArchive;
3329  drfs_open_owner_archive_recursively_from_relative_path(pNextArchive, "", pathseg.path + pathseg.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive);
3330  if (pOwnerArchive != NULL) {
3331  pParentArchive->callbacks.end_iteration(pParentArchive->internalArchiveHandle, iterator);
3332  *ppArchiveOut = pOwnerArchive;
3333  return drfs_success;
3334  } else {
3335  drfs_close_archive(pNextArchive);
3336  }
3337  }
3338  }
3339  }
3340 
3341  pParentArchive->callbacks.end_iteration(pParentArchive->internalArchiveHandle, iterator);
3342  }
3343 
3344  } while (drfs_drpath_next(&pathseg));
3345  }
3346  }
3347 
3348  return drfs_does_not_exist;
3349 }
3350 
3351 // Opens the archive that owns the file at the given path that's relative to the given base path. This supports non-verbose paths and will search
3352 // the file system for the archive.
3353 static drfs_result drfs_open_owner_archive_from_relative_path(drfs_context* pContext, const char* absoluteBasePath, const char* relativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut)
3354 {
3355  assert(pContext != NULL);
3356  assert(absoluteBasePath != NULL);
3357  assert(relativePath != NULL);
3358  assert(drfs_drpath_is_absolute(absoluteBasePath));
3359  assert(ppArchiveOut != NULL);
3360 
3361  *ppArchiveOut = NULL;
3362 
3363  char adjustedRelativePath[DRFS_MAX_PATH];
3364  char relativeBasePath[DRFS_MAX_PATH];
3365  relativeBasePath[0] = '\0';
3366 
3367  // The base path should be absolute and verbose. It does not need to be an actual directory.
3368  drfs_archive* pBaseArchive = NULL;
3369  if (drfs_is_native_directory(absoluteBasePath))
3370  {
3371  drfs_result result = drfs_open_native_archive(pContext, absoluteBasePath, accessMode, &pBaseArchive);
3372  if (result != drfs_success) {
3373  return result;
3374  }
3375 
3376  if (drfs__strcpy_s(adjustedRelativePath, sizeof(adjustedRelativePath), relativePath) != 0) {
3377  drfs_close_archive(pBaseArchive);
3378  return drfs_path_too_long;
3379  }
3380  }
3381  else
3382  {
3383  drfs_result result = drfs_open_owner_archive(pContext, absoluteBasePath, accessMode, relativeBasePath, sizeof(relativeBasePath), &pBaseArchive);
3384  if (result != drfs_success) {
3385  return result;
3386  }
3387 
3388  if (!drfs_drpath_copy_and_append(adjustedRelativePath, sizeof(adjustedRelativePath), relativeBasePath, relativePath)) {
3389  drfs_close_archive(pBaseArchive);
3390  return drfs_path_too_long;
3391  }
3392  }
3393 
3394 
3395  // We couldn't open the archive file from here, so we'll need to search for the owner archive recursively.
3396  drfs_archive* pOwnerArchive;
3397  drfs_result result = drfs_open_owner_archive_recursively_from_relative_path(pBaseArchive, relativeBasePath, adjustedRelativePath, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive);
3398  if (pOwnerArchive == NULL) {
3399  return result;
3400  }
3401 
3402  drfs_recursively_claim_ownership_or_parent_archive(pOwnerArchive);
3403 
3404  *ppArchiveOut = pOwnerArchive;
3405  return drfs_success;
3406 }
3407 
3408 // Opens an archive from a path that's relative to the given base path. This supports non-verbose paths and will search
3409 // the file system for the archive.
3410 static drfs_result drfs_open_archive_from_relative_path(drfs_context* pContext, const char* absoluteBasePath, const char* relativePath, unsigned int accessMode, drfs_archive** ppArchiveOut)
3411 {
3412  assert(pContext != NULL);
3413  assert(absoluteBasePath != NULL);
3414  assert(relativePath != NULL);
3415  assert(drfs_drpath_is_absolute(absoluteBasePath));
3416  assert(ppArchiveOut != NULL);
3417 
3418  *ppArchiveOut = NULL;
3419 
3420  char adjustedRelativePath[DRFS_MAX_PATH];
3421  char relativeBasePath[DRFS_MAX_PATH];
3422  relativeBasePath[0] = '\0';
3423 
3424  // The base path should be absolute and verbose. It does not need to be an actual directory.
3425  drfs_archive* pBaseArchive = NULL;
3426  if (drfs_is_native_directory(absoluteBasePath))
3427  {
3428  drfs_result result = drfs_open_native_archive(pContext, absoluteBasePath, accessMode, &pBaseArchive);
3429  if (result != drfs_success) {
3430  return result;
3431  }
3432 
3433  if (drfs__strcpy_s(adjustedRelativePath, sizeof(adjustedRelativePath), relativePath) != 0) {
3434  drfs_close_archive(pBaseArchive);
3435  return drfs_path_too_long;
3436  }
3437  }
3438  else
3439  {
3440  drfs_result result = drfs_open_owner_archive(pContext, absoluteBasePath, accessMode, relativeBasePath, sizeof(relativeBasePath), &pBaseArchive);
3441  if (result != drfs_success) {
3442  return result;
3443  }
3444 
3445  if (!drfs_drpath_copy_and_append(adjustedRelativePath, sizeof(adjustedRelativePath), relativeBasePath, relativePath)) {
3446  drfs_close_archive(pBaseArchive);
3447  return drfs_path_too_long;
3448  }
3449  }
3450 
3451 
3452  // We can try opening the file directly from the base path. If this doesn't work, we can try recursively opening the owner archive
3453  // an open from there.
3454  drfs_archive* pArchive;
3455  drfs_result result = drfs_open_non_native_archive_from_path(pBaseArchive, adjustedRelativePath, accessMode, &pArchive);
3456  if (pArchive == NULL)
3457  {
3458  // We couldn't open the archive file from here, so we'll need to search for the owner archive recursively.
3459  char archiveRelativePath[DRFS_MAX_PATH];
3460  drfs_archive* pOwnerArchive;
3461  result = drfs_open_owner_archive_recursively_from_relative_path(pBaseArchive, relativeBasePath, adjustedRelativePath, accessMode, archiveRelativePath, sizeof(archiveRelativePath), &pOwnerArchive);
3462  if (pOwnerArchive != NULL)
3463  {
3464  drfs_recursively_claim_ownership_or_parent_archive(pOwnerArchive);
3465 
3466  result = drfs_open_non_native_archive_from_path(pOwnerArchive, archiveRelativePath, accessMode, &pArchive);
3467  if (pArchive == NULL) {
3468  drfs_close_archive(pOwnerArchive);
3469  return result;
3470  }
3471  }
3472  }
3473 
3474  if (pArchive == NULL) {
3475  drfs_close_archive(pBaseArchive);
3476  return drfs_does_not_exist;
3477  }
3478 
3479  *ppArchiveOut = pArchive;
3480  return drfs_success;
3481 }
3482 
3484 
3485 #ifndef DR_FS_NO_ZIP
3486 // Registers the archive callbacks which enables support for Zip files.
3487 static void drfs_register_zip_backend(drfs_context* pContext);
3488 #endif
3489 
3490 #ifndef DR_FS_NO_PAK
3491 // Registers the archive callbacks which enables support for Quake 2 pak files.
3492 static void drfs_register_pak_backend(drfs_context* pContext);
3493 #endif
3494 
3495 #ifndef DR_FS_NO_MTL
3496 // Registers the archive callbacks which enables support for Quake 2 pak files.
3497 static void drfs_register_mtl_backend(drfs_context* pContext);
3498 #endif
3499 
3500 
3501 
3503 
3505 {
3506  if (pContext == NULL) return drfs_invalid_args;
3507  memset(pContext, 0, sizeof(*pContext));
3508 
3509  if (!drfs_callbacklist_init(&pContext->archiveCallbacks) || !drfs_basedirs_init(&pContext->baseDirectories)) {
3510  return drfs_unknown_error;
3511  }
3512 
3513  pContext->isWriteGuardEnabled = 0;
3514 
3515 #ifndef DR_FS_NO_ZIP
3516  drfs_register_zip_backend(pContext);
3517 #endif
3518 
3519 #ifndef DR_FS_NO_PAK
3520  drfs_register_pak_backend(pContext);
3521 #endif
3522 
3523 #ifndef DR_FS_NO_MTL
3524  drfs_register_mtl_backend(pContext);
3525 #endif
3526 
3527  return drfs_success;
3528 }
3529 
3531 {
3532  if (pContext == NULL) return drfs_invalid_args;
3533 
3534  drfs_basedirs_uninit(&pContext->baseDirectories);
3535  drfs_callbacklist_uninit(&pContext->archiveCallbacks);
3536  return drfs_success;
3537 }
3538 
3539 
3541 {
3542  drfs_context* pContext = (drfs_context*)malloc(sizeof(*pContext));
3543  if (pContext == NULL) {
3544  return NULL;
3545  }
3546 
3547  if (drfs_init(pContext) != drfs_success) {
3548  free(pContext);
3549  return NULL;
3550  }
3551 
3552  return pContext;
3553 }
3554 
3555 void drfs_delete_context(drfs_context* pContext)
3556 {
3557  if (pContext == NULL) {
3558  return;
3559  }
3560 
3561  drfs_uninit(pContext);
3562  free(pContext);
3563 }
3564 
3565 
3567 {
3568  if (pContext == NULL) {
3569  return;
3570  }
3571 
3572  drfs_callbacklist_pushback(&pContext->archiveCallbacks, callbacks);
3573 }
3574 
3575 
3576 void drfs_insert_base_directory(drfs_context* pContext, const char* absolutePath, unsigned int index)
3577 {
3578  if (pContext == NULL) {
3579  return;
3580  }
3581 
3582  drfs_basedirs_insert(&pContext->baseDirectories, absolutePath, index);
3583 }
3584 
3585 void drfs_add_base_directory(drfs_context* pContext, const char* absolutePath)
3586 {
3587  if (pContext == NULL) {
3588  return;
3589  }
3590 
3591  drfs_insert_base_directory(pContext, absolutePath, drfs_get_base_directory_count(pContext));
3592 }
3593 
3594 void drfs_remove_base_directory(drfs_context* pContext, const char* absolutePath)
3595 {
3596  if (pContext == NULL) {
3597  return;
3598  }
3599 
3600  for (unsigned int iPath = 0; iPath < pContext->baseDirectories.count; /*DO NOTHING*/) {
3601  if (drfs_drpath_equal(pContext->baseDirectories.pBuffer[iPath].absolutePath, absolutePath)) {
3602  drfs_basedirs_remove(&pContext->baseDirectories, iPath);
3603  } else {
3604  ++iPath;
3605  }
3606  }
3607 }
3608 
3609 void drfs_remove_base_directory_by_index(drfs_context* pContext, unsigned int index)
3610 {
3611  if (pContext == NULL) {
3612  return;
3613  }
3614 
3615  drfs_basedirs_remove(&pContext->baseDirectories, index);
3616 }
3617 
3619 {
3620  if (pContext == NULL) {
3621  return;
3622  }
3623 
3624  drfs_basedirs_clear(&pContext->baseDirectories);
3625 }
3626 
3627 unsigned int drfs_get_base_directory_count(drfs_context* pContext)
3628 {
3629  if (pContext == NULL) {
3630  return 0;
3631  }
3632 
3633  return pContext->baseDirectories.count;
3634 }
3635 
3636 const char* drfs_get_base_directory_by_index(drfs_context* pContext, unsigned int index)
3637 {
3638  if (pContext == NULL || index >= pContext->baseDirectories.count) {
3639  return NULL;
3640  }
3641 
3642  return pContext->baseDirectories.pBuffer[index].absolutePath;
3643 }
3644 
3645 
3646 void drfs_set_base_write_directory(drfs_context* pContext, const char* absolutePath)
3647 {
3648  if (pContext == NULL) {
3649  return;
3650  }
3651 
3652  if (absolutePath == NULL) {
3653  memset(pContext->writeBaseDirectory, 0, sizeof(pContext->writeBaseDirectory));
3654  } else {
3655  drfs__strcpy_s(pContext->writeBaseDirectory, sizeof(pContext->writeBaseDirectory), absolutePath);
3656  }
3657 }
3658 
3659 dr_bool32 drfs_get_base_write_directory(drfs_context* pContext, char* absolutePathOut, unsigned int absolutePathOutSize)
3660 {
3661  if (pContext == NULL || absolutePathOut == NULL || absolutePathOutSize == 0) {
3662  return DR_FALSE;
3663  }
3664 
3665  return drfs__strcpy_s(absolutePathOut, absolutePathOutSize, pContext->writeBaseDirectory) == 0;
3666 }
3667 
3669 {
3670  if (pContext == NULL) {
3671  return;
3672  }
3673 
3674  pContext->isWriteGuardEnabled = DR_TRUE;
3675 }
3676 
3678 {
3679  if (pContext == NULL) {
3680  return;
3681  }
3682 
3683  pContext->isWriteGuardEnabled = DR_FALSE;
3684 }
3685 
3687 {
3688  if (pContext == NULL) {
3689  return DR_FALSE;
3690  }
3691 
3692  return pContext->isWriteGuardEnabled;
3693 }
3694 
3695 
3696 
3697 
3698 drfs_result drfs_open_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_archive** ppArchiveOut)
3699 {
3700  if (ppArchiveOut == NULL) {
3701  return drfs_invalid_args;
3702  }
3703 
3704  *ppArchiveOut = NULL;
3705 
3706  if (pContext == NULL || absoluteOrRelativePath == NULL) {
3707  return drfs_invalid_args;
3708  }
3709 
3710  if (drfs_drpath_is_absolute(absoluteOrRelativePath))
3711  {
3712  if (drfs_is_native_directory(absoluteOrRelativePath))
3713  {
3714  // It's a native directory.
3715  return drfs_open_native_archive(pContext, absoluteOrRelativePath, accessMode, ppArchiveOut);
3716  }
3717  else
3718  {
3719  // It's not a native directory. We can just use drfs_open_owner_archive() in this case.
3720  char relativePath[DRFS_MAX_PATH];
3721  drfs_archive* pOwnerArchive;
3722  drfs_result result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, accessMode, relativePath, sizeof(relativePath), &pOwnerArchive);
3723  if (result != drfs_success) {
3724  return result;
3725  }
3726 
3727  drfs_archive* pArchive;
3728  result = drfs_open_non_native_archive_from_path(pOwnerArchive, relativePath, accessMode, &pArchive);
3729  if (result != drfs_success) {
3730  drfs_close_archive(pOwnerArchive);
3731  return result;
3732  }
3733 
3734  drfs_recursively_claim_ownership_or_parent_archive(pArchive);
3735 
3736  *ppArchiveOut = pArchive;
3737  return drfs_success;
3738  }
3739  }
3740  else
3741  {
3742  // The input path is not absolute. We need to check each base directory.
3743 
3744  for (unsigned int iBaseDir = 0; iBaseDir < drfs_get_base_directory_count(pContext); ++iBaseDir)
3745  {
3746  drfs_archive* pArchive;
3747  drfs_result result = drfs_open_archive_from_relative_path(pContext, drfs_get_base_directory_by_index(pContext, iBaseDir), absoluteOrRelativePath, accessMode, &pArchive);
3748  if (result == drfs_success && pArchive != NULL) {
3749  drfs_recursively_claim_ownership_or_parent_archive(pArchive);
3750 
3751  *ppArchiveOut = pArchive;
3752  return drfs_success;
3753  }
3754  }
3755  }
3756 
3757  return drfs_does_not_exist;
3758 }
3759 
3760 drfs_result drfs_open_owner_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut)
3761 {
3762  if (ppArchiveOut == NULL) {
3763  return drfs_invalid_args;
3764  }
3765 
3766  *ppArchiveOut = NULL;
3767 
3768  if (pContext == NULL || absoluteOrRelativePath == NULL) {
3769  return drfs_invalid_args;
3770  }
3771 
3772  if (drfs_drpath_is_absolute(absoluteOrRelativePath))
3773  {
3774  if (drfs_is_native_file(absoluteOrRelativePath) || drfs_is_native_directory(absoluteOrRelativePath))
3775  {
3776  // It's a native file. The owner archive is the directory it's directly sitting in.
3777  char dirAbsolutePath[DRFS_MAX_PATH];
3778  drfs_drpath_copy_base_path(absoluteOrRelativePath, dirAbsolutePath, sizeof(dirAbsolutePath));
3779 
3780  drfs_archive* pArchive;
3781  drfs_result result = drfs_open_archive(pContext, dirAbsolutePath, accessMode, &pArchive);
3782  if (result != drfs_success) {
3783  return result;
3784  }
3785 
3786  if (relativePathOut) {
3787  if (drfs__strcpy_s(relativePathOut, relativePathOutSize, drfs_drpath_file_name(absoluteOrRelativePath)) != 0) {
3788  drfs_close_archive(pArchive);
3789  return drfs_path_too_long;
3790  }
3791  }
3792 
3793  *ppArchiveOut = pArchive;
3794  return drfs_success;
3795  }
3796  else
3797  {
3798  // It's not a native file or directory.
3799  drfs_archive* pArchive;
3800  drfs_result result = drfs_open_owner_archive_from_absolute_path(pContext, absoluteOrRelativePath, accessMode, relativePathOut, relativePathOutSize, &pArchive);
3801  if (pArchive == NULL) {
3802  return result;
3803  }
3804 
3805  drfs_recursively_claim_ownership_or_parent_archive(pArchive);
3806 
3807  *ppArchiveOut = pArchive;
3808  return drfs_success;
3809  }
3810  }
3811  else
3812  {
3813  // The input path is not absolute. We need to loop through each base directory.
3814 
3815  for (unsigned int iBaseDir = 0; iBaseDir < drfs_get_base_directory_count(pContext); ++iBaseDir)
3816  {
3817  drfs_archive* pArchive;
3818  drfs_result result = drfs_open_owner_archive_from_relative_path(pContext, drfs_get_base_directory_by_index(pContext, iBaseDir), absoluteOrRelativePath, accessMode, relativePathOut, relativePathOutSize, &pArchive);
3819  if (result == drfs_success && pArchive != NULL) {
3820  drfs_recursively_claim_ownership_or_parent_archive(pArchive);
3821 
3822  *ppArchiveOut = pArchive;
3823  return drfs_success;
3824  }
3825  }
3826  }
3827 
3828  return drfs_does_not_exist;
3829 }
3830 
3831 void drfs_close_archive(drfs_archive* pArchive)
3832 {
3833  if (pArchive == NULL) {
3834  return;
3835  }
3836 
3837  // The internal handle needs to be closed.
3838  if (pArchive->callbacks.close_archive) {
3839  pArchive->callbacks.close_archive(pArchive->internalArchiveHandle);
3840  }
3841 
3842  drfs_close(pArchive->pFile);
3843 
3844 
3845  if ((pArchive->flags & DR_FS_OWNS_PARENT_ARCHIVE) != 0) {
3846  drfs_close_archive(pArchive->pParentArchive);
3847  }
3848 
3849  free(pArchive);
3850 }
3851 
3852 drfs_result drfs_open_file_from_archive(drfs_archive* pArchive, const char* relativePath, unsigned int accessMode, drfs_file** ppFileOut)
3853 {
3854  if (ppFileOut == NULL) {
3855  return drfs_invalid_args;
3856  }
3857 
3858  *ppFileOut = NULL;
3859 
3860  if (pArchive == NULL || relativePath == NULL || pArchive->callbacks.open_file == NULL) {
3861  return drfs_invalid_args;
3862  }
3863 
3864  drfs_handle internalFileHandle;
3865  drfs_result result = pArchive->callbacks.open_file(pArchive->internalArchiveHandle, relativePath, accessMode, &internalFileHandle);
3866  if (result != drfs_success) {
3867  return result;
3868  }
3869 
3870  // At this point the file is opened and we can create the file object.
3871  drfs_file* pFile = (drfs_file*)malloc(sizeof(*pFile));
3872  if (pFile == NULL) {
3873  pArchive->callbacks.close_file(pArchive->internalArchiveHandle, internalFileHandle);
3874  return drfs_out_of_memory;
3875  }
3876 
3877  pFile->pArchive = pArchive;
3878  pFile->internalFileHandle = internalFileHandle;
3879  pFile->flags = 0;
3880 
3881  // The lock.
3882 #ifdef _WIN32
3883 #ifdef DR_FS_WIN32_USE_EVENT_MUTEX
3884  pFile->lock = CreateEvent(NULL, FALSE, TRUE, NULL);
3885 #else
3886  InitializeCriticalSection(&pFile->lock);
3887 #endif
3888 #else
3889  if (pthread_mutex_init(&pFile->lock, NULL) != 0) {
3890  drfs_close(pFile);
3891  return drfs_unknown_error;
3892  }
3893 #endif
3894 
3895  *ppFileOut = pFile;
3896  return drfs_success;
3897 }
3898 
3899 
3900 drfs_result drfs_open(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_file** ppFile)
3901 {
3902  if (ppFile == NULL) {
3903  return drfs_invalid_args;
3904  }
3905 
3906  // Always set the output file to null at the start for safety.
3907  *ppFile = NULL;
3908 
3909  if (pContext == NULL || absoluteOrRelativePath == NULL) {
3910  return drfs_invalid_args;
3911  }
3912 
3913  char absolutePathForWriteMode[DRFS_MAX_PATH];
3914  if ((accessMode & DRFS_WRITE) != 0) {
3915  if (drfs_validate_write_path(pContext, absoluteOrRelativePath, absolutePathForWriteMode, sizeof(absolutePathForWriteMode))) {
3916  absoluteOrRelativePath = absolutePathForWriteMode;
3917  } else {
3919  }
3920  }
3921 
3922  char relativePath[DRFS_MAX_PATH];
3923  drfs_archive* pArchive;
3924  drfs_result result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, drfs_archive_access_mode(accessMode), relativePath, sizeof(relativePath), &pArchive);
3925  if (result != drfs_success) {
3926  return result;
3927  }
3928 
3929  drfs_file* pFile;
3930  result = drfs_open_file_from_archive(pArchive, relativePath, accessMode, &pFile);
3931  if (result != drfs_success) {
3932  drfs_close_archive(pArchive);
3933  return result;
3934  }
3935 
3936  // When using this API, we want to claim ownership of the archive so that it's closed when we close this file.
3937  pFile->flags |= DR_FS_OWNS_PARENT_ARCHIVE;
3938 
3939  *ppFile = pFile;
3940  return drfs_success;
3941 }
3942 
3943 void drfs_close(drfs_file* pFile)
3944 {
3945  if (!drfs_lock(pFile)) {
3946  return;
3947  }
3948 
3949  if (pFile->pArchive != NULL && pFile->pArchive->callbacks.close_file) {
3950  pFile->pArchive->callbacks.close_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle);
3951  pFile->internalFileHandle = NULL;
3952  }
3953 
3954  if ((pFile->flags & DR_FS_OWNS_PARENT_ARCHIVE) != 0) {
3955  drfs_close_archive(pFile->pArchive);
3956  pFile->pArchive = NULL;
3957  }
3958 
3959  drfs_unlock(pFile);
3960 
3961 
3962  // The lock.
3963 #ifdef _WIN32
3964 #ifdef DR_FS_WIN32_USE_EVENT_MUTEX
3965  CloseHandle(pFile->lock);
3966 #else
3967  DeleteCriticalSection(&pFile->lock);
3968 #endif
3969 #else
3970  pthread_mutex_destroy(&pFile->lock);
3971 #endif
3972 
3973  free(pFile);
3974 }
3975 
3976 drfs_result drfs_read_nolock(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
3977 {
3978  if (pFile == NULL || pDataOut == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.read_file == NULL) {
3979  return drfs_invalid_args;
3980  }
3981 
3982  return pFile->pArchive->callbacks.read_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle, pDataOut, bytesToRead, pBytesReadOut);
3983 }
3984 
3985 drfs_result drfs_read(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
3986 {
3987  if (!drfs_lock(pFile)) {
3988  return drfs_unknown_error;
3989  }
3990 
3991  drfs_result result = drfs_read_nolock(pFile, pDataOut, bytesToRead, pBytesReadOut);
3992 
3993  drfs_unlock(pFile);
3994  return result;
3995 }
3996 
3997 drfs_result drfs_write_nolock(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
3998 {
3999  if (pFile == NULL || pData == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.write_file == NULL) {
4000  return drfs_invalid_args;
4001  }
4002 
4003  return pFile->pArchive->callbacks.write_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle, pData, bytesToWrite, pBytesWrittenOut);
4004 }
4005 
4006 drfs_result drfs_write(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
4007 {
4008  if (!drfs_lock(pFile)) {
4009  return drfs_unknown_error;
4010  }
4011 
4012  drfs_result result = drfs_write_nolock(pFile, pData, bytesToWrite, pBytesWrittenOut);
4013 
4014  drfs_unlock(pFile);
4015  return result;
4016 }
4017 
4018 drfs_result drfs_seek_nolock(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin)
4019 {
4020  if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.seek_file == NULL) {
4021  return drfs_invalid_args;
4022  }
4023 
4024  return pFile->pArchive->callbacks.seek_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle, bytesToSeek, origin);
4025 }
4026 
4027 drfs_result drfs_seek(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin)
4028 {
4029  if (!drfs_lock(pFile)) {
4030  return drfs_unknown_error;
4031  }
4032 
4033  drfs_result result = drfs_seek_nolock(pFile, bytesToSeek, origin);
4034 
4035  drfs_unlock(pFile);
4036  return result;
4037 }
4038 
4040 {
4041  if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.tell_file == NULL) {
4042  return DR_FALSE;
4043  }
4044 
4045  return pFile->pArchive->callbacks.tell_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle);
4046 }
4047 
4049 {
4050  if (!drfs_lock(pFile)) {
4051  return 0;
4052  }
4053 
4054  dr_uint64 result = drfs_tell_nolock(pFile);
4055 
4056  drfs_unlock(pFile);
4057  return result;
4058 }
4059 
4061 {
4062  if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.file_size == NULL) {
4063  return 0;
4064  }
4065 
4066  return pFile->pArchive->callbacks.file_size(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle);
4067 }
4068 
4070 {
4071  if (!drfs_lock(pFile)) {
4072  return DR_FALSE;
4073  }
4074 
4075  dr_uint64 result = drfs_size_nolock(pFile);
4076 
4077  drfs_unlock(pFile);
4078  return result;
4079 }
4080 
4081 void drfs_flush(drfs_file* pFile)
4082 {
4083  if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.flush_file == NULL) {
4084  return;
4085  }
4086 
4087  pFile->pArchive->callbacks.flush_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle);
4088 }
4089 
4090 
4092 {
4093  if (pFile == NULL || pFile->internalFileHandle == NULL) {
4094  return DR_FALSE;
4095  }
4096 
4097 #ifdef _WIN32
4098 #ifdef DR_FS_WIN32_USE_EVENT_MUTEX
4099  WaitForSingleObject(pFile->lock, INFINITE);
4100 #else
4101  EnterCriticalSection(&pFile->lock);
4102 #endif
4103 #else
4104  pthread_mutex_lock(&pFile->lock);
4105 #endif
4106 
4107  return DR_TRUE;
4108 }
4109 
4110 void drfs_unlock(drfs_file* pFile)
4111 {
4112  if (pFile == NULL || pFile->internalFileHandle == NULL) {
4113  return;
4114  }
4115 
4116 #ifdef _WIN32
4117 #ifdef DR_FS_WIN32_USE_EVENT_MUTEX
4118  SetEvent(pFile->lock);
4119 #else
4120  LeaveCriticalSection(&pFile->lock);
4121 #endif
4122 #else
4123  pthread_mutex_unlock(&pFile->lock);
4124 #endif
4125 }
4126 
4127 
4128 drfs_result drfs_get_file_info(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_file_info* fi)
4129 {
4130  if (pContext == NULL || absoluteOrRelativePath == NULL) {
4131  return drfs_invalid_args;
4132  }
4133 
4134  char relativePath[DRFS_MAX_PATH];
4135  drfs_archive* pOwnerArchive;
4136  drfs_result result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, DRFS_READ, relativePath, sizeof(relativePath), &pOwnerArchive);
4137  if (result != drfs_success) {
4138  return result;
4139  }
4140 
4141  result = drfs_no_backend;
4142  if (pOwnerArchive->callbacks.get_file_info) {
4143  result = pOwnerArchive->callbacks.get_file_info(pOwnerArchive->internalArchiveHandle, relativePath, fi);
4144  }
4145 
4146  if (result == drfs_success && fi != NULL) {
4147  drfs_drpath_copy_and_append(fi->absolutePath, sizeof(fi->absolutePath), pOwnerArchive->absolutePath, relativePath);
4148  }
4149 
4150  drfs_close_archive(pOwnerArchive);
4151  return result;
4152 }
4153 
4154 
4155 dr_bool32 drfs_begin(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_iterator* pIteratorOut)
4156 {
4157  if (pIteratorOut == NULL) return DR_FALSE;
4158  memset(pIteratorOut, 0, sizeof(*pIteratorOut));
4159 
4160  if (pContext == NULL || absoluteOrRelativePath == NULL) {
4161  return DR_FALSE;
4162  }
4163 
4164  // We need to open the archive that the given folder is in. The path could, however, point to an actual archive which is allowed
4165  // in which case we just iterate over the root directory within that archive. What we do is first try using the path as an actual
4166  // archive. If it fails we assume the path is to a folder within an archive (such as a zip file) in which case we just try opening
4167  // the owner archive. If both fail, we return DR_FALSE.
4168 
4169  char relativePath[DRFS_MAX_PATH];
4170  drfs_result result = drfs_open_archive(pContext, absoluteOrRelativePath, DRFS_READ, &pIteratorOut->pArchive);
4171  if (result == drfs_success) {
4172  relativePath[0] = '\0';
4173  } else {
4174  result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, DRFS_READ, relativePath, sizeof(relativePath), &pIteratorOut->pArchive);
4175  if (result != drfs_success) {
4176  return DR_FALSE;
4177  }
4178  }
4179 
4180  assert(pIteratorOut->pArchive != NULL);
4181 
4182 
4183  if (pIteratorOut->pArchive->callbacks.begin_iteration) {
4184  pIteratorOut->internalIteratorHandle = pIteratorOut->pArchive->callbacks.begin_iteration(pIteratorOut->pArchive->internalArchiveHandle, relativePath);
4185  }
4186 
4187  if (pIteratorOut->internalIteratorHandle == NULL) {
4188  drfs_close_archive(pIteratorOut->pArchive);
4189  pIteratorOut->pArchive = NULL;
4190  return DR_FALSE;
4191  }
4192 
4193 
4194  dr_bool32 foundFirst = drfs_next(pContext, pIteratorOut);
4195  if (!foundFirst) {
4196  drfs_end(pContext, pIteratorOut);
4197  }
4198 
4199  return foundFirst;
4200 }
4201 
4202 dr_bool32 drfs_next(drfs_context* pContext, drfs_iterator* pIterator)
4203 {
4204  if (pIterator == NULL) {
4205  return DR_FALSE;
4206  }
4207 
4208  memset(&pIterator->info, 0, sizeof(pIterator->info));
4209 
4210  if (pContext == NULL || pIterator->pArchive == NULL) {
4211  return DR_FALSE;
4212  }
4213 
4214  dr_bool32 result = DR_FALSE;
4215  if (pIterator->pArchive->callbacks.next_iteration) {
4216  result = pIterator->pArchive->callbacks.next_iteration(pIterator->pArchive->internalArchiveHandle, pIterator->internalIteratorHandle, &pIterator->info);
4217  }
4218 
4219  // At this point the pIterator->info.absolutePath is actually referring to a relative path. We need to convert it to an absolute path.
4220  if (result) {
4221  char relativePath[DRFS_MAX_PATH];
4222  drfs__strcpy_s(relativePath, sizeof(relativePath), pIterator->info.absolutePath);
4223  drfs_drpath_copy_and_append(pIterator->info.absolutePath, sizeof(pIterator->info.absolutePath), pIterator->pArchive->absolutePath, relativePath);
4224  }
4225 
4226  return result;
4227 }
4228 
4229 void drfs_end(drfs_context* pContext, drfs_iterator* pIterator)
4230 {
4231  if (pContext == NULL || pIterator == NULL) {
4232  return;
4233  }
4234 
4235  if (pIterator->pArchive != NULL && pIterator->pArchive->callbacks.end_iteration) {
4236  pIterator->pArchive->callbacks.end_iteration(pIterator->pArchive->internalArchiveHandle, pIterator->internalIteratorHandle);
4237  }
4238 
4239  drfs_close_archive(pIterator->pArchive);
4240  memset(pIterator, 0, sizeof(*pIterator));
4241 }
4242 
4243 drfs_result drfs_delete_file(drfs_context* pContext, const char* path)
4244 {
4245  if (pContext == NULL || path == NULL) {
4246  return drfs_invalid_args;
4247  }
4248 
4249  char absolutePath[DRFS_MAX_PATH];
4250  if (!drfs_validate_write_path(pContext, path, absolutePath, sizeof(absolutePath))) {
4252  }
4253 
4254 
4255  char relativePath[DRFS_MAX_PATH];
4256  drfs_archive* pArchive;
4257  drfs_result result = drfs_open_owner_archive(pContext, absolutePath, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePath, sizeof(relativePath), &pArchive);
4258  if (result != drfs_success) {
4259  return result;
4260  }
4261 
4262  result = drfs_no_backend;
4263  if (pArchive->callbacks.delete_file) {
4264  result = pArchive->callbacks.delete_file(pArchive->internalArchiveHandle, relativePath);
4265  }
4266 
4267  drfs_close_archive(pArchive);
4268  return result;
4269 }
4270 
4271 drfs_result drfs_create_directory(drfs_context* pContext, const char* path)
4272 {
4273  if (pContext == NULL || path == NULL) {
4274  return drfs_invalid_args;
4275  }
4276 
4277  char absolutePath[DRFS_MAX_PATH];
4278  if (!drfs_validate_write_path(pContext, path, absolutePath, sizeof(absolutePath))) {
4280  }
4281 
4282  char relativePath[DRFS_MAX_PATH];
4283  drfs_archive* pArchive;
4284  drfs_result result = drfs_open_owner_archive(pContext, absolutePath, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePath, sizeof(relativePath), &pArchive);
4285  if (result != drfs_success) {
4286  return result;
4287  }
4288 
4289  result = drfs_no_backend;
4290  if (pArchive->callbacks.create_directory) {
4291  result = pArchive->callbacks.create_directory(pArchive->internalArchiveHandle, relativePath);
4292  }
4293 
4294  drfs_close_archive(pArchive);
4295  return result;
4296 }
4297 
4298 drfs_result drfs_move_file(drfs_context* pContext, const char* pathOld, const char* pathNew)
4299 {
4300  // Renaming/moving is not supported across different archives.
4301 
4302  if (pContext == NULL || pathOld == NULL || pathNew == NULL) {
4303  return drfs_invalid_args;
4304  }
4305 
4306 
4307  char absolutePathOld[DRFS_MAX_PATH];
4308  if (drfs_validate_write_path(pContext, pathOld, absolutePathOld, sizeof(absolutePathOld))) {
4309  pathOld = absolutePathOld;
4310  } else {
4312  }
4313 
4314  char absolutePathNew[DRFS_MAX_PATH];
4315  if (drfs_validate_write_path(pContext, pathNew, absolutePathNew, sizeof(absolutePathNew))) {
4316  pathNew = absolutePathNew;
4317  } else {
4319  }
4320 
4321 
4322 
4323 
4324 
4325  char relativePathOld[DRFS_MAX_PATH];
4326  drfs_archive* pArchiveOld;
4327  drfs_result result = drfs_open_owner_archive(pContext, pathOld, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePathOld, sizeof(relativePathOld), &pArchiveOld);
4328  if (pArchiveOld != NULL)
4329  {
4330  char relativePathNew[DRFS_MAX_PATH];
4331  drfs_archive* pArchiveNew;
4332  result = drfs_open_owner_archive(pContext, pathNew, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePathNew, sizeof(relativePathNew), &pArchiveNew);
4333  if (pArchiveNew != NULL)
4334  {
4335  dr_bool32 areBothArchivesNative = (pArchiveOld->callbacks.move_file == pArchiveNew->callbacks.move_file && pArchiveNew->callbacks.move_file == drfs_move_file__native);
4336  if (areBothArchivesNative)
4337  {
4338  result = drfs_move_native_file(absolutePathOld, absolutePathNew);
4339  }
4340  else
4341  {
4342  if (drfs_drpath_equal(pArchiveOld->absolutePath, pArchiveNew->absolutePath) && pArchiveOld->callbacks.move_file) {
4343  result = pArchiveOld->callbacks.move_file(pArchiveOld, relativePathOld, relativePathNew);
4344  } else {
4345  result = drfs_permission_denied; // Attempting to rename across different archives which is not supported.
4346  }
4347  }
4348 
4349  drfs_close_archive(pArchiveNew);
4350 
4351  }
4352 
4353  drfs_close_archive(pArchiveOld);
4354  }
4355 
4356  return result;
4357 }
4358 
4359 drfs_result drfs_copy_file(drfs_context* pContext, const char* srcPath, const char* dstPath, dr_bool32 failIfExists)
4360 {
4361  if (pContext == NULL || srcPath == NULL || dstPath == NULL) {
4362  return drfs_invalid_args;
4363  }
4364 
4365  char dstPathAbsolute[DRFS_MAX_PATH];
4366  if (!drfs_validate_write_path(pContext, dstPath, dstPathAbsolute, sizeof(dstPathAbsolute))) {
4368  }
4369 
4370 
4371  // We want to open the archive of both the source and destination. If they are the same archive we'll do an intra-archive copy.
4372  char srcRelativePath[DRFS_MAX_PATH];
4373  drfs_archive* pSrcArchive;
4374  drfs_result result = drfs_open_owner_archive(pContext, srcPath, drfs_archive_access_mode(DRFS_READ), srcRelativePath, sizeof(srcRelativePath), &pSrcArchive);
4375  if (result != drfs_success) {
4376  return result;
4377  }
4378 
4379  char dstRelativePath[DRFS_MAX_PATH];
4380  drfs_archive* pDstArchive;
4381  result = drfs_open_owner_archive(pContext, dstPathAbsolute, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), dstRelativePath, sizeof(dstRelativePath), &pDstArchive);
4382  if (result != drfs_success) {
4383  drfs_close_archive(pSrcArchive);
4384  return result;
4385  }
4386 
4387 
4388 
4389  result = drfs_success;
4390  if (strcmp(pSrcArchive->absolutePath, pDstArchive->absolutePath) == 0 && pDstArchive->callbacks.copy_file)
4391  {
4392  // Intra-archive copy.
4393  result = pDstArchive->callbacks.copy_file(pDstArchive->internalArchiveHandle, srcRelativePath, dstRelativePath, failIfExists);
4394  }
4395  else
4396  {
4397  dr_bool32 areBothArchivesNative = (pSrcArchive->callbacks.copy_file == pDstArchive->callbacks.copy_file && pDstArchive->callbacks.copy_file == drfs_copy_file__native);
4398  if (areBothArchivesNative)
4399  {
4400  char srcPathAbsolute[DRFS_MAX_PATH];
4401  drfs_drpath_copy_and_append(srcPathAbsolute, sizeof(srcPathAbsolute), pSrcArchive->absolutePath, srcPath);
4402 
4403  result = drfs_copy_native_file(srcPathAbsolute, dstPathAbsolute, failIfExists);
4404  }
4405  else
4406  {
4407  // Inter-archive copy. This is a theoretically slower path because we do everything manually. Probably not much slower in practice, though.
4408  if (failIfExists && pDstArchive->callbacks.get_file_info(pDstArchive, dstRelativePath, NULL) == drfs_success)
4409  {
4410  result = drfs_already_exists;
4411  }
4412  else
4413  {
4414  drfs_file* pSrcFile;
4415  result = drfs_open_file_from_archive(pSrcArchive, srcRelativePath, DRFS_READ, &pSrcFile);
4416  if (result != drfs_success) {
4417  return result;
4418  }
4419 
4420  drfs_file* pDstFile;
4421  result = drfs_open_file_from_archive(pDstArchive, dstRelativePath, DRFS_WRITE | DRFS_TRUNCATE, &pDstFile);
4422  if (result != drfs_success) {
4423  drfs_close(pSrcFile);
4424  return result;
4425  }
4426 
4427  assert(pSrcFile != NULL);
4428  assert(pDstFile != NULL);
4429 
4430  char chunk[4096];
4431  size_t bytesRead;
4432  while (drfs_read(pSrcFile, chunk, sizeof(chunk), &bytesRead) == drfs_success && bytesRead > 0)
4433  {
4434  result = drfs_write(pDstFile, chunk, bytesRead, NULL);
4435  if (result != drfs_success) {
4436  break;
4437  }
4438  }
4439 
4440  drfs_close(pSrcFile);
4441  drfs_close(pDstFile);
4442  }
4443  }
4444  }
4445 
4446 
4447  drfs_close_archive(pSrcArchive);
4448  drfs_close_archive(pDstArchive);
4449 
4450  return result;
4451 }
4452 
4453 
4454 dr_bool32 drfs_is_archive_path(drfs_context* pContext, const char* path)
4455 {
4456  return drfs_find_backend_by_extension(pContext, drfs_drpath_extension(path), NULL);
4457 }
4458 
4459 
4460 
4462 //
4463 // High Level API
4464 //
4466 void drfs_free(void* p)
4467 {
4468  free(p);
4469 }
4470 
4471 drfs_result drfs_find_absolute_path(drfs_context* pContext, const char* relativePath, char* absolutePathOut, size_t absolutePathOutSize)
4472 {
4473  if (absolutePathOut == NULL) return drfs_invalid_args;
4474  if (absolutePathOutSize > 0) absolutePathOut[0] = '\0';
4475 
4476  if (pContext == NULL || relativePath == NULL || absolutePathOutSize == 0) {
4477  return drfs_invalid_args;
4478  }
4479 
4480  drfs_file_info fi;
4481  if (drfs_get_file_info(pContext, relativePath, &fi) == drfs_success) {
4482  if (drfs__strcpy_s(absolutePathOut, absolutePathOutSize, fi.absolutePath) == 0) {
4483  return drfs_success;
4484  }
4485  }
4486 
4487  return drfs_does_not_exist;
4488 }
4489 
4490 drfs_result drfs_find_absolute_path_explicit_base(drfs_context* pContext, const char* relativePath, const char* highestPriorityBasePath, char* absolutePathOut, size_t absolutePathOutSize)
4491 {
4492  if (absolutePathOut == NULL) return drfs_invalid_args;
4493  if (absolutePathOutSize > 0) absolutePathOut[0] = '\0';
4494 
4495  if (pContext == NULL || relativePath == NULL || highestPriorityBasePath == NULL || absolutePathOutSize == 0) {
4496  return drfs_invalid_args;
4497  }
4498 
4500  drfs_insert_base_directory(pContext, highestPriorityBasePath, 0);
4501  {
4502  result = drfs_find_absolute_path(pContext, relativePath, absolutePathOut, absolutePathOutSize);
4503  }
4505 
4506  return result;
4507 }
4508 
4509 dr_bool32 drfs_is_base_directory(drfs_context* pContext, const char* baseDir)
4510 {
4511  if (pContext == NULL) {
4512  return DR_FALSE;
4513  }
4514 
4515  for (unsigned int i = 0; i < drfs_get_base_directory_count(pContext); ++i) {
4516  if (drfs_drpath_equal(drfs_get_base_directory_by_index(pContext, i), baseDir)) {
4517  return DR_TRUE;
4518  }
4519  }
4520 
4521  return DR_FALSE;
4522 }
4523 
4524 drfs_result drfs_write_string(drfs_file* pFile, const char* str)
4525 {
4526  return drfs_write(pFile, str, (unsigned int)strlen(str), NULL);
4527 }
4528 
4529 drfs_result drfs_write_line(drfs_file* pFile, const char* str)
4530 {
4531  drfs_result result = drfs_write_string(pFile, str);
4532  if (result != drfs_success) {
4533  return result;
4534  }
4535 
4536  return drfs_write_string(pFile, "\n");
4537 }
4538 
4539 
4540 void* drfs_open_and_read_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut)
4541 {
4542  drfs_file* pFile;
4543  if (drfs_open(pContext, absoluteOrRelativePath, DRFS_READ, &pFile) != drfs_success) {
4544  return NULL;
4545  }
4546 
4547  dr_uint64 fileSize = drfs_size(pFile);
4548  if (fileSize > SIZE_MAX)
4549  {
4550  // File's too big.
4551  drfs_close(pFile);
4552  return NULL;
4553  }
4554 
4555 
4556  void* pData = malloc((size_t)fileSize);
4557  if (pData == NULL)
4558  {
4559  // Failed to allocate memory.
4560  drfs_close(pFile);
4561  return NULL;
4562  }
4563 
4564 
4565  if (drfs_read(pFile, pData, (size_t)fileSize, NULL) != drfs_success)
4566  {
4567  free(pData);
4568  if (pSizeInBytesOut != NULL) {
4569  *pSizeInBytesOut = 0;
4570  }
4571 
4572  drfs_close(pFile);
4573  return NULL;
4574  }
4575 
4576 
4577  if (pSizeInBytesOut != NULL) {
4578  *pSizeInBytesOut = (size_t)fileSize;
4579  }
4580 
4581  drfs_close(pFile);
4582  return pData;
4583 }
4584 
4585 char* drfs_open_and_read_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut)
4586 {
4587  drfs_file* pFile;
4588  if (drfs_open(pContext, absoluteOrRelativePath, DRFS_READ, &pFile) != drfs_success) {
4589  return NULL;
4590  }
4591 
4592  dr_uint64 fileSize = drfs_size(pFile);
4593  if (fileSize > SIZE_MAX)
4594  {
4595  // File's too big.
4596  drfs_close(pFile);
4597  return NULL;
4598  }
4599 
4600 
4601  void* pData = malloc((size_t)fileSize + 1); // +1 for null terminator.
4602  if (pData == NULL)
4603  {
4604  // Failed to allocate memory.
4605  drfs_close(pFile);
4606  return NULL;
4607  }
4608 
4609 
4610  if (drfs_read(pFile, pData, (size_t)fileSize, NULL) != drfs_success)
4611  {
4612  free(pData);
4613  if (pSizeInBytesOut != NULL) {
4614  *pSizeInBytesOut = 0;
4615  }
4616 
4617  drfs_close(pFile);
4618  return NULL;
4619  }
4620 
4621  // Null terminator.
4622  ((char*)pData)[fileSize] = '\0';
4623 
4624 
4625  if (pSizeInBytesOut != NULL) {
4626  *pSizeInBytesOut = (size_t)fileSize;
4627  }
4628 
4629  drfs_close(pFile);
4630  return (char*)pData;
4631 }
4632 
4633 drfs_result drfs_open_and_write_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, const void* pData, size_t dataSize)
4634 {
4636 
4637  drfs_file* pFile;
4638  result = drfs_open(pContext, absoluteOrRelativePath, DRFS_WRITE | DRFS_TRUNCATE, &pFile);
4639  if (result != drfs_success) {
4640  return result;
4641  }
4642 
4643  result = drfs_write(pFile, pData, dataSize, NULL);
4644 
4645  drfs_close(pFile);
4646  return result;
4647 }
4648 
4649 drfs_result drfs_open_and_write_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, const char* pTextData)
4650 {
4651  return drfs_open_and_write_binary_file(pContext, absoluteOrRelativePath, pTextData, strlen(pTextData));
4652 }
4653 
4654 
4655 dr_bool32 drfs_exists(drfs_context* pContext, const char* absoluteOrRelativePath)
4656 {
4657  drfs_file_info fi;
4658  return drfs_get_file_info(pContext, absoluteOrRelativePath, &fi) == drfs_success;
4659 }
4660 
4661 dr_bool32 drfs_is_existing_file(drfs_context* pContext, const char* absoluteOrRelativePath)
4662 {
4663  drfs_file_info fi;
4664  if (drfs_get_file_info(pContext, absoluteOrRelativePath, &fi) == drfs_success)
4665  {
4666  return (fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) == 0;
4667  }
4668 
4669  return DR_FALSE;
4670 }
4671 
4672 dr_bool32 drfs_is_existing_directory(drfs_context* pContext, const char* absoluteOrRelativePath)
4673 {
4674  drfs_file_info fi;
4675  if (drfs_get_file_info(pContext, absoluteOrRelativePath, &fi) == drfs_success)
4676  {
4677  return (fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) != 0;
4678  }
4679 
4680  return DR_FALSE;
4681 }
4682 
4683 drfs_result drfs_create_directory_recursive(drfs_context* pContext, const char* path)
4684 {
4685  if (pContext == NULL || path == NULL) {
4686  return drfs_invalid_args;
4687  }
4688 
4689  // We just iterate over each segment and try creating each directory if it doesn't exist.
4690  char absolutePath[DRFS_MAX_PATH];
4691  if (drfs_validate_write_path(pContext, path, absolutePath, DRFS_MAX_PATH)) {
4692  path = absolutePath;
4693  } else {
4695  }
4696 
4697 
4698  char runningPath[DRFS_MAX_PATH];
4699  runningPath[0] = '\0';
4700 
4701  drfs_drpath_iterator iPathSeg;
4702  if (!drfs_drpath_first(absolutePath, &iPathSeg)) {
4703  return drfs_invalid_args;
4704  }
4705 
4706  // Never check the first segment because we can assume it always exists - it will always be the drive root.
4707  if (drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg))
4708  {
4709  // Loop over every directory until we find one that does not exist.
4710  while (drfs_drpath_next(&iPathSeg))
4711  {
4712  if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) {
4713  return drfs_path_too_long;
4714  }
4715 
4716  if (!drfs_is_existing_directory(pContext, runningPath)) {
4717  drfs_result result = drfs_create_directory(pContext, runningPath);
4718  if (result != drfs_success) {
4719  return result;
4720  }
4721 
4722  break;
4723  }
4724  }
4725 
4726 
4727  // At this point all we need to do is create the remaining directories - we can assert that the directory does not exist
4728  // rather than actually checking it which should be a bit more efficient.
4729  while (drfs_drpath_next(&iPathSeg))
4730  {
4731  if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) {
4732  return drfs_path_too_long;
4733  }
4734 
4735  assert(!drfs_is_existing_directory(pContext, runningPath));
4736 
4737  drfs_result result = drfs_create_directory(pContext, runningPath);
4738  if (result != drfs_success) {
4739  return result;
4740  }
4741  }
4742 
4743 
4744  return drfs_success;
4745  }
4746  else
4747  {
4748  return drfs_invalid_args;
4749  }
4750 }
4751 
4753 {
4754  return drfs_tell(pFile) == drfs_size(pFile);
4755 }
4756 
4757 
4758 
4759 
4761 //
4762 // ZIP
4763 //
4765 #ifndef DR_FS_NO_ZIP
4766 
4767 #if defined(_MSC_VER)
4768  #pragma warning(push)
4769  #pragma warning(disable:4334)
4770 #endif
4771 #if defined(__GNUC__)
4772  #pragma GCC diagnostic push
4773  #pragma GCC diagnostic ignored "-Wunused-macros"
4774  #pragma GCC diagnostic ignored "-Wcast-align"
4775  #pragma GCC diagnostic ignored "-Wextra"
4776 
4777 #if __GNUC__ >= 6 && !defined __clang__
4778  #pragma GCC diagnostic ignored "-Wmisleading-indentation"
4779 #endif
4780 #endif
4781 
4782 #ifndef DRFS_MINIZ_HEADER_INCLUDED
4783 #define DRFS_MINIZ_HEADER_INCLUDED
4784 
4785 // If DRFS_MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or
4786 // get/set file times, and the C run-time funcs that get/set times won't be called.
4787 // The current downside is the times written to your archives will be from 1979.
4788 //#define DRFS_MINIZ_NO_TIME
4789 
4790 // Define DRFS_MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's.
4791 //#define DRFS_MINIZ_NO_ARCHIVE_APIS
4792 
4793 #if defined(__TINYC__) && (defined(__linux) || defined(__linux__))
4794  // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux
4795  #define DRFS_MINIZ_NO_TIME
4796 #endif
4797 
4798 #if !defined(DRFS_MINIZ_NO_TIME) && !defined(DRFS_MINIZ_NO_ARCHIVE_APIS)
4799  #include <time.h>
4800 #endif
4801 
4802 #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
4803 // DRFS_MINIZ_X86_OR_X64_CPU is only used to help set the below macros.
4804 #define DRFS_MINIZ_X86_OR_X64_CPU 1
4805 #endif
4806 
4807 #if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || DRFS_MINIZ_X86_OR_X64_CPU
4808 // Set DRFS_MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian.
4809 #define DRFS_MINIZ_LITTLE_ENDIAN 1
4810 #endif
4811 
4812 #if DRFS_MINIZ_X86_OR_X64_CPU
4813 // Set DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses.
4814 #define DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
4815 #endif
4816 
4817 #if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__)
4818 // Set DRFS_MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions).
4819 #define DRFS_MINIZ_HAS_64BIT_REGISTERS 1
4820 #endif
4821 
4822 #ifdef __cplusplus
4823 extern "C" {
4824 #endif
4825 
4826 // ------------------- zlib-style API Definitions.
4827 
4828 // For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: drfs_mz_ulong can be either 32 or 64-bits!
4829 typedef unsigned long drfs_mz_ulong;
4830 
4831 // drfs_mz_free() internally uses the DRFS_MZ_FREE() macro (which by default calls free() unless you've modified the DRFS_MZ_MALLOC macro) to release a block allocated from the heap.
4832 void drfs_mz_free(void *p);
4833 
4834 #define DRFS_MZ_ADLER32_INIT (1)
4835 // drfs_mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL.
4836 drfs_mz_ulong drfs_mz_adler32(drfs_mz_ulong adler, const unsigned char *ptr, size_t buf_len);
4837 
4838 #define DRFS_MZ_CRC32_INIT (0)
4839 // drfs_mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL.
4840 drfs_mz_ulong drfs_mz_crc32(drfs_mz_ulong crc, const unsigned char *ptr, size_t buf_len);
4841 
4842 // Compression strategies.
4843 enum { DRFS_MZ_DEFAULT_STRATEGY = 0, DRFS_MZ_FILTERED = 1, DRFS_MZ_HUFFMAN_ONLY = 2, DRFS_MZ_RLE = 3, DRFS_MZ_FIXED = 4 };
4844 
4845 // Method
4846 #define DRFS_MZ_DEFLATED 8
4847 
4848 // Heap allocation callbacks.
4849 // Note that drfs_mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long.
4850 typedef void *(*drfs_mz_alloc_func)(void *opaque, size_t items, size_t size);
4851 typedef void (*drfs_mz_free_func)(void *opaque, void *address);
4852 typedef void *(*drfs_mz_realloc_func)(void *opaque, void *address, size_t items, size_t size);
4853 
4854 // ------------------- Types and macros
4855 
4856 typedef unsigned char drfs_mz_uint8;
4857 typedef signed short drfs_mz_int16;
4858 typedef unsigned short drfs_drfs_mz_uint16;
4859 typedef unsigned int drfs_drfs_mz_uint32;
4860 typedef unsigned int drfs_mz_uint;
4861 typedef long long drfs_mz_int64;
4862 typedef unsigned long long drfs_mz_uint64;
4863 typedef int drfs_mz_bool;
4864 
4865 #define DRFS_MZ_FALSE (0)
4866 #define DRFS_MZ_TRUE (1)
4867 
4868 // An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message.
4869 #ifdef _MSC_VER
4870  #define DRFS_MZ_MACRO_END while (0, 0)
4871 #else
4872  #define DRFS_MZ_MACRO_END while (0)
4873 #endif
4874 
4875 // ------------------- ZIP archive reading/writing
4876 
4877 #ifndef DRFS_MINIZ_NO_ARCHIVE_APIS
4878 
4879 enum
4880 {
4881  DRFS_MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024,
4882  DRFS_MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260,
4883  DRFS_MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256
4884 };
4885 
4886 typedef struct
4887 {
4888  drfs_drfs_mz_uint32 m_file_index;
4889  drfs_drfs_mz_uint32 m_central_dir_ofs;
4890  drfs_drfs_mz_uint16 m_version_made_by;
4891  drfs_drfs_mz_uint16 m_version_needed;
4892  drfs_drfs_mz_uint16 m_bit_flag;
4893  drfs_drfs_mz_uint16 m_method;
4894 #ifndef DRFS_MINIZ_NO_TIME
4895  time_t m_time;
4896 #endif
4897  drfs_drfs_mz_uint32 m_crc32;
4898  drfs_mz_uint64 m_comp_size;
4899  drfs_mz_uint64 m_uncomp_size;
4900  drfs_drfs_mz_uint16 m_internal_attr;
4901  drfs_drfs_mz_uint32 m_external_attr;
4902  drfs_mz_uint64 m_local_header_ofs;
4903  drfs_drfs_mz_uint32 m_comment_size;
4904  char m_filename[DRFS_MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];
4905  char m_comment[DRFS_MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE];
4906 } drfs_drfs_mz_zip_archive_file_stat;
4907 
4908 typedef size_t (*drfs_mz_file_read_func)(void *pOpaque, drfs_mz_uint64 file_ofs, void *pBuf, size_t n);
4909 typedef size_t (*drfs_mz_file_write_func)(void *pOpaque, drfs_mz_uint64 file_ofs, const void *pBuf, size_t n);
4910 
4911 struct drfs_mz_zip_internal_state_tag;
4912 typedef struct drfs_mz_zip_internal_state_tag drfs_mz_zip_internal_state;
4913 
4914 typedef enum
4915 {
4916  DRFS_MZ_ZIP_MODE_INVALID = 0,
4917  DRFS_MZ_ZIP_MODE_READING = 1,
4918  DRFS_MZ_ZIP_MODE_WRITING = 2,
4919  DRFS_MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3
4920 } drfs_mz_zip_mode;
4921 
4922 typedef struct drfs_mz_zip_archive_tag
4923 {
4924  drfs_mz_uint64 m_archive_size;
4925  drfs_mz_uint64 m_central_directory_file_ofs;
4926  drfs_mz_uint m_total_files;
4927  drfs_mz_zip_mode m_zip_mode;
4928 
4929  drfs_mz_uint m_file_offset_alignment;
4930 
4931  drfs_mz_alloc_func m_pAlloc;
4932  drfs_mz_free_func m_pFree;
4933  drfs_mz_realloc_func m_pRealloc;
4934  void *m_pAlloc_opaque;
4935 
4936  drfs_mz_file_read_func m_pRead;
4937  drfs_mz_file_write_func m_pWrite;
4938  void *m_pIO_opaque;
4939 
4940  drfs_mz_zip_internal_state *m_pState;
4941 
4942 } drfs_mz_zip_archive;
4943 
4944 typedef enum
4945 {
4946  DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100,
4947  DRFS_MZ_ZIP_FLAG_IGNORE_PATH = 0x0200,
4948  DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400,
4949  DRFS_MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800
4950 } drfs_mz_zip_flags;
4951 
4952 // ZIP archive reading
4953 
4954 // Inits a ZIP archive reader.
4955 // These functions read and validate the archive's central directory.
4956 drfs_mz_bool drfs_mz_zip_reader_init(drfs_mz_zip_archive *pZip, drfs_mz_uint64 size, drfs_drfs_mz_uint32 flags);
4957 
4958 // Returns the total number of files in the archive.
4959 drfs_mz_uint drfs_mz_zip_reader_get_num_files(drfs_mz_zip_archive *pZip);
4960 
4961 // Returns detailed information about an archive file entry.
4962 drfs_mz_bool drfs_mz_zip_reader_file_stat(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, drfs_drfs_mz_zip_archive_file_stat *pStat);
4963 
4964 // Determines if an archive file entry is a directory entry.
4965 drfs_mz_bool drfs_mz_zip_reader_is_file_a_directory(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index);
4966 
4967 // Retrieves the filename of an archive file entry.
4968 // Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename.
4969 drfs_mz_uint drfs_mz_zip_reader_get_filename(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, char *pFilename, drfs_mz_uint filename_buf_size);
4970 
4971 // Attempts to locates a file in the archive's central directory.
4972 // Valid flags: DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE, DRFS_MZ_ZIP_FLAG_IGNORE_PATH
4973 // Returns -1 if the file cannot be found.
4974 int drfs_mz_zip_reader_locate_file(drfs_mz_zip_archive *pZip, const char *pName, const char *pComment, drfs_mz_uint flags);
4975 
4976 // Extracts a archive file to a memory buffer using no memory allocation.
4977 drfs_mz_bool drfs_mz_zip_reader_extract_to_mem_no_alloc(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
4978 drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem_no_alloc(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
4979 
4980 // Extracts a archive file to a memory buffer.
4981 drfs_mz_bool drfs_mz_zip_reader_extract_to_mem(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags);
4982 drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags);
4983 
4984 // Extracts a archive file to a dynamically allocated heap buffer.
4985 void *drfs_mz_zip_reader_extract_to_heap(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, size_t *pSize, drfs_mz_uint flags);
4986 
4987 // Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used.
4988 drfs_mz_bool drfs_mz_zip_reader_end(drfs_mz_zip_archive *pZip);
4989 
4990 #endif // #ifndef DRFS_MINIZ_NO_ARCHIVE_APIS
4991 
4992 // ------------------- Low-level Decompression API Definitions
4993 
4994 // Decompression flags used by drfs_tinfl_decompress().
4995 // DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream.
4996 // DRFS_TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input.
4997 // DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB).
4998 // DRFS_TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes.
4999 enum
5000 {
5001  DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER = 1,
5002  DRFS_TINFL_FLAG_HAS_MORE_INPUT = 2,
5003  DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,
5004  DRFS_TINFL_FLAG_COMPUTE_ADLER32 = 8
5005 };
5006 
5007 // High level decompression functions:
5008 // drfs_tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc().
5009 // On entry:
5010 // pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress.
5011 // On return:
5012 // Function returns a pointer to the decompressed data, or NULL on failure.
5013 // *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data.
5014 // The caller must call drfs_mz_free() on the returned block when it's no longer needed.
5015 void *drfs_tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);
5016 
5017 // drfs_tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory.
5018 // Returns DRFS_TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success.
5019 #define DRFS_TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))
5020 size_t drfs_tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);
5021 
5022 // drfs_tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer.
5023 // Returns 1 on success or 0 on failure.
5024 typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser);
5025 int drfs_tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);
5026 
5027 struct drfs_tinfl_decompressor_tag; typedef struct drfs_tinfl_decompressor_tag drfs_tinfl_decompressor;
5028 
5029 // Max size of LZ dictionary.
5030 #define DRFS_TINFL_LZ_DICT_SIZE 32768
5031 
5032 // Return status.
5033 typedef enum
5034 {
5035  DRFS_TINFL_STATUS_BAD_PARAM = -3,
5036  DRFS_TINFL_STATUS_ADLER32_MISMATCH = -2,
5037  DRFS_TINFL_STATUS_FAILED = -1,
5038  DRFS_TINFL_STATUS_DONE = 0,
5039  DRFS_TINFL_STATUS_NEEDS_MORE_INPUT = 1,
5040  DRFS_TINFL_STATUS_HAS_MORE_OUTPUT = 2
5041 } drfs_tinfl_status;
5042 
5043 // Initializes the decompressor to its initial state.
5044 #define drfs_tinfl_init(r) do { (r)->m_state = 0; } DRFS_MZ_MACRO_END
5045 #define drfs_tinfl_get_adler32(r) (r)->m_check_adler32
5046 
5047 // Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability.
5048 // This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output.
5049 drfs_tinfl_status drfs_tinfl_decompress(drfs_tinfl_decompressor *r, const drfs_mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, drfs_mz_uint8 *pOut_buf_start, drfs_mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const drfs_drfs_mz_uint32 decomp_flags);
5050 
5051 // Internal/private bits follow.
5052 enum
5053 {
5054  DRFS_TINFL_MAX_HUFF_TABLES = 3, DRFS_TINFL_MAX_HUFF_SYMBOLS_0 = 288, DRFS_TINFL_MAX_HUFF_SYMBOLS_1 = 32, DRFS_TINFL_MAX_HUFF_SYMBOLS_2 = 19,
5055  DRFS_TINFL_FAST_LOOKUP_BITS = 10, DRFS_TINFL_FAST_LOOKUP_SIZE = 1 << DRFS_TINFL_FAST_LOOKUP_BITS
5056 };
5057 
5058 typedef struct
5059 {
5060  drfs_mz_uint8 m_code_size[DRFS_TINFL_MAX_HUFF_SYMBOLS_0];
5061  drfs_mz_int16 m_look_up[DRFS_TINFL_FAST_LOOKUP_SIZE], m_tree[DRFS_TINFL_MAX_HUFF_SYMBOLS_0 * 2];
5062 } drfs_tinfl_huff_table;
5063 
5064 #if DRFS_MINIZ_HAS_64BIT_REGISTERS
5065  #define DRFS_TINFL_USE_64BIT_BITBUF 1
5066 #endif
5067 
5068 #if DRFS_TINFL_USE_64BIT_BITBUF
5069  typedef drfs_mz_uint64 drfs_tinfl_bit_buf_t;
5070  #define DRFS_TINFL_BITBUF_SIZE (64)
5071 #else
5072  typedef drfs_drfs_mz_uint32 drfs_tinfl_bit_buf_t;
5073  #define DRFS_TINFL_BITBUF_SIZE (32)
5074 #endif
5075 
5076 struct drfs_tinfl_decompressor_tag
5077 {
5078  drfs_drfs_mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[DRFS_TINFL_MAX_HUFF_TABLES];
5079  drfs_tinfl_bit_buf_t m_bit_buf;
5080  size_t m_dist_from_out_buf_start;
5081  drfs_tinfl_huff_table m_tables[DRFS_TINFL_MAX_HUFF_TABLES];
5082  drfs_mz_uint8 m_raw_header[4], m_len_codes[DRFS_TINFL_MAX_HUFF_SYMBOLS_0 + DRFS_TINFL_MAX_HUFF_SYMBOLS_1 + 137];
5083 };
5084 
5085 #ifdef __cplusplus
5086 }
5087 #endif
5088 
5089 #endif // MINIZ_HEADER_INCLUDED
5090 
5091 // ------------------- End of Header: Implementation follows. (If you only want the header, define DRFS_MINIZ_HEADER_FILE_ONLY.)
5092 
5093 #ifndef DRFS_MINIZ_HEADER_FILE_ONLY
5094 
5095 typedef unsigned char mz_validate_uint16[sizeof(drfs_drfs_mz_uint16)==2 ? 1 : -1];
5096 typedef unsigned char mz_validate_uint32[sizeof(drfs_drfs_mz_uint32)==4 ? 1 : -1];
5097 typedef unsigned char mz_validate_uint64[sizeof(drfs_mz_uint64)==8 ? 1 : -1];
5098 
5099 #include <string.h>
5100 #include <assert.h>
5101 
5102 #define DRFS_MZ_ASSERT(x) assert(x)
5103 
5104 #define DRFS_MZ_MALLOC(x) malloc(x)
5105 #define DRFS_MZ_FREE(x) free(x)
5106 #define DRFS_MZ_REALLOC(p, x) realloc(p, x)
5107 
5108 #define DRFS_MZ_MAX(a,b) (((a)>(b))?(a):(b))
5109 #define DRFS_MZ_MIN(a,b) (((a)<(b))?(a):(b))
5110 #define DRFS_MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))
5111 
5112 #if DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES && DRFS_MINIZ_LITTLE_ENDIAN
5113  #define DRFS_MZ_READ_LE16(p) *((const drfs_drfs_mz_uint16 *)(p))
5114  #define DRFS_MZ_READ_LE32(p) *((const drfs_drfs_mz_uint32 *)(p))
5115 #else
5116  #define DRFS_MZ_READ_LE16(p) ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[0]) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[1]) << 8U))
5117  #define DRFS_MZ_READ_LE32(p) ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[0]) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[1]) << 8U) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[2]) << 16U) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[3]) << 24U))
5118 #endif
5119 
5120 #ifdef _MSC_VER
5121  #define DRFS_MZ_FORCEINLINE __forceinline
5122 #elif defined(__GNUC__)
5123  #define DRFS_MZ_FORCEINLINE inline __attribute__((__always_inline__))
5124 #else
5125  #define DRFS_MZ_FORCEINLINE inline
5126 #endif
5127 
5128 #ifdef __cplusplus
5129  extern "C" {
5130 #endif
5131 
5132 static void *drfs__def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return DRFS_MZ_MALLOC(items * size); }
5133 static void drfs__def_free_func(void *opaque, void *address) { (void)opaque, (void)address; DRFS_MZ_FREE(address); }
5134 static void *drfs__def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return DRFS_MZ_REALLOC(address, items * size); }
5135 
5136 // ------------------- zlib-style API's
5137 
5138 drfs_mz_ulong drfs_mz_adler32(drfs_mz_ulong adler, const unsigned char *ptr, size_t buf_len)
5139 {
5140  drfs_drfs_mz_uint32 i, s1 = (drfs_drfs_mz_uint32)(adler & 0xffff), s2 = (drfs_drfs_mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552;
5141  if (!ptr) return DRFS_MZ_ADLER32_INIT;
5142  while (buf_len) {
5143  for (i = 0; i + 7 < block_len; i += 8, ptr += 8) {
5144  s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
5145  s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
5146  }
5147  for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1;
5148  s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
5149  }
5150  return (s2 << 16) + s1;
5151 }
5152 
5153 // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/
5154 drfs_mz_ulong drfs_mz_crc32(drfs_mz_ulong crc, const drfs_mz_uint8 *ptr, size_t buf_len)
5155 {
5156  static const drfs_drfs_mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
5157  0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
5158  drfs_drfs_mz_uint32 crcu32 = (drfs_drfs_mz_uint32)crc;
5159  if (!ptr) return DRFS_MZ_CRC32_INIT;
5160  crcu32 = ~crcu32; while (buf_len--) { drfs_mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; }
5161  return ~crcu32;
5162 }
5163 
5164 void drfs_mz_free(void *p)
5165 {
5166  DRFS_MZ_FREE(p);
5167 }
5168 
5169 
5170 // ------------------- Low-level Decompression (completely independent from all compression API's)
5171 
5172 #define DRFS_TINFL_MEMCPY(d, s, l) memcpy(d, s, l)
5173 #define DRFS_TINFL_MEMSET(p, c, l) memset(p, c, l)
5174 
5175 #define DRFS_TINFL_CR_BEGIN switch(r->m_state) { case 0:
5176 #define DRFS_TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } DRFS_MZ_MACRO_END
5177 #define DRFS_TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { DRFS_TINFL_CR_RETURN(state_index, result); } } DRFS_MZ_MACRO_END
5178 #define DRFS_TINFL_CR_FINISH }
5179 
5180 // TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never
5181 // reads ahead more than it needs to. Currently DRFS_TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario.
5182 #define DRFS_TINFL_GET_BYTE(state_index, c) do { \
5183  if (pIn_buf_cur >= pIn_buf_end) { \
5184  for ( ; ; ) { \
5185  if (decomp_flags & DRFS_TINFL_FLAG_HAS_MORE_INPUT) { \
5186  DRFS_TINFL_CR_RETURN(state_index, DRFS_TINFL_STATUS_NEEDS_MORE_INPUT); \
5187  if (pIn_buf_cur < pIn_buf_end) { \
5188  c = *pIn_buf_cur++; \
5189  break; \
5190  } \
5191  } else { \
5192  c = 0; \
5193  break; \
5194  } \
5195  } \
5196  } else c = *pIn_buf_cur++; } DRFS_MZ_MACRO_END
5197 
5198 #define DRFS_TINFL_NEED_BITS(state_index, n) do { drfs_mz_uint c; DRFS_TINFL_GET_BYTE(state_index, c); bit_buf |= (((drfs_tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (drfs_mz_uint)(n))
5199 #define DRFS_TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (drfs_mz_uint)(n)) { DRFS_TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } DRFS_MZ_MACRO_END
5200 #define DRFS_TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (drfs_mz_uint)(n)) { DRFS_TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } DRFS_MZ_MACRO_END
5201 
5202 // DRFS_TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2.
5203 // It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a
5204 // Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the
5205 // bit buffer contains >=15 bits (deflate's max. Huffman code size).
5206 #define DRFS_TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \
5207  do { \
5208  temp = (pHuff)->m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]; \
5209  if (temp >= 0) { \
5210  code_len = temp >> 9; \
5211  if ((code_len) && (num_bits >= code_len)) \
5212  break; \
5213  } else if (num_bits > DRFS_TINFL_FAST_LOOKUP_BITS) { \
5214  code_len = DRFS_TINFL_FAST_LOOKUP_BITS; \
5215  do { \
5216  temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
5217  } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \
5218  } DRFS_TINFL_GET_BYTE(state_index, c); bit_buf |= (((drfs_tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \
5219  } while (num_bits < 15);
5220 
5221 // DRFS_TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read
5222 // beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully
5223 // decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32.
5224 // The slow path is only executed at the very end of the input buffer.
5225 #define DRFS_TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \
5226  int temp; drfs_mz_uint code_len, c; \
5227  if (num_bits < 15) { \
5228  if ((pIn_buf_end - pIn_buf_cur) < 2) { \
5229  DRFS_TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \
5230  } else { \
5231  bit_buf |= (((drfs_tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((drfs_tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \
5232  } \
5233  } \
5234  if ((temp = (pHuff)->m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \
5235  code_len = temp >> 9, temp &= 511; \
5236  else { \
5237  code_len = DRFS_TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \
5238  } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } DRFS_MZ_MACRO_END
5239 
5240 drfs_tinfl_status drfs_tinfl_decompress(drfs_tinfl_decompressor *r, const drfs_mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, drfs_mz_uint8 *pOut_buf_start, drfs_mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const drfs_drfs_mz_uint32 decomp_flags)
5241 {
5242  static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 };
5243  static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };
5244  static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
5245  static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
5246  static const drfs_mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
5247  static const int s_min_table_sizes[3] = { 257, 1, 4 };
5248 
5249  drfs_tinfl_status status = DRFS_TINFL_STATUS_FAILED; drfs_drfs_mz_uint32 num_bits, dist, counter, num_extra; drfs_tinfl_bit_buf_t bit_buf;
5250  const drfs_mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size;
5251  drfs_mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size;
5252  size_t out_buf_size_mask = (decomp_flags & DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start;
5253 
5254  // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter).
5255  if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return DRFS_TINFL_STATUS_BAD_PARAM; }
5256 
5257  num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start;
5258  DRFS_TINFL_CR_BEGIN
5259 
5260  bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1;
5261  if (decomp_flags & DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER)
5262  {
5263  DRFS_TINFL_GET_BYTE(1, r->m_zhdr0); DRFS_TINFL_GET_BYTE(2, r->m_zhdr1);
5264  counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8));
5265  if (!(decomp_flags & DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4)))));
5266  if (counter) { DRFS_TINFL_CR_RETURN_FOREVER(36, DRFS_TINFL_STATUS_FAILED); }
5267  }
5268 
5269  do
5270  {
5271  DRFS_TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1;
5272  if (r->m_type == 0)
5273  {
5274  DRFS_TINFL_SKIP_BITS(5, num_bits & 7);
5275  for (counter = 0; counter < 4; ++counter) { if (num_bits) DRFS_TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else DRFS_TINFL_GET_BYTE(7, r->m_raw_header[counter]); }
5276  if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (drfs_mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { DRFS_TINFL_CR_RETURN_FOREVER(39, DRFS_TINFL_STATUS_FAILED); }
5277  while ((counter) && (num_bits))
5278  {
5279  DRFS_TINFL_GET_BITS(51, dist, 8);
5280  while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(52, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); }
5281  *pOut_buf_cur++ = (drfs_mz_uint8)dist;
5282  counter--;
5283  }
5284  while (counter)
5285  {
5286  size_t n; while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(9, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); }
5287  while (pIn_buf_cur >= pIn_buf_end)
5288  {
5289  if (decomp_flags & DRFS_TINFL_FLAG_HAS_MORE_INPUT)
5290  {
5291  DRFS_TINFL_CR_RETURN(38, DRFS_TINFL_STATUS_NEEDS_MORE_INPUT);
5292  }
5293  else
5294  {
5295  DRFS_TINFL_CR_RETURN_FOREVER(40, DRFS_TINFL_STATUS_FAILED);
5296  }
5297  }
5298  n = DRFS_MZ_MIN(DRFS_MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);
5299  DRFS_TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (drfs_mz_uint)n;
5300  }
5301  }
5302  else if (r->m_type == 3)
5303  {
5304  DRFS_TINFL_CR_RETURN_FOREVER(10, DRFS_TINFL_STATUS_FAILED);
5305  }
5306  else
5307  {
5308  if (r->m_type == 1)
5309  {
5310  drfs_mz_uint8 *p = r->m_tables[0].m_code_size; drfs_mz_uint i;
5311  r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; DRFS_TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32);
5312  for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8;
5313  }
5314  else
5315  {
5316  for (counter = 0; counter < 3; counter++) { DRFS_TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; }
5317  DRFS_MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { drfs_mz_uint s; DRFS_TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (drfs_mz_uint8)s; }
5318  r->m_table_sizes[2] = 19;
5319  }
5320  for ( ; (int)r->m_type >= 0; r->m_type--)
5321  {
5322  int tree_next, tree_cur; drfs_tinfl_huff_table *pTable;
5323  drfs_mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; DRFS_MZ_CLEAR_OBJ(total_syms); DRFS_MZ_CLEAR_OBJ(pTable->m_look_up); DRFS_MZ_CLEAR_OBJ(pTable->m_tree);
5324  for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++;
5325  used_syms = 0, total = 0; next_code[0] = next_code[1] = 0;
5326  for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); }
5327  if ((65536 != total) && (used_syms > 1))
5328  {
5329  DRFS_TINFL_CR_RETURN_FOREVER(35, DRFS_TINFL_STATUS_FAILED);
5330  }
5331  for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index)
5332  {
5333  drfs_mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue;
5334  cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1);
5335  if (code_size <= DRFS_TINFL_FAST_LOOKUP_BITS) { drfs_mz_int16 k = (drfs_mz_int16)((code_size << 9) | sym_index); while (rev_code < DRFS_TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; }
5336  if (0 == (tree_cur = pTable->m_look_up[rev_code & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)] = (drfs_mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; }
5337  rev_code >>= (DRFS_TINFL_FAST_LOOKUP_BITS - 1);
5338  for (j = code_size; j > (DRFS_TINFL_FAST_LOOKUP_BITS + 1); j--)
5339  {
5340  tree_cur -= ((rev_code >>= 1) & 1);
5341  if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (drfs_mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1];
5342  }
5343  tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (drfs_mz_int16)sym_index;
5344  }
5345  if (r->m_type == 2)
5346  {
5347  for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); )
5348  {
5349  drfs_mz_uint s; DRFS_TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (drfs_mz_uint8)dist; continue; }
5350  if ((dist == 16) && (!counter))
5351  {
5352  DRFS_TINFL_CR_RETURN_FOREVER(17, DRFS_TINFL_STATUS_FAILED);
5353  }
5354  num_extra = "\02\03\07"[dist - 16]; DRFS_TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16];
5355  DRFS_TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s;
5356  }
5357  if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)
5358  {
5359  DRFS_TINFL_CR_RETURN_FOREVER(21, DRFS_TINFL_STATUS_FAILED);
5360  }
5361  DRFS_TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); DRFS_TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]);
5362  }
5363  }
5364  for ( ; ; )
5365  {
5366  drfs_mz_uint8 *pSrc;
5367  for ( ; ; )
5368  {
5369  if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))
5370  {
5371  DRFS_TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]);
5372  if (counter >= 256)
5373  break;
5374  while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(24, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); }
5375  *pOut_buf_cur++ = (drfs_mz_uint8)counter;
5376  }
5377  else
5378  {
5379  int sym2; drfs_mz_uint code_len;
5380 #if DRFS_TINFL_USE_64BIT_BITBUF
5381  if (num_bits < 30) { bit_buf |= (((drfs_tinfl_bit_buf_t)DRFS_MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; }
5382 #else
5383  if (num_bits < 15) { bit_buf |= (((drfs_tinfl_bit_buf_t)DRFS_MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
5384 #endif
5385  if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
5386  code_len = sym2 >> 9;
5387  else
5388  {
5389  code_len = DRFS_TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0);
5390  }
5391  counter = sym2; bit_buf >>= code_len; num_bits -= code_len;
5392  if (counter & 256)
5393  break;
5394 
5395 #if !DRFS_TINFL_USE_64BIT_BITBUF
5396  if (num_bits < 15) { bit_buf |= (((drfs_tinfl_bit_buf_t)DRFS_MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
5397 #endif
5398  if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
5399  code_len = sym2 >> 9;
5400  else
5401  {
5402  code_len = DRFS_TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0);
5403  }
5404  bit_buf >>= code_len; num_bits -= code_len;
5405 
5406  pOut_buf_cur[0] = (drfs_mz_uint8)counter;
5407  if (sym2 & 256)
5408  {
5409  pOut_buf_cur++;
5410  counter = sym2;
5411  break;
5412  }
5413  pOut_buf_cur[1] = (drfs_mz_uint8)sym2;
5414  pOut_buf_cur += 2;
5415  }
5416  }
5417  if ((counter &= 511) == 256) break;
5418 
5419  num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257];
5420  if (num_extra) { drfs_mz_uint extra_bits; DRFS_TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; }
5421 
5422  DRFS_TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]);
5423  num_extra = s_dist_extra[dist]; dist = s_dist_base[dist];
5424  if (num_extra) { drfs_mz_uint extra_bits; DRFS_TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; }
5425 
5426  dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start;
5427  if ((dist > dist_from_out_buf_start) && (decomp_flags & DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
5428  {
5429  DRFS_TINFL_CR_RETURN_FOREVER(37, DRFS_TINFL_STATUS_FAILED);
5430  }
5431 
5432  pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);
5433 
5434  if ((DRFS_MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)
5435  {
5436  while (counter--)
5437  {
5438  while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(53, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); }
5439  *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];
5440  }
5441  continue;
5442  }
5443 #if DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES
5444  else if ((counter >= 9) && (counter <= dist))
5445  {
5446  const drfs_mz_uint8 *pSrc_end = pSrc + (counter & ~7);
5447  do
5448  {
5449  ((drfs_drfs_mz_uint32 *)pOut_buf_cur)[0] = ((const drfs_drfs_mz_uint32 *)pSrc)[0];
5450  ((drfs_drfs_mz_uint32 *)pOut_buf_cur)[1] = ((const drfs_drfs_mz_uint32 *)pSrc)[1];
5451  pOut_buf_cur += 8;
5452  } while ((pSrc += 8) < pSrc_end);
5453  if ((counter &= 7) < 3)
5454  {
5455  if (counter)
5456  {
5457  pOut_buf_cur[0] = pSrc[0];
5458  if (counter > 1)
5459  pOut_buf_cur[1] = pSrc[1];
5460  pOut_buf_cur += counter;
5461  }
5462  continue;
5463  }
5464  }
5465 #endif
5466  do
5467  {
5468  pOut_buf_cur[0] = pSrc[0];
5469  pOut_buf_cur[1] = pSrc[1];
5470  pOut_buf_cur[2] = pSrc[2];
5471  pOut_buf_cur += 3; pSrc += 3;
5472  } while ((int)(counter -= 3) > 2);
5473  if ((int)counter > 0)
5474  {
5475  pOut_buf_cur[0] = pSrc[0];
5476  if ((int)counter > 1)
5477  pOut_buf_cur[1] = pSrc[1];
5478  pOut_buf_cur += counter;
5479  }
5480  }
5481  }
5482  } while (!(r->m_final & 1));
5483  if (decomp_flags & DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER)
5484  {
5485  DRFS_TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { drfs_mz_uint s; if (num_bits) DRFS_TINFL_GET_BITS(41, s, 8); else DRFS_TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; }
5486  }
5487  DRFS_TINFL_CR_RETURN_FOREVER(34, DRFS_TINFL_STATUS_DONE);
5488  DRFS_TINFL_CR_FINISH
5489 
5490 common_exit:
5491  r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start;
5492  *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next;
5493  if ((decomp_flags & (DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER | DRFS_TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))
5494  {
5495  const drfs_mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size;
5496  drfs_drfs_mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552;
5497  while (buf_len)
5498  {
5499  for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
5500  {
5501  s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
5502  s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
5503  }
5504  for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1;
5505  s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
5506  }
5507  r->m_check_adler32 = (s2 << 16) + s1; if ((status == DRFS_TINFL_STATUS_DONE) && (decomp_flags & DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = DRFS_TINFL_STATUS_ADLER32_MISMATCH;
5508  }
5509  return status;
5510 }
5511 
5512 // Higher level helper functions.
5513 void *drfs_tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
5514 {
5515  drfs_tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0;
5516  *pOut_len = 0;
5517  drfs_tinfl_init(&decomp);
5518  for ( ; ; )
5519  {
5520  size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity;
5521  drfs_tinfl_status status = drfs_tinfl_decompress(&decomp, (const drfs_mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (drfs_mz_uint8*)pBuf, pBuf ? (drfs_mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size,
5522  (flags & ~DRFS_TINFL_FLAG_HAS_MORE_INPUT) | DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
5523  if ((status < 0) || (status == DRFS_TINFL_STATUS_NEEDS_MORE_INPUT))
5524  {
5525  DRFS_MZ_FREE(pBuf); *pOut_len = 0; return NULL;
5526  }
5527  src_buf_ofs += src_buf_size;
5528  *pOut_len += dst_buf_size;
5529  if (status == DRFS_TINFL_STATUS_DONE) break;
5530  new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128;
5531  pNew_buf = DRFS_MZ_REALLOC(pBuf, new_out_buf_capacity);
5532  if (!pNew_buf)
5533  {
5534  DRFS_MZ_FREE(pBuf); *pOut_len = 0; return NULL;
5535  }
5536  pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity;
5537  }
5538  return pBuf;
5539 }
5540 
5541 size_t drfs_tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
5542 {
5543  drfs_tinfl_decompressor decomp; drfs_tinfl_status status; drfs_tinfl_init(&decomp);
5544  status = drfs_tinfl_decompress(&decomp, (const drfs_mz_uint8*)pSrc_buf, &src_buf_len, (drfs_mz_uint8*)pOut_buf, (drfs_mz_uint8*)pOut_buf, &out_buf_len, (flags & ~DRFS_TINFL_FLAG_HAS_MORE_INPUT) | DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
5545  return (status != DRFS_TINFL_STATUS_DONE) ? DRFS_TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len;
5546 }
5547 
5548 int drfs_tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
5549 {
5550  int result = 0;
5551  drfs_tinfl_decompressor decomp;
5552  drfs_mz_uint8 *pDict = (drfs_mz_uint8*)DRFS_MZ_MALLOC(DRFS_TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0;
5553  if (!pDict)
5554  return DRFS_TINFL_STATUS_FAILED;
5555  drfs_tinfl_init(&decomp);
5556  for ( ; ; )
5557  {
5558  size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = DRFS_TINFL_LZ_DICT_SIZE - dict_ofs;
5559  drfs_tinfl_status status = drfs_tinfl_decompress(&decomp, (const drfs_mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size,
5560  (flags & ~(DRFS_TINFL_FLAG_HAS_MORE_INPUT | DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)));
5561  in_buf_ofs += in_buf_size;
5562  if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user)))
5563  break;
5564  if (status != DRFS_TINFL_STATUS_HAS_MORE_OUTPUT)
5565  {
5566  result = (status == DRFS_TINFL_STATUS_DONE);
5567  break;
5568  }
5569  dict_ofs = (dict_ofs + dst_buf_size) & (DRFS_TINFL_LZ_DICT_SIZE - 1);
5570  }
5571  DRFS_MZ_FREE(pDict);
5572  *pIn_buf_size = in_buf_ofs;
5573  return result;
5574 }
5575 
5576 
5577 // ------------------- .ZIP archive reading
5578 
5579 #ifndef DRFS_MINIZ_NO_ARCHIVE_APIS
5580 
5581 #define DRFS_MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
5582 
5583 // Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff.
5584 enum
5585 {
5586  // ZIP archive identifiers and record sizes
5587  DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50,
5588  DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,
5589  // Central directory header record offsets
5590  DRFS_MZ_ZIP_CDH_SIG_OFS = 0, DRFS_MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, DRFS_MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, DRFS_MZ_ZIP_CDH_BIT_FLAG_OFS = 8,
5591  DRFS_MZ_ZIP_CDH_METHOD_OFS = 10, DRFS_MZ_ZIP_CDH_FILE_TIME_OFS = 12, DRFS_MZ_ZIP_CDH_FILE_DATE_OFS = 14, DRFS_MZ_ZIP_CDH_CRC32_OFS = 16,
5592  DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS = 30,
5593  DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, DRFS_MZ_ZIP_CDH_DISK_START_OFS = 34, DRFS_MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, DRFS_MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, DRFS_MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42,
5594  // Local directory header offsets
5595  DRFS_MZ_ZIP_LDH_SIG_OFS = 0, DRFS_MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, DRFS_MZ_ZIP_LDH_BIT_FLAG_OFS = 6, DRFS_MZ_ZIP_LDH_METHOD_OFS = 8, DRFS_MZ_ZIP_LDH_FILE_TIME_OFS = 10,
5596  DRFS_MZ_ZIP_LDH_FILE_DATE_OFS = 12, DRFS_MZ_ZIP_LDH_CRC32_OFS = 14, DRFS_MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, DRFS_MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22,
5597  DRFS_MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, DRFS_MZ_ZIP_LDH_EXTRA_LEN_OFS = 28,
5598  // End of central directory offsets
5599  DRFS_MZ_ZIP_ECDH_SIG_OFS = 0, DRFS_MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, DRFS_MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, DRFS_MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8,
5600  DRFS_MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, DRFS_MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, DRFS_MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, DRFS_MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,
5601 };
5602 
5603 typedef struct
5604 {
5605  void *m_p;
5606  size_t m_size, m_capacity;
5607  drfs_mz_uint m_element_size;
5608 } drfs_mz_zip_array;
5609 
5610 struct drfs_mz_zip_internal_state_tag
5611 {
5612  drfs_mz_zip_array m_central_dir;
5613  drfs_mz_zip_array m_central_dir_offsets;
5614  drfs_mz_zip_array m_sorted_central_dir_offsets;
5615  void* *m_pFile;
5616  void *m_pMem;
5617  size_t m_mem_size;
5618  size_t m_mem_capacity;
5619 };
5620 
5621 #define DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size
5622 #define DRFS_MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index]
5623 
5624 static DRFS_MZ_FORCEINLINE void drfs_mz_zip_array_clear(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray)
5625 {
5626  pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p);
5627  memset(pArray, 0, sizeof(drfs_mz_zip_array));
5628 }
5629 
5630 static drfs_mz_bool drfs_mz_zip_array_ensure_capacity(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t min_new_capacity, drfs_mz_uint growing)
5631 {
5632  void *pNew_p; size_t new_capacity = min_new_capacity; DRFS_MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return DRFS_MZ_TRUE;
5633  if (growing) { new_capacity = DRFS_MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; }
5634  if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return DRFS_MZ_FALSE;
5635  pArray->m_p = pNew_p; pArray->m_capacity = new_capacity;
5636  return DRFS_MZ_TRUE;
5637 }
5638 
5639 static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_reserve(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t new_capacity, drfs_mz_uint growing)
5640 {
5641  if (new_capacity > pArray->m_capacity) { if (!drfs_mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return DRFS_MZ_FALSE; }
5642  return DRFS_MZ_TRUE;
5643 }
5644 
5645 static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_resize(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t new_size, drfs_mz_uint growing)
5646 {
5647  if (new_size > pArray->m_capacity) { if (!drfs_mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return DRFS_MZ_FALSE; }
5648  pArray->m_size = new_size;
5649  return DRFS_MZ_TRUE;
5650 }
5651 
5652 static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_ensure_room(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t n)
5653 {
5654  return drfs_mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, DRFS_MZ_TRUE);
5655 }
5656 
5657 static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_push_back(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, const void *pElements, size_t n)
5658 {
5659  size_t orig_size = pArray->m_size; if (!drfs_mz_zip_array_resize(pZip, pArray, orig_size + n, DRFS_MZ_TRUE)) return DRFS_MZ_FALSE;
5660  memcpy((drfs_mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size);
5661  return DRFS_MZ_TRUE;
5662 }
5663 
5664 #ifndef DRFS_MINIZ_NO_TIME
5665 static time_t drfs_mz_zip_dos_to_time_t(int dos_time, int dos_date)
5666 {
5667  struct tm tm;
5668  memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1;
5669  tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31;
5670  tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62;
5671  return mktime(&tm);
5672 }
5673 #endif
5674 
5675 static drfs_mz_bool drfs_mz_zip_reader_init_internal(drfs_mz_zip_archive *pZip, drfs_drfs_mz_uint32 flags)
5676 {
5677  (void)flags;
5678  if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_INVALID))
5679  return DRFS_MZ_FALSE;
5680 
5681  if (!pZip->m_pAlloc) pZip->m_pAlloc = drfs__def_alloc_func;
5682  if (!pZip->m_pFree) pZip->m_pFree = drfs__def_free_func;
5683  if (!pZip->m_pRealloc) pZip->m_pRealloc = drfs__def_realloc_func;
5684 
5685  pZip->m_zip_mode = DRFS_MZ_ZIP_MODE_READING;
5686  pZip->m_archive_size = 0;
5687  pZip->m_central_directory_file_ofs = 0;
5688  pZip->m_total_files = 0;
5689 
5690  if (NULL == (pZip->m_pState = (drfs_mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(drfs_mz_zip_internal_state))))
5691  return DRFS_MZ_FALSE;
5692  memset(pZip->m_pState, 0, sizeof(drfs_mz_zip_internal_state));
5693  DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(drfs_mz_uint8));
5694  DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(drfs_drfs_mz_uint32));
5695  DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(drfs_drfs_mz_uint32));
5696  return DRFS_MZ_TRUE;
5697 }
5698 
5699 static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_reader_filename_less(const drfs_mz_zip_array *pCentral_dir_array, const drfs_mz_zip_array *pCentral_dir_offsets, drfs_mz_uint l_index, drfs_mz_uint r_index)
5700 {
5701  const drfs_mz_uint8 *pL = &DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, drfs_drfs_mz_uint32, l_index)), *pE;
5702  const drfs_mz_uint8 *pR = &DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, drfs_drfs_mz_uint32, r_index));
5703  drfs_mz_uint l_len = DRFS_MZ_READ_LE16(pL + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = DRFS_MZ_READ_LE16(pR + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS);
5704  drfs_mz_uint8 l = 0, r = 0;
5705  pL += DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
5706  pE = pL + DRFS_MZ_MIN(l_len, r_len);
5707  while (pL < pE)
5708  {
5709  if ((l = DRFS_MZ_TOLOWER(*pL)) != (r = DRFS_MZ_TOLOWER(*pR)))
5710  break;
5711  pL++; pR++;
5712  }
5713  return (pL == pE) ? (l_len < r_len) : (l < r);
5714 }
5715 
5716 #define DRFS_MZ_SWAP_UINT32(a, b) do { drfs_drfs_mz_uint32 t = a; a = b; b = t; } DRFS_MZ_MACRO_END
5717 
5718 // Heap sort of lowercased filenames, used to help accelerate plain central directory searches by drfs_mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.)
5719 static void drfs_mz_zip_reader_sort_central_dir_offsets_by_filename(drfs_mz_zip_archive *pZip)
5720 {
5721  drfs_mz_zip_internal_state *pState = pZip->m_pState;
5722  const drfs_mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
5723  const drfs_mz_zip_array *pCentral_dir = &pState->m_central_dir;
5724  drfs_drfs_mz_uint32 *pIndices = &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, drfs_drfs_mz_uint32, 0);
5725  const int size = pZip->m_total_files;
5726  int start = (size - 2) >> 1, end;
5727  while (start >= 0)
5728  {
5729  int child, root = start;
5730  for ( ; ; )
5731  {
5732  if ((child = (root << 1) + 1) >= size)
5733  break;
5734  child += (((child + 1) < size) && (drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])));
5735  if (!drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
5736  break;
5737  DRFS_MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child;
5738  }
5739  start--;
5740  }
5741 
5742  end = size - 1;
5743  while (end > 0)
5744  {
5745  int child, root = 0;
5746  DRFS_MZ_SWAP_UINT32(pIndices[end], pIndices[0]);
5747  for ( ; ; )
5748  {
5749  if ((child = (root << 1) + 1) >= end)
5750  break;
5751  child += (((child + 1) < end) && drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]));
5752  if (!drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
5753  break;
5754  DRFS_MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child;
5755  }
5756  end--;
5757  }
5758 }
5759 
5760 static drfs_mz_bool drfs_mz_zip_reader_read_central_dir(drfs_mz_zip_archive *pZip, drfs_drfs_mz_uint32 flags)
5761 {
5762  drfs_mz_uint cdir_size, num_this_disk, cdir_disk_index;
5763  drfs_mz_uint64 cdir_ofs;
5764  drfs_mz_int64 cur_file_ofs;
5765  const drfs_mz_uint8 *p;
5766  drfs_drfs_mz_uint32 buf_u32[4096 / sizeof(drfs_drfs_mz_uint32)]; drfs_mz_uint8 *pBuf = (drfs_mz_uint8 *)buf_u32;
5767  drfs_mz_bool sort_central_dir = ((flags & DRFS_MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
5768  // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there.
5769  if (pZip->m_archive_size < DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
5770  return DRFS_MZ_FALSE;
5771  // Find the end of central directory record by scanning the file from the end towards the beginning.
5772  cur_file_ofs = DRFS_MZ_MAX((drfs_mz_int64)pZip->m_archive_size - (drfs_mz_int64)sizeof(buf_u32), 0);
5773  for ( ; ; )
5774  {
5775  int i, n = (int)DRFS_MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);
5776  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (drfs_mz_uint)n)
5777  return DRFS_MZ_FALSE;
5778  for (i = n - 4; i >= 0; --i)
5779  if (DRFS_MZ_READ_LE32(pBuf + i) == DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
5780  break;
5781  if (i >= 0)
5782  {
5783  cur_file_ofs += i;
5784  break;
5785  }
5786  if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))
5787  return DRFS_MZ_FALSE;
5788  cur_file_ofs = DRFS_MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);
5789  }
5790  // Read and verify the end of central directory record.
5791  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
5792  return DRFS_MZ_FALSE;
5793  if ((DRFS_MZ_READ_LE32(pBuf + DRFS_MZ_ZIP_ECDH_SIG_OFS) != DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) ||
5794  ((pZip->m_total_files = DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS)))
5795  return DRFS_MZ_FALSE;
5796 
5797  num_this_disk = DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
5798  cdir_disk_index = DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);
5799  if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1)))
5800  return DRFS_MZ_FALSE;
5801 
5802  if ((cdir_size = DRFS_MZ_READ_LE32(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
5803  return DRFS_MZ_FALSE;
5804 
5805  cdir_ofs = DRFS_MZ_READ_LE32(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_OFS_OFS);
5806  if ((cdir_ofs + (drfs_mz_uint64)cdir_size) > pZip->m_archive_size)
5807  return DRFS_MZ_FALSE;
5808 
5809  pZip->m_central_directory_file_ofs = cdir_ofs;
5810 
5811  if (pZip->m_total_files)
5812  {
5813  drfs_mz_uint i, n;
5814 
5815  // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices.
5816  if ((!drfs_mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, DRFS_MZ_FALSE)) ||
5817  (!drfs_mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, DRFS_MZ_FALSE)))
5818  return DRFS_MZ_FALSE;
5819 
5820  if (sort_central_dir)
5821  {
5822  if (!drfs_mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, DRFS_MZ_FALSE))
5823  return DRFS_MZ_FALSE;
5824  }
5825 
5826  if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size)
5827  return DRFS_MZ_FALSE;
5828 
5829  // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported).
5830  p = (const drfs_mz_uint8 *)pZip->m_pState->m_central_dir.m_p;
5831  for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i)
5832  {
5833  drfs_mz_uint total_header_size, comp_size, decomp_size, disk_index;
5834  if ((n < DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (DRFS_MZ_READ_LE32(p) != DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIG))
5835  return DRFS_MZ_FALSE;
5836  DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, i) = (drfs_drfs_mz_uint32)(p - (const drfs_mz_uint8 *)pZip->m_pState->m_central_dir.m_p);
5837  if (sort_central_dir)
5838  DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, drfs_drfs_mz_uint32, i) = i;
5839  comp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
5840  decomp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
5841  if (((!DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF))
5842  return DRFS_MZ_FALSE;
5843  disk_index = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_DISK_START_OFS);
5844  if ((disk_index != num_this_disk) && (disk_index != 1))
5845  return DRFS_MZ_FALSE;
5846  if (((drfs_mz_uint64)DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_LOCAL_HEADER_OFS) + DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
5847  return DRFS_MZ_FALSE;
5848  if ((total_header_size = DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS) + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS) + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n)
5849  return DRFS_MZ_FALSE;
5850  n -= total_header_size; p += total_header_size;
5851  }
5852  }
5853 
5854  if (sort_central_dir)
5855  drfs_mz_zip_reader_sort_central_dir_offsets_by_filename(pZip);
5856 
5857  return DRFS_MZ_TRUE;
5858 }
5859 
5860 drfs_mz_bool drfs_mz_zip_reader_init(drfs_mz_zip_archive *pZip, drfs_mz_uint64 size, drfs_drfs_mz_uint32 flags)
5861 {
5862  if ((!pZip) || (!pZip->m_pRead))
5863  return DRFS_MZ_FALSE;
5864  if (!drfs_mz_zip_reader_init_internal(pZip, flags))
5865  return DRFS_MZ_FALSE;
5866  pZip->m_archive_size = size;
5867  if (!drfs_mz_zip_reader_read_central_dir(pZip, flags))
5868  {
5869  drfs_mz_zip_reader_end(pZip);
5870  return DRFS_MZ_FALSE;
5871  }
5872  return DRFS_MZ_TRUE;
5873 }
5874 
5875 drfs_mz_uint drfs_mz_zip_reader_get_num_files(drfs_mz_zip_archive *pZip)
5876 {
5877  return pZip ? pZip->m_total_files : 0;
5878 }
5879 
5880 static DRFS_MZ_FORCEINLINE const drfs_mz_uint8 *drfs_mz_zip_reader_get_cdh(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index)
5881 {
5882  if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_READING))
5883  return NULL;
5884  return &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, file_index));
5885 }
5886 
5887 drfs_mz_bool drfs_mz_zip_reader_is_file_a_directory(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index)
5888 {
5889  drfs_mz_uint filename_len, external_attr;
5890  const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index);
5891  if (!p)
5892  return DRFS_MZ_FALSE;
5893 
5894  // First see if the filename ends with a '/' character.
5895  filename_len = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS);
5896  if (filename_len)
5897  {
5898  if (*(p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/')
5899  return DRFS_MZ_TRUE;
5900  }
5901 
5902  // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct.
5903  // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field.
5904  // FIXME: Remove this check? Is it necessary - we already check the filename.
5905  external_attr = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
5906  if ((external_attr & 0x10) != 0)
5907  return DRFS_MZ_TRUE;
5908 
5909  return DRFS_MZ_FALSE;
5910 }
5911 
5912 drfs_mz_bool drfs_mz_zip_reader_file_stat(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, drfs_drfs_mz_zip_archive_file_stat *pStat)
5913 {
5914  drfs_mz_uint n;
5915  const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index);
5916  if ((!p) || (!pStat))
5917  return DRFS_MZ_FALSE;
5918 
5919  // Unpack the central directory record.
5920  pStat->m_file_index = file_index;
5921  pStat->m_central_dir_ofs = DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, file_index);
5922  pStat->m_version_made_by = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_VERSION_MADE_BY_OFS);
5923  pStat->m_version_needed = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_VERSION_NEEDED_OFS);
5924  pStat->m_bit_flag = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_BIT_FLAG_OFS);
5925  pStat->m_method = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_METHOD_OFS);
5926 #ifndef DRFS_MINIZ_NO_TIME
5927  pStat->m_time = drfs_mz_zip_dos_to_time_t(DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILE_TIME_OFS), DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILE_DATE_OFS));
5928 #endif
5929  pStat->m_crc32 = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_CRC32_OFS);
5930  pStat->m_comp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
5931  pStat->m_uncomp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
5932  pStat->m_internal_attr = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_INTERNAL_ATTR_OFS);
5933  pStat->m_external_attr = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
5934  pStat->m_local_header_ofs = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_LOCAL_HEADER_OFS);
5935 
5936  // Copy as much of the filename and comment as possible.
5937  n = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS); n = DRFS_MZ_MIN(n, DRFS_MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1);
5938  memcpy(pStat->m_filename, p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0';
5939 
5940  n = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS); n = DRFS_MZ_MIN(n, DRFS_MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1);
5941  pStat->m_comment_size = n;
5942  memcpy(pStat->m_comment, p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS) + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0';
5943 
5944  return DRFS_MZ_TRUE;
5945 }
5946 
5947 drfs_mz_uint drfs_mz_zip_reader_get_filename(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, char *pFilename, drfs_mz_uint filename_buf_size)
5948 {
5949  drfs_mz_uint n;
5950  const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index);
5951  if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; }
5952  n = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS);
5953  if (filename_buf_size)
5954  {
5955  n = DRFS_MZ_MIN(n, filename_buf_size - 1);
5956  memcpy(pFilename, p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
5957  pFilename[n] = '\0';
5958  }
5959  return n + 1;
5960 }
5961 
5962 static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_reader_string_equal(const char *pA, const char *pB, drfs_mz_uint len, drfs_mz_uint flags)
5963 {
5964  drfs_mz_uint i;
5965  if (flags & DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE)
5966  return 0 == memcmp(pA, pB, len);
5967  for (i = 0; i < len; ++i)
5968  if (DRFS_MZ_TOLOWER(pA[i]) != DRFS_MZ_TOLOWER(pB[i]))
5969  return DRFS_MZ_FALSE;
5970  return DRFS_MZ_TRUE;
5971 }
5972 
5973 static DRFS_MZ_FORCEINLINE int drfs_mz_zip_reader_filename_compare(const drfs_mz_zip_array *pCentral_dir_array, const drfs_mz_zip_array *pCentral_dir_offsets, drfs_mz_uint l_index, const char *pR, drfs_mz_uint r_len)
5974 {
5975  const drfs_mz_uint8 *pL = &DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, drfs_drfs_mz_uint32, l_index)), *pE;
5976  drfs_mz_uint l_len = DRFS_MZ_READ_LE16(pL + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS);
5977  drfs_mz_uint8 l = 0, r = 0;
5978  pL += DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
5979  pE = pL + DRFS_MZ_MIN(l_len, r_len);
5980  while (pL < pE)
5981  {
5982  if ((l = DRFS_MZ_TOLOWER(*pL)) != (r = DRFS_MZ_TOLOWER(*pR)))
5983  break;
5984  pL++; pR++;
5985  }
5986  return (pL == pE) ? (int)(l_len - r_len) : (l - r);
5987 }
5988 
5989 static int drfs_mz_zip_reader_locate_file_binary_search(drfs_mz_zip_archive *pZip, const char *pFilename)
5990 {
5991  drfs_mz_zip_internal_state *pState = pZip->m_pState;
5992  const drfs_mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
5993  const drfs_mz_zip_array *pCentral_dir = &pState->m_central_dir;
5994  drfs_drfs_mz_uint32 *pIndices = &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, drfs_drfs_mz_uint32, 0);
5995  const int size = pZip->m_total_files;
5996  const drfs_mz_uint filename_len = (drfs_mz_uint)strlen(pFilename);
5997  int l = 0, h = size - 1;
5998  while (l <= h)
5999  {
6000  int m = (l + h) >> 1, file_index = pIndices[m], comp = drfs_mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len);
6001  if (!comp)
6002  return file_index;
6003  else if (comp < 0)
6004  l = m + 1;
6005  else
6006  h = m - 1;
6007  }
6008  return -1;
6009 }
6010 
6011 int drfs_mz_zip_reader_locate_file(drfs_mz_zip_archive *pZip, const char *pName, const char *pComment, drfs_mz_uint flags)
6012 {
6013  drfs_mz_uint file_index; size_t name_len, comment_len;
6014  if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_READING))
6015  return -1;
6016  if (((flags & (DRFS_MZ_ZIP_FLAG_IGNORE_PATH | DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size))
6017  return drfs_mz_zip_reader_locate_file_binary_search(pZip, pName);
6018  name_len = strlen(pName); if (name_len > 0xFFFF) return -1;
6019  comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1;
6020  for (file_index = 0; file_index < pZip->m_total_files; file_index++)
6021  {
6022  const drfs_mz_uint8 *pHeader = &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, file_index));
6023  drfs_mz_uint filename_len = DRFS_MZ_READ_LE16(pHeader + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS);
6024  const char *pFilename = (const char *)pHeader + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
6025  if (filename_len < name_len)
6026  continue;
6027  if (comment_len)
6028  {
6029  drfs_mz_uint file_extra_len = DRFS_MZ_READ_LE16(pHeader + DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = DRFS_MZ_READ_LE16(pHeader + DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS);
6030  const char *pFile_comment = pFilename + filename_len + file_extra_len;
6031  if ((file_comment_len != comment_len) || (!drfs_mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags)))
6032  continue;
6033  }
6034  if ((flags & DRFS_MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len))
6035  {
6036  int ofs = filename_len - 1;
6037  do
6038  {
6039  if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':'))
6040  break;
6041  } while (--ofs >= 0);
6042  ofs++;
6043  pFilename += ofs; filename_len -= ofs;
6044  }
6045  if ((filename_len == name_len) && (drfs_mz_zip_reader_string_equal(pName, pFilename, filename_len, flags)))
6046  return file_index;
6047  }
6048  return -1;
6049 }
6050 
6051 drfs_mz_bool drfs_mz_zip_reader_extract_to_mem_no_alloc(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
6052 {
6053  int status = DRFS_TINFL_STATUS_DONE;
6054  drfs_mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail;
6055  drfs_drfs_mz_zip_archive_file_stat file_stat;
6056  void *pRead_buf;
6057  drfs_drfs_mz_uint32 local_header_u32[(DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(drfs_drfs_mz_uint32) - 1) / sizeof(drfs_drfs_mz_uint32)]; drfs_mz_uint8 *pLocal_header = (drfs_mz_uint8 *)local_header_u32;
6058  drfs_tinfl_decompressor inflator;
6059 
6060  if ((buf_size) && (!pBuf))
6061  return DRFS_MZ_FALSE;
6062 
6063  if (!drfs_mz_zip_reader_file_stat(pZip, file_index, &file_stat))
6064  return DRFS_MZ_FALSE;
6065 
6066  // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes)
6067  if (!file_stat.m_comp_size)
6068  return DRFS_MZ_TRUE;
6069 
6070  // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers).
6071  // I'm torn how to handle this case - should it fail instead?
6072  if (drfs_mz_zip_reader_is_file_a_directory(pZip, file_index))
6073  return DRFS_MZ_TRUE;
6074 
6075  // Encryption and patch files are not supported.
6076  if (file_stat.m_bit_flag & (1 | 32))
6077  return DRFS_MZ_FALSE;
6078 
6079  // This function only supports stored and deflate.
6080  if ((!(flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != DRFS_MZ_DEFLATED))
6081  return DRFS_MZ_FALSE;
6082 
6083  // Ensure supplied output buffer is large enough.
6084  needed_size = (flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;
6085  if (buf_size < needed_size)
6086  return DRFS_MZ_FALSE;
6087 
6088  // Read and parse the local directory entry.
6089  cur_file_ofs = file_stat.m_local_header_ofs;
6090  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
6091  return DRFS_MZ_FALSE;
6092  if (DRFS_MZ_READ_LE32(pLocal_header) != DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIG)
6093  return DRFS_MZ_FALSE;
6094 
6095  cur_file_ofs += DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE + DRFS_MZ_READ_LE16(pLocal_header + DRFS_MZ_ZIP_LDH_FILENAME_LEN_OFS) + DRFS_MZ_READ_LE16(pLocal_header + DRFS_MZ_ZIP_LDH_EXTRA_LEN_OFS);
6096  if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
6097  return DRFS_MZ_FALSE;
6098 
6099  if ((flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
6100  {
6101  // The file is stored or the caller has requested the compressed data.
6102  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size)
6103  return DRFS_MZ_FALSE;
6104  return ((flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (drfs_mz_crc32(DRFS_MZ_CRC32_INIT, (const drfs_mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32);
6105  }
6106 
6107  // Decompress the file either directly from memory or from a file input buffer.
6108  drfs_tinfl_init(&inflator);
6109 
6110  if (pZip->m_pState->m_pMem)
6111  {
6112  // Read directly from the archive in memory.
6113  pRead_buf = (drfs_mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
6114  read_buf_size = read_buf_avail = file_stat.m_comp_size;
6115  comp_remaining = 0;
6116  }
6117  else if (pUser_read_buf)
6118  {
6119  // Use a user provided read buffer.
6120  if (!user_read_buf_size)
6121  return DRFS_MZ_FALSE;
6122  pRead_buf = (drfs_mz_uint8 *)pUser_read_buf;
6123  read_buf_size = user_read_buf_size;
6124  read_buf_avail = 0;
6125  comp_remaining = file_stat.m_comp_size;
6126  }
6127  else
6128  {
6129  // Temporarily allocate a read buffer.
6130  read_buf_size = DRFS_MZ_MIN(file_stat.m_comp_size, DRFS_MZ_ZIP_MAX_IO_BUF_SIZE);
6131 #ifdef _MSC_VER
6132  if (((0, sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
6133 #else
6134  if (((sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
6135 #endif
6136  return DRFS_MZ_FALSE;
6137  if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
6138  return DRFS_MZ_FALSE;
6139  read_buf_avail = 0;
6140  comp_remaining = file_stat.m_comp_size;
6141  }
6142 
6143  do
6144  {
6145  size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs);
6146  if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
6147  {
6148  read_buf_avail = DRFS_MZ_MIN(read_buf_size, comp_remaining);
6149  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
6150  {
6151  status = DRFS_TINFL_STATUS_FAILED;
6152  break;
6153  }
6154  cur_file_ofs += read_buf_avail;
6155  comp_remaining -= read_buf_avail;
6156  read_buf_ofs = 0;
6157  }
6158  in_buf_size = (size_t)read_buf_avail;
6159  status = drfs_tinfl_decompress(&inflator, (drfs_mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (drfs_mz_uint8 *)pBuf, (drfs_mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? DRFS_TINFL_FLAG_HAS_MORE_INPUT : 0));
6160  read_buf_avail -= in_buf_size;
6161  read_buf_ofs += in_buf_size;
6162  out_buf_ofs += out_buf_size;
6163  } while (status == DRFS_TINFL_STATUS_NEEDS_MORE_INPUT);
6164 
6165  if (status == DRFS_TINFL_STATUS_DONE)
6166  {
6167  // Make sure the entire file was decompressed, and check its CRC.
6168  if ((out_buf_ofs != file_stat.m_uncomp_size) || (drfs_mz_crc32(DRFS_MZ_CRC32_INIT, (const drfs_mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32))
6169  status = DRFS_TINFL_STATUS_FAILED;
6170  }
6171 
6172  if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf))
6173  pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
6174 
6175  return status == DRFS_TINFL_STATUS_DONE;
6176 }
6177 
6178 drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem_no_alloc(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
6179 {
6180  int file_index = drfs_mz_zip_reader_locate_file(pZip, pFilename, NULL, flags);
6181  if (file_index < 0)
6182  return DRFS_MZ_FALSE;
6183  return drfs_mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size);
6184 }
6185 
6186 drfs_mz_bool drfs_mz_zip_reader_extract_to_mem(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags)
6187 {
6188  return drfs_mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0);
6189 }
6190 
6191 drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags)
6192 {
6193  return drfs_mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0);
6194 }
6195 
6196 void *drfs_mz_zip_reader_extract_to_heap(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, size_t *pSize, drfs_mz_uint flags)
6197 {
6198  drfs_mz_uint64 comp_size, uncomp_size, alloc_size;
6199  const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index);
6200  void *pBuf;
6201 
6202  if (pSize)
6203  *pSize = 0;
6204  if (!p)
6205  return NULL;
6206 
6207  comp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
6208  uncomp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
6209 
6210  alloc_size = (flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size;
6211 #ifdef _MSC_VER
6212  if (((0, sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (alloc_size > 0x7FFFFFFF))
6213 #else
6214  if (((sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (alloc_size > 0x7FFFFFFF))
6215 #endif
6216  return NULL;
6217  if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size)))
6218  return NULL;
6219 
6220  if (!drfs_mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags))
6221  {
6222  pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
6223  return NULL;
6224  }
6225 
6226  if (pSize) *pSize = (size_t)alloc_size;
6227  return pBuf;
6228 }
6229 
6230 drfs_mz_bool drfs_mz_zip_reader_end(drfs_mz_zip_archive *pZip)
6231 {
6232  if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_READING))
6233  return DRFS_MZ_FALSE;
6234 
6235  if (pZip->m_pState)
6236  {
6237  drfs_mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL;
6238  drfs_mz_zip_array_clear(pZip, &pState->m_central_dir);
6239  drfs_mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
6240  drfs_mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);
6241 
6242  pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
6243  }
6244  pZip->m_zip_mode = DRFS_MZ_ZIP_MODE_INVALID;
6245 
6246  return DRFS_MZ_TRUE;
6247 }
6248 
6249 #endif // #ifndef DRFS_MINIZ_NO_ARCHIVE_APIS
6250 
6251 #ifdef __cplusplus
6252 }
6253 #endif
6254 
6255 #endif // DRFS_MINIZ_HEADER_FILE_ONLY
6256 
6257 #if defined(__GNUC__)
6258  #pragma GCC diagnostic pop
6259 #endif
6260 #if defined(_MSC_VER)
6261  #pragma warning(pop)
6262 #endif
6263 
6264 
6265 
6266 
6267 typedef struct
6268 {
6269  // The current index of the iterator. When this hits the file count, the iteration is finished.
6270  unsigned int index;
6271 
6272  // The directory being iterated.
6273  char directoryPath[DRFS_MAX_PATH];
6274 
6275 }drfs_iterator_zip;
6276 
6277 typedef struct
6278 {
6279  // The file index within the archive.
6280  drfs_mz_uint index;
6281 
6282  // A pointer to the buffer containing the entire uncompressed data of the file. Unfortunately this is the only way I'm aware of for
6283  // reading file data from miniz.c so we'll just stick with it for now. We use a pointer to an 8-bit type so we can easily calculate
6284  // offsets.
6285  drfs_mz_uint8* pData;
6286 
6287  // The size of the file in bytes so we can guard against overflowing reads.
6288  size_t sizeInBytes;
6289 
6290  // The current position of the file's read pointer.
6291  size_t readPointer;
6292 
6293 }drfs_openedfile_zip;
6294 
6295 static size_t drfs_drfs_mz_file_read_func(void *pOpaque, drfs_mz_uint64 file_ofs, void *pBuf, size_t n)
6296 {
6297  // The opaque type is a pointer to a drfs_file object which represents the file of the archive.
6298  drfs_file* pZipFile = (drfs_file*)pOpaque;
6299  assert(pZipFile != NULL);
6300 
6301  if (!drfs_lock(pZipFile)) {
6302  return 0;
6303  }
6304 
6305  drfs_seek_nolock(pZipFile, (dr_int64)file_ofs, drfs_origin_start);
6306 
6307  size_t bytesRead;
6308  drfs_result result = drfs_read_nolock(pZipFile, pBuf, (unsigned int)n, &bytesRead);
6309  if (result != drfs_success) {
6310  // Failed to read the file.
6311  bytesRead = 0;
6312  }
6313 
6314  drfs_unlock(pZipFile);
6315  return (size_t)bytesRead;
6316 }
6317 
6318 
6319 static dr_bool32 drfs_is_valid_extension__zip(const char* extension)
6320 {
6321  return drfs__stricmp(extension, "zip") == 0;
6322 }
6323 
6324 static drfs_result drfs_open_archive__zip(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut)
6325 {
6326  assert(pArchiveFile != NULL);
6327  assert(pHandleOut != NULL);
6328  assert(drfs_tell(pArchiveFile) == 0);
6329 
6330  *pHandleOut = NULL;
6331 
6332  // Only support read-only mode at the moment.
6333  if ((accessMode & DRFS_WRITE) != 0) {
6334  return drfs_permission_denied;
6335  }
6336 
6337 
6338  drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)malloc(sizeof(drfs_mz_zip_archive));
6339  if (pZip == NULL) {
6340  return drfs_out_of_memory;
6341  }
6342 
6343  memset(pZip, 0, sizeof(drfs_mz_zip_archive));
6344 
6345  pZip->m_pRead = drfs_drfs_mz_file_read_func;
6346  pZip->m_pIO_opaque = pArchiveFile;
6347  if (!drfs_mz_zip_reader_init(pZip, drfs_size(pArchiveFile), 0)) {
6348  free(pZip);
6349  return drfs_invalid_archive;
6350  }
6351 
6352  *pHandleOut = pZip;
6353  return drfs_success;
6354 }
6355 
6356 static void drfs_close_archive__zip(drfs_handle archive)
6357 {
6358  assert(archive != NULL);
6359 
6360  drfs_mz_zip_reader_end((drfs_mz_zip_archive*)archive);
6361  free(archive);
6362 }
6363 
6364 static drfs_result drfs_get_file_info__zip(drfs_handle archive, const char* relativePath, drfs_file_info* fi)
6365 {
6366  assert(archive != NULL);
6367 
6368  drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive;
6369  int fileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE);
6370  if (fileIndex == -1)
6371  {
6372  // We failed to locate the file, but there's a chance it could actually be a folder. Here's the problem - folders
6373  // can be named such that they include a trailing slash. We'll want to check for that. Another problem is that
6374  // sometimes the folders won't actually be included in the central directory at all which means we need to do a
6375  // manual check across every file in the archive.
6376  char relativePathWithSlash[DRFS_MAX_PATH];
6377  drfs__strcpy_s(relativePathWithSlash, sizeof(relativePathWithSlash), relativePath);
6378  drfs__strcat_s(relativePathWithSlash, sizeof(relativePathWithSlash), "/");
6379  fileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE);
6380  if (fileIndex == -1)
6381  {
6382  // We still couldn't find the directory even with the trailing slash. There's a chace it's a folder that's
6383  // simply not included in the central directory. It's appears the "Send to -> Compressed (zipped) folder"
6384  // functionality in Windows does this.
6385  drfs_mz_uint numFiles = drfs_mz_zip_reader_get_num_files(pZip);
6386  for (drfs_mz_uint iFile = 0; iFile < numFiles; ++iFile)
6387  {
6388  char filePath[DRFS_MAX_PATH];
6389  if (drfs_mz_zip_reader_get_filename(pZip, iFile, filePath, DRFS_MAX_PATH) > 0)
6390  {
6391  if (drfs_drpath_is_child(filePath, relativePath))
6392  {
6393  // This file is within a folder with a path of relativePath which means we can imply that relativePath
6394  // is a folder.
6395  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath);
6396  fi->sizeInBytes = 0;
6397  fi->lastModifiedTime = 0;
6399 
6400  return drfs_success;
6401  }
6402  }
6403  }
6404 
6405  return drfs_does_not_exist;
6406  }
6407  }
6408 
6409  assert(fileIndex != -1);
6410 
6411  if (fi != NULL)
6412  {
6413  drfs_drfs_mz_zip_archive_file_stat zipStat;
6414  if (drfs_mz_zip_reader_file_stat(pZip, (drfs_mz_uint)fileIndex, &zipStat))
6415  {
6416  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath);
6417  fi->sizeInBytes = zipStat.m_uncomp_size;
6418  fi->lastModifiedTime = (dr_uint64)zipStat.m_time;
6420  if (drfs_mz_zip_reader_is_file_a_directory(pZip, (drfs_mz_uint)fileIndex)) {
6422  }
6423 
6424  return drfs_success;
6425  }
6426  }
6427 
6428  return drfs_success;
6429 }
6430 
6431 static drfs_handle drfs_begin_iteration__zip(drfs_handle archive, const char* relativePath)
6432 {
6433  assert(relativePath != NULL);
6434 
6435  drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive;
6436  assert(pZip != NULL);
6437 
6438  int directoryFileIndex = -1;
6439  if (relativePath[0] == '\0') {
6440  directoryFileIndex = 0;
6441  } else {
6442  directoryFileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE);
6443  }
6444 
6445  if (directoryFileIndex == -1)
6446  {
6447  // The same issue applies here as documented in drfs_get_file_info__zip().
6448  char relativePathWithSlash[DRFS_MAX_PATH];
6449  drfs__strcpy_s(relativePathWithSlash, sizeof(relativePathWithSlash), relativePath);
6450  drfs__strcat_s(relativePathWithSlash, sizeof(relativePathWithSlash), "/");
6451  directoryFileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE);
6452  if (directoryFileIndex == -1)
6453  {
6454  // We still couldn't find the directory even with the trailing slash. There's a chace it's a folder that's
6455  // simply not included in the central directory. It's appears the "Send to -> Compressed (zipped) folder"
6456  // functionality in Windows does this.
6457  drfs_mz_uint numFiles = drfs_mz_zip_reader_get_num_files(pZip);
6458  for (drfs_mz_uint iFile = 0; iFile < numFiles; ++iFile)
6459  {
6460  char filePath[DRFS_MAX_PATH];
6461  if (drfs_mz_zip_reader_get_filename(pZip, iFile, filePath, DRFS_MAX_PATH) > 0)
6462  {
6463  if (drfs_drpath_is_child(filePath, relativePath))
6464  {
6465  // This file is within a folder with a path of relativePath which means we can imply that relativePath
6466  // is a folder.
6467  goto on_success;
6468  }
6469  }
6470  }
6471 
6472  return NULL;
6473  }
6474  }
6475 
6476 
6477 
6478 on_success:;
6479  drfs_iterator_zip* pZipIterator = (drfs_iterator_zip*)malloc(sizeof(drfs_iterator_zip));
6480  if (pZipIterator != NULL)
6481  {
6482  pZipIterator->index = 0;
6483  drfs__strcpy_s(pZipIterator->directoryPath, sizeof(pZipIterator->directoryPath), relativePath);
6484  }
6485 
6486  return pZipIterator;
6487 }
6488 
6489 static void drfs_end_iteration__zip(drfs_handle archive, drfs_handle iterator)
6490 {
6491  (void)archive;
6492  assert(archive != NULL);
6493  assert(iterator != NULL);
6494 
6495  free(iterator);
6496 }
6497 
6498 static dr_bool32 drfs_next_iteration__zip(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi)
6499 {
6500  (void)archive;
6501  assert(archive != NULL);
6502  assert(iterator != NULL);
6503 
6504  drfs_iterator_zip* pZipIterator = (drfs_iterator_zip*)iterator;
6505  if (pZipIterator == NULL) {
6506  return DR_FALSE;
6507  }
6508 
6509  drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive;
6510  while (pZipIterator->index < drfs_mz_zip_reader_get_num_files(pZip))
6511  {
6512  unsigned int iFile = pZipIterator->index++;
6513 
6514  char filePath[DRFS_MAX_PATH];
6515  if (drfs_mz_zip_reader_get_filename(pZip, iFile, filePath, DRFS_MAX_PATH) > 0)
6516  {
6517  if (drfs_drpath_is_child(filePath, pZipIterator->directoryPath))
6518  {
6519  if (fi != NULL)
6520  {
6521  drfs_drfs_mz_zip_archive_file_stat zipStat;
6522  if (drfs_mz_zip_reader_file_stat(pZip, iFile, &zipStat))
6523  {
6524  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), filePath);
6525  fi->sizeInBytes = zipStat.m_uncomp_size;
6526  fi->lastModifiedTime = (dr_uint64)zipStat.m_time;
6528  if (drfs_mz_zip_reader_is_file_a_directory(pZip, iFile)) {
6530  }
6531 
6532  // If we have a directory we need to ensure we don't have a trailing slash.
6533  if ((fi->attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) != 0) {
6534  size_t absolutePathLen = strlen(fi->absolutePath);
6535  if (absolutePathLen > 0 && (fi->absolutePath[absolutePathLen - 1] == '/' || fi->absolutePath[absolutePathLen - 1] == '\\')) {
6536  fi->absolutePath[absolutePathLen - 1] = '\0';
6537  }
6538  }
6539  }
6540  }
6541 
6542  return DR_TRUE;
6543  }
6544  }
6545  }
6546 
6547  return DR_FALSE;
6548 }
6549 
6550 static drfs_result drfs_open_file__zip(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut)
6551 {
6552  assert(archive != NULL);
6553  assert(pHandleOut != NULL);
6554  assert(relativePath != NULL);
6555 
6556  *pHandleOut = NULL;
6557 
6558  // Only supporting read-only for now.
6559  if ((accessMode & DRFS_WRITE) != 0) {
6560  return drfs_permission_denied;
6561  }
6562 
6563 
6564  drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive;
6565  int fileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE);
6566  if (fileIndex == -1) {
6567  return drfs_does_not_exist;
6568  }
6569 
6570  drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)malloc(sizeof(*pOpenedFile));
6571  if (pOpenedFile == NULL) {
6572  return drfs_out_of_memory;
6573  }
6574 
6575  pOpenedFile->pData = (drfs_mz_uint8*)drfs_mz_zip_reader_extract_to_heap(pZip, (drfs_mz_uint)fileIndex, &pOpenedFile->sizeInBytes, 0);
6576  if (pOpenedFile->pData == NULL) {
6577  free(pOpenedFile);
6578  return drfs_unknown_error;
6579  }
6580 
6581  pOpenedFile->index = (drfs_mz_uint)fileIndex;
6582  pOpenedFile->readPointer = 0;
6583 
6584  *pHandleOut = pOpenedFile;
6585  return drfs_success;
6586 }
6587 
6588 static void drfs_close_file__zip(drfs_handle archive, drfs_handle file)
6589 {
6590  drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file;
6591  assert(pOpenedFile != NULL);
6592 
6593  drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive;
6594  assert(pZip != NULL);
6595 
6596  pZip->m_pFree(pZip->m_pAlloc_opaque, pOpenedFile->pData);
6597  free(pOpenedFile);
6598 }
6599 
6600 static drfs_result drfs_read_file__zip(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
6601 {
6602  (void)archive;
6603  assert(archive != NULL);
6604  assert(file != NULL);
6605  assert(pDataOut != NULL);
6606  assert(bytesToRead > 0);
6607 
6608  drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file;
6609  if (pOpenedFile == NULL) {
6610  return drfs_invalid_args;
6611  }
6612 
6613  size_t bytesAvailable = pOpenedFile->sizeInBytes - pOpenedFile->readPointer;
6614  if (bytesAvailable < bytesToRead) {
6615  bytesToRead = bytesAvailable;
6616  }
6617 
6618  if (bytesToRead == 0) {
6619  return drfs_at_end_of_file; // Nothing left to read.
6620  }
6621 
6622 
6623  memcpy(pDataOut, pOpenedFile->pData + pOpenedFile->readPointer, bytesToRead);
6624  pOpenedFile->readPointer += bytesToRead;
6625 
6626  if (pBytesReadOut) {
6627  *pBytesReadOut = bytesToRead;
6628  }
6629 
6630  return drfs_success;
6631 }
6632 
6633 static drfs_result drfs_write_file__zip(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
6634 {
6635  (void)archive;
6636  (void)file;
6637  (void)pData;
6638  (void)bytesToWrite;
6639 
6640  assert(archive != NULL);
6641  assert(file != NULL);
6642  assert(pData != NULL);
6643  assert(bytesToWrite > 0);
6644 
6645  // All files are read-only for now.
6646  if (pBytesWrittenOut) {
6647  *pBytesWrittenOut = 0;
6648  }
6649 
6650  return drfs_permission_denied;
6651 }
6652 
6653 static drfs_result drfs_seek_file__zip(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin)
6654 {
6655  (void)archive;
6656 
6657  assert(archive != NULL);
6658  assert(file != NULL);
6659 
6660  drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file;
6661  if (pOpenedFile == NULL) {
6662  return drfs_invalid_args;
6663  }
6664 
6665  dr_uint64 newPos = pOpenedFile->readPointer;
6666  if (origin == drfs_origin_current)
6667  {
6668  if ((dr_int64)newPos + bytesToSeek >= 0)
6669  {
6670  newPos = (dr_uint64)((dr_int64)newPos + bytesToSeek);
6671  }
6672  else
6673  {
6674  // Trying to seek to before the beginning of the file.
6675  return drfs_invalid_args;
6676  }
6677  }
6678  else if (origin == drfs_origin_start)
6679  {
6680  assert(bytesToSeek >= 0);
6681  newPos = (dr_uint64)bytesToSeek;
6682  }
6683  else if (origin == drfs_origin_end)
6684  {
6685  assert(bytesToSeek >= 0);
6686  if ((dr_uint64)bytesToSeek <= pOpenedFile->sizeInBytes)
6687  {
6688  newPos = pOpenedFile->sizeInBytes - (dr_uint64)bytesToSeek;
6689  }
6690  else
6691  {
6692  // Trying to seek to before the beginning of the file.
6693  return drfs_invalid_args;
6694  }
6695  }
6696  else
6697  {
6698  // Should never get here.
6699  return drfs_unknown_error;
6700  }
6701 
6702 
6703  if (newPos > pOpenedFile->sizeInBytes) {
6704  return drfs_invalid_args;
6705  }
6706 
6707  pOpenedFile->readPointer = (size_t)newPos;
6708  return drfs_success;
6709 }
6710 
6711 static dr_uint64 drfs_tell_file__zip(drfs_handle archive, drfs_handle file)
6712 {
6713  (void)archive;
6714 
6715  drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file;
6716  assert(pOpenedFile != NULL);
6717 
6718  return pOpenedFile->readPointer;
6719 }
6720 
6721 static dr_uint64 drfs_file_size__zip(drfs_handle archive, drfs_handle file)
6722 {
6723  (void)archive;
6724 
6725  drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file;
6726  assert(pOpenedFile != NULL);
6727 
6728  return pOpenedFile->sizeInBytes;
6729 }
6730 
6731 static void drfs_flush__zip(drfs_handle archive, drfs_handle file)
6732 {
6733  (void)archive;
6734  (void)file;
6735 
6736  assert(archive != NULL);
6737  assert(file != NULL);
6738 
6739  // All files are read-only for now.
6740 }
6741 
6742 
6743 static void drfs_register_zip_backend(drfs_context* pContext)
6744 {
6745  if (pContext == NULL) {
6746  return;
6747  }
6748 
6749  drfs_archive_callbacks callbacks;
6750  callbacks.is_valid_extension = drfs_is_valid_extension__zip;
6751  callbacks.open_archive = drfs_open_archive__zip;
6752  callbacks.close_archive = drfs_close_archive__zip;
6753  callbacks.get_file_info = drfs_get_file_info__zip;
6754  callbacks.begin_iteration = drfs_begin_iteration__zip;
6755  callbacks.end_iteration = drfs_end_iteration__zip;
6756  callbacks.next_iteration = drfs_next_iteration__zip;
6757  callbacks.delete_file = NULL;
6758  callbacks.move_file = NULL;
6759  callbacks.create_directory = NULL;
6760  callbacks.copy_file = NULL;
6761  callbacks.open_file = drfs_open_file__zip;
6762  callbacks.close_file = drfs_close_file__zip;
6763  callbacks.read_file = drfs_read_file__zip;
6764  callbacks.write_file = drfs_write_file__zip;
6765  callbacks.seek_file = drfs_seek_file__zip;
6766  callbacks.tell_file = drfs_tell_file__zip;
6767  callbacks.file_size = drfs_file_size__zip;
6768  callbacks.flush_file = drfs_flush__zip;
6769  drfs_register_archive_backend(pContext, callbacks);
6770 }
6771 #endif //DR_FS_NO_ZIP
6772 
6773 
6774 
6776 //
6777 // Quake 2 PAK
6778 //
6780 #ifndef DR_FS_NO_PAK
6781 typedef struct
6782 {
6783  char path[64];
6784 
6785 }drfs_path_pak;
6786 
6787 typedef struct
6788 {
6789  // The file name.
6790  char name[56];
6791 
6792  // The position within the file of the first byte of the file.
6793  unsigned int offset;
6794 
6795  // The size of the file, in bytes.
6796  unsigned int sizeInBytes;
6797 
6798 }drfs_file_pak;
6799 
6800 typedef struct
6801 {
6802  // A pointer to the archive file for reading data.
6803  drfs_file* pArchiveFile;
6804 
6805 
6806  // The 4-byte identifiers: "PACK"
6807  char id[4];
6808 
6809  // The offset of the directory.
6810  unsigned int directoryOffset;
6811 
6812  // The size of the directory. This should a multiple of 64.
6813  unsigned int directoryLength;
6814 
6815 
6816  // The access mode.
6817  unsigned int accessMode;
6818 
6819  // A pointer to the buffer containing the file information. The number of items in this array is equal to directoryLength / 64.
6820  drfs_file_pak* pFiles;
6821 
6822 }drfs_archive_pak;
6823 
6824 
6825 typedef struct
6826 {
6827  // The current index of the iterator. When this hits childCount, the iteration is finished.
6828  unsigned int index;
6829 
6830  // The directory being iterated.
6831  char directoryPath[DRFS_MAX_PATH];
6832 
6833 
6834  // The number of directories that have previously been iterated.
6835  unsigned int processedDirCount;
6836 
6837  // The directories that were previously iterated.
6838  drfs_path_pak* pProcessedDirs;
6839 
6840 }drfs_iterator_pak;
6841 
6842 static dr_bool32 drfs_iterator_pak_append_processed_dir(drfs_iterator_pak* pIterator, const char* path)
6843 {
6844  if (pIterator != NULL && path != NULL)
6845  {
6846  drfs_path_pak* pOldBuffer = pIterator->pProcessedDirs;
6847  drfs_path_pak* pNewBuffer = (drfs_path_pak*)malloc(sizeof(drfs_path_pak) * (pIterator->processedDirCount + 1));
6848 
6849  if (pNewBuffer != 0)
6850  {
6851  for (unsigned int iDst = 0; iDst < pIterator->processedDirCount; ++iDst)
6852  {
6853  pNewBuffer[iDst] = pOldBuffer[iDst];
6854  }
6855 
6856  drfs__strcpy_s(pNewBuffer[pIterator->processedDirCount].path, 64, path);
6857 
6858 
6859  pIterator->pProcessedDirs = pNewBuffer;
6860  pIterator->processedDirCount += 1;
6861 
6862  drfs_free(pOldBuffer);
6863 
6864  return 1;
6865  }
6866  }
6867 
6868  return 0;
6869 }
6870 
6871 static dr_bool32 drfs_iterator_pak_has_dir_been_processed(drfs_iterator_pak* pIterator, const char* path)
6872 {
6873  for (unsigned int i = 0; i < pIterator->processedDirCount; ++i)
6874  {
6875  if (strcmp(path, pIterator->pProcessedDirs[i].path) == 0)
6876  {
6877  return 1;
6878  }
6879  }
6880 
6881  return 0;
6882 }
6883 
6884 
6885 typedef struct
6886 {
6887  // The offset of the first byte of the file's data.
6888  size_t offsetInArchive;
6889 
6890  // The size of the file in bytes so we can guard against overflowing reads.
6891  size_t sizeInBytes;
6892 
6893  // The current position of the file's read pointer.
6894  size_t readPointer;
6895 
6896 }drfs_openedfile_pak;
6897 
6898 
6899 
6900 static drfs_archive_pak* drfs_pak_create(drfs_file* pArchiveFile, unsigned int accessMode)
6901 {
6902  drfs_archive_pak* pak = (drfs_archive_pak*)malloc(sizeof(*pak));
6903  if (pak != NULL)
6904  {
6905  pak->pArchiveFile = pArchiveFile;
6906  pak->directoryOffset = 0;
6907  pak->directoryLength = 0;
6908  pak->accessMode = accessMode;
6909  pak->pFiles = NULL;
6910  }
6911 
6912  return pak;
6913 }
6914 
6915 static void drfs_pak_delete(drfs_archive_pak* pArchive)
6916 {
6917  free(pArchive->pFiles);
6918  free(pArchive);
6919 }
6920 
6921 
6922 
6923 
6924 static dr_bool32 drfs_is_valid_extension__pak(const char* extension)
6925 {
6926  return drfs__stricmp(extension, "pak") == 0;
6927 }
6928 
6929 
6930 static drfs_result drfs_open_archive__pak(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut)
6931 {
6932  assert(pArchiveFile != NULL);
6933  assert(pHandleOut != NULL);
6934  assert(drfs_tell(pArchiveFile) == 0);
6935 
6936  *pHandleOut = NULL;
6937 
6938  if (!drfs_lock(pArchiveFile)) {
6939  return drfs_unknown_error;
6940  }
6941 
6942  drfs_result result = drfs_success;
6943  drfs_archive_pak* pak = drfs_pak_create(pArchiveFile, accessMode);
6944  if (pak != NULL)
6945  {
6946  // First 4 bytes should equal "PACK"
6947  if (drfs_read_nolock(pArchiveFile, pak->id, 4, NULL) == drfs_success)
6948  {
6949  if (pak->id[0] == 'P' && pak->id[1] == 'A' && pak->id[2] == 'C' && pak->id[3] == 'K')
6950  {
6951  if (drfs_read_nolock(pArchiveFile, &pak->directoryOffset, 4, NULL) == drfs_success)
6952  {
6953  if (drfs_read_nolock(pArchiveFile, &pak->directoryLength, 4, NULL) == drfs_success)
6954  {
6955  // We loaded the header just fine so now we want to allocate space for each file in the directory and load them. Note that
6956  // this does not load the file data itself, just information about the files like their name and size.
6957  if (pak->directoryLength % 64 == 0)
6958  {
6959  unsigned int fileCount = pak->directoryLength / 64;
6960  if (fileCount > 0)
6961  {
6962  assert((sizeof(drfs_file_pak) * fileCount) == pak->directoryLength);
6963 
6964  pak->pFiles = (drfs_file_pak*)malloc(pak->directoryLength);
6965  if (pak->pFiles != NULL)
6966  {
6967  // Seek to the directory listing before trying to read it.
6968  if (drfs_seek_nolock(pArchiveFile, pak->directoryOffset, drfs_origin_start) == drfs_success)
6969  {
6970  size_t bytesRead;
6971  if (drfs_read_nolock(pArchiveFile, pak->pFiles, pak->directoryLength, &bytesRead) == drfs_success && bytesRead == pak->directoryLength)
6972  {
6973  // All good!
6974  }
6975  else
6976  {
6977  // Failed to read the directory listing.
6978  drfs_pak_delete(pak);
6979  pak = NULL;
6980  result = drfs_invalid_archive;
6981  }
6982  }
6983  else
6984  {
6985  // Failed to seek to the directory listing.
6986  drfs_pak_delete(pak);
6987  pak = NULL;
6988  result = drfs_invalid_archive;
6989  }
6990  }
6991  else
6992  {
6993  // Failed to allocate memory for the file info buffer.
6994  drfs_pak_delete(pak);
6995  pak = NULL;
6996  result = drfs_out_of_memory;
6997  }
6998  }
6999  }
7000  else
7001  {
7002  // The directory length is not a multiple of 64 - something is wrong with the file.
7003  drfs_pak_delete(pak);
7004  pak = NULL;
7005  result = drfs_invalid_archive;
7006  }
7007  }
7008  else
7009  {
7010  // Failed to read the directory length.
7011  drfs_pak_delete(pak);
7012  pak = NULL;
7013  result = drfs_invalid_archive;
7014  }
7015  }
7016  else
7017  {
7018  // Failed to read the directory offset.
7019  drfs_pak_delete(pak);
7020  pak = NULL;
7021  result = drfs_invalid_archive;
7022  }
7023  }
7024  else
7025  {
7026  // Not a pak file.
7027  drfs_pak_delete(pak);
7028  pak = NULL;
7029  result = drfs_invalid_archive;
7030  }
7031  }
7032  else
7033  {
7034  // Failed to read the header.
7035  drfs_pak_delete(pak);
7036  pak = NULL;
7037  result = drfs_invalid_archive;
7038  }
7039  }
7040 
7041  drfs_unlock(pArchiveFile);
7042  *pHandleOut = pak;
7043  return result;
7044 }
7045 
7046 static void drfs_close_archive__pak(drfs_handle archive)
7047 {
7048  drfs_archive_pak* pPak = (drfs_archive_pak*)archive;
7049  assert(pPak != NULL);
7050 
7051  drfs_pak_delete(pPak);
7052 }
7053 
7054 static drfs_result drfs_get_file_info__pak(drfs_handle archive, const char* relativePath, drfs_file_info* fi)
7055 {
7056  // We can determine whether or not the path refers to a file or folder by checking it the path is parent of any
7057  // files in the archive. If so, it's a folder, otherwise it's a file (so long as it exists).
7058  drfs_archive_pak* pak = (drfs_archive_pak*)archive;
7059  assert(pak != NULL);
7060 
7061  unsigned int fileCount = pak->directoryLength / 64;
7062  for (unsigned int i = 0; i < fileCount; ++i)
7063  {
7064  drfs_file_pak* pFile = pak->pFiles + i;
7065  if (strcmp(pFile->name, relativePath) == 0)
7066  {
7067  // It's a file.
7068  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath);
7069  fi->sizeInBytes = (dr_uint64)pFile->sizeInBytes;
7070  fi->lastModifiedTime = 0;
7072 
7073  return drfs_success;
7074  }
7075  else if (drfs_drpath_is_descendant(pFile->name, relativePath))
7076  {
7077  // It's a directory.
7078  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath);
7079  fi->sizeInBytes = 0;
7080  fi->lastModifiedTime = 0;
7082 
7083  return drfs_success;
7084  }
7085  }
7086 
7087  return drfs_does_not_exist;
7088 }
7089 
7090 static drfs_handle drfs_begin_iteration__pak(drfs_handle archive, const char* relativePath)
7091 {
7092  (void)archive;
7093  assert(relativePath != NULL);
7094 
7095  drfs_iterator_pak* pIterator = (drfs_iterator_pak*)malloc(sizeof(drfs_iterator_pak));
7096  if (pIterator != NULL)
7097  {
7098  pIterator->index = 0;
7099  drfs__strcpy_s(pIterator->directoryPath, sizeof(pIterator->directoryPath), relativePath);
7100  pIterator->processedDirCount = 0;
7101  pIterator->pProcessedDirs = NULL;
7102  }
7103 
7104  return pIterator;
7105 }
7106 
7107 static void drfs_end_iteration__pak(drfs_handle archive, drfs_handle iterator)
7108 {
7109  (void)archive;
7110 
7111  drfs_iterator_pak* pIterator = (drfs_iterator_pak*)iterator;
7112  assert(pIterator != NULL);
7113 
7114  free(pIterator);
7115 }
7116 
7117 static dr_bool32 drfs_next_iteration__pak(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi)
7118 {
7119  drfs_iterator_pak* pIterator = (drfs_iterator_pak*)iterator;
7120  assert(pIterator != NULL);
7121 
7122  drfs_archive_pak* pak = (drfs_archive_pak*)archive;
7123  assert(pak != NULL);
7124 
7125  unsigned int fileCount = pak->directoryLength / 64;
7126  while (pIterator->index < fileCount)
7127  {
7128  unsigned int iFile = pIterator->index++;
7129 
7130  drfs_file_pak* pFile = pak->pFiles + iFile;
7131  if (drfs_drpath_is_child(pFile->name, pIterator->directoryPath))
7132  {
7133  // It's a file.
7134  drfs__strcpy_s(fi->absolutePath, DRFS_MAX_PATH, pFile->name);
7135  fi->sizeInBytes = (dr_uint64)pFile->sizeInBytes;
7136  fi->lastModifiedTime = 0;
7138 
7139  return DR_TRUE;
7140  }
7141  else if (drfs_drpath_is_descendant(pFile->name, pIterator->directoryPath))
7142  {
7143  // It's a directory. This needs special handling because we don't want to iterate over the same directory multiple times.
7144  const char* childDirEnd = pFile->name + strlen(pIterator->directoryPath) + 1; // +1 for the slash.
7145  while (childDirEnd[0] != '\0' && childDirEnd[0] != '/' && childDirEnd[0] != '\\')
7146  {
7147  childDirEnd += 1;
7148  }
7149 
7150  char childDir[64];
7151  memcpy(childDir, pFile->name, childDirEnd - pFile->name);
7152  childDir[childDirEnd - pFile->name] = '\0';
7153 
7154  if (!drfs_iterator_pak_has_dir_been_processed(pIterator, childDir))
7155  {
7156  // It's a directory.
7157  drfs__strcpy_s(fi->absolutePath, DRFS_MAX_PATH, childDir);
7158  fi->sizeInBytes = 0;
7159  fi->lastModifiedTime = 0;
7161 
7162  drfs_iterator_pak_append_processed_dir(pIterator, childDir);
7163 
7164  return DR_TRUE;
7165  }
7166  }
7167  }
7168 
7169  return DR_FALSE;
7170 }
7171 
7172 static drfs_result drfs_open_file__pak(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut)
7173 {
7174  assert(relativePath != NULL);
7175  assert(pHandleOut != NULL);
7176 
7177  // Only supporting read-only for now.
7178  if ((accessMode & DRFS_WRITE) != 0) {
7179  return drfs_permission_denied;
7180  }
7181 
7182 
7183  drfs_archive_pak* pak = (drfs_archive_pak*)archive;
7184  assert(pak != NULL);
7185 
7186  for (unsigned int iFile = 0; iFile < (pak->directoryLength / 64); ++iFile)
7187  {
7188  if (strcmp(relativePath, pak->pFiles[iFile].name) == 0)
7189  {
7190  // We found the file.
7191  drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)malloc(sizeof(*pOpenedFile));
7192  if (pOpenedFile != NULL)
7193  {
7194  pOpenedFile->offsetInArchive = pak->pFiles[iFile].offset;
7195  pOpenedFile->sizeInBytes = pak->pFiles[iFile].sizeInBytes;
7196  pOpenedFile->readPointer = 0;
7197 
7198  *pHandleOut = pOpenedFile;
7199  return drfs_success;
7200  }
7201  }
7202  }
7203 
7204 
7205  return drfs_does_not_exist;
7206 }
7207 
7208 static void drfs_close_file__pak(drfs_handle archive, drfs_handle file)
7209 {
7210  (void)archive;
7211 
7212  drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file;
7213  assert(pOpenedFile != NULL);
7214 
7215  free(pOpenedFile);
7216 }
7217 
7218 static drfs_result drfs_read_file__pak(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
7219 {
7220  assert(pDataOut != NULL);
7221  assert(bytesToRead > 0);
7222 
7223  drfs_archive_pak* pak = (drfs_archive_pak*)archive;
7224  assert(pak != NULL);
7225 
7226  drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file;
7227  assert(pOpenedFile != NULL);
7228 
7229  // The read pointer should never go past the file size.
7230  assert(pOpenedFile->sizeInBytes >= pOpenedFile->readPointer);
7231 
7232 
7233  size_t bytesAvailable = pOpenedFile->sizeInBytes - pOpenedFile->readPointer;
7234  if (bytesAvailable < bytesToRead) {
7235  bytesToRead = bytesAvailable; // Safe cast, as per the check above.
7236  }
7237 
7238  if (!drfs_lock(pak->pArchiveFile)) {
7239  return drfs_unknown_error;
7240  }
7241 
7242  drfs_seek_nolock(pak->pArchiveFile, (dr_int64)(pOpenedFile->offsetInArchive + pOpenedFile->readPointer), drfs_origin_start);
7243  drfs_result result = drfs_read_nolock(pak->pArchiveFile, pDataOut, bytesToRead, pBytesReadOut);
7244  if (result == drfs_success) {
7245  pOpenedFile->readPointer += bytesToRead;
7246  }
7247 
7248  drfs_unlock(pak->pArchiveFile);
7249  return result;
7250 }
7251 
7252 static drfs_result drfs_write_file__pak(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
7253 {
7254  (void)archive;
7255  (void)file;
7256  (void)pData;
7257  (void)bytesToWrite;
7258 
7259  assert(archive != NULL);
7260  assert(file != NULL);
7261  assert(pData != NULL);
7262  assert(bytesToWrite > 0);
7263 
7264  // All files are read-only for now.
7265  if (pBytesWrittenOut) {
7266  *pBytesWrittenOut = 0;
7267  }
7268 
7269  return drfs_permission_denied;
7270 }
7271 
7272 static drfs_result drfs_seek_file__pak(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin)
7273 {
7274  (void)archive;
7275 
7276  drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file;
7277  assert(pOpenedFile != NULL);
7278 
7279  dr_uint64 newPos = pOpenedFile->readPointer;
7280  if (origin == drfs_origin_current) {
7281  if ((dr_int64)newPos + bytesToSeek >= 0) {
7282  newPos = (dr_uint64)((dr_int64)newPos + bytesToSeek);
7283  } else {
7284  return drfs_invalid_args; // Trying to seek to before the beginning of the file.
7285  }
7286  } else if (origin == drfs_origin_start) {
7287  assert(bytesToSeek >= 0);
7288  newPos = (dr_uint64)bytesToSeek;
7289  } else if (origin == drfs_origin_end) {
7290  assert(bytesToSeek >= 0);
7291  if ((dr_uint64)bytesToSeek <= pOpenedFile->sizeInBytes) {
7292  newPos = pOpenedFile->sizeInBytes - (dr_uint64)bytesToSeek;
7293  } else {
7294  return drfs_invalid_args; // Trying to seek to before the beginning of the file.
7295  }
7296  } else {
7297  return drfs_unknown_error; // Should never get here.
7298  }
7299 
7300 
7301  if (newPos > pOpenedFile->sizeInBytes) {
7302  return drfs_invalid_args;
7303  }
7304 
7305  pOpenedFile->readPointer = (size_t)newPos;
7306  return drfs_success;
7307 }
7308 
7309 static dr_uint64 drfs_tell_file__pak(drfs_handle archive, drfs_handle file)
7310 {
7311  (void)archive;
7312 
7313  drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file;
7314  assert(pOpenedFile != NULL);
7315 
7316  return pOpenedFile->readPointer;
7317 }
7318 
7319 static dr_uint64 drfs_file_size__pak(drfs_handle archive, drfs_handle file)
7320 {
7321  (void)archive;
7322 
7323  drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file;
7324  assert(pOpenedFile != NULL);
7325 
7326  return pOpenedFile->sizeInBytes;
7327 }
7328 
7329 static void drfs_flush__pak(drfs_handle archive, drfs_handle file)
7330 {
7331  (void)archive;
7332  (void)file;
7333 
7334  assert(archive != NULL);
7335  assert(file != NULL);
7336 
7337  // All files are read-only for now.
7338 }
7339 
7340 static void drfs_register_pak_backend(drfs_context* pContext)
7341 {
7342  if (pContext == NULL) {
7343  return;
7344  }
7345 
7346  drfs_archive_callbacks callbacks;
7347  callbacks.is_valid_extension = drfs_is_valid_extension__pak;
7348  callbacks.open_archive = drfs_open_archive__pak;
7349  callbacks.close_archive = drfs_close_archive__pak;
7350  callbacks.get_file_info = drfs_get_file_info__pak;
7351  callbacks.begin_iteration = drfs_begin_iteration__pak;
7352  callbacks.end_iteration = drfs_end_iteration__pak;
7353  callbacks.next_iteration = drfs_next_iteration__pak;
7354  callbacks.delete_file = NULL;
7355  callbacks.move_file = NULL;
7356  callbacks.create_directory = NULL;
7357  callbacks.copy_file = NULL;
7358  callbacks.open_file = drfs_open_file__pak;
7359  callbacks.close_file = drfs_close_file__pak;
7360  callbacks.read_file = drfs_read_file__pak;
7361  callbacks.write_file = drfs_write_file__pak;
7362  callbacks.seek_file = drfs_seek_file__pak;
7363  callbacks.tell_file = drfs_tell_file__pak;
7364  callbacks.file_size = drfs_file_size__pak;
7365  callbacks.flush_file = drfs_flush__pak;
7366  drfs_register_archive_backend(pContext, callbacks);
7367 }
7368 #endif //DR_FS_NO_PAK
7369 
7370 
7371 
7373 //
7374 // Wavefront MTL
7375 //
7377 #ifndef DR_FS_NO_MTL
7378 typedef struct
7379 {
7380  // The byte offset within the archive
7381  dr_uint64 offset;
7382 
7383  // The size of the file, in bytes.
7384  dr_uint64 sizeInBytes;
7385 
7386  // The name of the material. The specification says this can be any length, but we're going to clamp it to 255 + null terminator which should be fine.
7387  char name[256];
7388 
7389 }drfs_file_mtl;
7390 
7391 typedef struct
7392 {
7393  // A pointer to the archive's file so we can read data.
7394  drfs_file* pArchiveFile;
7395 
7396  // The access mode.
7397  unsigned int accessMode;
7398 
7399  // The buffer containing the list of files.
7400  drfs_file_mtl* pFiles;
7401 
7402  // The number of files in the archive.
7403  unsigned int fileCount;
7404 
7405 }drfs_archive_mtl;
7406 
7407 typedef struct
7408 {
7409  // The current index of the iterator. When this hits the file count, the iteration is finished.
7410  unsigned int index;
7411 
7412 }drfs_iterator_mtl;
7413 
7414 typedef struct
7415 {
7416  // The offset within the archive file the first byte of the file is located.
7417  dr_uint64 offsetInArchive;
7418 
7419  // The size of the file in bytes so we can guard against overflowing reads.
7420  dr_uint64 sizeInBytes;
7421 
7422  // The current position of the file's read pointer.
7423  dr_uint64 readPointer;
7424 
7425 }drfs_openedfile_mtl;
7426 
7427 
7428 static drfs_archive_mtl* drfs_mtl_create(drfs_file* pArchiveFile, unsigned int accessMode)
7429 {
7430  drfs_archive_mtl* mtl = (drfs_archive_mtl*)malloc(sizeof(drfs_archive_mtl));
7431  if (mtl != NULL)
7432  {
7433  mtl->pArchiveFile = pArchiveFile;
7434  mtl->accessMode = accessMode;
7435  mtl->pFiles = NULL;
7436  mtl->fileCount = 0;
7437  }
7438 
7439  return mtl;
7440 }
7441 
7442 static void drfs_mtl_delete(drfs_archive_mtl* pArchive)
7443 {
7444  free(pArchive->pFiles);
7445  free(pArchive);
7446 }
7447 
7448 static void drfs_mtl_addfile(drfs_archive_mtl* pArchive, drfs_file_mtl* pFile)
7449 {
7450  if (pArchive != NULL && pFile != NULL)
7451  {
7452  drfs_file_mtl* pOldBuffer = pArchive->pFiles;
7453  drfs_file_mtl* pNewBuffer = (drfs_file_mtl*)malloc(sizeof(drfs_file_mtl) * (pArchive->fileCount + 1));
7454 
7455  if (pNewBuffer != 0)
7456  {
7457  for (unsigned int iDst = 0; iDst < pArchive->fileCount; ++iDst) {
7458  pNewBuffer[iDst] = pOldBuffer[iDst];
7459  }
7460 
7461  pNewBuffer[pArchive->fileCount] = *pFile;
7462 
7463  pArchive->pFiles = pNewBuffer;
7464  pArchive->fileCount += 1;
7465 
7466  free(pOldBuffer);
7467  }
7468  }
7469 }
7470 
7471 
7472 typedef struct
7473 {
7474  dr_uint64 archiveSizeInBytes;
7475  dr_uint64 bytesRemaining;
7476  drfs_file* pFile;
7477  char* chunkPointer;
7478  char* chunkEnd;
7479  char chunk[4096];
7480  unsigned int chunkSize;
7481 
7482 }drfs_openarchive_mtl_state;
7483 
7484 static dr_bool32 drfs_mtl_loadnextchunk(drfs_openarchive_mtl_state* pState)
7485 {
7486  assert(pState != NULL);
7487 
7488  if (pState->bytesRemaining > 0)
7489  {
7490  pState->chunkSize = (pState->bytesRemaining > 4096) ? 4096 : (unsigned int)pState->bytesRemaining;
7491  assert(pState->chunkSize);
7492 
7493  if (drfs_read_nolock(pState->pFile, pState->chunk, pState->chunkSize, NULL) == drfs_success)
7494  {
7495  pState->bytesRemaining -= pState->chunkSize;
7496  pState->chunkPointer = pState->chunk;
7497  pState->chunkEnd = pState->chunk + pState->chunkSize;
7498 
7499  return DR_TRUE;
7500  }
7501  else
7502  {
7503  // An error occured while reading. Just reset everything to make it look like an error occured.
7504  pState->bytesRemaining = 0;
7505  pState->chunkSize = 0;
7506  pState->chunkPointer = pState->chunk;
7507  pState->chunkEnd = pState->chunkPointer;
7508  }
7509  }
7510 
7511  return DR_FALSE;
7512 }
7513 
7514 static dr_bool32 drfs_mtl_loadnewmtl(drfs_openarchive_mtl_state* pState)
7515 {
7516  assert(pState != NULL);
7517 
7518  const char newmtl[7] = "newmtl";
7519  for (unsigned int i = 0; i < 6; ++i)
7520  {
7521  // Check if we need a new chunk.
7522  if (pState->chunkPointer >= pState->chunkEnd)
7523  {
7524  if (!drfs_mtl_loadnextchunk(pState))
7525  {
7526  return DR_FALSE;
7527  }
7528  }
7529 
7530 
7531  if (pState->chunkPointer[0] != newmtl[i])
7532  {
7533  return DR_FALSE;
7534  }
7535 
7536  pState->chunkPointer += 1;
7537  }
7538 
7539  // At this point the first 6 characters equal "newmtl".
7540  return DR_TRUE;
7541 }
7542 
7543 static dr_bool32 drfs_mtl_skipline(drfs_openarchive_mtl_state* pState)
7544 {
7545  assert(pState != NULL);
7546 
7547  // Keep looping until we find a new line character.
7548  while (pState->chunkPointer < pState->chunkEnd)
7549  {
7550  if (pState->chunkPointer[0] == '\n')
7551  {
7552  // Found the new line. Now move forward by one to get past the new line character.
7553  pState->chunkPointer += 1;
7554  if (pState->chunkPointer >= pState->chunkEnd)
7555  {
7556  return drfs_mtl_loadnextchunk(pState);
7557  }
7558 
7559  return DR_TRUE;
7560  }
7561 
7562  pState->chunkPointer += 1;
7563  }
7564 
7565  // If we get here it means we got past the end of the chunk. We just read the next chunk and call this recursively.
7566  if (drfs_mtl_loadnextchunk(pState))
7567  {
7568  return drfs_mtl_skipline(pState);
7569  }
7570 
7571  return DR_FALSE;
7572 }
7573 
7574 static dr_bool32 drfs_mtl_skipwhitespace(drfs_openarchive_mtl_state* pState)
7575 {
7576  assert(pState != NULL);
7577 
7578  while (pState->chunkPointer < pState->chunkEnd)
7579  {
7580  const char c = pState->chunkPointer[0];
7581  if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
7582  {
7583  return DR_TRUE;
7584  }
7585 
7586  pState->chunkPointer += 1;
7587  }
7588 
7589  if (drfs_mtl_loadnextchunk(pState))
7590  {
7591  return drfs_mtl_skipwhitespace(pState);
7592  }
7593 
7594  return DR_FALSE;
7595 }
7596 
7597 static dr_bool32 drfs_mtl_loadmtlname(drfs_openarchive_mtl_state* pState, void* dst, unsigned int dstSizeInBytes)
7598 {
7599  assert(pState != NULL);
7600 
7601  // We loop over character by character until we find a whitespace, "#" character or the end of the file.
7602  char* dst8 = (char*)dst;
7603  while (dstSizeInBytes > 0 && pState->chunkPointer < pState->chunkEnd)
7604  {
7605  const char c = pState->chunkPointer[0];
7606  if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '#')
7607  {
7608  // We've found the end of the name. Null terminate and return.
7609  *dst8 = '\0';
7610  return DR_TRUE;
7611  }
7612  else
7613  {
7614  *dst8++ = c;
7615  dstSizeInBytes -= 1;
7616  pState->chunkPointer += 1;
7617  }
7618  }
7619 
7620  // At this point we either ran out of space in the destination buffer or the chunk.
7621  if (dstSizeInBytes > 0)
7622  {
7623  // We got to the end of the chunk, so we need to load the next chunk and call this recursively.
7624  assert(pState->chunkPointer == pState->chunkEnd);
7625 
7626  if (drfs_mtl_loadnextchunk(pState))
7627  {
7628  return drfs_mtl_loadmtlname(pState, dst8, dstSizeInBytes);
7629  }
7630  else
7631  {
7632  // We reached the end of the file, but the name may be valid.
7633  return DR_TRUE;
7634  }
7635  }
7636  else
7637  {
7638  // We ran out of room in the buffer.
7639  return DR_FALSE;
7640  }
7641 }
7642 
7643 
7644 static dr_bool32 drfs_is_valid_extension__mtl(const char* extension)
7645 {
7646  return drfs__stricmp(extension, "mtl") == 0;
7647 }
7648 
7649 static drfs_result drfs_open_archive__mtl(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut)
7650 {
7651  assert(pArchiveFile != NULL);
7652  assert(pHandleOut != NULL);
7653  assert(drfs_tell(pArchiveFile) == 0);
7654 
7655  *pHandleOut = NULL;
7656 
7657  if (!drfs_lock(pArchiveFile)) {
7658  return drfs_unknown_error;
7659  }
7660 
7661  drfs_archive_mtl* mtl = drfs_mtl_create(pArchiveFile, accessMode);
7662  if (mtl == NULL) {
7663  return drfs_invalid_archive;
7664  }
7665 
7666  drfs_result result = drfs_success;
7667 
7668  // We create a state object that is used to help us with chunk management.
7669  drfs_openarchive_mtl_state state;
7670  state.pFile = pArchiveFile;
7671  state.archiveSizeInBytes = drfs_size_nolock(pArchiveFile);
7672  state.bytesRemaining = state.archiveSizeInBytes;
7673  state.chunkSize = 0;
7674  state.chunkPointer = state.chunk;
7675  state.chunkEnd = state.chunk;
7676  if (drfs_mtl_loadnextchunk(&state))
7677  {
7678  while (state.bytesRemaining > 0 || state.chunkPointer < state.chunkEnd)
7679  {
7680  ptrdiff_t bytesRemainingInChunk = state.chunkEnd - state.chunkPointer;
7681  assert(bytesRemainingInChunk > 0);
7682 
7683  dr_uint64 newmtlOffset = state.archiveSizeInBytes - state.bytesRemaining - ((dr_uint64)bytesRemainingInChunk);
7684 
7685  if (drfs_mtl_loadnewmtl(&state))
7686  {
7687  if (state.chunkPointer[0] == ' ' || state.chunkPointer[0] == '\t')
7688  {
7689  // We found a new material. We need to iterate until we hit the first whitespace, "#", or the end of the file.
7690  if (drfs_mtl_skipwhitespace(&state))
7691  {
7692  drfs_file_mtl file;
7693  if (drfs_mtl_loadmtlname(&state, file.name, 256))
7694  {
7695  // Everything worked out. We now need to create the file and add it to our list. At this point we won't know the size. We determine
7696  // the size in a post-processing step later.
7697  file.offset = newmtlOffset;
7698  drfs_mtl_addfile(mtl, &file);
7699  }
7700  }
7701  }
7702  }
7703 
7704  // Move to the next line.
7705  drfs_mtl_skipline(&state);
7706  }
7707 
7708 
7709  // The files will have been read at this point, but we need to do a post-processing step to retrieve the size of each file.
7710  for (unsigned int iFile = 0; iFile < mtl->fileCount; ++iFile)
7711  {
7712  if (iFile < mtl->fileCount - 1)
7713  {
7714  // It's not the last item. The size of this item is the offset of the next file minus the offset of this file.
7715  mtl->pFiles[iFile].sizeInBytes = mtl->pFiles[iFile + 1].offset - mtl->pFiles[iFile].offset;
7716  }
7717  else
7718  {
7719  // It's the last item. The size of this item is the size of the archive file minus the file's offset.
7720  mtl->pFiles[iFile].sizeInBytes = state.archiveSizeInBytes - mtl->pFiles[iFile].offset;
7721  }
7722  }
7723  }
7724  else
7725  {
7726  drfs_mtl_delete(mtl);
7727  mtl = NULL;
7728  result = drfs_invalid_archive;
7729  }
7730 
7731  drfs_unlock(pArchiveFile);
7732  *pHandleOut = mtl;
7733  return result;
7734 }
7735 
7736 static void drfs_close_archive__mtl(drfs_handle archive)
7737 {
7738  drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive;
7739  assert(mtl != NULL);
7740 
7741  drfs_mtl_delete(mtl);
7742 }
7743 
7744 static drfs_result drfs_get_file_info__mtl(drfs_handle archive, const char* relativePath, drfs_file_info* fi)
7745 {
7746  drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive;
7747  assert(mtl != NULL);
7748 
7749  for (unsigned int iFile = 0; iFile < mtl->fileCount; ++iFile)
7750  {
7751  if (strcmp(relativePath, mtl->pFiles[iFile].name) == 0)
7752  {
7753  // We found the file.
7754  if (fi != NULL)
7755  {
7756  drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath);
7757  fi->sizeInBytes = mtl->pFiles[iFile].sizeInBytes;
7758  fi->lastModifiedTime = 0;
7760  }
7761 
7762  return drfs_success;
7763  }
7764  }
7765 
7766  return drfs_does_not_exist;
7767 }
7768 
7769 static drfs_handle drfs_begin_iteration__mtl(drfs_handle archive, const char* relativePath)
7770 {
7771  assert(relativePath != NULL);
7772 
7773  drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive;
7774  assert(mtl != NULL);
7775 
7776  if (mtl->fileCount > 0)
7777  {
7778  if (relativePath[0] == '\0' || (relativePath[0] == '/' && relativePath[1] == '\0')) // This is a flat archive, so no sub-folders.
7779  {
7780  drfs_iterator_mtl* pIterator = (drfs_iterator_mtl*)malloc(sizeof(*pIterator));
7781  if (pIterator != NULL)
7782  {
7783  pIterator->index = 0;
7784  return pIterator;
7785  }
7786  }
7787  }
7788 
7789  return NULL;
7790 }
7791 
7792 static void drfs_end_iteration__mtl(drfs_handle archive, drfs_handle iterator)
7793 {
7794  (void)archive;
7795 
7796  drfs_iterator_mtl* pIterator = (drfs_iterator_mtl*)iterator;
7797  free(pIterator);
7798 }
7799 
7800 static dr_bool32 drfs_next_iteration__mtl(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi)
7801 {
7802  drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive;
7803  assert(mtl != NULL);
7804 
7805  drfs_iterator_mtl* pIterator = (drfs_iterator_mtl*)iterator;
7806  assert(pIterator != NULL);
7807 
7808  if (pIterator->index < mtl->fileCount)
7809  {
7810  if (fi != NULL)
7811  {
7812  drfs__strcpy_s(fi->absolutePath, DRFS_MAX_PATH, mtl->pFiles[pIterator->index].name);
7813  fi->sizeInBytes = mtl->pFiles[pIterator->index].sizeInBytes;
7814  fi->lastModifiedTime = 0;
7816  }
7817 
7818  pIterator->index += 1;
7819  return DR_TRUE;
7820  }
7821 
7822  return DR_FALSE;
7823 }
7824 
7825 static drfs_result drfs_open_file__mtl(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut)
7826 {
7827  assert(relativePath != NULL);
7828  assert(pHandleOut != NULL);
7829 
7830  *pHandleOut = NULL;
7831 
7832  // Only supporting read-only for now.
7833  if ((accessMode & DRFS_WRITE) != 0) {
7834  return drfs_permission_denied;
7835  }
7836 
7837  drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive;
7838  assert(mtl != NULL);
7839 
7840  for (unsigned int iFile = 0; iFile < mtl->fileCount; ++iFile)
7841  {
7842  if (strcmp(relativePath, mtl->pFiles[iFile].name) == 0)
7843  {
7844  // We found the file.
7845  drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)malloc(sizeof(drfs_openedfile_mtl));
7846  if (pOpenedFile != NULL)
7847  {
7848  pOpenedFile->offsetInArchive = mtl->pFiles[iFile].offset;
7849  pOpenedFile->sizeInBytes = mtl->pFiles[iFile].sizeInBytes;
7850  pOpenedFile->readPointer = 0;
7851 
7852  *pHandleOut = pOpenedFile;
7853  return drfs_success;
7854  }
7855  }
7856  }
7857 
7858  return drfs_does_not_exist;
7859 }
7860 
7861 static void drfs_close_file__mtl(drfs_handle archive, drfs_handle file)
7862 {
7863  (void)archive;
7864 
7865  drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file;
7866  assert(pOpenedFile != NULL);
7867 
7868  free(pOpenedFile);
7869 }
7870 
7871 static drfs_result drfs_read_file__mtl(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut)
7872 {
7873  assert(pDataOut != NULL);
7874  assert(bytesToRead > 0);
7875 
7876  drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive;
7877  assert(mtl != NULL);
7878 
7879  drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file;
7880  assert(pOpenedFile != NULL);
7881 
7882  // The read pointer should never go past the file size.
7883  assert(pOpenedFile->sizeInBytes >= pOpenedFile->readPointer);
7884 
7885  dr_uint64 bytesAvailable = pOpenedFile->sizeInBytes - pOpenedFile->readPointer;
7886  if (bytesAvailable < bytesToRead) {
7887  bytesToRead = (size_t)bytesAvailable; // Safe cast, as per the check above.
7888  }
7889 
7890  if (!drfs_lock(mtl->pArchiveFile)) {
7891  return drfs_unknown_error;
7892  }
7893 
7894  drfs_seek_nolock(mtl->pArchiveFile, (dr_int64)(pOpenedFile->offsetInArchive + pOpenedFile->readPointer), drfs_origin_start);
7895  drfs_result result = drfs_read_nolock(mtl->pArchiveFile, pDataOut, bytesToRead, pBytesReadOut);
7896  if (result == drfs_success) {
7897  pOpenedFile->readPointer += bytesToRead;
7898  }
7899 
7900  return result;
7901 }
7902 
7903 static drfs_result drfs_write_file__mtl(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut)
7904 {
7905  (void)archive;
7906  (void)file;
7907  (void)pData;
7908  (void)bytesToWrite;
7909 
7910  assert(archive != NULL);
7911  assert(file != NULL);
7912  assert(pData != NULL);
7913  assert(bytesToWrite > 0);
7914 
7915  // All files are read-only for now.
7916  if (pBytesWrittenOut) {
7917  *pBytesWrittenOut = 0;
7918  }
7919 
7920  return drfs_success;
7921 }
7922 
7923 static drfs_result drfs_seek_file__mtl(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin)
7924 {
7925  (void)archive;
7926 
7927  drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file;
7928  assert(pOpenedFile != NULL);
7929 
7930  dr_uint64 newPos = pOpenedFile->readPointer;
7931  if (origin == drfs_origin_current) {
7932  if ((dr_int64)newPos + bytesToSeek >= 0) {
7933  newPos = (dr_uint64)((dr_int64)newPos + bytesToSeek);
7934  } else {
7935  return drfs_invalid_args; // Trying to seek to before the beginning of the file.
7936  }
7937  } else if (origin == drfs_origin_start) {
7938  assert(bytesToSeek >= 0);
7939  newPos = (dr_uint64)bytesToSeek;
7940  } else if (origin == drfs_origin_end) {
7941  assert(bytesToSeek >= 0);
7942  if ((dr_uint64)bytesToSeek <= pOpenedFile->sizeInBytes) {
7943  newPos = pOpenedFile->sizeInBytes - (dr_uint64)bytesToSeek;
7944  } else {
7945  return drfs_invalid_args; // Trying to seek to before the beginning of the file.
7946  }
7947  } else {
7948  return drfs_unknown_error; // Should never get here.
7949  }
7950 
7951 
7952  if (newPos > pOpenedFile->sizeInBytes) {
7953  return drfs_invalid_args;
7954  }
7955 
7956  pOpenedFile->readPointer = newPos;
7957  return drfs_success;
7958 }
7959 
7960 static dr_uint64 drfs_tell_file__mtl(drfs_handle archive, drfs_handle file)
7961 {
7962  (void)archive;
7963 
7964  drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file;
7965  assert(pOpenedFile != NULL);
7966 
7967  return pOpenedFile->readPointer;
7968 }
7969 
7970 static dr_uint64 drfs_file_size__mtl(drfs_handle archive, drfs_handle file)
7971 {
7972  (void)archive;
7973 
7974  drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file;
7975  assert(pOpenedFile != NULL);
7976 
7977  return pOpenedFile->sizeInBytes;
7978 }
7979 
7980 static void drfs_flush__mtl(drfs_handle archive, drfs_handle file)
7981 {
7982  (void)archive;
7983  (void)file;
7984 
7985  assert(archive != NULL);
7986  assert(file != NULL);
7987 
7988  // All files are read-only for now.
7989 }
7990 
7991 
7992 static void drfs_register_mtl_backend(drfs_context* pContext)
7993 {
7994  if (pContext == NULL) {
7995  return;
7996  }
7997 
7998  drfs_archive_callbacks callbacks;
7999  callbacks.is_valid_extension = drfs_is_valid_extension__mtl;
8000  callbacks.open_archive = drfs_open_archive__mtl;
8001  callbacks.close_archive = drfs_close_archive__mtl;
8002  callbacks.get_file_info = drfs_get_file_info__mtl;
8003  callbacks.begin_iteration = drfs_begin_iteration__mtl;
8004  callbacks.end_iteration = drfs_end_iteration__mtl;
8005  callbacks.next_iteration = drfs_next_iteration__mtl;
8006  callbacks.delete_file = NULL;
8007  callbacks.move_file = NULL;
8008  callbacks.create_directory = NULL;
8009  callbacks.copy_file = NULL;
8010  callbacks.open_file = drfs_open_file__mtl;
8011  callbacks.close_file = drfs_close_file__mtl;
8012  callbacks.read_file = drfs_read_file__mtl;
8013  callbacks.write_file = drfs_write_file__mtl;
8014  callbacks.seek_file = drfs_seek_file__mtl;
8015  callbacks.tell_file = drfs_tell_file__mtl;
8016  callbacks.file_size = drfs_file_size__mtl;
8017  callbacks.flush_file = drfs_flush__mtl;
8018  drfs_register_archive_backend(pContext, callbacks);
8019 }
8020 #endif //DR_FS_NO_MTL
8021 
8022 
8023 
8024 #endif //DR_FS_IMPLEMENTATION
8025 
8026 /*
8027 This is free and unencumbered software released into the public domain.
8028 
8029 Anyone is free to copy, modify, publish, use, compile, sell, or
8030 distribute this software, either in source code form or as a compiled
8031 binary, for any purpose, commercial or non-commercial, and by any
8032 means.
8033 
8034 In jurisdictions that recognize copyright laws, the author or authors
8035 of this software dedicate any and all copyright interest in the
8036 software to the public domain. We make this dedication for the benefit
8037 of the public at large and to the detriment of our heirs and
8038 successors. We intend this dedication to be an overt act of
8039 relinquishment in perpetuity of all present and future rights to this
8040 software under copyright law.
8041 
8042 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
8043 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
8044 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
8045 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
8046 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
8047 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
8048 OTHER DEALINGS IN THE SOFTWARE.
8049 
8050 For more information, please refer to <http://unlicense.org/>
8051 */
dr_bool8
dr_uint8 dr_bool8
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:205
DRFS_CREATE_DIRS
#define DRFS_CREATE_DIRS
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:230
drfs_result
drfs_result
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:236
drfs_archive_callbacks::get_file_info
drfs_get_file_info_proc get_file_info
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:299
drfs_context::archiveCallbacks
drfs_callbacklist archiveCallbacks
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:366
drfs_seek_origin
drfs_seek_origin
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:258
drfs_path_too_long
@ drfs_path_too_long
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:248
drfs_write_line
drfs_result drfs_write_line(drfs_file *pFile, const char *str)
drfs_open
drfs_result drfs_open(drfs_context *pContext, const char *absoluteOrRelativePath, unsigned int accessMode, drfs_file **ppFileOut)
drfs_is_write_directory_guard_enabled
dr_bool32 drfs_is_write_directory_guard_enabled(drfs_context *pContext)
drfs_next
dr_bool32 drfs_next(drfs_context *pContext, drfs_iterator *pIterator)
drfs_result
drfs_result
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:236
dr_uint32
uint32_t dr_uint32
Definition: porcupine/demo/c/dr_libs/old/dr.h:65
drfs_enable_write_directory_guard
void drfs_enable_write_directory_guard(drfs_context *pContext)
drfs_file_info
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:317
drfs_permission_denied
@ drfs_permission_denied
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:243
drfs_does_not_exist
@ drfs_does_not_exist
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:241
dr_int64
int64_t dr_int64
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:202
drfs_invalid_archive
@ drfs_invalid_archive
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:253
NULL
#define NULL
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/extras/speex_resampler/thirdparty/resample.c:92
drfs_archive_callbacks::read_file
drfs_read_file_proc read_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:309
dr_uint8
uint8_t dr_uint8
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:197
drfs_write_file_proc
drfs_result(* drfs_write_file_proc)(drfs_handle archive, drfs_handle file, const void *pData, size_t bytesToWrite, size_t *pBytesWrittenOut)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:288
drfs_open_and_write_binary_file
drfs_result drfs_open_and_write_binary_file(drfs_context *pContext, const char *absoluteOrRelativePath, const void *pData, size_t dataSize)
drfs_origin_end
@ drfs_origin_end
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:262
DRFS_FILE_ATTRIBUTE_READONLY
#define DRFS_FILE_ATTRIBUTE_READONLY
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:233
drfs_find_absolute_path
drfs_result drfs_find_absolute_path(drfs_context *pContext, const char *relativePath, char *absolutePathOut, size_t absolutePathOutSize)
drfs_tell_nolock
dr_uint64 drfs_tell_nolock(drfs_file *pFile)
drfs_eof
dr_bool32 drfs_eof(drfs_file *pFile)
drfs_context
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:363
drfs_archive_callbacks::close_archive
drfs_close_archive_proc close_archive
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:298
errno
int errno
drfs_callbacklist
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:345
DRFS_FILE_ATTRIBUTE_DIRECTORY
#define DRFS_FILE_ATTRIBUTE_DIRECTORY
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:232
dr_uint32
uint32_t dr_uint32
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:201
s
XmlRpcServer s
drfs_create_directory
drfs_result drfs_create_directory(drfs_context *pContext, const char *path)
drfs_find_absolute_path_explicit_base
drfs_result drfs_find_absolute_path_explicit_base(drfs_context *pContext, const char *relativePath, const char *highestPriorityBasePath, char *absolutePathOut, size_t absolutePathOutSize)
_stricmp
DR_INLINE int _stricmp(const char *string1, const char *string2)
Definition: porcupine/demo/c/dr_libs/old/dr.h:178
drfs_move_file
drfs_result drfs_move_file(drfs_context *pContext, const char *pathOld, const char *pathNew)
drfs_archive_callbacks::open_file
drfs_open_file_proc open_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:307
drfs_disable_write_directory_guard
void drfs_disable_write_directory_guard(drfs_context *pContext)
drfs_create_context
drfs_context * drfs_create_context()
drfs_seek_nolock
drfs_result drfs_seek_nolock(drfs_file *pFile, dr_int64 bytesToSeek, drfs_seek_origin origin)
drfs_seek_file_proc
drfs_result(* drfs_seek_file_proc)(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:289
time.h
FALSE
#define FALSE
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/extras/stb_vorbis.c:642
drfs_is_valid_extension_proc
dr_bool32(* drfs_is_valid_extension_proc)(const char *extension)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:274
drfs_invalid_args
@ drfs_invalid_args
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:240
drfs_archive_callbacks::file_size
drfs_file_size_proc file_size
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:313
DR_TRUE
#define DR_TRUE
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:207
drfs_flush_file_proc
void(* drfs_flush_file_proc)(drfs_handle archive, drfs_handle file)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:292
drfs_tell
dr_uint64 drfs_tell(drfs_file *pFile)
drfs_callbacklist::pBuffer
drfs_archive_callbacks * pBuffer
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:347
drfs_delete_file
drfs_result drfs_delete_file(drfs_context *pContext, const char *path)
drfs_archive_callbacks::flush_file
drfs_flush_file_proc flush_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:314
drfs_at_end_of_file
@ drfs_at_end_of_file
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:252
drfs_iterator
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:332
drfs_is_base_directory
dr_bool32 drfs_is_base_directory(drfs_context *pContext, const char *baseDir)
dr_int32
int32_t dr_int32
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:200
DRFS_WRITE
#define DRFS_WRITE
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:227
drfs_get_file_info_proc
drfs_result(* drfs_get_file_info_proc)(drfs_handle archive, const char *relativePath, drfs_file_info *fi)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:277
drfs_archive_callbacks::create_directory
drfs_create_directory_proc create_directory
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:304
strncpy_s
DR_INLINE int strncpy_s(char *dst, size_t dstSizeInBytes, const char *src, size_t count)
Definition: porcupine/demo/c/dr_libs/old/dr.h:162
drfs_size
dr_uint64 drfs_size(drfs_file *pFile)
drfs_size_nolock
dr_uint64 drfs_size_nolock(drfs_file *pFile)
drfs_archive
struct drfs_archive drfs_archive
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:269
drfs_read_file_proc
drfs_result(* drfs_read_file_proc)(drfs_handle archive, drfs_handle file, void *pDataOut, size_t bytesToRead, size_t *pBytesReadOut)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:287
drfs_flush
void drfs_flush(drfs_file *pFile)
drfs_is_existing_directory
dr_bool32 drfs_is_existing_directory(drfs_context *pContext, const char *absoluteOrRelativePath)
drfs_not_directory
@ drfs_not_directory
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:250
drfs_iterator::internalIteratorHandle
drfs_handle internalIteratorHandle
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:338
strcat_s
DR_INLINE int strcat_s(char *dst, size_t dstSizeInBytes, const char *src)
Definition: porcupine/demo/c/dr_libs/old/dr.h:167
drfs_handle
void * drfs_handle
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:266
drfs_basepath::absolutePath
char absolutePath[DRFS_MAX_PATH]
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:353
drfs_out_of_memory
@ drfs_out_of_memory
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:246
drfs_open_file_from_archive
drfs_result drfs_open_file_from_archive(drfs_archive *pArchive, const char *relativePath, unsigned int accessMode, drfs_file **ppFileOut)
drfs_delete_file_proc
drfs_result(* drfs_delete_file_proc)(drfs_handle archive, const char *relativePath)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:281
drfs_init
drfs_result drfs_init(drfs_context *pContext)
drfs_is_existing_file
dr_bool32 drfs_is_existing_file(drfs_context *pContext, const char *absoluteOrRelativePath)
drfs_handle
void * drfs_handle
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:266
drfs_remove_base_directory_by_index
void drfs_remove_base_directory_by_index(drfs_context *pContext, unsigned int index)
drfs_uninit
drfs_result drfs_uninit(drfs_context *pContext)
drfs_too_many_open_files
@ drfs_too_many_open_files
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:244
drfs_already_exists
@ drfs_already_exists
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:242
drfs_success
@ drfs_success
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:238
drfs_open_archive_proc
drfs_result(* drfs_open_archive_proc)(drfs_file *pArchiveFile, unsigned int accessMode, drfs_handle *pHandleOut)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:275
drfs_basedirs::count
unsigned int count
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:360
python.setup.name
name
Definition: porcupine/binding/python/setup.py:69
drfs_context::isWriteGuardEnabled
dr_bool32 isWriteGuardEnabled
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:375
drfs_exists
dr_bool32 drfs_exists(drfs_context *pContext, const char *absoluteOrRelativePath)
drfs_not_in_write_directory
@ drfs_not_in_write_directory
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:247
drfs_no_backend
@ drfs_no_backend
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:245
dr_int16
int16_t dr_int16
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:198
drfs_create_directory_proc
drfs_result(* drfs_create_directory_proc)(drfs_handle archive, const char *relativePath)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:282
drfs_write
drfs_result drfs_write(drfs_file *pFile, const void *pData, size_t bytesToWrite, size_t *pBytesWrittenOut)
drfs_is_archive_path
dr_bool32 drfs_is_archive_path(drfs_context *pContext, const char *path)
drfs_open_owner_archive
drfs_result drfs_open_owner_archive(drfs_context *pContext, const char *absoluteOrRelativePath, unsigned int accessMode, char *relativePathOut, size_t relativePathOutSize, drfs_archive **ppArchiveOut)
dr_uint64
uint64_t dr_uint64
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:203
TRUE
#define TRUE
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/extras/stb_vorbis.c:641
strcpy_s
DR_INLINE int strcpy_s(char *dst, size_t dstSizeInBytes, const char *src)
Definition: porcupine/demo/c/dr_libs/old/dr.h:157
drfs_context::writeBaseDirectory
char writeBaseDirectory[DRFS_MAX_PATH]
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:372
DR_FALSE
#define DR_FALSE
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:208
drfs_archive_callbacks::delete_file
drfs_delete_file_proc delete_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:303
drfs_seek
drfs_result drfs_seek(drfs_file *pFile, dr_int64 bytesToSeek, drfs_seek_origin origin)
dr_uint16
uint16_t dr_uint16
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:199
dr_bool32
dr_uint32 dr_bool32
Definition: porcupine/demo/c/dr_libs/old/dr.h:70
drfs_remove_base_directory
void drfs_remove_base_directory(drfs_context *pContext, const char *absolutePath)
count
size_t count
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/tests/test_common/ma_test_common.c:31
drfs_archive_callbacks::next_iteration
drfs_next_iteration_proc next_iteration
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:302
dr_uint64
uint64_t dr_uint64
Definition: porcupine/demo/c/dr_libs/old/dr.h:67
dr_uint8
uint8_t dr_uint8
Definition: porcupine/demo/c/dr_libs/old/dr.h:61
drfs_begin
dr_bool32 drfs_begin(drfs_context *pContext, const char *absoluteOrRelativePath, drfs_iterator *pIteratorOut)
drfs_open_and_read_binary_file
void * drfs_open_and_read_binary_file(drfs_context *pContext, const char *absoluteOrRelativePath, size_t *pSizeInBytesOut)
drfs_file_info::attributes
unsigned int attributes
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:329
dr_int64
int64_t dr_int64
Definition: porcupine/demo/c/dr_libs/old/dr.h:66
drfs_close_archive
void drfs_close_archive(drfs_archive *pArchive)
drfs_next_iteration_proc
dr_bool32(* drfs_next_iteration_proc)(drfs_handle archive, drfs_handle iterator, drfs_file_info *fi)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:280
drfs_archive_callbacks::open_archive
drfs_open_archive_proc open_archive
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:297
drfs_file_info::lastModifiedTime
dr_uint64 lastModifiedTime
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:326
drfs_callbacklist::count
unsigned int count
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:348
drfs_basepath
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:351
drfs_archive_callbacks::seek_file
drfs_seek_file_proc seek_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:311
drfs_file
struct drfs_file drfs_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:270
drfs_no_space
@ drfs_no_space
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:249
drfs_close_archive_proc
void(* drfs_close_archive_proc)(drfs_handle archive)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:276
drfs_archive_callbacks::write_file
drfs_write_file_proc write_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:310
drfs_write_nolock
drfs_result drfs_write_nolock(drfs_file *pFile, const void *pData, size_t bytesToWrite, size_t *pBytesWrittenOut)
drfs_archive_callbacks
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:294
drfs_archive_callbacks::begin_iteration
drfs_begin_iteration_proc begin_iteration
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:300
drfs_get_base_directory_by_index
const char * drfs_get_base_directory_by_index(drfs_context *pContext, unsigned int index)
drfs_read
drfs_result drfs_read(drfs_file *pFile, void *pDataOut, size_t bytesToRead, size_t *pBytesReadOut)
drfs_negative_seek
@ drfs_negative_seek
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:254
drfs_write_string
drfs_result drfs_write_string(drfs_file *pFile, const char *str)
drfs_archive_callbacks::move_file
drfs_move_file_proc move_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:305
drfs_origin_start
@ drfs_origin_start
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:261
drfs_context::baseDirectories
drfs_basedirs baseDirectories
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:369
drfs_open_and_write_text_file
drfs_result drfs_open_and_write_text_file(drfs_context *pContext, const char *absoluteOrRelativePath, const char *pTextData)
start
ROSCPP_DECL void start()
drfs_get_file_info
drfs_result drfs_get_file_info(drfs_context *pContext, const char *absoluteOrRelativePath, drfs_file_info *fi)
drfs_file_info::sizeInBytes
dr_uint64 sizeInBytes
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:323
drfs_origin_current
@ drfs_origin_current
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:260
write_file
def write_file(filename, text)
drfs_seek_origin
drfs_seek_origin
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:258
drfs_copy_file_proc
drfs_result(* drfs_copy_file_proc)(drfs_handle archive, const char *relativePathSrc, const char *relativePathDst, dr_bool32 failIfExists)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:284
drfs_copy_file
drfs_result drfs_copy_file(drfs_context *pContext, const char *srcPath, const char *dstPath, dr_bool32 failIfExists)
DRFS_READ
#define DRFS_READ
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:226
drfs_iterator::info
drfs_file_info info
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:341
drfs_basedirs::capacity
unsigned int capacity
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:359
drfs_file_info::absolutePath
char absolutePath[DRFS_MAX_PATH]
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:320
drfs_free
void drfs_free(void *p)
drfs_insert_base_directory
void drfs_insert_base_directory(drfs_context *pContext, const char *absolutePath, unsigned int index)
drfs_begin_iteration_proc
drfs_handle(* drfs_begin_iteration_proc)(drfs_handle archive, const char *relativePath)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:278
DRFS_MAX_PATH
#define DRFS_MAX_PATH
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:222
drfs_remove_all_base_directories
void drfs_remove_all_base_directories(drfs_context *pContext)
drfs_unlock
void drfs_unlock(drfs_file *pFile)
drfs_get_base_directory_count
unsigned int drfs_get_base_directory_count(drfs_context *pContext)
drfs_archive_callbacks::is_valid_extension
drfs_is_valid_extension_proc is_valid_extension
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:296
dr_bool32
dr_uint32 dr_bool32
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:206
drfs_file_size_proc
dr_uint64(* drfs_file_size_proc)(drfs_handle archive, drfs_handle file)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:291
porcupine_demo_file.read_file
def read_file(file_name, sample_rate)
Definition: porcupine_demo_file.py:20
drfs_archive_callbacks::close_file
drfs_close_file_proc close_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:308
drfs_close_file_proc
void(* drfs_close_file_proc)(drfs_handle archive, drfs_handle file)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:286
drfs_create_directory_recursive
drfs_result drfs_create_directory_recursive(drfs_context *pContext, const char *path)
drfs_lock
dr_bool32 drfs_lock(drfs_file *pFile)
drfs_archive_callbacks::copy_file
drfs_copy_file_proc copy_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:306
drfs_register_archive_backend
void drfs_register_archive_backend(drfs_context *pContext, drfs_archive_callbacks callbacks)
drfs_open_archive
drfs_result drfs_open_archive(drfs_context *pContext, const char *absoluteOrRelativePath, unsigned int accessMode, drfs_archive **ppArchiveOut)
drfs_archive_callbacks::end_iteration
drfs_end_iteration_proc end_iteration
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:301
drfs_set_base_write_directory
void drfs_set_base_write_directory(drfs_context *pContext, const char *absolutePath)
drfs_basedirs::pBuffer
drfs_basepath * pBuffer
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:358
drfs_end_iteration_proc
void(* drfs_end_iteration_proc)(drfs_handle archive, drfs_handle iterator)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:279
drfs_get_base_write_directory
dr_bool32 drfs_get_base_write_directory(drfs_context *pContext, char *absolutePathOut, unsigned int absolutePathOutSize)
assert.h
DRFS_EXISTING
#define DRFS_EXISTING
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:228
drfs_iterator::pArchive
drfs_archive * pArchive
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:335
dr_int8
int8_t dr_int8
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:196
drfs_tell_file_proc
dr_uint64(* drfs_tell_file_proc)(drfs_handle archive, drfs_handle file)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:290
drfs_basedirs
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:356
drfs_delete_context
void drfs_delete_context(drfs_context *pContext)
drfs_open_and_read_text_file
char * drfs_open_and_read_text_file(drfs_context *pContext, const char *absoluteOrRelativePath, size_t *pSizeInBytesOut)
drfs_too_large
@ drfs_too_large
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:251
drfs_archive_callbacks::tell_file
drfs_tell_file_proc tell_file
Definition: porcupine/demo/c/dr_libs/old/dr_fs.h:312
drfs_end
void drfs_end(drfs_context *pContext, drfs_iterator *pIterator)
drfs_read_nolock
drfs_result drfs_read_nolock(drfs_file *pFile, void *pDataOut, size_t bytesToRead, size_t *pBytesReadOut)
drfs_close
void drfs_close(drfs_file *pFile)
drfs_move_file_proc
drfs_result(* drfs_move_file_proc)(drfs_handle archive, const char *relativePathOld, const char *relativePathNew)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:283
drfs_unknown_error
@ drfs_unknown_error
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:239
drfs_add_base_directory
void drfs_add_base_directory(drfs_context *pContext, const char *absolutePath)
DRFS_TRUNCATE
#define DRFS_TRUNCATE
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:229
drfs_open_file_proc
drfs_result(* drfs_open_file_proc)(drfs_handle archive, const char *relativePath, unsigned int accessMode, drfs_handle *pHandleOut)
Definition: rhino/demo/c/dr_libs/old/dr_fs.h:285


picovoice_driver
Author(s):
autogenerated on Fri Apr 1 2022 02:13:53