1 /* 2 Inochi2D Puppet file format 3 4 Copyright © 2020, Inochi2D Project 5 Distributed under the 2-Clause BSD License, see LICENSE file. 6 7 Authors: Luna Nielsen 8 */ 9 module inochi2d.fmt; 10 import inochi2d.fmt.binfmt; 11 public import inochi2d.fmt.serialize; 12 import inochi2d.integration; 13 import inochi2d.core; 14 import std.bitmanip; 15 import std.exception; 16 import std.path; 17 import std.file; 18 import std.format; 19 import imagefmt; 20 21 private bool isLoadingINP_ = false; 22 23 /** 24 Gets whether the current loading state is set to INP loading 25 */ 26 bool inIsINPMode() { 27 return isLoadingINP_; 28 } 29 30 /** 31 Loads a puppet from a file 32 */ 33 Puppet inLoadPuppet(string file) { 34 ubyte[] buffer = cast(ubyte[])read(file); 35 36 switch(extension(file)) { 37 38 case ".json": 39 enforce(!inVerifyMagicBytes(buffer), "Tried loading INP format as JSON format, rename file to .inp extension"); 40 return inLoadJSONPuppet(cast(string)buffer); 41 42 case ".inp": 43 enforce(inVerifyMagicBytes(buffer), "Invalid data format for INP puppet"); 44 return inLoadINPPuppet(buffer); 45 46 case ".inx": 47 enforce(inVerifyMagicBytes(buffer), "Invalid data format for Inochi Creator INX"); 48 return inLoadINPPuppet(buffer); 49 50 default: 51 throw new Exception("Invalid file format of %s at path %s".format(extension(file), file)); 52 } 53 } 54 55 /** 56 Loads a puppet from memory 57 */ 58 Puppet inLoadPuppetFromMemory(ubyte[] data) { 59 return deserialize!Puppet(cast(string)data); 60 } 61 62 /** 63 Loads a JSON based puppet 64 */ 65 Puppet inLoadJSONPuppet(string data) { 66 isLoadingINP_ = false; 67 return inLoadJsonDataFromMemory!Puppet(data); 68 } 69 70 /** 71 Loads a INP based puppet 72 */ 73 Puppet inLoadINPPuppet(ubyte[] buffer) { 74 size_t bufferOffset = 0; 75 isLoadingINP_ = true; 76 77 enforce(inVerifyMagicBytes(buffer), "Invalid data format for INP puppet"); 78 bufferOffset += 8; // Magic bytes are 8 bytes 79 80 // Find the puppet data 81 uint puppetDataLength; 82 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], puppetDataLength); 83 84 string puppetData = cast(string)buffer[bufferOffset..bufferOffset+=puppetDataLength]; 85 86 enforce(inVerifySection(buffer[bufferOffset..bufferOffset+=8], TEX_SECTION), "Expected Texture Blob section, got nothing!"); 87 88 // Load textures in to memory 89 version (InDoesRender) { 90 inBeginTextureLoading(); 91 92 // Get amount of slots 93 uint slotCount; 94 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], slotCount); 95 96 Texture[] slots; 97 foreach(i; 0..slotCount) { 98 99 uint textureLength; 100 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], textureLength); 101 102 ubyte textureType = buffer[bufferOffset++]; 103 if (textureLength == 0) { 104 inAddTextureBinary(ShallowTexture([], 0, 0, 4)); 105 } else inAddTextureBinary(ShallowTexture(buffer[bufferOffset..bufferOffset+=textureLength])); 106 107 // Readd to puppet so that stuff doesn't break if we re-save the puppet 108 slots ~= inGetLatestTexture(); 109 } 110 111 Puppet puppet = inLoadJsonDataFromMemory!Puppet(puppetData); 112 puppet.textureSlots = slots; 113 puppet.updateTextureState(); 114 inEndTextureLoading(); 115 } else version(InRenderless) { 116 inCurrentPuppetTextureSlots.length = 0; 117 118 // Get amount of slots 119 uint slotCount; 120 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], slotCount); 121 foreach(i; 0..slotCount) { 122 123 uint textureLength; 124 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], textureLength); 125 126 ubyte textureType = buffer[bufferOffset++]; 127 if (textureLength == 0) { 128 continue; 129 } else inCurrentPuppetTextureSlots ~= TextureBlob(textureType, buffer[bufferOffset..bufferOffset+=textureLength]); 130 } 131 132 Puppet puppet = inLoadJsonDataFromMemory!Puppet(puppetData); 133 } 134 135 if (buffer.length >= bufferOffset + 8 && inVerifySection(buffer[bufferOffset..bufferOffset+=8], EXT_SECTION)) { 136 uint sectionCount; 137 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], sectionCount); 138 139 foreach(section; 0..sectionCount) { 140 import std.json : parseJSON; 141 142 // Get name of payload/vendor extended data 143 uint sectionNameLength; 144 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], sectionNameLength); 145 string sectionName = cast(string)buffer[bufferOffset..bufferOffset+=sectionNameLength]; 146 147 // Get length of data 148 uint payloadLength; 149 inInterpretDataFromBuffer(buffer[bufferOffset..bufferOffset+=4], payloadLength); 150 151 // Load the vendor JSON data in to the extData section of the puppet 152 ubyte[] payload = buffer[bufferOffset..bufferOffset+=payloadLength]; 153 puppet.extData[sectionName] = payload; 154 } 155 } 156 157 // We're done! 158 return puppet; 159 } 160 161 /** 162 Writes Inochi2D puppet to file 163 */ 164 void inWriteINPPuppet(Puppet p, string file) { 165 import inochi2d.ver : IN_VERSION; 166 import std.range : appender; 167 import std.json : JSONValue; 168 169 isLoadingINP_ = true; 170 auto app = appender!(ubyte[]); 171 172 // Write the current used Inochi2D version to the version_ meta tag. 173 p.meta.version_ = IN_VERSION; 174 string puppetJson = inToJson(p); 175 176 app ~= MAGIC_BYTES; 177 app ~= nativeToBigEndian(cast(uint)puppetJson.length)[0..4]; 178 app ~= cast(ubyte[])puppetJson; 179 180 // Begin texture section 181 app ~= TEX_SECTION; 182 app ~= nativeToBigEndian(cast(uint)p.textureSlots.length)[0..4]; 183 foreach(texture; p.textureSlots) { 184 int e; 185 ubyte[] tex = write_image_mem(IF_TGA, texture.width, texture.height, texture.getTextureData(), 4, e); 186 app ~= nativeToBigEndian(cast(uint)tex.length)[0..4]; 187 app ~= (cast(ubyte)IN_TEX_TGA); 188 app ~= (tex); 189 } 190 191 // Don't waste bytes on empty EXT data sections 192 if (p.extData.length > 0) { 193 // Begin extended section 194 app ~= EXT_SECTION; 195 app ~= nativeToBigEndian(cast(uint)p.extData.length)[0..4]; 196 197 foreach(name, payload; p.extData) { 198 199 // Write payload name and its length 200 app ~= nativeToBigEndian(cast(uint)name.length)[0..4]; 201 app ~= cast(ubyte[])name; 202 203 // Write payload length and payload 204 app ~= nativeToBigEndian(cast(uint)payload.length)[0..4]; 205 app ~= payload; 206 207 } 208 } 209 210 // Write it out to file 211 write(file, app.data); 212 } 213 214 enum IN_TEX_PNG = 0u; /// PNG encoded Inochi2D texture 215 enum IN_TEX_TGA = 1u; /// TGA encoded Inochi2D texture 216 enum IN_TEX_BC7 = 2u; /// BC7 encoded Inochi2D texture 217 218 /** 219 Writes a puppet to file 220 */ 221 void inWriteJSONPuppet(Puppet p, string file) { 222 isLoadingINP_ = false; 223 write(file, inToJson(p)); 224 }