1 module cubf.reader;
2 import std.typecons : Tuple, tuple;
3 
4 struct CUBFReader {
5 	private {
6 		import std.system : Endian, endian;
7 		import std.bitmanip : bigEndianToNative, littleEndianToNative;
8 
9 		ubyte[] source;
10 		bool requireChecksum, hadAnyChunksSoFar;
11 		Type current;
12 		Endian endianess;
13 		ubyte whereHeader;
14 		ulong headerOffset;
15 	}
16 
17 	this(ubyte[] source, bool requireChecksum=false) {
18 		this.requireChecksum = requireChecksum;
19 
20 		this.whereHeader = source[4];
21 		if (source[5] == 'B')
22 			this.endianess = Endian.bigEndian;
23 		else
24 			this.endianess = Endian.littleEndian;
25 
26 		if (this.whereHeader == 'O') {
27 			if (endianess == Endian.bigEndian) {
28 				headerOffset = bigEndianToNative!ulong(source[8 .. 16])-16;
29 			} else {
30 				headerOffset = littleEndianToNative!ulong(source[8 .. 16])-16;
31 			}
32 
33 			this.source = source[16 .. $];
34 		} else {
35 			this.source = source[8 .. $];
36 		}
37 
38 		if (source.length > 0)
39 			popFront;
40 	}
41 
42 	@property {
43 		Type front() {
44 			return current;
45 		}
46 
47 		bool empty() {
48 			return source.length == 0 && current is Type.init;
49 		}
50 	}
51 
52 	CUBFReader save() { 
53 		// implicit copy ;)
54 		return this;
55 	}
56 
57 	void popFront() {
58 		if (source.length == 0) {
59 			current = Type.init;
60 			return;
61 		}
62 
63 		uint length, hash;
64 		bool isHeader;
65 
66 		if (endianess == Endian.bigEndian) {
67 			length = bigEndianToNative!uint(source[$-4 .. $][0 .. 4]);
68 			hash = bigEndianToNative!uint(source[$-8 .. $-4][0 .. 4]);
69 		} else {
70 			length = littleEndianToNative!uint(source[$-4 .. $][0 .. 4]);
71 			hash = littleEndianToNative!uint(source[$-8 .. $-4][0 .. 4]);
72 		}
73 
74 		if (this.whereHeader == 'O') {
75 			isHeader = headerOffset == source.length-1;
76 		} else if (this.whereHeader == 'S') {
77 			isHeader = !this.hadAnyChunksSoFar;
78 		} else if (this.whereHeader == 'E') {
79 			isHeader = this.source.length <= length + 12;
80 		}
81 		this.hadAnyChunksSoFar = true;
82 
83 		ubyte[] data;
84 		if (this.source.length > length + 12) {
85 			data = source[$-(length + 12) .. $-12];
86 			current = Type(cast(char[4])source[$-12 .. $-8][0 .. 4], data, isHeader);
87 			this.source = source[0 .. $-(length + 12)];
88 		} else {
89 			data = source[0 .. $-12];
90 			current = Type(cast(char[4])source[$-12 .. $-8][0 .. 4], data, isHeader);
91 			this.source = null;
92 		}
93 
94 		if (requireChecksum) {
95 			import std.digest.crc;
96 
97 			if (*cast(uint*)crc32Of(data).ptr != hash) {
98 				assert(0, "hash not equals for data");
99 			}
100 		}
101 	}
102 
103 	alias Type = Tuple!(char[4], "name", ubyte[], "data", bool, "isHeader");
104 }