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 }