forked from mackron/dr_libs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdr_fs.h
8035 lines (6535 loc) · 282 KB
/
dr_fs.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Public Domain. See "unlicense" statement at the end of this file.
//
// Includes code from miniz.c which can be found here: https://github.com/richgel999/miniz
// NOTE: dr_fs is very early in development and should be considered unstable. Expect many APIs to change.
// ABOUT
//
// dr_fs is a simple library which abstracts file IO to allow one to open files from both the native file
// system and archive/package files such as Zip files using a common API.
//
// This file includes code from miniz.c which has been stripped down to include only what's needed to support
// Zip files at basic level. Every public API has been namespaced with "drfs_" to avoid naming conflicts.
//
// Some noteworthy features:
// - Supports verbose absolute paths to avoid ambiguity. For example you can specify a path
// such as "my/package.zip/file.txt"
// - Supports shortened, transparent paths by automatically scanning for supported archives. The
// path "my/package.zip/file.txt" can be shortened to "my/file.txt", for example. This does not
// work for absolute paths, however. See notes below.
// - Fully recursive. A path such as "pack1.zip/pack2.zip/file.txt" should work just fine.
// - Easily supports custom package formats without the need to modify the original source code.
// Look at drfs_register_archive_backend() and the implementation of Zip archives for an
// example.
// - No dependencies except for the C standard library.
//
// Limitations:
// - When a file contained within a Zip file is opened, the entire uncompressed data is loaded
// onto the heap. Keep this in mind when working with large files.
// - Zip, PAK and Wavefront MTL archives are read-only at the moment.
// - dr_fs is not fully thread-safe. See notes below.
// - Asynchronous IO is not supported.
//
//
//
// USAGE
//
// This is a single-file library. To use it, do something like the following in one .c file.
// #define DR_FS_IMPLEMENTATION
// #include "dr_fs.h"
//
// You can then #include dr_fs.h in other parts of the program as you would with any other header file.
//
// Example:
// // Create a context.
// drfs_context* pVFS = drfs_create_context();
// if (pVFS == NULL) {
// // There was an error creating the context.
// }
//
// // Add your base directories for loading from relative paths. If you do not specify at
// // least one base directory you will need to load from absolute paths.
// drfs_add_base_directory(pVFS, "C:/Users/Admin");
// drfs_add_base_directory(pVFS, "C:/My/Folder");
//
// ...
//
// // Open a file. A relative path was specified which means it will first check it against
// // "C:/Users/Admin". If it can't be found it will then check against "C:/My/Folder".
// drfs_file* pFile;
// drfs_result result = drfs_open(pVFS, "my/file.txt", DRFS_READ, &pFile);
// if (result != drfs_success) {
// // There was an error loading the file. It probably doesn't exist.
// }
//
// result = drfs_read(pFile, buffer, bufferSize, NULL);
// if (result != drfs_success) {
// // There was an error reading the file.
// }
//
// drfs_close(pFile);
//
// ...
//
// // Shutdown.
// drfs_delete_context(pVFS);
//
//
//
// OPTIONS
//
// To use these options, just place the appropriate #define's in the .c file before including this file.
//
// #define DR_FS_NO_ZIP
// Disable built-in support for Zip files.
//
// #define DR_FS_NO_PAK
// Disable support for Quake 2 PAK files.
//
// #define DR_FS_NO_MTL
// Disable support for Wavefront MTL files.
//
//
//
// THREAD SAFETY
//
// dr_fs is not fully thread safe. Known unsafe functionality includes:
// - Opening a file while adding or removing base directories and backends
// - Closing a file while doing anything on that file object
// - drfs_open() will malloc() the drfs_file object, and drfs_close() will free() it with no garbage collection
// nor reference counting.
//
// The issues mentioned above should not be an issue for the vast majority of cases. Base directories and backends
// will typically be registered once during initialization when the context is created, and it's unlikely an
// application will want to close a file while simultaneously trying to use it on another thread without doing it's
// own synchronization anyway.
//
// Thread-safety has not been completely ignored either. It is possible to read, write and seek on multiple threads.
// In this case it is a simple matter of first-in first-served. Also, APIs are in place to allow an application to
// do it's own synchronization. An application can use drfs_lock() and drfs_unlock() to lock and unlock a file using
// simple mutal exclusion. Inside the lock/unlock pair the application can then use the "_nolock" variation of the
// relevant APIs:
// - drfs_read_nolock()
// - drfs_write_nolock()
// - drfs_seek_nolock()
// - drfs_tell_nolock()
// - drfs_size_nolock()
//
// Opening two files that share the same archive should work fine across multiple threads. For example, if you have
// an archive called MyArchive.zip and then open two files within that archive, you can do work with each of those
// files independently on separate threads. This functionality depends on the implementation of the relevant backend,
// however.
//
// When implementing a backend, it is important to keep synchronization in mind when reading data from the host
// archive file. To help with this, use the drfs_lock() and drfs_unlock() combined with the "_nolock" variations
// of the APIs listed above.
//
//
//
// QUICK NOTES
//
// - The library works by using the notion of an "archive" to create an abstraction around the file system.
// - Conceptually, and archive is just a grouping of files and folders. An archive can be a directory on the native
// file system or an actual archive file such as a .zip file.
// - When iterating over files and folder, the order is undefined. Do not assume alphabetical.
// - When a path includes the name of the package file, such as "my/package.zip/file.txt" (note how the .zip file is
// included in the path), it is referred to as a verbose path.
// - When specifying an absolute path, it is assumed to be verbose. When specifying a relative path, it does not need
// to be verbose, in which case the library will try to search for it. A path such as "my/package.zip/file.txt" is
// equivalent to "my/file.txt".
// - Archive backends are selected based on their extension.
// - Archive backends cannot currently share the same extension. For example, many package file formats use the .pak
// extension, however only one backend can use that .pak extension.
// - For safety, if you want to overwrite a file you must explicitly call drfs_open() with the DRFS_TRUNCATE flag.
// - Platforms other than Windows do not use buffering. That is, they use read() and write() instead of fread() and
// fwrite().
// - On Linux platforms, if you are having issues with opening files larger than 2GB, make sure this file is the first
// file included in the .c file. This ensures the _LARGEFILE64_SOURCE macro is defined before any other header file
// as required for the use of 64-bit variants of the POSIX APIs.
// - Base paths must be absolute and verbose.
//
//
//
// TODO:
//
// - Test result code consistency.
// - Document performance issues.
// - Consider making it so persistent constant strings (such as base paths) use dynamically allocated strings rather
// than fixed sized arrays of DRFS_MAX_PATH.
// - Replace the miniz reading functionality with a custom one:
// - There is a sort-of-bug where miniz does not correctly enumerate directories in a zip file that was created with
// the "Send to -> Compressed (zipped) folder" functionality in Windows Explorer. This is more of a thing with
// Windows Explorer more than anything, but it'd be nice if it would Just Work.
// - miniz does not support streamed reading yet. Instead, one must decompress the entire file onto the heap which
// is a bit untidy and doesn't work well with very large files.
// - ZIP64 is not supported.
#ifndef dr_fs_h
#define dr_fs_h
// These need to be defined before including any headers, but we don't want to expose it to the public header.
#if defined(DR_FS_IMPLEMENTATION) && !defined(_WIN32)
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#endif
#include <stdint.h>
#include <stddef.h>
#ifndef DR_SIZED_TYPES_DEFINED
#define DR_SIZED_TYPES_DEFINED
#if defined(_MSC_VER) && _MSC_VER < 1600
typedef signed char dr_int8;
typedef unsigned char dr_uint8;
typedef signed short dr_int16;
typedef unsigned short dr_uint16;
typedef signed int dr_int32;
typedef unsigned int dr_uint32;
typedef signed __int64 dr_int64;
typedef unsigned __int64 dr_uint64;
#else
#include <stdint.h>
typedef int8_t dr_int8;
typedef uint8_t dr_uint8;
typedef int16_t dr_int16;
typedef uint16_t dr_uint16;
typedef int32_t dr_int32;
typedef uint32_t dr_uint32;
typedef int64_t dr_int64;
typedef uint64_t dr_uint64;
#endif
typedef dr_int8 dr_bool8;
typedef dr_int32 dr_bool32;
#define DR_TRUE 1
#define DR_FALSE 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
// 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
// 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
// not a problem to increase the size if you are encountering issues. Note that increasing this value will increase memory usage
// on both the heap and the stack.
#ifndef DRFS_MAX_PATH
//#define DRFS_MAX_PATH 256
#define DRFS_MAX_PATH 1024
//#define DRFS_MAX_PATH 4096
#endif
#define DRFS_READ (1 << 0)
#define DRFS_WRITE (1 << 1)
#define DRFS_EXISTING (1 << 2)
#define DRFS_TRUNCATE (1 << 3)
#define DRFS_CREATE_DIRS (1 << 4) // Creates the directory structure if required.
#define DRFS_FILE_ATTRIBUTE_DIRECTORY 0x00000001
#define DRFS_FILE_ATTRIBUTE_READONLY 0x00000002
// Result codes.
typedef enum
{
drfs_success = 0,
drfs_unknown_error = -1,
drfs_invalid_args = -2, // Bad input arguments like a file path string is equal to NULL or whatnot.
drfs_does_not_exist = -3,
drfs_already_exists = -4,
drfs_permission_denied = -5,
drfs_too_many_open_files = -6,
drfs_no_backend = -7,
drfs_out_of_memory = -8,
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.
drfs_path_too_long = -10,
drfs_no_space = -11,
drfs_not_directory = -12,
drfs_too_large = -13,
drfs_at_end_of_file = -14,
drfs_invalid_archive = -15,
drfs_negative_seek = -16
} drfs_result;
// The allowable seeking origins.
typedef enum
{
drfs_origin_current,
drfs_origin_start,
drfs_origin_end
}drfs_seek_origin;
typedef void* drfs_handle;
typedef struct drfs_context drfs_context;
typedef struct drfs_archive drfs_archive;
typedef struct drfs_file drfs_file;
typedef struct drfs_file_info drfs_file_info;
typedef struct drfs_iterator drfs_iterator;
typedef dr_bool32 (* drfs_is_valid_extension_proc)(const char* extension);
typedef drfs_result (* drfs_open_archive_proc) (drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut);
typedef void (* drfs_close_archive_proc) (drfs_handle archive);
typedef drfs_result (* drfs_get_file_info_proc) (drfs_handle archive, const char* relativePath, drfs_file_info* fi);
typedef drfs_handle (* drfs_begin_iteration_proc) (drfs_handle archive, const char* relativePath);
typedef void (* drfs_end_iteration_proc) (drfs_handle archive, drfs_handle iterator);
typedef dr_bool32 (* drfs_next_iteration_proc) (drfs_handle archive, drfs_handle iterator, drfs_file_info* fi);
typedef drfs_result (* drfs_delete_file_proc) (drfs_handle archive, const char* relativePath);
typedef drfs_result (* drfs_create_directory_proc) (drfs_handle archive, const char* relativePath);
typedef drfs_result (* drfs_move_file_proc) (drfs_handle archive, const char* relativePathOld, const char* relativePathNew);
typedef drfs_result (* drfs_copy_file_proc) (drfs_handle archive, const char* relativePathSrc, const char* relativePathDst, dr_bool32 failIfExists);
typedef drfs_result (* drfs_open_file_proc) (drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut);
typedef void (* drfs_close_file_proc) (drfs_handle archive, drfs_handle file);
typedef drfs_result (* drfs_read_file_proc) (drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut);
typedef drfs_result (* drfs_write_file_proc) (drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut);
typedef drfs_result (* drfs_seek_file_proc) (drfs_handle archive, drfs_handle file, int64_t bytesToSeek, drfs_seek_origin origin);
typedef uint64_t (* drfs_tell_file_proc) (drfs_handle archive, drfs_handle file);
typedef uint64_t (* drfs_file_size_proc) (drfs_handle archive, drfs_handle file);
typedef void (* drfs_flush_file_proc) (drfs_handle archive, drfs_handle file);
typedef struct
{
drfs_is_valid_extension_proc is_valid_extension;
drfs_open_archive_proc open_archive;
drfs_close_archive_proc close_archive;
drfs_get_file_info_proc get_file_info;
drfs_begin_iteration_proc begin_iteration;
drfs_end_iteration_proc end_iteration;
drfs_next_iteration_proc next_iteration;
drfs_delete_file_proc delete_file;
drfs_create_directory_proc create_directory;
drfs_move_file_proc move_file;
drfs_copy_file_proc copy_file;
drfs_open_file_proc open_file;
drfs_close_file_proc close_file;
drfs_read_file_proc read_file;
drfs_write_file_proc write_file;
drfs_seek_file_proc seek_file;
drfs_tell_file_proc tell_file;
drfs_file_size_proc file_size;
drfs_flush_file_proc flush_file;
} drfs_archive_callbacks;
struct drfs_file_info
{
// The absolute path of the file.
char absolutePath[DRFS_MAX_PATH];
// The size of the file, in bytes.
uint64_t sizeInBytes;
// The time the file was last modified.
uint64_t lastModifiedTime;
// File attributes.
unsigned int attributes;
};
struct drfs_iterator
{
// A pointer to the archive that contains the folder being iterated.
drfs_archive* pArchive;
// A pointer to the iterator's internal handle that was returned with begin_iteration().
drfs_handle internalIteratorHandle;
// The file info.
drfs_file_info info;
};
// Creates an empty context.
drfs_context* drfs_create_context();
// Deletes the given context.
//
// This does not close any files or archives - it is up to the application to ensure those are tidied up.
void drfs_delete_context(drfs_context* pContext);
// Registers an archive back-end.
void drfs_register_archive_backend(drfs_context* pContext, drfs_archive_callbacks callbacks);
// Inserts a base directory at a specific priority position.
//
// A lower value index means a higher priority. This must be in the range of [0, drfs_get_base_directory_count()].
void drfs_insert_base_directory(drfs_context* pContext, const char* absolutePath, unsigned int index);
// Adds a base directory to the end of the list.
//
// The further down the list the base directory, the lower priority is will receive. This adds it to the end which
// means it it given a lower priority to those that are already in the list. Use drfs_insert_base_directory() to
// insert the base directory at a specific position.
//
// Base directories must be an absolute path to a real directory.
void drfs_add_base_directory(drfs_context* pContext, const char* absolutePath);
// Removes the given base directory.
void drfs_remove_base_directory(drfs_context* pContext, const char* absolutePath);
// Removes the directory at the given index.
//
// If you need to remove every base directory, use drfs_remove_all_base_directories() since that is more efficient.
void drfs_remove_base_directory_by_index(drfs_context* pContext, unsigned int index);
// Removes every base directory from the given context.
void drfs_remove_all_base_directories(drfs_context* pContext);
// Retrieves the number of base directories attached to the given context.
unsigned int drfs_get_base_directory_count(drfs_context* pContext);
// Retrieves the base directory at the given index.
const char* drfs_get_base_directory_by_index(drfs_context* pContext, unsigned int index);
// Sets the base directory for write operations (including delete).
//
// When doing a write operation using a relative path, the full path will be resolved using this directory as the base.
//
// If the base write directory is not set, and absolute path must be used for all write operations.
//
// If the write directory guard is enabled, all write operations that are attempted at a higher level than this directory
// will fail.
void drfs_set_base_write_directory(drfs_context* pContext, const char* absolutePath);
// Retrieves the base write directory.
dr_bool32 drfs_get_base_write_directory(drfs_context* pContext, char* absolutePathOut, unsigned int absolutePathOutSize);
// Enables the write directory guard.
void drfs_enable_write_directory_guard(drfs_context* pContext);
// Disables the write directory guard.
void drfs_disable_write_directory_guard(drfs_context* pContext);
// Determines whether or not the base directory guard is enabled.
dr_bool32 drfs_is_write_directory_guard_enabled(drfs_context* pContext);
// Opens an archive at the given path.
//
// If the given path points to a directory on the native file system an archive will be created at that
// directory. If the path points to an archive file such as a .zip file, dr_fs will hold a handle to
// that file until the archive is closed with drfs_close_archive(). Keep this in mind if you are keeping
// many archives open at a time on platforms that limit the number of files one can have open at any given
// time.
//
// The given path must be either absolute, or relative to one of the base directories.
//
// The path can be nested, such as "C:/my_zip_file.zip/my_inner_zip_file.zip".
drfs_result drfs_open_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_archive** ppArchiveOut);
// Opens the archive that owns the given file.
//
// This is different to drfs_open_archive() in that it can accept non-archive files. It will open the
// archive that directly owns the file. In addition, it will output the path of the file, relative to the
// archive.
//
// If the given file is an archive itself, the archive that owns that archive will be opened. If the file
// is a file on the native file system, the returned archive will represent the folder it's directly
// contained in.
drfs_result drfs_open_owner_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut);
// Closes the given archive.
void drfs_close_archive(drfs_archive* pArchive);
// Opens a file relative to the given archive.
drfs_result drfs_open_file_from_archive(drfs_archive* pArchive, const char* relativePath, unsigned int accessMode, drfs_file** ppFileOut);
// Opens a file.
//
// When opening the file in write mode, the write pointer will always be sitting at the start of the file.
drfs_result drfs_open(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_file** ppFileOut);
// Closes the given file.
void drfs_close(drfs_file* pFile);
// Reads data from the given file.
//
// 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.
//
// 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>.
drfs_result drfs_read(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut);
// Writes data to the given file.
drfs_result drfs_write(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut);
// Seeks the file pointer by the given number of bytes, relative to the specified origin.
drfs_result drfs_seek(drfs_file* pFile, int64_t bytesToSeek, drfs_seek_origin origin);
// Retrieves the current position of the file pointer.
uint64_t drfs_tell(drfs_file* pFile);
// Retrieves the size of the given file.
uint64_t drfs_size(drfs_file* pFile);
// Flushes the given file.
void drfs_flush(drfs_file* pFile);
// Locks the given file for simple mutal exclusion.
//
// If DR_FALSE is returned it means there was an error and the operation should be aborted.
dr_bool32 drfs_lock(drfs_file* pFile);
// Unlocks the given file for simple mutal exclusion.
void drfs_unlock(drfs_file* pFile);
// Unlocked drfs_read() - should only be called inside a drfs_lock()/drfs_unlock() pair.
drfs_result drfs_read_nolock(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut);
// Unlocked drfs_write() - should only be called inside a drfs_lock()/drfs_unlock() pair.
drfs_result drfs_write_nolock(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut);
// Unlocked drfs_seek() - should only be called inside a drfs_lock()/drfs_unlock() pair.
drfs_result drfs_seek_nolock(drfs_file* pFile, int64_t bytesToSeek, drfs_seek_origin origin);
// Unlocked drfs_tell() - should only be called inside a drfs_lock()/drfs_unlock() pair.
uint64_t drfs_tell_nolock(drfs_file* pFile);
// Unlocked drfs_size() - should only be called inside a drfs_lock()/drfs_unlock() pair.
uint64_t drfs_size_nolock(drfs_file* pFile);
// Retrieves information about the file at the given path.
//
// <fi> is allowed to be null, in which case the call is equivalent to simply checking if the file exists.
drfs_result drfs_get_file_info(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_file_info* fi);
// Creates an iterator for iterating over the files and folders in the given directory.
dr_bool32 drfs_begin(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_iterator* pIteratorOut);
// Goes to the next file or folder based on the given iterator.
dr_bool32 drfs_next(drfs_context* pContext, drfs_iterator* pIterator);
// Closes the given iterator.
//
// This is not needed if drfs_next_iteration() returns DR_FALSE naturally. If iteration is terminated early, however, this
// needs to be called on the iterator to ensure internal resources are freed.
void drfs_end(drfs_context* pContext, drfs_iterator* pIterator);
// Deletes the file at the given path.
//
// The path must be a absolute, or relative to the write directory.
drfs_result drfs_delete_file(drfs_context* pContext, const char* path);
// Creates a directory.
//
// The path must be a absolute, or relative to the write directory.
drfs_result drfs_create_directory(drfs_context* pContext, const char* path);
// Moves or renames the given file.
//
// The path must be a absolute, or relative to the write directory. This will fail if:
// - the file already exists
// - the old and new paths are across different archives
// - the archive containing both the old and new paths is read-only
// - the destinations directory structure does not exist
//
// Consider using drfs_copy_file() for more flexibility with moving files to a different location.
drfs_result drfs_move_file(drfs_context* pContext, const char* pathOld, const char* pathNew);
// Copies a file.
//
// The destination path must be a absolute, or relative to the write directory.
drfs_result drfs_copy_file(drfs_context* pContext, const char* srcPath, const char* dstPath, dr_bool32 failIfExists);
// Determines whether or not the given path refers to an archive file.
//
// This does not validate that the archive file exists or is valid. This will also return DR_FALSE if the path refers
// to a folder on the normal file system.
//
// Use drfs_open_archive() to check that the archive file actually exists.
dr_bool32 drfs_is_archive_path(drfs_context* pContext, const char* path);
///////////////////////////////////////////////////////////////////////////////
//
// High Level API
//
///////////////////////////////////////////////////////////////////////////////
// TODO: Change the boolean return codes to drfs_result.
// Free's memory that was allocated internally by dr_fs. This is used when dr_fs allocates memory via a high-level helper API
// and the application is done with that memory.
void drfs_free(void* p);
// Finds the absolute, verbose path of the given path.
dr_bool32 drfs_find_absolute_path(drfs_context* pContext, const char* relativePath, char* absolutePathOut, size_t absolutePathOutSize);
// Finds the absolute, verbose path of the given path, using the given path as the higest priority base path.
dr_bool32 drfs_find_absolute_path_explicit_base(drfs_context* pContext, const char* relativePath, const char* highestPriorityBasePath, char* absolutePathOut, size_t absolutePathOutSize);
// Helper function for determining whether or not the given path refers to a base directory.
dr_bool32 drfs_is_base_directory(drfs_context* pContext, const char* baseDir);
// Helper function for writing a string.
dr_bool32 drfs_write_string(drfs_file* pFile, const char* str);
// Helper function for writing a string, and then inserting a new line right after it.
//
// The new line character is "\n" and NOT "\r\n".
dr_bool32 drfs_write_line(drfs_file* pFile, const char* str);
// Helper function for opening a binary file and retrieving it's data in one go.
//
// Free the returned pointer with drfs_free()
void* drfs_open_and_read_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut);
// Helper function for opening a text file and retrieving it's data in one go.
//
// Free the returned pointer with drfs_free().
//
// The returned string is null terminated. The size returned by pSizeInBytesOut does not include the null terminator.
char* drfs_open_and_read_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut);
// Helper function for opening a file, writing the given data, and then closing it. This deletes the contents of the existing file, if any.
dr_bool32 drfs_open_and_write_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, const void* pData, size_t dataSize);
// 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.
dr_bool32 drfs_open_and_write_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, const char* pTextData);
// Helper function for determining whether or not the given path refers to an existing file or directory.
dr_bool32 drfs_exists(drfs_context* pContext, const char* absoluteOrRelativePath);
// Determines if the given path refers to an existing file (not a directory).
//
// This will return DR_FALSE for directories. Use drfs_exists() to check for either a file or directory.
dr_bool32 drfs_is_existing_file(drfs_context* pContext, const char* absoluteOrRelativePath);
// Determines if the given path refers to an existing directory.
dr_bool32 drfs_is_existing_directory(drfs_context* pContext, const char* absoluteOrRelativePath);
// Same as drfs_create_directory(), except creates the entire directory structure recursively.
drfs_result drfs_create_directory_recursive(drfs_context* pContext, const char* path);
// Determines whether or not the given file is at the end.
//
// This is just a high-level helper function equivalent to drfs_tell(pFile) == drfs_size(pFile).
dr_bool32 drfs_eof(drfs_file* pFile);
#ifdef __cplusplus
}
#endif
#endif //dr_fs_h
///////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////////////
#ifdef DR_FS_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <strings.h>
#include <errno.h>
#include <pthread.h>
#endif
// Whether or not the file owns the archive object it's part of.
#define DR_FS_OWNS_PARENT_ARCHIVE 0x00000001
static int drfs__strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
{
#ifdef _MSC_VER
return strcpy_s(dst, dstSizeInBytes, src);
#else
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
size_t i;
for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
if (i < dstSizeInBytes) {
dst[i] = '\0';
return 0;
}
dst[0] = '\0';
return ERANGE;
#endif
}
static int drfs__strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
#ifdef _MSC_VER
return strncpy_s(dst, dstSizeInBytes, src, count);
#else
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return EINVAL;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
size_t maxcount = count;
if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE
maxcount = dstSizeInBytes - 1;
}
size_t i;
for (i = 0; i < maxcount && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
if (src[i] == '\0' || i == count || count == ((size_t)-1)) {
dst[i] = '\0';
return 0;
}
dst[0] = '\0';
return ERANGE;
#endif
}
static int drfs__strcat_s(char* dst, size_t dstSizeInBytes, const char* src)
{
#ifdef _MSC_VER
return strcat_s(dst, dstSizeInBytes, src);
#else
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
char* dstorig = dst;
while (dstSizeInBytes > 0 && dst[0] != '\0') {
dst += 1;
dstSizeInBytes -= 1;
}
if (dstSizeInBytes == 0) {
return EINVAL; // Unterminated.
}
while (dstSizeInBytes > 0 && src[0] != '\0') {
*dst++ = *src++;
dstSizeInBytes -= 1;
}
if (dstSizeInBytes > 0) {
dst[0] = '\0';
} else {
dstorig[0] = '\0';
return ERANGE;
}
return 0;
#endif
}
static int drfs__stricmp(const char* string1, const char* string2)
{
#ifdef _MSC_VER
return _stricmp(string1, string2);
#else
return strcasecmp(string1, string2);
#endif
}
typedef struct
{
// The absolute path of the base path.
char absolutePath[DRFS_MAX_PATH];
} drfs_basepath;
typedef struct
{
// A pointer to the buffer containing the list of base paths.
drfs_basepath* pBuffer;
// The size of the buffer, in drfs_basepath's.
unsigned int bufferSize;
// The number of items in the list.
unsigned int count;
} drfs_basedirs;
static dr_bool32 drfs_basedirs_init(drfs_basedirs* pBasePaths)
{
if (pBasePaths == NULL) {
return DR_FALSE;
}
pBasePaths->pBuffer = 0;
pBasePaths->bufferSize = 0;
pBasePaths->count = 0;
return DR_TRUE;
}
static void drfs_basedirs_uninit(drfs_basedirs* pBasePaths)
{
if (pBasePaths == NULL) {
return;
}
free(pBasePaths->pBuffer);
}
static dr_bool32 drfs_basedirs_inflateandinsert(drfs_basedirs* pBasePaths, const char* absolutePath, unsigned int index)
{
if (pBasePaths == NULL) {
return DR_FALSE;
}
unsigned int newBufferSize = (pBasePaths->bufferSize == 0) ? 2 : pBasePaths->bufferSize*2;
drfs_basepath* pOldBuffer = pBasePaths->pBuffer;
drfs_basepath* pNewBuffer = (drfs_basepath*)malloc(newBufferSize * sizeof(drfs_basepath));
if (pNewBuffer == NULL) {
return DR_FALSE;
}
for (unsigned int iDst = 0; iDst < index; ++iDst) {
memcpy(pNewBuffer + iDst, pOldBuffer + iDst, sizeof(drfs_basepath));
}
drfs__strcpy_s((pNewBuffer + index)->absolutePath, DRFS_MAX_PATH, absolutePath);
for (unsigned int iDst = index; iDst < pBasePaths->count; ++iDst) {
memcpy(pNewBuffer + iDst + 1, pOldBuffer + iDst, sizeof(drfs_basepath));
}
pBasePaths->pBuffer = pNewBuffer;
pBasePaths->bufferSize = newBufferSize;
pBasePaths->count += 1;
free(pOldBuffer);
return DR_TRUE;
}
static dr_bool32 drfs_basedirs_movedown1slot(drfs_basedirs* pBasePaths, unsigned int index)
{
if (pBasePaths == NULL || pBasePaths->count >= pBasePaths->bufferSize) {
return DR_FALSE;
}
for (unsigned int iDst = pBasePaths->count; iDst > index; --iDst) {
memcpy(pBasePaths->pBuffer + iDst, pBasePaths->pBuffer + iDst - 1, sizeof(drfs_basepath));
}
return DR_TRUE;
}
static dr_bool32 drfs_basedirs_insert(drfs_basedirs* pBasePaths, const char* absolutePath, unsigned int index)
{
if (pBasePaths == NULL || index > pBasePaths->count) {
return DR_FALSE;
}
if (pBasePaths->count == pBasePaths->bufferSize) {
return drfs_basedirs_inflateandinsert(pBasePaths, absolutePath, index);
} else {
if (!drfs_basedirs_movedown1slot(pBasePaths, index)) {
return DR_FALSE;
}
drfs__strcpy_s((pBasePaths->pBuffer + index)->absolutePath, DRFS_MAX_PATH, absolutePath);
pBasePaths->count += 1;
return DR_TRUE;
}
}
static dr_bool32 drfs_basedirs_remove(drfs_basedirs* pBasePaths, unsigned int index)
{
if (pBasePaths == NULL || index >= pBasePaths->count) {
return DR_FALSE;
}
assert(pBasePaths->count > 0);
for (unsigned int iDst = index; iDst < pBasePaths->count - 1; ++iDst) {
memcpy(pBasePaths->pBuffer + iDst, pBasePaths->pBuffer + iDst + 1, sizeof(drfs_basepath));
}
pBasePaths->count -= 1;
return DR_TRUE;
}
static void drfs_basedirs_clear(drfs_basedirs* pBasePaths)
{
if (pBasePaths == NULL) {
return;
}
drfs_basedirs_uninit(pBasePaths);
drfs_basedirs_init(pBasePaths);
}
typedef struct
{
// A pointer to the buffer containing the list of base paths.
drfs_archive_callbacks* pBuffer;
// The number of items in the list and buffer. This is a tightly packed list so the size of the buffer is always the
// same as the count.
unsigned int count;
} drfs_callbacklist;
static dr_bool32 drfs_callbacklist_init(drfs_callbacklist* pList)
{
if (pList == NULL) {
return DR_FALSE;
}
pList->pBuffer = 0;
pList->count = 0;
return DR_TRUE;
}
static void drfs_callbacklist_uninit(drfs_callbacklist* pList)
{
if (pList == NULL) {
return;
}
free(pList->pBuffer);
}
static dr_bool32 drfs_callbacklist_inflate(drfs_callbacklist* pList)
{
if (pList == NULL) {
return DR_FALSE;
}
drfs_archive_callbacks* pOldBuffer = pList->pBuffer;
drfs_archive_callbacks* pNewBuffer = (drfs_archive_callbacks*)malloc((pList->count + 1) * sizeof(drfs_archive_callbacks));
if (pNewBuffer == NULL) {
return DR_FALSE;
}
for (unsigned int iDst = 0; iDst < pList->count; ++iDst) {
memcpy(pNewBuffer + iDst, pOldBuffer + iDst, sizeof(drfs_archive_callbacks));
}
pList->pBuffer = pNewBuffer;
free(pOldBuffer);
return DR_TRUE;
}
static dr_bool32 drfs_callbacklist_pushback(drfs_callbacklist* pList, drfs_archive_callbacks callbacks)
{
if (pList == NULL) {
return DR_FALSE;
}
if (!drfs_callbacklist_inflate(pList)) {
return DR_FALSE;
}
pList->pBuffer[pList->count] = callbacks;
pList->count += 1;
return DR_TRUE;
}
struct drfs_context
{
// The list of archive callbacks which are used for loading non-native archives. This does not include the native callbacks.
drfs_callbacklist archiveCallbacks;
// The list of base directories.
drfs_basedirs baseDirectories;
// The write base directory.
char writeBaseDirectory[DRFS_MAX_PATH];