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 }