/src/assimp/code/AssetLib/Blender/BlenderDNA.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Open Asset Import Library (assimp) |
3 | | ---------------------------------------------------------------------- |
4 | | |
5 | | Copyright (c) 2006-2025, assimp team |
6 | | |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use of this software in source and binary forms, |
10 | | with or without modification, are permitted provided that the |
11 | | following conditions are met: |
12 | | |
13 | | * Redistributions of source code must retain the above |
14 | | copyright notice, this list of conditions and the |
15 | | following disclaimer. |
16 | | |
17 | | * Redistributions in binary form must reproduce the above |
18 | | copyright notice, this list of conditions and the |
19 | | following disclaimer in the documentation and/or other |
20 | | materials provided with the distribution. |
21 | | |
22 | | * Neither the name of the assimp team, nor the names of its |
23 | | contributors may be used to endorse or promote products |
24 | | derived from this software without specific prior |
25 | | written permission of the assimp team. |
26 | | |
27 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | | |
39 | | ---------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | /** @file BlenderDNA.cpp |
43 | | * @brief Implementation of the Blender `DNA`, that is its own |
44 | | * serialized set of data structures. |
45 | | */ |
46 | | |
47 | | #ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER |
48 | | #include "BlenderDNA.h" |
49 | | #include <assimp/StreamReader.h> |
50 | | #include <assimp/TinyFormatter.h> |
51 | | #include <assimp/fast_atof.h> |
52 | | |
53 | | using namespace Assimp; |
54 | | using namespace Assimp::Blender; |
55 | | using namespace Assimp::Formatter; |
56 | | |
57 | 0 | static bool match4(StreamReaderAny &stream, const char *string) { |
58 | 0 | ai_assert(nullptr != string); |
59 | 0 | char tmp[4]; |
60 | 0 | tmp[0] = (stream).GetI1(); |
61 | 0 | tmp[1] = (stream).GetI1(); |
62 | 0 | tmp[2] = (stream).GetI1(); |
63 | 0 | tmp[3] = (stream).GetI1(); |
64 | 0 | return (tmp[0] == string[0] && tmp[1] == string[1] && tmp[2] == string[2] && tmp[3] == string[3]); |
65 | 0 | } |
66 | | |
67 | | struct Type { |
68 | | size_t size; |
69 | | std::string name; |
70 | | }; |
71 | | |
72 | | // ------------------------------------------------------------------------------------------------ |
73 | 0 | void DNAParser::Parse() { |
74 | 0 | StreamReaderAny &stream = *db.reader; |
75 | 0 | DNA &dna = db.dna; |
76 | |
|
77 | 0 | if (!match4(stream, "SDNA")) { |
78 | 0 | throw DeadlyImportError("BlenderDNA: Expected SDNA chunk"); |
79 | 0 | } |
80 | | |
81 | | // name dictionary |
82 | 0 | if (!match4(stream, "NAME")) { |
83 | 0 | throw DeadlyImportError("BlenderDNA: Expected NAME field"); |
84 | 0 | } |
85 | | |
86 | 0 | std::vector<std::string> names(stream.GetI4()); |
87 | 0 | for (std::string &s : names) { |
88 | 0 | while (char c = stream.GetI1()) { |
89 | 0 | s += c; |
90 | 0 | } |
91 | 0 | } |
92 | | |
93 | | // type dictionary |
94 | 0 | for (; stream.GetCurrentPos() & 0x3; stream.GetI1()) |
95 | 0 | ; |
96 | 0 | if (!match4(stream, "TYPE")) { |
97 | 0 | throw DeadlyImportError("BlenderDNA: Expected TYPE field"); |
98 | 0 | } |
99 | | |
100 | 0 | std::vector<Type> types(stream.GetI4()); |
101 | 0 | for (Type &s : types) { |
102 | 0 | while (char c = stream.GetI1()) { |
103 | 0 | s.name += c; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | // type length dictionary |
108 | 0 | for (; stream.GetCurrentPos() & 0x3; stream.GetI1()) |
109 | 0 | ; |
110 | 0 | if (!match4(stream, "TLEN")) { |
111 | 0 | throw DeadlyImportError("BlenderDNA: Expected TLEN field"); |
112 | 0 | } |
113 | | |
114 | 0 | for (Type &s : types) { |
115 | 0 | s.size = stream.GetI2(); |
116 | 0 | } |
117 | | |
118 | | // structures dictionary |
119 | 0 | for (; stream.GetCurrentPos() & 0x3; stream.GetI1()) |
120 | 0 | ; |
121 | 0 | if (!match4(stream, "STRC")) { |
122 | 0 | throw DeadlyImportError("BlenderDNA: Expected STRC field"); |
123 | 0 | } |
124 | | |
125 | 0 | size_t end = stream.GetI4(), fields = 0; |
126 | |
|
127 | 0 | dna.structures.reserve(end); |
128 | 0 | for (size_t i = 0; i != end; ++i) { |
129 | |
|
130 | 0 | uint16_t n = stream.GetI2(); |
131 | 0 | if (n >= types.size()) { |
132 | 0 | throw DeadlyImportError("BlenderDNA: Invalid type index in structure name", n, " (there are only ", types.size(), " entries)"); |
133 | 0 | } |
134 | | |
135 | | // maintain separate indexes |
136 | 0 | dna.indices[types[n].name] = dna.structures.size(); |
137 | |
|
138 | 0 | dna.structures.push_back(Structure()); |
139 | 0 | Structure &s = dna.structures.back(); |
140 | 0 | s.name = types[n].name; |
141 | |
|
142 | 0 | n = stream.GetI2(); |
143 | 0 | s.fields.reserve(n); |
144 | |
|
145 | 0 | size_t offset = 0; |
146 | 0 | for (size_t m = 0; m < n; ++m, ++fields) { |
147 | |
|
148 | 0 | uint16_t j = stream.GetI2(); |
149 | 0 | if (j >= types.size()) { |
150 | 0 | throw DeadlyImportError("BlenderDNA: Invalid type index in structure field ", j, " (there are only ", types.size(), " entries)"); |
151 | 0 | } |
152 | 0 | s.fields.push_back(Field()); |
153 | 0 | Field &f = s.fields.back(); |
154 | 0 | f.offset = offset; |
155 | |
|
156 | 0 | f.type = types[j].name; |
157 | 0 | f.size = types[j].size; |
158 | |
|
159 | 0 | j = stream.GetI2(); |
160 | 0 | if (j >= names.size()) { |
161 | 0 | throw DeadlyImportError("BlenderDNA: Invalid name index in structure field ", j, " (there are only ", names.size(), " entries)"); |
162 | 0 | } |
163 | | |
164 | 0 | f.name = names[j]; |
165 | 0 | f.flags = 0u; |
166 | | |
167 | | // pointers always specify the size of the pointee instead of their own. |
168 | | // The pointer asterisk remains a property of the lookup name. |
169 | 0 | if (f.name[0] == '*') { |
170 | 0 | f.size = db.i64bit ? 8 : 4; |
171 | 0 | f.flags |= FieldFlag_Pointer; |
172 | 0 | } |
173 | | |
174 | | // arrays, however, specify the size of a single element so we |
175 | | // need to parse the (possibly multi-dimensional) array declaration |
176 | | // in order to obtain the actual size of the array in the file. |
177 | | // Also we need to alter the lookup name to include no array |
178 | | // brackets anymore or size fixup won't work (if our size does |
179 | | // not match the size read from the DNA). |
180 | 0 | if (*f.name.rbegin() == ']') { |
181 | 0 | const std::string::size_type rb = f.name.find('['); |
182 | 0 | if (rb == std::string::npos) { |
183 | 0 | throw DeadlyImportError("BlenderDNA: Encountered invalid array declaration ", f.name); |
184 | 0 | } |
185 | | |
186 | 0 | f.flags |= FieldFlag_Array; |
187 | 0 | DNA::ExtractArraySize(f.name, f.array_sizes); |
188 | 0 | f.name = f.name.substr(0, rb); |
189 | |
|
190 | 0 | f.size *= f.array_sizes[0] * f.array_sizes[1]; |
191 | 0 | } |
192 | | |
193 | | // maintain separate indexes |
194 | 0 | s.indices[f.name] = s.fields.size() - 1; |
195 | 0 | offset += f.size; |
196 | 0 | } |
197 | 0 | s.size = offset; |
198 | 0 | } |
199 | | |
200 | 0 | ASSIMP_LOG_DEBUG("BlenderDNA: Got ", dna.structures.size(), " structures with totally ", fields, " fields"); |
201 | |
|
202 | | #if ASSIMP_BUILD_BLENDER_DEBUG_DNA |
203 | | dna.DumpToFile(); |
204 | | #endif |
205 | |
|
206 | 0 | dna.AddPrimitiveStructures(); |
207 | 0 | dna.RegisterConverters(); |
208 | 0 | } |
209 | | |
210 | | #if ASSIMP_BUILD_BLENDER_DEBUG_DNA |
211 | | |
212 | | #include <fstream> |
213 | | // ------------------------------------------------------------------------------------------------ |
214 | | void DNA ::DumpToFile() { |
215 | | // we don't bother using the VFS here for this is only for debugging. |
216 | | // (and all your bases are belong to us). |
217 | | |
218 | | std::ofstream f("dna.txt"); |
219 | | if (f.fail()) { |
220 | | ASSIMP_LOG_ERROR("Could not dump dna to dna.txt"); |
221 | | return; |
222 | | } |
223 | | f << "Field format: type name offset size" |
224 | | << "\n"; |
225 | | f << "Structure format: name size" |
226 | | << "\n"; |
227 | | |
228 | | for (const Structure &s : structures) { |
229 | | f << s.name << " " << s.size << "\n\n"; |
230 | | for (const Field &ff : s.fields) { |
231 | | f << "\t" << ff.type << " " << ff.name << " " << ff.offset << " " << ff.size << "\n"; |
232 | | } |
233 | | f << "\n"; |
234 | | } |
235 | | f << std::flush; |
236 | | |
237 | | ASSIMP_LOG_INFO("BlenderDNA: Dumped dna to dna.txt"); |
238 | | } |
239 | | #endif // ASSIMP_BUILD_BLENDER_DEBUG_DNA |
240 | | |
241 | | // ------------------------------------------------------------------------------------------------ |
242 | | /*static*/ void DNA ::ExtractArraySize( |
243 | | const std::string &out, |
244 | 0 | size_t array_sizes[2]) { |
245 | 0 | array_sizes[0] = array_sizes[1] = 1; |
246 | 0 | std::string::size_type pos = out.find('['); |
247 | 0 | if (pos++ == std::string::npos) { |
248 | 0 | return; |
249 | 0 | } |
250 | 0 | array_sizes[0] = strtoul10(&out[pos]); |
251 | |
|
252 | 0 | pos = out.find('[', pos); |
253 | 0 | if (pos++ == std::string::npos) { |
254 | 0 | return; |
255 | 0 | } |
256 | 0 | array_sizes[1] = strtoul10(&out[pos]); |
257 | 0 | } |
258 | | |
259 | | // ------------------------------------------------------------------------------------------------ |
260 | | std::shared_ptr<ElemBase> DNA ::ConvertBlobToStructure( |
261 | | const Structure &structure, |
262 | 0 | const FileDatabase &db) const { |
263 | 0 | std::map<std::string, FactoryPair>::const_iterator it = converters.find(structure.name); |
264 | 0 | if (it == converters.end()) { |
265 | 0 | return std::shared_ptr<ElemBase>(); |
266 | 0 | } |
267 | | |
268 | 0 | std::shared_ptr<ElemBase> ret = (structure.*((*it).second.first))(); |
269 | 0 | (structure.*((*it).second.second))(ret, db); |
270 | |
|
271 | 0 | return ret; |
272 | 0 | } |
273 | | |
274 | | // ------------------------------------------------------------------------------------------------ |
275 | | DNA::FactoryPair DNA ::GetBlobToStructureConverter( |
276 | | const Structure &structure, |
277 | | const FileDatabase & /*db*/ |
278 | 0 | ) const { |
279 | 0 | std::map<std::string, FactoryPair>::const_iterator it = converters.find(structure.name); |
280 | 0 | return it == converters.end() ? FactoryPair() : (*it).second; |
281 | 0 | } |
282 | | |
283 | | // basing on http://www.blender.org/development/architecture/notes-on-sdna/ |
284 | | // ------------------------------------------------------------------------------------------------ |
285 | 0 | void DNA ::AddPrimitiveStructures() { |
286 | | // NOTE: these are just dummies. Their presence enforces |
287 | | // Structure::Convert<target_type> to be called on these |
288 | | // empty structures. These converters are special |
289 | | // overloads which scan the name of the structure and |
290 | | // perform the required data type conversion if one |
291 | | // of these special names is found in the structure |
292 | | // in question. |
293 | |
|
294 | 0 | indices["int"] = structures.size(); |
295 | 0 | structures.push_back(Structure()); |
296 | 0 | structures.back().name = "int"; |
297 | 0 | structures.back().size = 4; |
298 | |
|
299 | 0 | indices["short"] = structures.size(); |
300 | 0 | structures.push_back(Structure()); |
301 | 0 | structures.back().name = "short"; |
302 | 0 | structures.back().size = 2; |
303 | |
|
304 | 0 | indices["char"] = structures.size(); |
305 | 0 | structures.push_back(Structure()); |
306 | 0 | structures.back().name = "char"; |
307 | 0 | structures.back().size = 1; |
308 | |
|
309 | 0 | indices["float"] = structures.size(); |
310 | 0 | structures.push_back(Structure()); |
311 | 0 | structures.back().name = "float"; |
312 | 0 | structures.back().size = 4; |
313 | |
|
314 | 0 | indices["double"] = structures.size(); |
315 | 0 | structures.push_back(Structure()); |
316 | 0 | structures.back().name = "double"; |
317 | 0 | structures.back().size = 8; |
318 | | |
319 | | // no long, seemingly. |
320 | 0 | } |
321 | | |
322 | | // ------------------------------------------------------------------------------------------------ |
323 | 0 | void SectionParser ::Next() { |
324 | 0 | stream.SetCurrentPos(current.start + current.size); |
325 | |
|
326 | 0 | const char tmp[] = { |
327 | 0 | (char)stream.GetI1(), |
328 | 0 | (char)stream.GetI1(), |
329 | 0 | (char)stream.GetI1(), |
330 | 0 | (char)stream.GetI1() |
331 | 0 | }; |
332 | 0 | current.id = std::string(tmp, tmp[3] ? 4 : tmp[2] ? 3 : tmp[1] ? 2 : 1); |
333 | |
|
334 | 0 | current.size = stream.GetI4(); |
335 | 0 | current.address.val = ptr64 ? stream.GetU8() : stream.GetU4(); |
336 | |
|
337 | 0 | current.dna_index = stream.GetI4(); |
338 | 0 | current.num = stream.GetI4(); |
339 | |
|
340 | 0 | current.start = stream.GetCurrentPos(); |
341 | 0 | if (stream.GetRemainingSizeToLimit() < current.size) { |
342 | 0 | throw DeadlyImportError("BLEND: invalid size of file block"); |
343 | 0 | } |
344 | | |
345 | 0 | #ifdef ASSIMP_BUILD_BLENDER_DEBUG |
346 | 0 | ASSIMP_LOG_VERBOSE_DEBUG(current.id); |
347 | 0 | #endif |
348 | 0 | } |
349 | | |
350 | | #endif |