1 /** 2 INP2 Format Writer 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.writer; 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 Write the contents of a DataNode into the INP File. 26 27 Params: 28 stream = The stream to write to. 29 node = A DataNode object following the INP structure. 30 */ 31 void writeINP2(Stream stream, ref DataNode node) { 32 scope StreamWriter writer = new StreamWriter(stream); 33 writer.writeUTF8(INP2_MAGIC); 34 writer.writeINP2Impl(node); 35 } 36 37 38 39 40 // 41 // IMPLEMENTATION DETAILS 42 // 43 private: 44 45 /// Function which pads null bytes until the alignment is correct. 46 void writeINP2Padding(StreamWriter writer) { 47 size_t location = writer.stream.tell(); 48 ubyte[4] null_ = [0x00, 0x00, 0x00, 0x00]; 49 writer.stream.write(null_[0..nu_alignup(location, 4)-location]); 50 } 51 52 void writeINP2Key(StreamWriter writer, string key) { 53 writer.writeLE!uint(cast(uint)(INP2_TAG_KEY | (min(key.length, 255) << 8))); 54 writer.writeUTF8(key[0..min(key.length, 255)]); 55 writer.writeINP2Padding(); 56 } 57 58 void writeINP2Impl()(StreamWriter writer, auto ref DataNode node) { 59 final switch(node.type) { 60 case DataNodeType.undefined: 61 writer.writeLE!uint(INP2_TAG_NIL); 62 return; 63 64 case DataNodeType.boolean_: 65 writer.writeLE!uint(INP2_TAG_BOOL); 66 writer.writeLE!uint(cast(uint)node.tryCoerce!bool); 67 return; 68 69 case DataNodeType.int_: 70 writer.writeLE!uint(INP2_TAG_INT); 71 writer.writeLE!int(node.tryCoerce!int); 72 return; 73 74 case DataNodeType.uint_: 75 writer.writeLE!uint(INP2_TAG_UINT); 76 writer.writeLE!uint(node.tryCoerce!uint); 77 return; 78 79 case DataNodeType.float_: 80 writer.writeLE!uint(INP2_TAG_FLOAT); 81 writer.writeLE!float(node.tryCoerce!float); 82 return; 83 84 case DataNodeType.string_: 85 if (node.text.length > 0xFFFFFE) { 86 writer.writeLE!uint(INP2_TAG_STRING | 0xFFFFFF00); 87 writer.writeLE!uint(cast(uint)node.text.length); 88 writer.writeUTF8(node.text); 89 writer.writeINP2Padding(); 90 return; 91 } 92 93 writer.writeLE!uint(cast(uint)(INP2_TAG_STRING | (node.text.length << 8))); 94 writer.writeUTF8(node.text); 95 writer.writeINP2Padding(); 96 return; 97 98 case DataNodeType.blob_: 99 if (node.blob.length > 0xFFFFFE) { 100 writer.writeLE!uint(INP2_TAG_BLOB | 0xFFFFFF00); 101 writer.writeLE!uint(cast(uint)node.blob.length); 102 writer.stream.write(node.blob); 103 writer.writeINP2Padding(); 104 105 // Blobs have crc32 checksums. 106 writer.writeLE(crc32(node.blob)); 107 return; 108 } 109 110 writer.writeLE!uint(cast(uint)(INP2_TAG_BLOB | (node.blob.length << 8))); 111 writer.stream.write(node.blob); 112 writer.writeINP2Padding(); 113 114 // Blobs have crc32 checksums. 115 writer.writeLE(crc32(node.blob)); 116 return; 117 118 case DataNodeType.array_: 119 writer.writeLE!uint(cast(uint)(INP2_TAG_ARRAY_BEGIN | node.length << 8)); 120 foreach(n; node.array) { 121 writer.writeINP2Impl(n); 122 } 123 writer.writeLE!uint(INP2_TAG_ARRAY_END); 124 return; 125 126 case DataNodeType.object_: 127 writer.writeLE!uint(cast(uint)(INP2_TAG_OBJECT_BEGIN | node.length << 8)); 128 foreach(string key, ref DataNode value; node.object) { 129 writer.writeINP2Key(key); 130 writer.writeINP2Impl(value); 131 } 132 writer.writeLE!uint(INP2_TAG_OBJECT_END); 133 return; 134 } 135 }