1 /** 2 INP2 Format Reader 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inp.format.inp2.reader; 14 import inp.format.node; 15 import inp.format.inp2; 16 import nulib.digest.crc; 17 import nulib.io.stream; 18 import nulib.io.stream.rw; 19 import nulib.math; 20 import numem; 21 22 @nogc: 23 24 /** 25 Reads an INP2 File. 26 27 Params: 28 stream = The stream to read. 29 30 Returns: 31 A result type containing either a $(D DataNode) 32 or an error message. 33 */ 34 Result!DataNode readINP2(Stream stream) @nogc { 35 scope StreamReader reader = new StreamReader(stream); 36 DataNode result; 37 38 size_t start = stream.tell(); 39 40 // Verify magic bytes. 41 auto magic = reader.readUTF8(8); 42 if (magic != INP2_MAGIC) { 43 stream.seek(start); 44 return error!DataNode("Invalid magic bytes!"); 45 } 46 47 // Read the tags. 48 if (string err = reader.readINP2Impl(result)) { 49 nogc_delete(result); 50 stream.seek(start); 51 return error!DataNode(err); 52 } 53 return ok(result.move()); 54 } 55 56 57 58 59 // 60 // IMPLEMENTATION DETAILS 61 // 62 private: 63 64 /// Realigns the reader. 65 void realignINP2(StreamReader reader) { 66 reader.stream.seek(nu_alignup(reader.stream.tell(), 4)); 67 } 68 69 uint peekTagINP2(StreamReader reader) { 70 uint tag = reader.readU32LE(); 71 reader.stream.seek(-4, SeekOrigin.relative); 72 return tag; 73 } 74 75 string readKeyINP2(StreamReader reader) { 76 uint tag = reader.readU32LE(); 77 if ((tag & INP2_TAG_MASK) != INP2_TAG_KEY) 78 return null; 79 80 uint len = tag >> 8; 81 82 auto key = reader.readUTF8(len); 83 reader.realignINP2(); 84 return key.take(); 85 } 86 87 string readINP2Impl()(StreamReader reader, ref DataNode node) { 88 uint tag = reader.readU32LE(); 89 switch(tag & INP2_TAG_MASK) { 90 default: return null; 91 92 case INP2_TAG_UINT: 93 node = DataNode(reader.readU32LE()); 94 return null; 95 96 case INP2_TAG_INT: 97 node = DataNode(reader.readI32LE()); 98 return null; 99 100 case INP2_TAG_FLOAT: 101 node = DataNode(reader.readF32LE()); 102 return null; 103 104 case INP2_TAG_STRING: 105 uint len = (tag & INP2_META_MASK) == 0xFFFFFF00 ? 106 reader.readU32LE() : 107 (tag & INP2_META_MASK) >> 8; 108 109 auto str = reader.readUTF8(len); 110 node = DataNode(str[]); 111 reader.realignINP2(); 112 return null; 113 114 case INP2_TAG_BLOB: 115 uint len = (tag & INP2_META_MASK) == 0xFFFFFF00 ? 116 reader.readU32LE() : 117 (tag & INP2_META_MASK) >> 8; 118 119 ubyte[] buffer = nu_malloca!ubyte(len); 120 121 reader.stream.read(buffer); 122 reader.realignINP2(); 123 uint crc = reader.readU32LE(); 124 if (crc32(buffer) != crc) { 125 return "Malformed CRC"; 126 } 127 128 node = DataNode(buffer); 129 nu_freea(buffer); 130 return null; 131 132 case INP2_TAG_ARRAY_BEGIN: 133 uint count = tag >> 8; 134 node = DataNode.createArray(); 135 foreach(i; 0..count) { 136 137 // Array ended early? Escape. 138 if (reader.peekTagINP2() == INP2_TAG_ARRAY_END) 139 break; 140 141 DataNode value; 142 if (auto error = reader.readINP2Impl(value)) 143 return error; 144 145 node ~= value.move(); 146 } 147 return null; 148 149 case INP2_TAG_OBJECT_BEGIN: 150 uint count = tag >> 8; 151 node = DataNode.createObject(); 152 foreach(i; 0..count) { 153 154 // Array ended early? Escape. 155 if (reader.peekTagINP2() == INP2_TAG_OBJECT_END) 156 break; 157 158 if (string key = reader.readKeyINP2()) { 159 DataNode value; 160 if (auto error = reader.readINP2Impl(value)) 161 return error; 162 163 node[key] = value; 164 nu_freea(key); 165 } 166 } 167 return null; 168 169 } 170 }