1 module cubf.writer; 2 import std.experimental.allocator : theAllocator, IAllocator, makeArray, dispose, expandArray; 3 4 final class CUBFWriter { 5 private { 6 size_t numberOfChunksSoFar, numberOfChunksAfterHeader; 7 ubyte* ptrToHeaderLength; 8 9 ubyte[] data; 10 IAllocator allocator; 11 12 bool finalized; 13 } 14 15 this(Endianess endianToUse = Endianess.Native, IAllocator allocator = theAllocator()) { 16 if (endianToUse == Endianess.Native) { 17 import std.system : Endian, endian; 18 19 if (endian == Endian.bigEndian) { 20 endianToUse = Endianess.Big; 21 } else { 22 endianToUse = Endianess.Little; 23 } 24 } 25 26 this.allocator = allocator; 27 // allocates enough space to hold the 'magic' number. 28 data = allocator.makeArray!ubyte(16); 29 30 data[8 .. 12] = cast(ubyte[])"cubf"; 31 data[12] = 'I'; 32 data[13 .. 15] = cast(ubyte[])(endianToUse == Endianess.Big ? "BE" : "LE"); 33 data[15] = 0; 34 } 35 36 ~this() { 37 allocator.dispose(data); 38 } 39 40 void appendChunk(char[4] name, ubyte[] data) { 41 import std.bitmanip : nativeToBigEndian, nativeToLittleEndian, littleEndianToNative; 42 import std.digest.crc; 43 44 assert(!finalized); 45 46 size_t realSize = data.length + 12; 47 allocator.expandArray(this.data, realSize); 48 this.data[$-realSize .. $-12] = data; 49 this.data[$-12 .. $-8] = cast(ubyte[4])name; 50 this.data[$-8 .. $-4] = crc32Of(data); 51 52 if (data[14] == 'B') { 53 this.data[$-8 .. $-4] = nativeToBigEndian(littleEndianToNative!uint(crc32Of(data))); 54 this.data[$-4 .. $] = nativeToBigEndian(cast(uint)data.length); 55 } else { 56 this.data[$-8 .. $-4] = nativeToLittleEndian(littleEndianToNative!uint(crc32Of(data))); 57 this.data[$-4 .. $] = nativeToLittleEndian(cast(uint)data.length); 58 } 59 60 numberOfChunksSoFar++; 61 if (ptrToHeaderLength !is null) { 62 numberOfChunksAfterHeader++; 63 } 64 } 65 66 void appendEndOfChunkInput(ubyte[] data) { 67 appendChunk(cast(char[4])"EOCI", data); 68 } 69 70 void appendHeaderChunk(char[4] name, ubyte[] data) { 71 assert(ptrToHeaderLength is null); 72 73 appendChunk(name, data); 74 ptrToHeaderLength = this.data.ptr + this.data.length; 75 } 76 77 /// any appending after this is invalid. 78 ubyte[] finalize() { 79 import std.bitmanip : nativeToBigEndian, nativeToLittleEndian; 80 81 if (!finalized) { 82 finalized = true; 83 84 if (ptrToHeaderLength !is null) { 85 if (numberOfChunksAfterHeader == 0) { 86 data[12] = 'E'; 87 data = data[8 .. $]; 88 } else if (numberOfChunksAfterHeader == numberOfChunksSoFar-1) { 89 data[12] = 'S'; 90 data = data[8 .. $]; 91 } else { 92 ulong offset = cast(ulong)(ptrToHeaderLength - (data.ptr + 1)); 93 data[0 .. 8] = data[8 .. 16]; 94 data[4] = 'O'; 95 96 if (data[6] == 'B') { 97 data[8 .. 16] = nativeToBigEndian(offset); 98 } else { 99 data[8 .. 16] = nativeToLittleEndian(offset); 100 } 101 } 102 } else { 103 data = data[8 .. $]; 104 } 105 } 106 107 return data; 108 } 109 110 enum Endianess { 111 Native, 112 Little, 113 Big 114 } 115 }