Coverage Report

Created: 2025-08-26 06:41

/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