summaryrefslogtreecommitdiff
path: root/source/core/slang-riff.h
blob: 3a964df2d4b9648a7976adb2b14fcaecfbb4a7b4 (plain)
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
#ifndef SLANG_RIFF_H
#define SLANG_RIFF_H

#include "slang-basic.h"
#include "slang-stream.h"
#include "slang-memory-arena.h"
#include "slang-writer.h"
#include "slang-semantic-version.h"

namespace Slang
{

// http://fileformats.archiveteam.org/wiki/RIFF
// http://www.fileformat.info/format/riff/egff.htm


typedef uint32_t FourCC;

/* Use of macros to construct and extract from FourCC means the FourCC ordering can be fixed for endian differences. */

#if SLANG_LITTLE_ENDIAN 

#define SLANG_FOUR_CC(c0, c1, c2, c3) ((FourCC(c0) << 0) | (FourCC(c1) << 8) | (FourCC(c2) << 16) | (FourCC(c3) << 24)) 

#define SLANG_FOUR_CC_GET_FIRST_CHAR(x) char((x) & 0xff)
#define SLANG_FOUR_CC_REPLACE_FIRST_CHAR(x, c) (((x) & 0xffffff00) | FourCC(c))

#else

#define SLANG_FOUR_CC(c0, c1, c2, c3) ((FourCC(c0) << 24) | (FourCC(c1) << 16) | (FourCC(c2) << 8) | (FourCC(c3) << 0)) 

#define SLANG_FOUR_CC_GET_FIRST_CHAR(x) char((x) >> 24)
#define SLANG_FOUR_CC_REPLACE_FIRST_CHAR(x, c) (((x) & 0x00ffffff) | (FourCC(c) << 24))

#endif

enum 
{
    kRiffPadSize = 2,       ///< We only align to 2 bytes
    kRiffPadMask = kRiffPadSize - 1,
};

// Uses it's own version of a hash
typedef int RiffHashCode;

struct RiffHeader
{
    FourCC type;                ///< The FourCC code that identifies this chunk
    uint32_t size;              ///< Size does *NOT* include the riff chunk size. The size can be byte sized, but on storage it will always be treated as aligned up by 4.
};

struct RiffListHeader
{
    RiffHeader chunk;
    FourCC subType;
    // This is then followed by the contained subchunk/s
};

struct RiffFourCC
{
        /// A 'riff' is the high level file container. It is followed by a subtype and then the contained chunks.
     static const FourCC kRiff = SLANG_FOUR_CC('R', 'I', 'F', 'F');
        /// A list is the same as a 'riff' except can be placed anywhere in hierarchy.  
     static const FourCC kList = SLANG_FOUR_CC('L', 'I', 'S', 'T');
private:
    RiffFourCC() = delete;
};

// Follows semantic version rules
// https://semver.org/
// 
// major.minor.patch
// Patch versions indicate a change. 
// Minor means a change that is backwards compatible with previous minor versions. A step in minor and/or major zeros patch.
// Major means a non compatible change. A step in major, zeros minor and patch. 
struct RiffSemanticVersion
{
    typedef RiffSemanticVersion ThisType;
    typedef uint32_t RawType;

        /// ==
    bool operator==(const ThisType& rhs) const { return m_raw == rhs.m_raw; }
    bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
    
        /// A patch change indices a different version but does not change the compatibility of the format
    int getPatch() const { return m_raw & 0xff; }
        /// A minor change implies a format change that is backwards compatible  
    int getMinor() const { return (m_raw >> 8) & 0xff; }
        /// A major change is binary incompatible by default
    int getMajor() const { return (m_raw >> 16); }

    SemanticVersion asSemanticVersion() const
    {
        return SemanticVersion(getMajor(), getMinor(), getPatch());
    }

    static RawType makeRaw(int major, int minor, int patch)
    {
        SLANG_ASSERT((major | minor | patch) >= 0);
        SLANG_ASSERT(major < 0x10000 && minor < 0x100 && patch < 0x100);
        return (RawType(major) << 16) | (RawType(minor) << 8) | RawType(patch);
    }

    static RiffSemanticVersion makeFromRaw(RawType raw)
    {
        ThisType version;
        version.m_raw = raw;
        return version;
    }

    static RiffSemanticVersion make(int major, int minor, int patch) { return makeFromRaw(makeRaw(major, minor, patch)); }
    static RiffSemanticVersion make(const SemanticVersion& in) { return makeFromRaw(makeRaw(in.m_major, in.m_minor, in.m_patch)); }

        /// True if the read version is compatible with the current version, based on semantic rules.
    static bool areCompatible(const ThisType& currentVersion, const ThisType& readVersion)
    {
        const RawType currentRaw = currentVersion.m_raw;
        const RawType readRaw = readVersion.m_raw;

        // Must have same major version.
        // For minor version, the read version must be less than or equal. 
        return ((currentRaw & 0xffff0000) == (readRaw & 0xffff0000)) && ((currentRaw & 0xff00) >= (readRaw & 0xff00));
    }

    RawType m_raw;
};

/* A helper class that makes reading data from a data block simpler */
class RiffReadHelper
{
public:
    template <typename T>
    SlangResult read(T& out)
    {
        if (m_cur + sizeof(T) > m_end)
        {
            return SLANG_FAIL;
        }
        // TODO: consider whether this type should enforce alignment.
        // SLANG_ASSERT((size_t(m_cur) & (SLANG_ALIGN_OF(T) - 1)) == 0);
        ::memcpy(&out, m_cur, sizeof(T));
        m_cur += sizeof(T);
        return SLANG_OK;
    }

        /// Get the data
    const uint8_t* getData() const { return m_cur; }
        /// Get the remaining size
    size_t getRemainingSize() const { return size_t(m_end - m_cur); }

    RiffReadHelper(const uint8_t* data, size_t size):
        m_start(data),
        m_end(data + size),
        m_cur(data)
    {
    }

    SlangResult skip(size_t size)
    {
        if (m_cur + size > m_end)
        {
            return SLANG_FAIL;
        }
        m_cur += size;
        return SLANG_OK;
    }

protected:
    const uint8_t* m_start;
    const uint8_t* m_end;
    const uint8_t* m_cur;
};

/* A container for data in RIFF format. Holds the contents in memory.

With the data held in memory allows for adding or removing chunks at will.

A future implementation does not necessarily have to be backed by memory when construction,
as data could be written to stream, and the chunk sizes written by seeking back over the file and setting the value.

In normal usage the chunk sizes are calculated during construction. If the structure is changed, the sizes may
need to be recalculated, before serialization.  
*/
class RiffContainer
{
public:
        // This alignment is only made for arena based allocations.
        // For external blocks it's client code to have appropriate alignment.
        // This is needed because when reading a RiffContainer, all allocation is arena based, and
        // if the payload contains 8 byte aligned data, the overall payload needs to be 8 byte aligned.
    static const size_t kPayloadMinAlignment = 8;

    enum class Ownership
    {
        Uninitialized,      ///< Doesn't contain anything
        NotOwned,           ///< It's not owned by the container
        Arena,              ///< It's owned and allocated on the arena
        Owned,              ///< It's owned, but wasn't allocated on the arena
    };

    struct Data
    {
            /// Get the payload
        void* getPayload() { return m_payload; }
            /// Get the end pointer
        void* getPayloadEnd() { return (void*)((uint8_t*)m_payload + m_size); }
            /// Get the size of the payload
        size_t getSize() const { return m_size; }
            /// Get the ownership of the data held in the payload
        Ownership getOwnership() const { return m_ownership; }

        void init()
        {
            m_ownership = Ownership::Uninitialized;
            m_size = 0;
            m_next = nullptr;
            m_payload = nullptr;
        }

        Ownership m_ownership;          ///< Stores the ownership of the payload
        size_t m_size;                  ///< The size of the payload
        void* m_payload;                ///< The payload
        Data* m_next;                   ///< The next Data block in the list
    };

    struct Chunk;
    struct ListChunk;
    struct DataChunk;

    typedef SlangResult(*VisitorCallback)(Chunk* chunk, void* data);
    
    class Visitor;
    struct Chunk
    {
        enum class Kind
        {
            List,           ///< Strictly speaking this can be a 'LIST' or a 'RIFF' as they have the same structure
            Data,
        };

        void init(Kind kind, FourCC fourCC)
        {
            m_kind = kind;
            m_fourCC = fourCC;
            m_payloadSize = 0;
            m_next = nullptr;
            m_parent = nullptr;
        }

        SlangResult visit(Visitor* visitor);
        SlangResult visitPostOrder(VisitorCallback callback, void* data);
        SlangResult visitPreOrder(VisitorCallback callback, void* data);

            /// Returns a single data chunk
        Data* getSingleData() const;

            /// Calculate the payload size
        size_t calcPayloadSize();

        Kind m_kind;                        ///< Kind of chunk
        FourCC m_fourCC;                    ///< The chunk type for data, or the sub type for a List (riff/list)
        size_t m_payloadSize;               ///< The payload size (ie does NOT include RiffChunk header). 
        Chunk* m_next;                      ///< Next chunk in this list
        ListChunk* m_parent;                ///< The chunk this belongs to
    };

    struct ListChunk : public Chunk
    {
        typedef Chunk Super;
        SLANG_FORCE_INLINE static bool isType(const Chunk* chunk) { return chunk->m_kind == Kind::List; }

        void init(FourCC subType)
        {
            Super::init(Kind::List, subType);
            m_containedChunks = nullptr;
            m_endChunk = nullptr;

            m_payloadSize = uint32_t(sizeof(RiffListHeader) - sizeof(RiffHeader));
        }

            /// Finds chunk (list or data) that matches type. For List/Riff, type is the subtype
        Chunk* findContained(FourCC type) const;

        void* findContainedData(FourCC type, size_t minSize) const;

        ListChunk* findContainedList(FourCC type);

            /// Finds the contained data. NOTE! Assumes that there is only as single data block, and will return nullptr if there is not
        Data* findContainedData(FourCC type) const;

        template <typename T>
        T* findContainedData(FourCC type) const { return (T*)findContainedData(type, sizeof(T)); }

            /// Find all contained that match the type
        void findContained(FourCC type, List<ListChunk*>& out);

            /// Find all contained that match the type
        void findContained(FourCC type, List<DataChunk*>& out);

            /// Find the list (including self) that matches subtype recursively
        ListChunk* findListRec(FourCC subType);

            /// NOTE! Assumes all contained chunks have correct payload sizes
        size_t calcPayloadSize();

            /// Get the sub type
        FourCC getSubType() const { return m_fourCC; }

            /// A singly linked list of contained chunks directly contained in this chunk
        Chunk* getFirstContainedChunk() const { return m_containedChunks; }

        Chunk* m_containedChunks;               ///< The contained chunks
        Chunk* m_endChunk;                      ///< The last chunk (only set when pushed, and used when popped)
    };

    struct DataChunk : public Chunk
    {
        typedef Chunk Super;
        SLANG_FORCE_INLINE static bool isType(const Chunk* chunk) { return chunk->m_kind == Kind::Data; }

            /// Calculate a hash (not necessarily very fast)
        RiffHashCode calcHash() const;
            /// Calculate the payload size
        size_t calcPayloadSize() const;

            /// Copy the payload to dst. Dst must be at least the payload size. 
        void getPayload(void* dst) const;

            /// True if payloads contents is equal to data
        bool isEqual(const void* data, size_t count) const;

            /// Get single data payload.
        Data* getSingleData() const;

            /// Return as read helper
        RiffReadHelper asReadHelper() const;

        void init(FourCC fourCC)
        {
            Super::init(Kind::Data, fourCC);
            m_dataList = nullptr;
            m_endData = nullptr;
        }

        Data* m_dataList;                 ///< List of 0 or more data items
        Data* m_endData;                  ///< The last data point
    };

    class ScopeChunk
    {
    public:
        ScopeChunk(RiffContainer* container, Chunk::Kind kind, FourCC fourCC) :
            m_container(container)
        {
            container->startChunk(kind, fourCC);
        }
        ~ScopeChunk()
        {
            m_container->endChunk();
        }
    private:
        RiffContainer* m_container;
    };

    class Visitor
    {
    public:
        virtual SlangResult enterList(ListChunk* list) = 0;
        virtual SlangResult handleData(DataChunk* data) = 0;
        virtual SlangResult leaveList(ListChunk* list) = 0;
    };


        /// Add a complete data chunk
    void addDataChunk(FourCC dataFourCC, const void* data, size_t dataSizeInBytes);

        /// Start a chunk
    void startChunk(Chunk::Kind kind, FourCC type);

        /// Write data into a chunk (can only be inside a Kind::Data)
    void write(const void* data, size_t size);

        /// Adds an empty data block
    Data* addData();
        /// Set the payload on a data. Payload can be passed as nullptr, if it is no memory will be copied.
    void setPayload(Data* data, const void* payload, size_t size);

        /// Move ownership to.
        /// NOTE! The payload *must* be deallocatable via 'free'
    void moveOwned(Data* data, void* payload, size_t size);
        /// Move unowned. The payload scope must last longer than the RiffContainer
    void setUnowned(Data* data, void* payload, size_t size);

        /// End a chunk
    void endChunk();

        /// Get the root
    ListChunk* getRoot() const { return m_rootList; }

        /// Get the current chunk
    Chunk* getCurrentChunk() { return m_dataChunk ? static_cast<Chunk*>(m_dataChunk) : static_cast<Chunk*>(m_listChunk); }

        /// Reset the container
    void reset();

        /// true if has a root container, and nothing remains open
    bool isFullyConstructed() { return m_rootList && m_listChunk == nullptr && m_dataChunk == nullptr; }

        /// Makes a data chunk contain a single contiguous data block
    Data* makeSingleData(DataChunk* dataChunk);

        /// Get the memory arena that is backing the storage of data
    MemoryArena& getMemoryArena() { return m_arena; }

        /// The if the list and sublists appear correct
    static bool isChunkOk(Chunk* chunk);

        /// Traverses over chunk hierarchy and sets the sizes
    static void calcAndSetSize(Chunk* chunk);

        /// Ctor
    RiffContainer();

protected:
    void _addChunk(Chunk* chunk);
    ListChunk* _newListChunk(FourCC subType);
    DataChunk* _newDataChunk(FourCC type);

    ListChunk* m_rootList;          ///< Root list

    ListChunk* m_listChunk;
    DataChunk* m_dataChunk;

    MemoryArena m_arena;            ///< Can be used to use other owned blocks
};

// -----------------------------------------------------------------------------
template <typename T>
T* as(RiffContainer::Chunk* chunk)
{
    return chunk && T::isType(chunk) ? static_cast<T*>(chunk) : nullptr;
}
// -----------------------------------------------------------------------------
template <typename T>
T* as(RiffContainer::Chunk* chunk, FourCC fourCC)
{
    return chunk && chunk->m_fourCC == fourCC && T::isType(chunk) ? static_cast<T*>(chunk) : nullptr;
}

struct RiffUtil
{
    typedef RiffContainer::Chunk Chunk;
    typedef RiffContainer::ListChunk ListChunk;
    typedef RiffContainer::DataChunk DataChunk;

    static int64_t calcChunkTotalSize(const RiffHeader& chunk);

    static SlangResult skip(const RiffHeader& chunk, Stream* stream, int64_t* remainingBytesInOut);

    static SlangResult readChunk(Stream* stream, RiffHeader& outChunk);

    static SlangResult writeData(const RiffHeader* header, size_t headerSize, const void* payload, size_t payloadSize, Stream* out);
    static SlangResult readData(Stream* stream, RiffHeader* outHeader, size_t headerSize, List<uint8_t>& data);

    static SlangResult readPayload(Stream* stream, size_t size, void* outData, size_t& outReadSize);

        /// Read a header. Handles special case of list/riff types
    static SlangResult readHeader(Stream* stream, RiffListHeader& outHeader);

        /// True if the type is a container type
    static bool isListType(FourCC type) { return type == RiffFourCC::kRiff || type == RiffFourCC::kList; }

       /// Dump the chunk structure
    static void dump(Chunk* chunk, WriterHelper writer);

        /// Get the size taking into account padding
    static size_t getPadSize(size_t in) { return (in + kRiffPadMask) & ~size_t(kRiffPadMask); }

        /// Write a chunk list and contents to a stream
    static SlangResult write(ListChunk* listChunk, bool isRoot, Stream* stream);
        /// Write a container to the stream
    static SlangResult write(RiffContainer* container, Stream* stream);

        /// Read the stream into the container
    static SlangResult read(Stream* stream, RiffContainer& outContainer);
};

}

#endif