1 /**
2     INP Format
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;
14 
15 import nulib.io.stream;
16 import numem.optional;
17 
18 public import inp.format.inp1;
19 public import inp.format.inp2;
20 public import inp.format.node;
21 
22 /**
23     The different INP file formats.
24 */
25 enum INPFileFormat : uint {
26     unknown = 0x00,
27     inp1    = 0x01,
28     inp2    = 0x02
29 }
30 
31 /**
32     Tag for the INP payload section.
33 */
34 enum char[8] INP_TAG_PAYLOAD = "INP_SECT";
35 
36 /**
37     Tag for the INP texture section.
38 */
39 enum char[8] INP_TAG_TEXTURES = "TEX_SECT";
40 
41 /**
42     Tag for the INP extended vendor data section.
43 */
44 enum char[8] INP_TAG_VENDOR = "EXT_SECT";
45 
46 /**
47     Texture format ID for PNG.
48 */
49 enum ubyte INP_TEX_FMT_PNG = 0;
50 
51 /**
52     Texture format ID for TGA.
53 */
54 enum ubyte INP_TEX_FMT_TGA = 1;
55 
56 /**
57     Texture format ID for BC7.
58 */
59 enum ubyte INP_TEX_FMT_BC7 = 2;
60 
61 /**
62     Determins the INP file format stored within the given stream.
63 
64     Params:
65         stream = The stream to detect the INP file format for.
66     
67     Returns:
68         The INP version of the file.
69 */
70 INPFileFormat detectFormat(Stream stream) @nogc nothrow {
71     assert(stream.canRead(), "Stream is not readable!");
72     assert(stream.canSeek(), "Stream is not seekable!");
73 
74     size_t start = stream.tell();
75     ubyte[8] magic;
76     
77     // Try reading magic bytes, length must be 8.
78     if (stream.read(magic) != 8) {
79         stream.seek(start);
80         return INPFileFormat.unknown;
81     }
82 
83     stream.seek(start);
84     switch(cast(char[8])magic) {
85         default:            return INPFileFormat.unknown;
86         case INP1_MAGIC:    return INPFileFormat.inp1;
87         case INP2_MAGIC:    return INPFileFormat.inp2;
88     }
89 }
90 
91 /**
92     Reads an INP File from a stream.
93 
94     Params:
95         stream = The stream to read from, must be readable and seekable.
96     
97     Returns:
98         A result that contains a $(D DataNode) on success,
99         otherwise contains an error.
100 */
101 Result!DataNode readINP(Stream stream) @nogc {
102     final switch(detectFormat(stream)) {
103         case INPFileFormat.unknown:
104             return error!DataNode("Unknown file format!");
105         
106         case INPFileFormat.inp1:
107             return readINP1(stream);
108 
109         case INPFileFormat.inp2:
110             return readINP2(stream);
111     }
112 }
113 
114 /**
115     Writes the given $(D DataNode) to the given $(D Stream).
116 
117     Params:
118         stream =    The stream to write to, must be writable.
119         node =      The node containing INP data.
120         format =    The file format to write, INP2 is recommended.
121 */
122 void writeINP(Stream stream, ref DataNode node, INPFileFormat format = INPFileFormat.inp2) @nogc {
123     final switch(format) {
124         case INPFileFormat.unknown:
125             return;
126 
127         case INPFileFormat.inp1:
128             stream.writeINP1(node);
129             return;
130             
131         case INPFileFormat.inp2:
132             stream.writeINP2(node);
133             return;
134     }
135 }