Coverage Report

Created: 2026-09-14 07:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Blender/BlenderDNA.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, 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
11.0k
static bool match4(StreamReaderAny &stream, const char *string) {
58
11.0k
    ai_assert(nullptr != string);
59
11.0k
    char tmp[4];
60
11.0k
    tmp[0] = (stream).GetI1();
61
11.0k
    tmp[1] = (stream).GetI1();
62
11.0k
    tmp[2] = (stream).GetI1();
63
11.0k
    tmp[3] = (stream).GetI1();
64
11.0k
    return (tmp[0] == string[0] && tmp[1] == string[1] && tmp[2] == string[2] && tmp[3] == string[3]);
65
11.0k
}
66
67
struct Type {
68
    size_t size;
69
    std::string name;
70
};
71
72
// ------------------------------------------------------------------------------------------------
73
2.22k
void DNAParser::Parse() {
74
2.22k
    StreamReaderAny &stream = *db.reader;
75
2.22k
    DNA &dna = db.dna;
76
77
2.22k
    if (!match4(stream, "SDNA")) {
78
0
        throw DeadlyImportError("BlenderDNA: Expected SDNA chunk");
79
0
    }
80
81
    // name dictionary
82
2.22k
    if (!match4(stream, "NAME")) {
83
0
        throw DeadlyImportError("BlenderDNA: Expected NAME field");
84
0
    }
85
86
2.22k
    std::vector<std::string> names(stream.GetI4());
87
6.57M
    for (std::string &s : names) {
88
69.8M
        while (char c = stream.GetI1()) {
89
63.2M
            s += c;
90
63.2M
        }
91
6.57M
    }
92
93
    // type dictionary
94
4.48k
    for (; stream.GetCurrentPos() & 0x3; stream.GetI1())
95
2.26k
        ;
96
2.22k
    if (!match4(stream, "TYPE")) {
97
2
        throw DeadlyImportError("BlenderDNA: Expected TYPE field");
98
2
    }
99
100
2.21k
    std::vector<Type> types(stream.GetI4());
101
1.20M
    for (Type &s : types) {
102
14.3M
        while (char c = stream.GetI1()) {
103
13.1M
            s.name += c;
104
13.1M
        }
105
1.20M
    }
106
107
    // type length dictionary
108
3.75k
    for (; stream.GetCurrentPos() & 0x3; stream.GetI1())
109
1.53k
        ;
110
2.21k
    if (!match4(stream, "TLEN")) {
111
5
        throw DeadlyImportError("BlenderDNA: Expected TLEN field");
112
5
    }
113
114
1.02M
    for (Type &s : types) {
115
1.02M
        s.size = stream.GetI2();
116
1.02M
    }
117
118
    // structures dictionary
119
5.17k
    for (; stream.GetCurrentPos() & 0x3; stream.GetI1())
120
2.96k
        ;
121
2.21k
    if (!match4(stream, "STRC")) {
122
0
        throw DeadlyImportError("BlenderDNA: Expected STRC field");
123
0
    }
124
125
2.21k
    size_t end = stream.GetI4(), fields = 0;
126
127
2.21k
    dna.structures.reserve(end);
128
874k
    for (size_t i = 0; i != end; ++i) {
129
130
872k
        uint16_t n = stream.GetI2();
131
872k
        if (n >= types.size()) {
132
7
            throw DeadlyImportError("BlenderDNA: Invalid type index in structure name", n, " (there are only ", types.size(), " entries)");
133
7
        }
134
135
        // maintain separate indexes
136
872k
        dna.indices[types[n].name] = dna.structures.size();
137
138
872k
        dna.structures.push_back(Structure());
139
872k
        Structure &s = dna.structures.back();
140
872k
        s.name = types[n].name;
141
142
872k
        n = stream.GetI2();
143
872k
        s.fields.reserve(n);
144
145
872k
        size_t offset = 0;
146
10.9M
        for (size_t m = 0; m < n; ++m, ++fields) {
147
148
10.1M
            uint16_t j = stream.GetI2();
149
10.1M
            if (j >= types.size()) {
150
55
                throw DeadlyImportError("BlenderDNA: Invalid type index in structure field ", j, " (there are only ", types.size(), " entries)");
151
55
            }
152
10.1M
            s.fields.push_back(Field());
153
10.1M
            Field &f = s.fields.back();
154
10.1M
            f.offset = offset;
155
156
10.1M
            f.type = types[j].name;
157
10.1M
            f.size = types[j].size;
158
159
10.1M
            j = stream.GetI2();
160
10.1M
            if (j >= names.size()) {
161
58
                throw DeadlyImportError("BlenderDNA: Invalid name index in structure field ", j, " (there are only ", names.size(), " entries)");
162
58
            }
163
164
10.1M
            f.name = names[j];
165
10.1M
            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
10.1M
            if (f.name[0] == '*') {
170
1.69M
                f.size = db.i64bit ? 8 : 4;
171
1.69M
                f.flags |= FieldFlag_Pointer;
172
1.69M
            }
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
10.1M
            if (*f.name.rbegin() == ']') {
181
1.38M
                const std::string::size_type rb = f.name.find('[');
182
1.38M
                if (rb == std::string::npos) {
183
2
                    throw DeadlyImportError("BlenderDNA: Encountered invalid array declaration ", f.name);
184
2
                }
185
186
1.38M
                f.flags |= FieldFlag_Array;
187
1.38M
                DNA::ExtractArraySize(f.name, f.array_sizes);
188
1.38M
                f.name = f.name.substr(0, rb);
189
190
1.38M
                f.size *= f.array_sizes[0] * f.array_sizes[1];
191
1.38M
            }
192
193
            // maintain separate indexes
194
10.1M
            s.indices[f.name] = s.fields.size() - 1;
195
10.1M
            offset += f.size;
196
10.1M
        }
197
872k
        s.size = offset;
198
872k
    }
199
200
2.21k
    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
2.09k
    dna.AddPrimitiveStructures();
207
2.09k
    dna.RegisterConverters();
208
2.09k
}
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
1.38M
        size_t array_sizes[2]) {
245
1.38M
    array_sizes[0] = array_sizes[1] = 1;
246
1.38M
    std::string::size_type pos = out.find('[');
247
1.38M
    if (pos++ == std::string::npos) {
248
0
        return;
249
0
    }
250
1.38M
    array_sizes[0] = strtoul10(&out[pos]);
251
252
1.38M
    pos = out.find('[', pos);
253
1.38M
    if (pos++ == std::string::npos) {
254
1.28M
        return;
255
1.28M
    }
256
99.2k
    array_sizes[1] = strtoul10(&out[pos]);
257
99.2k
}
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
5.84k
) const {
279
5.84k
    std::map<std::string, FactoryPair>::const_iterator it = converters.find(structure.name);
280
5.84k
    return it == converters.end() ? FactoryPair() : (*it).second;
281
5.84k
}
282
283
// basing on http://www.blender.org/development/architecture/notes-on-sdna/
284
// ------------------------------------------------------------------------------------------------
285
2.07k
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
2.07k
    indices["int"] = structures.size();
295
2.07k
    structures.push_back(Structure());
296
2.07k
    structures.back().name = "int";
297
2.07k
    structures.back().size = 4;
298
299
2.07k
    indices["short"] = structures.size();
300
2.07k
    structures.push_back(Structure());
301
2.07k
    structures.back().name = "short";
302
2.07k
    structures.back().size = 2;
303
304
2.07k
    indices["char"] = structures.size();
305
2.07k
    structures.push_back(Structure());
306
2.07k
    structures.back().name = "char";
307
2.07k
    structures.back().size = 1;
308
309
2.07k
    indices["float"] = structures.size();
310
2.07k
    structures.push_back(Structure());
311
2.07k
    structures.back().name = "float";
312
2.07k
    structures.back().size = 4;
313
314
2.07k
    indices["double"] = structures.size();
315
2.07k
    structures.push_back(Structure());
316
2.07k
    structures.back().name = "double";
317
2.07k
    structures.back().size = 8;
318
319
    // no long, seemingly.
320
2.07k
}
321
322
// ------------------------------------------------------------------------------------------------
323
1.89M
void SectionParser ::Next() {
324
1.89M
    stream.SetCurrentPos(current.start + current.size);
325
326
1.89M
    const char tmp[] = {
327
1.89M
        (char)stream.GetI1(),
328
1.89M
        (char)stream.GetI1(),
329
1.89M
        (char)stream.GetI1(),
330
1.89M
        (char)stream.GetI1()
331
1.89M
    };
332
1.89M
    current.id = std::string(tmp, tmp[3] ? 4 : tmp[2] ? 3 : tmp[1] ? 2 : 1);
333
334
1.89M
    current.size = stream.GetI4();
335
1.89M
    current.address.val = ptr64 ? stream.GetU8() : stream.GetU4();
336
337
1.89M
    current.dna_index = stream.GetI4();
338
1.89M
    current.num = stream.GetI4();
339
340
1.89M
    current.start = stream.GetCurrentPos();
341
1.89M
    if (stream.GetRemainingSizeToLimit() < current.size) {
342
115
        throw DeadlyImportError("BLEND: invalid size of file block");
343
115
    }
344
345
1.89M
#ifdef ASSIMP_BUILD_BLENDER_DEBUG
346
1.89M
    ASSIMP_LOG_VERBOSE_DEBUG(current.id);
347
1.89M
#endif
348
1.89M
}
349
350
#endif