1 /**
2     INP 1 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.inp1.reader;
14 import inp.format.json.reader;
15 import inp.format.node;
16 import inp.format.inp1;
17 import nulib.io.stream.rw;
18 import nulib.io.stream;
19 import nulib.math;
20 import numem;
21 
22 import inp.format : 
23     INP_TAG_PAYLOAD, 
24     INP_TAG_TEXTURES, 
25     INP_TAG_VENDOR;
26 
27 @nogc:
28 
29 /**
30     Reads an INP1 File.
31 
32     Params:
33         stream = The stream to read.
34     
35     Returns:
36         A result type containing either a $(D DataNode)
37         or an error message.
38 */
39 Result!DataNode readINP1(Stream stream) @nogc {
40     scope StreamReader reader = new StreamReader(stream);
41     DataNode result;
42 
43     size_t start = stream.tell();
44     reader.readINP1Impl(result);
45     stream.seek(start);
46 
47     return ok(result.move());
48 }
49 
50 
51 
52 //
53 //              IMPLEMENTATION DETAILS
54 //
55 private:
56 
57 void readINP1Impl()(StreamReader reader, ref DataNode node) {
58     node = DataNode.createObject();
59     node[INP_TAG_PAYLOAD] = DataNode.createObject();
60     node[INP_TAG_TEXTURES] = DataNode.createArray();
61     node[INP_TAG_VENDOR] = DataNode.createObject();
62 
63     ptrdiff_t streamLength = reader.stream.length;
64     readLoop: while(reader.stream.tell < streamLength) {
65         auto key = reader.readUTF8(8);
66         switch(key) {
67             default:
68                 break readLoop;
69 
70             case INP1_MAGIC:
71                 ptrdiff_t rstart = reader.stream.tell;
72                 uint payloadLength = reader.readU32BE();
73                 auto result = reader.readJson(node[INP_TAG_PAYLOAD], payloadLength);
74                 break;
75 
76             case INP_TAG_TEXTURES:
77                 uint count = reader.readU32BE();
78                 foreach(i; 0..count) {
79                     
80                     // Main data
81                     uint dataLength = reader.readU32BE();
82                     ubyte encoding = reader.readU8();
83 
84                     // NOTE:    Inochi2D 0.8.x sometimes could end up writing textures
85                     //          with 0 length into the file, to allow the loader to handle
86                     //          this we replace it with an undefined DataNode if empty.
87                     if (dataLength > 0) {
88                         DataNode result = DataNode.createObject();
89                         ubyte[] data = nu_malloca!ubyte(dataLength);
90                         reader.stream.read(data);
91 
92                         result["encoding"] = encoding;
93                         result["data"] = data;
94                         node[INP_TAG_TEXTURES] ~= result;
95 
96                         nu_freea(data);
97                     } else {
98                         node[INP_TAG_TEXTURES] ~= DataNode.init;
99                     }
100                 }
101                 break;
102 
103             case INP_TAG_VENDOR:
104                 uint count = reader.readU32BE();
105                 foreach(i; 0..count) {
106                     auto keyLength = reader.readU32BE();
107                     auto dataKey = reader.readUTF8(keyLength).take();
108                     auto dataLength = reader.readU32BE();
109                     auto dataValue = nu_malloca!ubyte(dataLength);
110 
111                     reader.stream.read(dataValue);
112                     node[INP_TAG_VENDOR][dataKey] = dataValue;
113                     
114                     nu_freea(dataValue);
115                     nu_freea(dataKey);
116                 }
117                 break;
118         }
119     }
120 }