-
Notifications
You must be signed in to change notification settings - Fork 0
/
libSUarchive.h
2075 lines (1769 loc) · 60.1 KB
/
libSUarchive.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
/*
libSWAarchive - a light, fast and portable library for handling Sonic World
Adventure's/Unleashed's archive file formats (.ar/.arl).
1. Introduction
===========================================================================
- To use the library, you must do the following in EXACTLY _one_ C/C++ file:
```c
#define SISWA_ARCHIVE_IMPLEMENTATION
#include "libSUarchive.h"
```
Once that's set, other files do not require the '#define' line.
- There are two primary methods of using the libSUarchive library. The first
one is using 'siswa_<ar/arl>Make(const char*)`, which utilizes the C standard
library's IO functions, and its heap memory allocation functions. Making use
of this method requires the C standard, as well as to call 'siswa_<ar/arl>Free`
or simply `free(var.data)` at the end.
If you cannot use the IO functions or want to specify your own buffer, the
`siswa_<ar/arl>MakeBuffer(void*, size_t)` functions achieve the same results
as the previously mentioned ones, except a buffer and the size of it is requested.
Both methods also contain expanded versions (siswa_<func>Ex), where you can
either specify for more memory to be allocated (required for adding more
entries) or to specify the full capacity of the buffer.
2. Configuration macros
===========================================================================
- For any of the macros to work, you must _always_ define it before including
the library. Example:
```c
#define SISWA_ARCHIVE_IMPLEMENTATION
#define SISWA_NO_STDLIB
#include "libSUarchive.h"
```
1. SISWA_NO_STDLIB:
- Disables the inclusion of the C standard and IO libraries, meaning
functions that utilizes said libraries (like 'siswa_arMake') won't work.
2. SISWA_NO_ASSERT:
- Disables any assertions in the codebase. Recommended to use for when
building the release version of the program. Defining NDEBUG achieves
the same result.
3. SISWA_NO_STDINT
- Disables the includes of <stdint.h> and <stddef.h>. If a non C99 compiler
is used, siswa will attempt to automatically find the correct types. If
the wrong types are found or specified, static assertions will be fired
out and in that situation you'll have to specify the correct types.
The list of types being:
- uint32_t, int32_t - 4 bytes, signed and unsigned.
- uint64_t, int64_t - 8 bytes, signed and unsigned.
- size_t - must equal to 'sizeof(void*)' bytes, unsigned.
4. SISWA_NO_DECOMPRESSION
- Completely disables any decompression features in the library.
5. SISWA_DEFINE_CUSTOM_DEFLATE_DECOMPRESSION
- Disables siswa's implementation of Deflate decompression, but keeps
intact SEGS decompression functions like 'siswa_arDecompressSegs'.
In turn the user must implement their own 'siswa_decompressDeflate'
function inside their source.
6. SISWA_USE_PRAGMA_PACK
- Uses '#pragma pack(push, 1)' for every struct inside the file to achieve
guaranteed correct struct sizes. Turned off by default for portability reasons.
7. SISWA_MEMCPY, SISWA_STRLEN, SISWA_STRNCMP or SISWA_MEMSET
- Replaces base C standard library version of the function with the
specified custom one when they're called in the library.
3. Other
===========================================================================
CREDITS:
- HedgeServer (discord) - helping out to figure out the behaviour and format
of .ar/.arl files.
- HedgeLib (https://github.com/Radfordhound/HedgeLib) - some of the header
documentation were taken from 'hl_hh_archive.h'.
- sinfl.h (https://github.com/vurtun/lib) - the base code forming the Deflate
decompressing function in siswa.
- General Schnitzel - some general help, moral support(?) as well as providing
useful .ar files to test the library with.
LICENSE:
- This software is licensed under the zlib license (see the LICENSE in the
bottom of the file).
NOTICE:
- This library is _slightly_ experimental and features may not work as
expected.
- This also means that functions may not be documented fully.
*/
#ifndef SISWA_INCLUDE_SISWA_H
#define SISWA_INCLUDE_SISWA_H
#if defined(__cplusplus)
extern "C" {
#endif
#ifndef SISWA_NO_STDINT
#if defined(__STDC_VERSION__) || (defined(__cplusplus) && __cplusplus >= 201103L)
#include <stdint.h>
#endif
#include <stddef.h>
#endif
#ifndef SISWA_NO_STDLIB
#include <stdlib.h>
#include <stdio.h>
#endif
#ifdef NDEBUG
#define SISWA_NO_ASSERT
#endif
#ifndef SISWA_NO_ASSERT
#ifndef SISWA_ASSERT_MSG
#define SISWA_ASSERT_MSG(condition, msg) do { \
if ((condition) == 0) { \
fprintf( \
stderr, \
"Assertion \"" #condition "\" at \"" __FILE__ ":%d\": " #msg ".\n", \
__LINE__ \
); \
abort(); \
} \
} while (0)
#endif
#else
#define SISWA_ASSERT_MSG(condition, msg) ((void)(condition), (void)(msg))
#endif
#ifndef SISWA_MEMCPY
#include <string.h>
#define SISWA_MEMCPY memcpy
#define SISWA_MEMSET memset
#endif
#ifndef SISWA_STRNCMP
#include <string.h>
#define SISWA_STRNCMP strncmp
#endif
#ifndef SISWA_STRLEN
#include <string.h>
#define SISWA_STRLEN strlen
#endif
#define SISWA_ASSERT_NOT_NULL(ptr) SISWA_ASSERT_MSG((ptr) != NULL, #ptr " must not be NULL.")
#define SISWA_ASSERT(condition) SISWA_ASSERT_MSG(condition, "Assertion '" #condition "` failed")
#define SISWA_PANIC() SISWA_ASSERT_MSG(SISWA_FALSE, "SISWA_PANIC()! Wrong data was given")
#define SISWA_STATIC_ASSERT2(cond, msg) typedef char static_assertion_##msg[(!!(cond)) * 2 - 1] /* NOTE(EimaMei): This is absolutely stupid but somehow it works so who cares */
#define SISWA_STATIC_ASSERT1(cond, line) SISWA_STATIC_ASSERT2(cond, line)
#define SISWA_STATIC_ASSERT(cond) SISWA_STATIC_ASSERT1(cond, __LINE__)
#if defined(SISWA_NO_STDINT) || !defined(UINT32_MAX)
#ifndef uint8_t
typedef unsigned char uint8_t;
#endif
#ifndef uint16_t
typedef unsigned short uint16_t;
#endif
#ifndef int32_t
typedef signed int int32_t;
#endif
#ifndef uint32_t
typedef unsigned int uint32_t;
#endif
#ifndef int64_t
#if defined(_win32) || defined(_win64) || (defined(__cygwin__) && !defined(_win32))
typedef signed __int64 int64_t;
#else
typedef signed long int64_t;
#endif
#endif
#ifndef yint64_t
#if defined(_win32) || defined(_win64) || (defined(__cygwin__) && !defined(_win32))
typedef unsigned __int64 int64_t;
#else
typedef unsigned long uint64_t;
#endif
#endif
#endif
SISWA_STATIC_ASSERT(sizeof(uint8_t) == 1);
SISWA_STATIC_ASSERT(sizeof(uint16_t) == 2);
SISWA_STATIC_ASSERT(sizeof(int32_t) == 4);
SISWA_STATIC_ASSERT(sizeof(uint32_t) == 4);
SISWA_STATIC_ASSERT(sizeof(int64_t) == 8);
SISWA_STATIC_ASSERT(sizeof(uint64_t) == 8);
SISWA_STATIC_ASSERT(sizeof(size_t) == sizeof(void*));
#ifndef siByte
typedef uint8_t siByte;
#endif
typedef uint32_t siBool;
/* The alignment value set in the archive files. The actual proper definition
* would be the processor's computing bit (hence why Sonic Adventure uses 64 for the
* alignment as the X360/PS3 are both 64-bit). If for whatever reason you're planning to use the
* library on different computing bit CPUs, you should change this value to the
* value that matches the computing bit of the CPU, or set it as '(sizeof(size_t) * 8)'.*/
#define SISWA_DEFAULT_HEADER_ALIGNMENT 64
/* Used for storing string pointers. The higher the value, the less likely a hash
* collision will happen. 8kb is a good middleground balance on the major OSses,
* but probably not for actual embedded systems.*/
#define SISWA_DEFAULT_STACK_SIZE (8 * 1024)
#define SISWA_TRUE 1
#define SISWA_FALSE 0
#define SISWA_SUCCESS SISWA_TRUE
#define SISWA_FAILURE SISWA_FALSE
/* Length of the array can be anything from 1 to beyond. */
#define SISWA_UNSPECIFIED_LEN 1
#define SISWA_IDENTIFIER_ARL2 0x324C5241
#define SISWA_IDENTIFIER_XCOMPRESSION 0xEE12F50F
#define SISWA_IDENTIFIER_SEGS 0x73676573
#ifdef SISWA_USE_PRAGMA_PACK
#pragma pack(push, 1)
#endif
typedef enum {
SISWA_FILE_REGULAR = 1,
SISWA_FILE_INVALID,
SISWA_FILE_XCOMPRESS,
SISWA_FILE_SEGS
} siFileType;
typedef struct {
uint32_t identifier;
uint16_t dummy;
uint16_t chunks;
uint32_t fullSize;
uint32_t fullZsize;
} siSegsHeader;
SISWA_STATIC_ASSERT(sizeof(siSegsHeader) == 16);
typedef struct {
uint16_t zSize;
uint16_t size;
uint32_t offset;
} siSegsEntry;
SISWA_STATIC_ASSERT(sizeof(siSegsEntry) == 8);
typedef struct { /* NOTE(EimaMei): All credit goes to https://github.com/mistydemeo/quickbms/blob/master/unz.c */
uint32_t identifier;
uint16_t version;
uint16_t reserved;
uint32_t contextFlags;
uint32_t flags;
uint32_t windowSize;
uint32_t compressionPartitionSize;
uint64_t uncompressedSize;
uint64_t compressedSize;
uint32_t uncompressedBlockSize;
uint32_t compressedBlockSizeMax;
} siXCompHeader;
SISWA_STATIC_ASSERT(sizeof(siXCompHeader) == 48);
typedef struct {
uint32_t compressedSize;
} siXCompEntry;
SISWA_STATIC_ASSERT(sizeof(siXCompEntry) == 4);
typedef struct {
/* Always 0. */
uint32_t unknown;
/* Size of the header struct. Same as sizeof(siArHeader), always 16 bytes. */
uint32_t headerSizeof;
/* Size of the entry struct. Same as sizeof(siArEntry), always 20 bytes. */
uint32_t entrySizeof;
/* Alignment that's used, usually 64 for 64-bit CPUs. */
uint32_t alignment;
} siArHeader;
SISWA_STATIC_ASSERT(sizeof(siArHeader) == 16);
typedef struct {
/* Entire size of the entry. Equivalent to '.dataSize + .offset + sizeof(siArEntry) = .size'. */
uint32_t size;
/* Size of the file data itself. */
uint32_t dataSize;
/* Pointer offset to the data. Usually equivalent to 'strlen(name) + pad + sizeof(siArEntry) = .offset'. */
uint32_t offset;
/* An unsigned 64-bit file date int. Can be expressed as a pointer to an uint64_t. */
uint8_t filedate[8];
} siArEntry;
SISWA_STATIC_ASSERT(sizeof(siArEntry) == 20);
typedef struct {
/* Pointer to the contents of the data.
* NOTE: If `siswa_<ar/arl>Make' or 'siswa_<ar/arl>CreateContent' was used
* to create this structure, the data was malloced and must be freed after use. */
siByte* data;
/* Current length of the content. Changes after each entry modification. */
size_t len;
/* Total memory capacity of '.data'. */
size_t cap;
/* Type of file. Denotes if the provided data is compressed or even valid. */
siFileType type;
/* Current entry offset, modified by 'siswa_<ar/arl>EntryPoll'. Should not
* be modified by the user under normal circumstances.*/
size_t __curOffset;
} siArFile;
/* Creates and returns a 'siArFile' structure from the specified file, while also
* allocating the contents of it into the heap.
* NOTE: The returned structure's '.data' member must be freed after use. */
siArFile siswa_arMake(const char* path);
/* Creates and returns a 'siArFile' structure from the specified file, while also
* allocating the contents of it plus the additionally specified more into the heap.
* NOTE: The returned structure's '.data' member must be freed after use. */
siArFile siswa_arMakeEx(const char* path, size_t additionalAllocSpace);
/* Creates and returns a 'siArFile' structure from the specified buffer and its
* length. */
siArFile siswa_arMakeBuffer(const void* data, size_t len);
/* Creates and returns a 'siArFile' structure from the specified buffer, its
* length and full capacity. */
siArFile siswa_arMakeBufferEx(const void* data, size_t len, size_t capacity);
/* Allocates 'sizeof(siArHeader) + capacity' amount of memory into the heap and
* writes an autocompleted archive header into it.
* NOTE: The returned structure's '.data' member must be freed after use. */
siArFile siswa_arCreateContent(size_t capacity);
/* Writes an autocompleted archive header into the specified buffer. */
siArFile siswa_arCreateContentEx(void* buffer, size_t capacity);
/* Gets the header of the archive file. */
siArHeader* siswa_arGetHeader(siArFile arFile);
/* Gets the total entry count of the archive file. */
size_t siswa_arGetEntryCount(siArFile arFile);
/* Polls for the next entry in the archive, as the pointer to the data gets written
* to 'outEntry' Returns 'SISWA_TRUE' if an entry was polled, 'SISWA_FALSE' if
* there are no more entries in the ar file. */
siBool siswa_arEntryPoll(siArFile* arFile, siArEntry** outEntry);
/* Resets the entry offset back to the start. */
void siswa_arOffsetReset(siArFile* arFile);
/* Finds an entry matching the provided name. Returns NULL if the entry doesn't exist. */
siArEntry* siswa_arEntryFind(siArFile arFile, const char* name);
/* Finds an entry matching the provided name with length. Returns NULL if the entry
* doesn't exist. */
siArEntry* siswa_arEntryFindEx(siArFile arFile, const char* name, size_t nameLen);
/* Gets the name of the provided entry. */
char* siswa_arEntryGetName(const siArEntry* entry);
/* Gets the data of the provided entry. */
void* siswa_arEntryGetData(const siArEntry* entry);
/* Adds a new entry in the archive. Fails if the entry name already exists or
* the capacity is too low. */
siBool siswa_arEntryAdd(siArFile* arFile, const char* name, const void* data,
uint32_t dataSize);
/* Adds a new entry in the archive. Fails if the entry name already exists or
* the capacity is too low. */
siBool siswa_arEntryAddEx(siArFile* arFile, const char* name, size_t nameLen,
const void* data, uint32_t dataSize);
/* Removes an entry in the archive. Fails if the entry doesn't exist. */
siBool siswa_arEntryRemove(siArFile* arFile, const char* name);
/* Removes an entry in the archive. Fails if the entry doesn't exist. */
siBool siswa_arEntryRemoveEx(siArFile* arFile, const char* name, size_t nameLen);
/* Updates the entry inside the archive. Fails if the entry doesn't exist. */
siBool siswa_arEntryUpdate(siArFile* arFile, const char* name, const void* data,
uint32_t dataSize);
/* Updates the entry inside the archive. Fails if the entry doesn't exist. */
siBool siswa_arEntryUpdateEx(siArFile* arFile, const char* name, size_t nameLen,
const void* data, uint32_t dataSize);
/* Creates a new archive by merging two archives into it, with the content being
* written to 'outBuffer'. Any duplicating entries from the archives get ignored.
* Fails if the capacity is too low to fit the two archive files in 'outBuffer'. */
siArFile siswa_arMerge(const siArFile ars[2], void* outBuffer, size_t capacity);
/* Creates a new archive by merging all of the archive files into it, with the content
* being written to 'outBuffer'. Any duplicating entries from the archives get ignored.
* Fails if the capacity is too low to fit all of the archive files in 'outBuffer'. */
siArFile siswa_arMergeMul(const siArFile* arrayOfArs, size_t arrayLen, void* outBuffer,
size_t capacity);
#ifndef SISWA_NO_DECOMPRESSION
/* Decompresses the given archive file depending on the contents of the data and
* writes the decompressed data into 'out'. This also sets 'arl.data' to 'out'.
* Setting 'freeCompData' to true will do 'free(arl.data)', freeing the compressed
* data from memory. */
void siswa_arDecompress(siArFile* ar, siByte* out, size_t capacity, siBool freeCompData);
/* Decompresses the given archive linker file using SEGS decompression and writes
* the decompressed data into 'out'. This also sets 'arl.data' to 'out'. Setting
* 'freeCompData' to true will do 'free(arl.data)', freeing the compressed
* data from memory. */
void siswa_arDecompressSegs(siArFile* ar, siByte* out, size_t capacity, siBool freeCompData);
/* Decompresses the given archive file using XCompression (LZX) decompression
* and writes the decompressed data into 'out'. This also sets 'arl.data' to 'out'.
* Setting 'freeCompData' to true will do 'free(arl.data)', freeing the compressed
* data from memory. */
void siswa_arDecompressXComp(siArFile* ar, siByte* out, size_t capacity, siBool freeCompData);
/* Gets the exact, raw decompressed size of the data if it's X or SEGS compressed. */
uint64_t siswa_arGetDecompressedSize(siArFile ar);
#endif
/* Frees arFile.buffer. Same as doing free(arFile.data) */
void siswa_arFree(siArFile arFile);
typedef struct {
/* 'ARL2' at the start of the file. */
uint32_t identifier;
/* Total number of archive files. */
uint32_t archiveCount;
/* The amount of bytes for each archive. Length of the array is 'archiveCount'.
archiveSizes[0] gives the 1st archive file's len, archiveSizes[1] gives
the 2nd and so forth. */
uint32_t archiveSizes[SISWA_UNSPECIFIED_LEN];
} siArlHeader;
SISWA_STATIC_ASSERT(sizeof(siArlHeader) == 12);
typedef siArFile siArlFile;
typedef struct {
/* Length of the filename. */
uint8_t len;
/* The filename The string is NOT null-terminated! */
char string[SISWA_UNSPECIFIED_LEN];
} siArlEntry;
SISWA_STATIC_ASSERT(sizeof(siArlEntry) == 2);
/* Creates a 'siArlFile' structure from an '.arl' file. */
siArlFile siswa_arlMake(const char* path);
/* Creates a 'siArlFile' structure from an '.arl' file and adds additional space
* to the capacity. */
siArlFile siswa_arlMakeEx(const char* path, size_t additionalAllocSpace);
/* Creates a 'siArlFile' structure from an archive linker's content in memory. */
siArlFile siswa_arlMakeBuffer(void* data, size_t len);
/* Creates a 'siArlFile' structure from an archive linker's content in memory,
* while also setting the capacity. */
siArlFile siswa_arlMakeBufferEx(void* data, size_t len, size_t capacity);
/* Creates a 'siArlFile' structure and allocates an archive linker file in memory
* from the provided capacity. */
siArlFile siswa_arlCreateContent(size_t capacity, size_t archiveCount);
/* Creates a 'siArlFile' structure and creates an archive linker in memory from the
* provided buffer and capacity. */
siArlFile siswa_arlCreateContentEx(void* buffer, size_t capacity, size_t archiveCount);
/* Generates an archive linker in 'outBuffer' from the provided archive. */
siArlFile siswa_arlCreateFromAr(siArFile arFile, void* outBuffer, size_t capacity);
/* Generates an archive linker in 'outBuffer' from the provided multiple archives. */
siArlFile siswa_arlCreateFromArMul(siArFile* arrayOfArs, size_t arrayLen,
void* outBuffer, size_t capacity);
/* Gets the header of the archive linker. */
siArlHeader* siswa_arlGetHeader(siArlFile arlFile);
/* Gets the total entry count of the archive linker. */
size_t siswa_arlGetEntryCount(siArlFile arlFile);
/* Gets the actual length of the linker's header. */
size_t siswa_arlGetHeaderLength(siArlFile arlFile);
/* Polls for the next entry in the archive linker, as the data gets written to
* 'entry'. Returns 'SISWA_TRUE' if the entry was polled successfully, 'SISWA_FALSE'
* if there are no more entries to poll. */
siBool siswa_arlEntryPoll(siArlFile* arlfile, siArlEntry** outEntry);
/* Resets the entry offset back to the start. */
void siswa_arlOffsetReset(siArFile* arFile);
/* Finds an entry matching the provided name. Returns NULL if the entry doesn't
* exist. */
siArlEntry* siswa_arlEntryFind(siArlFile arlfile, const char* name);
/* Finds an entry matching the provided name with length. Returns NULL if the entry
* doesn't exist. */
siArlEntry* siswa_arlEntryFindEx(siArlFile arlfile, const char* name, size_t nameLen);
/* Adds a new entry in the archive linker. Fails if the entry name already exists
* or the capacity is too low. */
siBool siswa_arlEntryAdd(siArlFile* arlFile, const char* name, size_t archiveIndex);
/* Adds a new entry in the archive linker. Fails if the entry name already exists
* or the capacity is too low. */
siBool siswa_arlEntryAddEx(siArlFile* arlFile, const char* name, uint8_t nameLen,
size_t archiveIndex);
/* Removes an entry in the archive linker. Fails if the entry doesn't exist. */
siBool siswa_arlEntryRemove(siArlFile* arlFile, const char* name, size_t archiveIndex);
/* Removes an entry in the archive linker. Fails if the entry doesn't exist. */
siBool siswa_arlEntryRemoveEx(siArlFile* arlFile, const char* name, uint8_t nameLen,
size_t archiveIndex);
/* Updates the entry inside the archive linker. Fails if the entry doesn't exist. */
siBool siswa_arlEntryUpdate(siArlFile* arlFile, const char* name, const char* newName,
size_t archiveIndex);
/* Updates the entry inside the archive linker. Fails if the entry doesn't exist. */
siBool siswa_arlEntryUpdateEx(siArlFile* arlFile, const char* name, uint8_t nameLen,
const char* newName, uint8_t newNameLen, size_t archiveIndex);
#ifndef SISWA_NO_DECOMPRESSION
/* Decompresses the given archive linker file depending on the contents of the data
* and writes the decompressed data into 'out'. This also sets 'arl.data' to 'out'.
* Setting 'freeCompData' to true will do 'free(arl.data)', freeing the compressed
* data from memory. */
void siswa_arlDecompress(siArlFile* arl, siByte* out, size_t capacity, siBool freeCompData);
/* Decompresses the given archive linker file using SEGS (Deflate) decompression
* and writes the decompressed data into 'out'. This also sets 'arl.data' to 'out'.
* Setting 'freeCompData' to true will do 'free(arl.data)', freeing the compressed
* data from memory. */
void siswa_arlDecompressSegs(siArlFile* arl, siByte* out, size_t capacity, siBool freeCompessedData);
/* Decompresses the given archive linker file using XCompression (LZX) decompression
* and writes the decompressed data into 'out'. This also sets 'arl.data' to 'out'.
* Setting 'freeCompData' to true will do 'free(arl.data)', freeing the compressed
* data from memory. */
void siswa_arlDecompressXComp(siArlFile* arl, siByte* out, size_t capacity, siBool freeCompData);
/* Gets the exact, raw decompressed size of the data if it's X or SEGS compressed. */
uint64_t siswa_arlGetDecompressedSize(siArFile ar);
#endif
/* Frees arlFile.buffer. Same as doing free(arlFile.data) */
void siswa_arlFree(siArlFile arlFile);
#ifndef SISWA_NO_DECOMPRESSION
/* Decompresses the given buffer using Deflate decompression and writes it into
* 'out'. Returns the length of the decompressed data. */
size_t siswa_decompressDeflate(siByte* data, size_t length, siByte* out, size_t capacity);
/* Decompresses the given buffer using LZX DELTA (1.03) decompression and writes
* it into 'out'. Returns the length of the decompressed data. */
size_t siswa_decompressLZXDelta(siByte* data, size_t length, siByte* out, size_t capacity);
#endif
#if defined(SISWA_ARCHIVE_IMPLEMENTATION)
#define siswa_swap16(x) \
((uint16_t)((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)))
#define siswa_swap32(x) \
((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) \
| (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24))
#define siswa_swap64(x) \
((((x) & (uint64_t)0xFF00000000000000) >> 56) \
| (((x) & (uint64_t)0x00FF000000000000) >> 40) \
| (((x) & (uint64_t)0x0000FF0000000000) >> 24) \
| (((x) & (uint64_t)0x000000FF00000000) >> 8) \
| (((x) & (uint64_t)0x00000000FF000000) << 8) \
| (((x) & (uint64_t)0x0000000000FF0000) << 24) \
| (((x) & (uint64_t)0x000000000000FF00) << 40) \
| (((x) & (uint64_t)0x00000000000000FF) << 56))
#if 1
static
siBool siswa_isLittleEndian(void) {
int32_t val = 1;
return (int32_t)*((uint8_t*)&val) == 1;
}
typedef struct {
char** entries;
size_t capacity;
} siHashTable;
#define SI_FNV_OFFSET 14695981039346656037UL
#define SI_FNV_PRIME 1099511628211UL
static
uint64_t siswa__hashKey(const char* key) {
uint64_t hash = SI_FNV_OFFSET;
const char* p;
for (p = key; *p; p++) {
hash ^= (uint64_t)(*p);
hash *= SI_FNV_PRIME;
}
return hash;
}
static
siHashTable* siswa__hashtableMakeReserve(void* mem, size_t capacity) {
siHashTable* table = (siHashTable*)mem;
table->capacity = capacity;
table->entries = (char**)(table + 1);
SISWA_MEMSET(table->entries, 0, capacity * sizeof(char*));
return table;
}
static
uint32_t siswa__hashtableExists(siHashTable* ht, const char* key) {
uint64_t hash = siswa__hashKey(key);
size_t index = (size_t)(hash & (uint64_t)(ht->capacity - 1));
char** entry = &ht->entries[index];
char** oldEntry = entry;
char** end = &ht->entries[ht->capacity];
do {
if (*entry == NULL) {
goto increment_entry;
}
if (
*key == **entry
&& strcmp(key + 1, *entry + 1) == 0) {
return SISWA_SUCCESS;
}
increment_entry:
entry += 1;
if (entry == end) {
entry = ht->entries;
}
} while (entry != oldEntry);
return SISWA_FAILURE;
}
static
char** siswa__hashtableSet(siHashTable* ht, const char* allocatedStr) {
uint64_t hash = siswa__hashKey(allocatedStr);
size_t index = (size_t)(hash & (uint64_t)(ht->capacity - 1));
char** entry = ht->entries + index;
char** end = ht->entries + ht->capacity;
while (*entry != NULL) {
entry += 1;
if (entry == end) {
entry = ht->entries;
}
}
*entry = (char*)allocatedStr;
return &ht->entries[index];
}
#undef SI_FNV_OFFSET
#undef SI_FNV_PRIME
#endif
siArFile siswa_arMake(const char* path) {
return siswa_arMakeEx(path, 0);
}
#ifndef SISWA_NO_STDLIB
siArFile siswa_arMakeEx(const char* path, size_t additionalAllocSpace) {
FILE* file;
siByte* data;
size_t dataLen;
siArFile ar;
SISWA_ASSERT_NOT_NULL(path);
file = fopen(path, "rb");
SISWA_ASSERT_NOT_NULL(file);
fseek(file, 0, SEEK_END);
dataLen = ftell(file);
rewind(file);
data = (siByte*)malloc(dataLen + additionalAllocSpace);
fread(data, dataLen, 1, file);
ar = siswa_arMakeBufferEx(data, dataLen, dataLen + additionalAllocSpace);
fclose(file);
return ar;
}
#endif
siArFile siswa_arMakeBuffer(const void* data, size_t len) {
return siswa_arMakeBufferEx(data, len, len);
}
siArFile siswa_arMakeBufferEx(const void* data, size_t len, size_t capacity) {
siArFile ar;
uint32_t identifier;
SISWA_ASSERT_NOT_NULL(data);
SISWA_ASSERT_MSG(len <= capacity, "The length cannot be larger than the capacity");
identifier = siswa_isLittleEndian()
? *(uint32_t*)data
: siswa_swap32(*(uint32_t*)data);
switch (identifier) {
case SISWA_IDENTIFIER_ARL2: SISWA_PANIC(); break;
case SISWA_IDENTIFIER_XCOMPRESSION: ar.type = SISWA_FILE_XCOMPRESS; break;
case SISWA_IDENTIFIER_SEGS: ar.type = SISWA_FILE_SEGS; break;
default: ar.type = SISWA_FILE_INVALID;
}
ar.data = (siByte*)data;
ar.len = len;
ar.cap = capacity;
ar.__curOffset = sizeof(siArHeader);
return ar;
}
#ifndef SISWA_NO_STDLIB
siArFile siswa_arCreateContent(size_t capacity) {
return siswa_arCreateContentEx(malloc(capacity + sizeof(siArHeader)), capacity);
}
#endif
siArFile siswa_arCreateContentEx(void* buffer, size_t capacity) {
siArHeader header;
siArFile ar;
SISWA_ASSERT_NOT_NULL(buffer);
SISWA_ASSERT_MSG(
capacity >= sizeof(siArHeader), "Capacity must be at least equal to or be higher than 'sizeof(siArHeader)'"
);
header.unknown = 0;
header.headerSizeof = sizeof(siArHeader);
header.entrySizeof = sizeof(siArEntry);
header.alignment = SISWA_DEFAULT_HEADER_ALIGNMENT;
SISWA_MEMCPY(buffer, &header, sizeof(siArHeader));
ar.data = (siByte*)buffer;
ar.len = sizeof(siArHeader);
ar.cap = capacity;
ar.type = SISWA_FILE_REGULAR;
ar.__curOffset = sizeof(siArHeader);
return ar;
}
siArHeader* siswa_arGetHeader(siArFile arFile) {
return (siArHeader*)arFile.data;
}
size_t siswa_arGetEntryCount(siArFile arFile) {
size_t count = 0;
siArEntry* entry;
while (siswa_arEntryPoll(&arFile, &entry)) {
count += 1;
}
return count;
}
siBool siswa_arEntryPoll(siArFile* arFile, siArEntry** outEntry) {
siArEntry* entry = (siArEntry*)&arFile->data[arFile->__curOffset];
if (arFile->__curOffset >= arFile->len) {
siswa_arOffsetReset(arFile);
return SISWA_FALSE;
}
arFile->__curOffset += entry->size;
*outEntry = entry;
return SISWA_TRUE;
}
void siswa_arOffsetReset(siArFile* arFile) {
SISWA_ASSERT_NOT_NULL(arFile);
arFile->__curOffset = sizeof(siArHeader);
}
siArEntry* siswa_arEntryFind(siArFile arFile, const char* name) {
return siswa_arEntryFindEx(arFile, name, SISWA_STRLEN(name));
}
siArEntry* siswa_arEntryFindEx(siArFile arFile, const char* name, size_t nameLen) {
siArEntry* entry;
SISWA_ASSERT_NOT_NULL(name);
arFile.__curOffset = sizeof(siArHeader);
while (siswa_arEntryPoll(&arFile, &entry)) {
char* entryName = siswa_arEntryGetName(entry);
if (*name == *entryName && SISWA_STRNCMP(name + 1, entryName + 1, nameLen - 1) == 0) {
return entry;
}
}
return NULL;
}
char* siswa_arEntryGetName(const siArEntry* entry) {
return (char*)entry + sizeof(siArEntry);
}
void* siswa_arEntryGetData(const siArEntry* entry) {
return (siByte*)entry + entry->offset;
}
siBool siswa_arEntryAdd(siArFile* arFile, const char* name, const void* data,
uint32_t dataSize) {
return siswa_arEntryAddEx(arFile, name, SISWA_STRLEN(name), data, dataSize);
}
siBool siswa_arEntryAddEx(siArFile* arFile, const char* name, size_t nameLen,
const void* data, uint32_t dataSize) {
siArEntry newEntry;
siByte* dataPtr;
size_t offset = sizeof(siArHeader);
SISWA_ASSERT_NOT_NULL(arFile);
SISWA_ASSERT_NOT_NULL(name);
SISWA_ASSERT_NOT_NULL(data);
{
siArEntry* entry;
siArFile tmpArFile = *arFile;
while (siswa_arEntryPoll(&tmpArFile, &entry)) {
const char* entryName = siswa_arEntryGetName(entry);
offset = tmpArFile.__curOffset;
if (*name == *entryName
&& SISWA_STRNCMP(&name[1], &entryName[1], nameLen - 1) == 0) {
return SISWA_FAILURE;
}
}
}
newEntry.size = dataSize + nameLen + 1 + sizeof(siArEntry);
newEntry.dataSize = dataSize;
newEntry.offset = nameLen + 1 + sizeof(siArEntry);
SISWA_MEMSET(newEntry.filedate, 0, sizeof(uint64_t));
SISWA_ASSERT_MSG(
offset + newEntry.size < arFile->cap,
"Not enough space inside the buffer to add a new entry"
);
dataPtr = arFile->data + offset;
SISWA_MEMCPY(dataPtr, &newEntry, sizeof(siArEntry));
dataPtr += sizeof(siArEntry);
SISWA_MEMCPY(dataPtr, name, nameLen);
dataPtr += nameLen;
*dataPtr = '\0';
dataPtr += 1;
SISWA_MEMCPY(dataPtr, data, dataSize);
arFile->len += newEntry.size;
return SISWA_SUCCESS;
}
siBool siswa_arEntryRemove(siArFile* arFile, const char* name) {
return siswa_arEntryRemoveEx(arFile, name, SISWA_STRLEN(name));
}
siBool siswa_arEntryRemoveEx(siArFile* arFile, const char* name, size_t nameLen) {
size_t offset;
siArEntry* entry;
siByte* entryPtr;
SISWA_ASSERT_NOT_NULL(name);
entry = siswa_arEntryFindEx(*arFile, name, nameLen);
entryPtr = (siByte*)entry;
if (entry == NULL) {
return SISWA_FAILURE;
}
offset = (size_t)entry - (size_t)arFile->data;
arFile->len -= entry->size;
SISWA_MEMCPY(entryPtr, entryPtr + entry->size, arFile->len - offset);
return SISWA_SUCCESS;
}
siBool siswa_arEntryUpdate(siArFile* arFile, const char* name, const void* data,
uint32_t dataSize) {
return siswa_arEntryUpdateEx(arFile, name, SISWA_STRLEN(name), data, dataSize);
}
siBool siswa_arEntryUpdateEx(siArFile* arFile, const char* name, size_t nameLen,
const void* data, uint32_t dataSize) {
size_t offset;
siArEntry* entry;
siByte* entryPtr;
SISWA_ASSERT_NOT_NULL(name);
SISWA_ASSERT_NOT_NULL(name);
entry = siswa_arEntryFindEx(*arFile, name, nameLen);
entryPtr = (siByte*)entry;
if (entry == NULL) {
return SISWA_FAILURE;
}
offset = (size_t)entry - (size_t)arFile->data;
{
int64_t oldSize = entry->size;
entry->size = dataSize + nameLen + 1 + sizeof(siArEntry);
entry->dataSize = dataSize;
SISWA_ASSERT_MSG(
offset + entry->size < arFile->cap,
"Not enough space inside the buffer to update the entry"
);
/* Copy the data _after_ the entry so that it doesn't get overwritten. */
SISWA_MEMCPY(
entryPtr + entry->size,
entryPtr + (size_t)oldSize,
arFile->len - offset - oldSize
);
/* Copy the new data into the entry. */
SISWA_MEMCPY(entryPtr + entry->offset, data, dataSize);
arFile->len -= oldSize - (int64_t)entry->size;
}
return SISWA_SUCCESS;
}
siArFile siswa_arMerge(const siArFile ars[2], void* outBuffer, size_t capacity) {
return siswa_arMergeMul(ars, 2, outBuffer, capacity);
}
siArFile siswa_arMergeMul(const siArFile* arrayOfArs, size_t arrayLen, void* outBuffer,
size_t capacity) {
siByte* ogBuffer = (siByte*)outBuffer;
siByte* buffer = ogBuffer;
siArHeader* header;
size_t i;
siHashTable* ht;
size_t totalSize = sizeof(siArHeader);
siArEntry* entry;
uint32_t res;
siArFile curAr;
char allocator[SISWA_DEFAULT_STACK_SIZE];
SISWA_ASSERT_NOT_NULL(arrayOfArs);
SISWA_ASSERT_NOT_NULL(outBuffer);
ht = siswa__hashtableMakeReserve(allocator, SISWA_DEFAULT_STACK_SIZE / sizeof(char*) - sizeof(siHashTable));
header = (siArHeader*)buffer;
header->unknown = 0;
header->headerSizeof = sizeof(siArHeader);
header->entrySizeof = sizeof(siArEntry);
header->alignment = SISWA_DEFAULT_HEADER_ALIGNMENT;
buffer += sizeof(siArHeader);
for (i = 0; i < arrayLen; i++) {
curAr = arrayOfArs[i];
while (siswa_arEntryPoll(&curAr, &entry)) {
char* name = siswa_arEntryGetName(entry);
res = siswa__hashtableExists(ht, name);
if (res == SISWA_FAILURE) {
if (i != arrayLen - 1) {
siswa__hashtableSet(ht, name);
}
totalSize += entry->size;
SISWA_ASSERT_MSG(capacity >= totalSize,
"Not enough space inside the buffer to merge all archive files"
);
SISWA_MEMCPY(buffer, entry, entry->size);
buffer += entry->size;
}
}
}
{
siArFile ar = siswa_arMakeBufferEx(ogBuffer, totalSize, capacity);
return ar;
}
}
void siswa_arDecompress(siArFile* ar, siByte* out, size_t capacity, siBool freeCompData) {
siswa_arlDecompress((siArlFile*)ar, out, capacity, freeCompData);
}
void siswa_arDecompressSegs(siArFile* ar, siByte *out, size_t capacity, siBool freeCompData) {
siswa_arlDecompressSegs((siArlFile*)ar, out, capacity, freeCompData);
}
void siswa_arDecompressXComp(siArFile* ar, siByte *out, size_t capacity, siBool freeCompData) {
siswa_arlDecompressXComp((siArlFile*)ar, out, capacity, freeCompData);
}
uint64_t siswa_arGetDecompressedSize(siArFile ar) {
return siswa_arlGetDecompressedSize(ar);
}
#ifndef SISWA_NO_STDLIB
void siswa_arFree(siArFile arFile) {
free(arFile.data);
}
#endif
siArlFile siswa_arlMake(const char* path) {
return siswa_arlMakeEx(path, 0);
}
#ifndef SISWA_NO_STDLIB
siArlFile siswa_arlMakeEx(const char* path, size_t additionalAllocSpace) {
FILE* file;
siByte* data;