Blender V4.5
writefile.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
60
61#include <cerrno>
62#include <climits>
63#include <cstdio>
64#include <cstdlib>
65#include <cstring>
66#include <fcntl.h>
67#include <iomanip>
68#include <sstream>
69
70#ifdef WIN32
71# include "BLI_winstuff.h"
72# include "winsock2.h"
73# include <io.h>
74#else
75# include <unistd.h> /* FreeBSD, for write() and close(). */
76#endif
77
78#include <fmt/format.h>
79
80#include "BLI_utildefines.h"
81
82#include "CLG_log.h"
83
84/* Allow writefile to use deprecated functionality (for forward compatibility code). */
85#define DNA_DEPRECATED_ALLOW
86
88#include "DNA_genfile.h"
89#include "DNA_key_types.h"
90#include "DNA_print.hh"
91#include "DNA_sdna_types.h"
92#include "DNA_userdef_types.h"
93
94#include "BLI_endian_defines.h"
95#include "BLI_endian_switch.h"
96#include "BLI_fileops.hh"
98#include "BLI_math_base.h"
100#include "BLI_path_utils.hh"
101#include "BLI_set.hh"
102#include "BLI_string.h"
103#include "BLI_threads.h"
104
105#include "MEM_guardedalloc.h" /* MEM_freeN */
106
107#include "BKE_asset.hh"
108#include "BKE_blender_version.h"
109#include "BKE_bpath.hh"
110#include "BKE_global.hh" /* For #Global `G`. */
111#include "BKE_idprop.hh"
112#include "BKE_idtype.hh"
113#include "BKE_layer.hh"
114#include "BKE_lib_id.hh"
115#include "BKE_lib_override.hh"
116#include "BKE_lib_query.hh"
117#include "BKE_library.hh"
118#include "BKE_main.hh"
119#include "BKE_main_namemap.hh"
120#include "BKE_node.hh"
121#include "BKE_packedFile.hh"
122#include "BKE_preferences.h"
123#include "BKE_report.hh"
124#include "BKE_workspace.hh"
125
126#include "DRW_engine.hh"
127
128#include "BLO_blend_validate.hh"
129#include "BLO_read_write.hh"
130#include "BLO_readfile.hh"
131#include "BLO_undofile.hh"
132#include "BLO_writefile.hh"
133
134#include "readfile.hh"
135
136#include <zstd.h>
137
138/* Make preferences read-only. */
139#define U (*((const UserDef *)&U))
140
145#define GENERATE_DEBUG_BLEND_FILE 0
146#define DEBUG_BLEND_FILE_SUFFIX ".debug.txt"
147
148/* ********* my write, buffered writing with minimum size chunks ************ */
149
150/* Use optimal allocation since blocks of this size are kept in memory for undo. */
151#define MEM_BUFFER_SIZE MEM_SIZE_OPTIMAL(1 << 17) /* 128kb */
152#define MEM_CHUNK_SIZE MEM_SIZE_OPTIMAL(1 << 15) /* ~32kb */
153
154#define ZSTD_BUFFER_SIZE (1 << 21) /* 2mb */
155#define ZSTD_CHUNK_SIZE (1 << 20) /* 1mb */
156
157#define ZSTD_COMPRESSION_LEVEL 3
158
159static CLG_LogRef LOG = {"blo.writefile"};
160
162// #define USE_WRITE_DATA_LEN
163
164/* -------------------------------------------------------------------- */
167
174
176 public:
177 virtual bool open(const char *filepath) = 0;
178 virtual bool close() = 0;
179 virtual bool write(const void *buf, size_t buf_len) = 0;
180
182 bool use_buf = true;
183};
184
185class RawWriteWrap : public WriteWrap {
186 public:
187 bool open(const char *filepath) override;
188 bool close() override;
189 bool write(const void *buf, size_t buf_len) override;
190
191 private:
192 int file_handle = 0;
193};
194
195bool RawWriteWrap::open(const char *filepath)
196{
197 int file;
198
199 file = BLI_open(filepath, O_BINARY + O_WRONLY + O_CREAT + O_TRUNC, 0666);
200
201 if (file != -1) {
202 file_handle = file;
203 return true;
204 }
205
206 return false;
207}
209{
210 return (::close(file_handle) != -1);
211}
212bool RawWriteWrap::write(const void *buf, size_t buf_len)
213{
214 return ::write(file_handle, buf, buf_len) == buf_len;
215}
216
217class ZstdWriteWrap : public WriteWrap {
218 WriteWrap &base_wrap;
219
220 ListBase threadpool = {};
221 ListBase tasks = {};
222 ThreadMutex mutex = {};
223 ThreadCondition condition = {};
224 int next_frame = 0;
225 int num_frames = 0;
226
227 ListBase frames = {};
228
229 bool write_error = false;
230
231 public:
232 ZstdWriteWrap(WriteWrap &base_wrap) : base_wrap(base_wrap) {}
233
234 bool open(const char *filepath) override;
235 bool close() override;
236 bool write(const void *buf, size_t buf_len) override;
237
238 private:
239 struct ZstdWriteBlockTask;
240 void write_task(ZstdWriteBlockTask *task);
241 void write_u32_le(uint32_t val);
242 void write_seekable_frames();
243};
244
247 void *data;
248 size_t size;
251
252 static void *write_task(void *userdata)
253 {
254 auto *task = static_cast<ZstdWriteBlockTask *>(userdata);
255 task->ww->write_task(task);
256 return nullptr;
257 }
258};
259
260void ZstdWriteWrap::write_task(ZstdWriteBlockTask *task)
261{
262 size_t out_buf_len = ZSTD_compressBound(task->size);
263 void *out_buf = MEM_mallocN(out_buf_len, "Zstd out buffer");
264 size_t out_size = ZSTD_compress(
265 out_buf, out_buf_len, task->data, task->size, ZSTD_COMPRESSION_LEVEL);
266
267 MEM_freeN(task->data);
268
270
271 while (next_frame != task->frame_number) {
272 BLI_condition_wait(&condition, &mutex);
273 }
274
275 if (ZSTD_isError(out_size)) {
276 write_error = true;
277 }
278 else {
279 if (base_wrap.write(out_buf, out_size)) {
280 ZstdFrame *frameinfo = MEM_mallocN<ZstdFrame>("zstd frameinfo");
281 frameinfo->uncompressed_size = task->size;
282 frameinfo->compressed_size = out_size;
283 BLI_addtail(&frames, frameinfo);
284 }
285 else {
286 write_error = true;
287 }
288 }
289
290 next_frame++;
291
292 BLI_mutex_unlock(&mutex);
293 BLI_condition_notify_all(&condition);
294
295 MEM_freeN(out_buf);
296}
297
298bool ZstdWriteWrap::open(const char *filepath)
299{
300 if (!base_wrap.open(filepath)) {
301 return false;
302 }
303
304 /* Leave one thread open for the main writing logic, unless we only have one HW thread. */
305 int num_threads = max_ii(1, BLI_system_thread_count() - 1);
306 BLI_threadpool_init(&threadpool, ZstdWriteBlockTask::write_task, num_threads);
307 BLI_mutex_init(&mutex);
308 BLI_condition_init(&condition);
309
310 return true;
311}
312
313void ZstdWriteWrap::write_u32_le(uint32_t val)
314{
315 if (ENDIAN_ORDER == B_ENDIAN) {
317 }
318 base_wrap.write(&val, sizeof(uint32_t));
319}
320
321/* In order to implement efficient seeking when reading the .blend, we append
322 * a skippable frame that encodes information about the other frames present
323 * in the file.
324 * The format here follows the upstream spec for seekable files:
325 * https://github.com/facebook/zstd/blob/master/contrib/seekable_format/zstd_seekable_compression_format.md
326 * If this information is not present in a file (e.g. if it was compressed
327 * with external tools), it can still be opened in Blender, but seeking will
328 * not be supported, so more memory might be needed. */
329void ZstdWriteWrap::write_seekable_frames()
330{
331 /* Write seek table header (magic number and frame size). */
332 write_u32_le(0x184D2A5E);
333
334 /* The actual frame number might not match num_frames if there was a write error. */
335 const uint32_t num_frames = BLI_listbase_count(&frames);
336 /* Each frame consists of two u32, so 8 bytes each.
337 * After the frames, a footer containing two u32 and one byte (9 bytes total) is written. */
338 const uint32_t frame_size = num_frames * 8 + 9;
339 write_u32_le(frame_size);
340
341 /* Write seek table entries. */
342 LISTBASE_FOREACH (ZstdFrame *, frame, &frames) {
343 write_u32_le(frame->compressed_size);
344 write_u32_le(frame->uncompressed_size);
345 }
346
347 /* Write seek table footer (number of frames, option flags and second magic number). */
348 write_u32_le(num_frames);
349 const char flags = 0; /* We don't store checksums for each frame. */
350 base_wrap.write(&flags, 1);
351 write_u32_le(0x8F92EAB1);
352}
353
355{
356 BLI_threadpool_end(&threadpool);
357 BLI_freelistN(&tasks);
358
359 BLI_mutex_end(&mutex);
360 BLI_condition_end(&condition);
361
362 write_seekable_frames();
363 BLI_freelistN(&frames);
364
365 return base_wrap.close() && !write_error;
366}
367
368bool ZstdWriteWrap::write(const void *buf, const size_t buf_len)
369{
370 if (write_error) {
371 return false;
372 }
373
375 task->data = MEM_mallocN(buf_len, __func__);
376 memcpy(task->data, buf, buf_len);
377 task->size = buf_len;
378 task->frame_number = num_frames++;
379 task->ww = this;
380
381 BLI_mutex_lock(&mutex);
382 BLI_addtail(&tasks, task);
383
384 /* If there's a free worker thread, just push the block into that thread.
385 * Otherwise, we wait for the earliest thread to finish.
386 * We look up the earliest thread while holding the mutex, but release it
387 * before joining the thread to prevent a deadlock. */
388 ZstdWriteBlockTask *first_task = static_cast<ZstdWriteBlockTask *>(tasks.first);
389 BLI_mutex_unlock(&mutex);
390 if (!BLI_available_threads(&threadpool)) {
391 BLI_threadpool_remove(&threadpool, first_task);
392
393 /* If the task list was empty before we pushed our task, there should
394 * always be a free thread. */
395 BLI_assert(first_task != task);
396 BLI_remlink(&tasks, first_task);
397 MEM_freeN(first_task);
398 }
399 BLI_threadpool_insert(&threadpool, task);
400
401 return true;
402}
403
405
406/* -------------------------------------------------------------------- */
409
410struct WriteData {
411 const SDNA *sdna;
412 std::ostream *debug_dst = nullptr;
413
414 struct {
418 size_t used_len;
419
421 size_t max_size;
425
426#ifdef USE_WRITE_DATA_LEN
428 size_t write_len;
429#endif
430
433
435 struct {
447
453
458
465};
466
469};
470
472{
473 WriteData *wd = MEM_new<WriteData>(__func__);
474
476
477 wd->ww = ww;
478
479 if ((ww == nullptr) || (ww->use_buf)) {
480 if (ww == nullptr) {
483 }
484 else {
487 }
488 wd->buffer.buf = MEM_malloc_arrayN<uchar>(wd->buffer.max_size, "wd->buffer.buf");
489 }
490
491 return wd;
492}
493
494static void writedata_do_write(WriteData *wd, const void *mem, const size_t memlen)
495{
496 if ((wd == nullptr) || wd->validation_data.critical_error || (mem == nullptr) || memlen < 1) {
497 return;
498 }
499
500 if (memlen > INT_MAX) {
501 BLI_assert_msg(0, "Cannot write chunks bigger than INT_MAX.");
502 return;
503 }
504
506 return;
507 }
508
509 /* Memory based save. */
510 if (wd->use_memfile) {
511 BLO_memfile_chunk_add(&wd->mem, static_cast<const char *>(mem), memlen);
512 }
513 else {
514 if (!wd->ww->write(mem, memlen)) {
516 }
517 }
518}
519
520static void writedata_free(WriteData *wd)
521{
522 if (wd->buffer.buf) {
523 MEM_freeN(wd->buffer.buf);
524 }
525 MEM_delete(wd);
526}
527
529
530/* -------------------------------------------------------------------- */
533
538static void mywrite_flush(WriteData *wd)
539{
540 if (wd->buffer.used_len != 0) {
542 wd->buffer.used_len = 0;
543 }
544}
545
551static void mywrite(WriteData *wd, const void *adr, size_t len)
552{
554 return;
555 }
556
557 if (UNLIKELY(adr == nullptr)) {
558 BLI_assert(0);
559 return;
560 }
561
562#ifdef USE_WRITE_DATA_LEN
563 wd->write_len += len;
564#endif
565
566 if (wd->buffer.buf == nullptr) {
567 writedata_do_write(wd, adr, len);
568 }
569 else {
570 /* If we have a single big chunk, write existing data in
571 * buffer and write out big chunk in smaller pieces. */
572 if (len > wd->buffer.chunk_size) {
573 if (wd->buffer.used_len != 0) {
575 wd->buffer.used_len = 0;
576 }
577
578 do {
579 const size_t writelen = std::min(len, wd->buffer.chunk_size);
580 writedata_do_write(wd, adr, writelen);
581 adr = (const char *)adr + writelen;
582 len -= writelen;
583 } while (len > 0);
584
585 return;
586 }
587
588 /* If data would overflow buffer, write out the buffer. */
589 if (len + wd->buffer.used_len > wd->buffer.max_size - 1) {
591 wd->buffer.used_len = 0;
592 }
593
594 /* Append data at end of buffer. */
595 memcpy(&wd->buffer.buf[wd->buffer.used_len], adr, len);
596 wd->buffer.used_len += len;
597 }
598}
599
607static WriteData *mywrite_begin(WriteWrap *ww, MemFile *compare, MemFile *current)
608{
609 WriteData *wd = writedata_new(ww);
610
611 if (current != nullptr) {
612 BLO_memfile_write_init(&wd->mem, current, compare);
613 wd->use_memfile = true;
614 }
615
616 return wd;
617}
618
625static bool mywrite_end(WriteData *wd)
626{
627 if (wd->buffer.used_len != 0) {
629 wd->buffer.used_len = 0;
630 }
631
632 if (wd->use_memfile) {
634 }
635
636 const bool err = wd->validation_data.critical_error;
637 writedata_free(wd);
638
639 return err;
640}
641
647static void mywrite_id_begin(WriteData *wd, ID *id)
648{
649 BLI_assert(wd->is_writing_id == false);
650 wd->is_writing_id = true;
651
653
654 if (wd->use_memfile) {
655 wd->mem.current_id_session_uid = id->session_uid;
656
657 /* If current next memchunk does not match the ID we are about to write, or is not the _first_
658 * one for said ID, try to find the correct memchunk in the mapping using ID's session_uid. */
659 const MemFileChunk *curr_memchunk = wd->mem.reference_current_chunk;
660 const MemFileChunk *prev_memchunk = curr_memchunk != nullptr ?
661 static_cast<MemFileChunk *>(curr_memchunk->prev) :
662 nullptr;
663 if (curr_memchunk == nullptr || curr_memchunk->id_session_uid != id->session_uid ||
664 (prev_memchunk != nullptr &&
665 (prev_memchunk->id_session_uid == curr_memchunk->id_session_uid)))
666 {
668 nullptr))
669 {
671 }
672 /* Else, no existing memchunk found, i.e. this is supposed to be a new ID. */
673 }
674 /* Otherwise, we try with the current memchunk in any case, whether it is matching current
675 * ID's session_uid or not. */
676 }
677}
678
684static void mywrite_id_end(WriteData *wd, ID * /*id*/)
685{
686 if (wd->use_memfile) {
687 /* Very important to do it after every ID write now, otherwise we cannot know whether a
688 * specific ID changed or not. */
689 mywrite_flush(wd);
691 }
692
695
696 BLI_assert(wd->is_writing_id == true);
697 wd->is_writing_id = false;
698}
699
701
702/* -------------------------------------------------------------------- */
705
713static bool write_at_address_validate(WriteData *wd, const int filecode, const void *address)
714{
715 /* Skip in undo case. */
716 if (wd->use_memfile) {
717 return true;
718 }
719
720 if (wd->is_writing_id && filecode == BLO_CODE_DATA) {
721 if (!wd->validation_data.per_id_addresses_set.add(address)) {
723 "Same identifier (old address) used several times for a same ID, skipping this "
724 "block to avoid critical corruption of the Blender file.");
725 return false;
726 }
727 }
728 return true;
729}
730
731static void write_bhead(WriteData *wd, const BHead &bhead)
732{
733 if constexpr (sizeof(void *) == 4) {
734 /* Always write #BHead4 in 32 bit builds. */
735 BHead4 bh;
736 bh.code = bhead.code;
737 bh.old = uint32_t(uintptr_t(bhead.old));
738 bh.nr = bhead.nr;
739 bh.SDNAnr = bhead.SDNAnr;
740 bh.len = bhead.len;
741 mywrite(wd, &bh, sizeof(bh));
742 return;
743 }
744 /* Write new #LargeBHead8 headers if enabled. Older Blender versions can't read those. */
745 if (USER_EXPERIMENTAL_TEST(&U, write_large_blend_file_blocks)) {
747 static_assert(sizeof(BHead) == sizeof(LargeBHead8));
748 mywrite(wd, &bhead, sizeof(bhead));
749 return;
750 }
751 }
752 /* Write older #SmallBHead8 headers so that Blender versions that don't support #LargeBHead8 can
753 * read the file. */
754 SmallBHead8 bh;
755 bh.code = bhead.code;
756 bh.old = uint64_t(bhead.old);
757 bh.nr = bhead.nr;
758 bh.SDNAnr = bhead.SDNAnr;
759 bh.len = bhead.len;
760 /* Check that the written buffer size is compatible with the limits of #SmallBHead8. */
761 if (bhead.len > std::numeric_limits<decltype(bh.len)>::max()) {
762 CLOG_ERROR(&LOG, "Written .blend file is corrupt, because a memory block is too large.");
763 return;
764 }
765 mywrite(wd, &bh, sizeof(bh));
766}
767
769 const int filecode,
770 const int struct_nr,
771 const int64_t nr,
772 const void *adr,
773 const void *data)
774{
775 BLI_assert(struct_nr > 0 && struct_nr <= blender::dna::sdna_struct_id_get_max());
776
777 if (adr == nullptr || data == nullptr || nr == 0) {
778 return;
779 }
780
781 if (!write_at_address_validate(wd, filecode, adr)) {
782 return;
783 }
784
785 const int64_t len_in_bytes = nr * DNA_struct_size(wd->sdna, struct_nr);
787 !USER_EXPERIMENTAL_TEST(&U, write_large_blend_file_blocks))
788 {
789 if (len_in_bytes > INT32_MAX) {
790 CLOG_ERROR(&LOG, "Cannot write chunks bigger than INT_MAX.");
791 return;
792 }
793 }
794
795 BHead bh;
796 bh.code = filecode;
797 bh.old = adr;
798 bh.nr = nr;
799 bh.SDNAnr = struct_nr;
800 bh.len = len_in_bytes;
801
802 if (bh.len == 0) {
803 return;
804 }
805
806 if (wd->debug_dst) {
807 blender::dna::print_structs_at_address(*wd->sdna, struct_nr, data, adr, nr, *wd->debug_dst);
808 }
809
810 write_bhead(wd, bh);
811 mywrite(wd, data, size_t(bh.len));
812}
813
814static void writestruct_nr(
815 WriteData *wd, const int filecode, const int struct_nr, const int64_t nr, const void *adr)
816{
817 writestruct_at_address_nr(wd, filecode, struct_nr, nr, adr, adr);
818}
819
820static void write_raw_data_in_debug_file(WriteData *wd, const size_t len, const void *adr)
821{
822 fmt::memory_buffer buf;
823 fmt::appender dst{buf};
824
825 fmt::format_to(dst, "<Raw Data> at {} ({} bytes)\n", adr, len);
826
827 constexpr int bytes_per_row = 8;
828 const int len_digits = std::to_string(std::max<size_t>(0, len - 1)).size();
829
830 for (size_t i = 0; i < len; i++) {
831 if (i % bytes_per_row == 0) {
832 fmt::format_to(dst, " {:{}}: ", i, len_digits);
833 }
834 fmt::format_to(dst, "{:02x} ", reinterpret_cast<const uint8_t *>(adr)[i]);
835 if (i % bytes_per_row == bytes_per_row - 1) {
836 fmt::format_to(dst, "\n");
837 }
838 }
839 if (len % bytes_per_row != 0) {
840 fmt::format_to(dst, "\n");
841 }
842
843 *wd->debug_dst << fmt::to_string(buf);
844}
845
849static void writedata(WriteData *wd, const int filecode, const size_t len, const void *adr)
850{
851 if (adr == nullptr || len == 0) {
852 return;
853 }
854
855 if (!write_at_address_validate(wd, filecode, adr)) {
856 return;
857 }
858
860 !USER_EXPERIMENTAL_TEST(&U, write_large_blend_file_blocks)) &&
861 len > INT_MAX)
862 {
863 BLI_assert_msg(0, "Cannot write chunks bigger than INT_MAX.");
864 return;
865 }
866
867 BHead bh;
868 bh.code = filecode;
869 bh.old = adr;
870 bh.nr = 1;
871 BLI_STATIC_ASSERT(SDNA_RAW_DATA_STRUCT_INDEX == 0, "'raw data' SDNA struct index should be 0")
873 bh.len = int64_t(len);
874
875 if (wd->debug_dst) {
877 }
878
879 write_bhead(wd, bh);
880 mywrite(wd, adr, len);
881}
882
886static void writelist_nr(WriteData *wd,
887 const int filecode,
888 const int struct_nr,
889 const ListBase *lb)
890{
891 const Link *link = static_cast<Link *>(lb->first);
892
893 while (link) {
894 writestruct_nr(wd, filecode, struct_nr, 1, link);
895 link = link->next;
896 }
897}
898
899#if 0
900static void writelist_id(WriteData *wd, const int filecode, const char *structname, const ListBase *lb)
901{
902 const Link *link = lb->first;
903 if (link) {
904
905 const int struct_nr = DNA_struct_find_with_alias(wd->sdna, structname);
906 if (struct_nr == -1) {
907 printf("error: can't find SDNA code <%s>\n", structname);
908 return;
909 }
910
911 while (link) {
912 writestruct_nr(wd, filecode, struct_nr, 1, link);
913 link = link->next;
914 }
915 }
916}
917#endif
918
919#define writestruct_at_address(wd, filecode, struct_id, nr, adr, data) \
920 writestruct_at_address_nr( \
921 wd, filecode, blender::dna::sdna_struct_id_get<struct_id>(), nr, adr, data)
922
923#define writestruct(wd, filecode, struct_id, nr, adr) \
924 writestruct_nr(wd, filecode, blender::dna::sdna_struct_id_get<struct_id>(), nr, adr)
925
927
928/* -------------------------------------------------------------------- */
933
938static void current_screen_compat(Main *mainvar,
939 const bool use_active_win,
940 bScreen **r_screen,
941 Scene **r_scene,
942 ViewLayer **r_view_layer)
943{
944 wmWindowManager *wm;
945 wmWindow *window = nullptr;
946
947 /* Find a global current screen in the first open window, to have
948 * a reasonable default for reading in older versions. */
949 wm = static_cast<wmWindowManager *>(mainvar->wm.first);
950
951 if (wm) {
952 if (use_active_win) {
953 /* Write the active window into the file, needed for multi-window undo #43424. */
954 for (window = static_cast<wmWindow *>(wm->windows.first); window; window = window->next) {
955 if (window->active) {
956 break;
957 }
958 }
959
960 /* Fallback. */
961 if (window == nullptr) {
962 window = static_cast<wmWindow *>(wm->windows.first);
963 }
964 }
965 else {
966 window = static_cast<wmWindow *>(wm->windows.first);
967 }
968 }
969
970 *r_screen = (window) ? BKE_workspace_active_screen_get(window->workspace_hook) : nullptr;
971 *r_scene = (window) ? window->scene : nullptr;
972 *r_view_layer = (window && *r_scene) ? BKE_view_layer_find(*r_scene, window->view_layer_name) :
973 nullptr;
974}
975
977 int sfra;
978 int efra;
980};
981
988static void write_renderinfo(WriteData *wd, Main *mainvar)
989{
990 bScreen *curscreen;
991 Scene *curscene = nullptr;
992 ViewLayer *view_layer;
993
994 /* XXX: in future, handle multiple windows with multiple screens? */
995 current_screen_compat(mainvar, false, &curscreen, &curscene, &view_layer);
996
997 LISTBASE_FOREACH (Scene *, sce, &mainvar->scenes) {
998 if (!ID_IS_LINKED(sce) && (sce == curscene || (sce->r.scemode & R_BG_RENDER))) {
1000 data.sfra = sce->r.sfra;
1001 data.efra = sce->r.efra;
1002 memset(data.scene_name, 0, sizeof(data.scene_name));
1003
1004 STRNCPY(data.scene_name, sce->id.name + 2);
1005
1006 writedata(wd, BLO_CODE_REND, sizeof(data), &data);
1007 }
1008 }
1009}
1010
1011static void write_keymapitem(BlendWriter *writer, const wmKeyMapItem *kmi)
1012{
1013 BLO_write_struct(writer, wmKeyMapItem, kmi);
1014 if (kmi->properties) {
1015 IDP_BlendWrite(writer, kmi->properties);
1016 }
1017}
1018
1019static void write_userdef(BlendWriter *writer, const UserDef *userdef)
1020{
1021 writestruct(writer->wd, BLO_CODE_USER, UserDef, 1, userdef);
1022
1023 LISTBASE_FOREACH (const bTheme *, btheme, &userdef->themes) {
1024 BLO_write_struct(writer, bTheme, btheme);
1025 }
1026
1027 LISTBASE_FOREACH (const wmKeyMap *, keymap, &userdef->user_keymaps) {
1028 BLO_write_struct(writer, wmKeyMap, keymap);
1029
1030 LISTBASE_FOREACH (const wmKeyMapDiffItem *, kmdi, &keymap->diff_items) {
1031 BLO_write_struct(writer, wmKeyMapDiffItem, kmdi);
1032 if (kmdi->remove_item) {
1033 write_keymapitem(writer, kmdi->remove_item);
1034 }
1035 if (kmdi->add_item) {
1036 write_keymapitem(writer, kmdi->add_item);
1037 }
1038 }
1039
1040 LISTBASE_FOREACH (const wmKeyMapItem *, kmi, &keymap->items) {
1041 write_keymapitem(writer, kmi);
1042 }
1043 }
1044
1045 LISTBASE_FOREACH (const wmKeyConfigPref *, kpt, &userdef->user_keyconfig_prefs) {
1046 BLO_write_struct(writer, wmKeyConfigPref, kpt);
1047 if (kpt->prop) {
1048 IDP_BlendWrite(writer, kpt->prop);
1049 }
1050 }
1051
1052 LISTBASE_FOREACH (const bUserMenu *, um, &userdef->user_menus) {
1053 BLO_write_struct(writer, bUserMenu, um);
1054 LISTBASE_FOREACH (const bUserMenuItem *, umi, &um->items) {
1055 if (umi->type == USER_MENU_TYPE_OPERATOR) {
1056 const bUserMenuItem_Op *umi_op = (const bUserMenuItem_Op *)umi;
1057 BLO_write_struct(writer, bUserMenuItem_Op, umi_op);
1058 if (umi_op->prop) {
1059 IDP_BlendWrite(writer, umi_op->prop);
1060 }
1061 }
1062 else if (umi->type == USER_MENU_TYPE_MENU) {
1063 const bUserMenuItem_Menu *umi_mt = (const bUserMenuItem_Menu *)umi;
1064 BLO_write_struct(writer, bUserMenuItem_Menu, umi_mt);
1065 }
1066 else if (umi->type == USER_MENU_TYPE_PROP) {
1067 const bUserMenuItem_Prop *umi_pr = (const bUserMenuItem_Prop *)umi;
1068 BLO_write_struct(writer, bUserMenuItem_Prop, umi_pr);
1069 }
1070 else {
1071 BLO_write_struct(writer, bUserMenuItem, umi);
1072 }
1073 }
1074 }
1075
1076 LISTBASE_FOREACH (const bAddon *, bext, &userdef->addons) {
1077 BLO_write_struct(writer, bAddon, bext);
1078 if (bext->prop) {
1079 IDP_BlendWrite(writer, bext->prop);
1080 }
1081 }
1082
1083 LISTBASE_FOREACH (const bPathCompare *, path_cmp, &userdef->autoexec_paths) {
1084 BLO_write_struct(writer, bPathCompare, path_cmp);
1085 }
1086
1087 LISTBASE_FOREACH (const bUserScriptDirectory *, script_dir, &userdef->script_directories) {
1088 BLO_write_struct(writer, bUserScriptDirectory, script_dir);
1089 }
1090
1091 LISTBASE_FOREACH (const bUserAssetLibrary *, asset_library_ref, &userdef->asset_libraries) {
1092 BLO_write_struct(writer, bUserAssetLibrary, asset_library_ref);
1093 }
1094
1095 LISTBASE_FOREACH (const bUserExtensionRepo *, repo_ref, &userdef->extension_repos) {
1096 BLO_write_struct(writer, bUserExtensionRepo, repo_ref);
1098 }
1100 const bUserAssetShelfSettings *, shelf_settings, &userdef->asset_shelves_settings)
1101 {
1102 BLO_write_struct(writer, bUserAssetShelfSettings, shelf_settings);
1103 BKE_asset_catalog_path_list_blend_write(writer, shelf_settings->enabled_catalog_paths);
1104 }
1105
1106 LISTBASE_FOREACH (const uiStyle *, style, &userdef->uistyles) {
1107 BLO_write_struct(writer, uiStyle, style);
1108 }
1109}
1110
1114static void write_id(WriteData *wd, ID *id)
1115{
1116 const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
1117 mywrite_id_begin(wd, id);
1118 if (id_type->blend_write != nullptr) {
1119 BlendWriter writer = {wd};
1120 BLO_Write_IDBuffer id_buffer{*id, wd->use_memfile, false};
1121 id_type->blend_write(&writer, id_buffer.get(), id);
1122 }
1123 mywrite_id_end(wd, id);
1124}
1125
1126static void write_id_placeholder(WriteData *wd, ID *id)
1127{
1128 mywrite_id_begin(wd, id);
1129
1130 /* Only copy required data for the placeholder ID. */
1131 BLO_Write_IDBuffer id_buffer{*id, wd->use_memfile, true};
1132
1133 writestruct_at_address(wd, ID_LINK_PLACEHOLDER, ID, 1, id, &id_buffer);
1134
1135 mywrite_id_end(wd, id);
1136}
1137
1139static void write_libraries(WriteData *wd, Main *bmain)
1140{
1141 /* Gather IDs coming from each library. */
1142 blender::MultiValueMap<Library *, ID *> linked_ids_by_library;
1143 {
1144 ID *id;
1145 FOREACH_MAIN_ID_BEGIN (bmain, id) {
1146 if (!ID_IS_LINKED(id)) {
1147 continue;
1148 }
1149 BLI_assert(id->lib);
1150 linked_ids_by_library.add(id->lib, id);
1151 }
1153 }
1154
1155 LISTBASE_FOREACH (Library *, library_ptr, &bmain->libraries) {
1156 Library &library = *library_ptr;
1157 const blender::Span<ID *> ids = linked_ids_by_library.lookup(&library);
1158
1159 /* Gather IDs that are somehow directly referenced by data in the current blend file. */
1160 blender::Vector<ID *> ids_used_from_library;
1161 for (ID *id : ids) {
1162 if (id->us == 0) {
1163 continue;
1164 }
1165 if (id->tag & ID_TAG_EXTERN) {
1166 ids_used_from_library.append(id);
1167 continue;
1168 }
1169 if ((id->tag & ID_TAG_INDIRECT) && (id->flag & ID_FLAG_INDIRECT_WEAK_LINK)) {
1170 ids_used_from_library.append(id);
1171 continue;
1172 }
1173 }
1174
1175 bool should_write_library = false;
1176 if (library.packedfile) {
1177 should_write_library = true;
1178 }
1179 else if (wd->use_memfile) {
1180 /* When writing undo step we always write all existing libraries. That makes reading undo
1181 * step much easier when dealing with purely indirectly used libraries. */
1182 should_write_library = true;
1183 }
1184 else {
1185 should_write_library = !ids_used_from_library.is_empty();
1186 }
1187
1188 if (!should_write_library) {
1189 /* Nothing from the library is used, so it does not have to be written. */
1190 continue;
1191 }
1192
1193 write_id(wd, &library.id);
1194
1195 /* Write placeholders for linked data-blocks that are used. */
1196 for (ID *id : ids_used_from_library) {
1197 if (!BKE_idtype_idcode_is_linkable(GS(id->name))) {
1198 CLOG_ERROR(&LOG,
1199 "Data-block '%s' from lib '%s' is not linkable, but is flagged as "
1200 "directly linked",
1201 id->name,
1202 library.runtime->filepath_abs);
1203 }
1204 write_id_placeholder(wd, id);
1205 }
1206 }
1207
1208 mywrite_flush(wd);
1209}
1210
1211#ifdef WITH_BUILDINFO
1212extern "C" ulong build_commit_timestamp;
1213extern "C" char build_hash[];
1214#endif
1215
1221static void write_global(WriteData *wd, const int fileflags, Main *mainvar)
1222{
1223 const bool is_undo = wd->use_memfile;
1224 FileGlobal fg;
1225 bScreen *screen;
1226 Scene *scene;
1227 ViewLayer *view_layer;
1228 char subvstr[8];
1229
1230 /* Prevent memory checkers from complaining. */
1231 memset(fg._pad, 0, sizeof(fg._pad));
1232 memset(fg.filepath, 0, sizeof(fg.filepath));
1233 memset(fg.build_hash, 0, sizeof(fg.build_hash));
1234 fg._pad1 = nullptr;
1235
1236 current_screen_compat(mainvar, is_undo, &screen, &scene, &view_layer);
1237
1238 /* XXX: still remap `G`. */
1239 fg.curscreen = screen;
1240 fg.curscene = scene;
1241 fg.cur_view_layer = view_layer;
1242
1243 /* Prevent to save this, is not good convention, and feature with concerns. */
1244 fg.fileflags = (fileflags & ~G_FILE_FLAG_ALL_RUNTIME);
1245
1246 fg.globalf = G.f;
1247 /* Write information needed for recovery. */
1248 if (fileflags & G_FILE_RECOVER_WRITE) {
1249 STRNCPY(fg.filepath, mainvar->filepath);
1250 /* Compression is often turned of when writing recovery files. However, when opening the file,
1251 * it should be enabled again. */
1252 fg.fileflags = G.fileflags & G_FILE_COMPRESS;
1253 }
1254 SNPRINTF(subvstr, "%4d", BLENDER_FILE_SUBVERSION);
1255 memcpy(fg.subvstr, subvstr, 4);
1256
1260#ifdef WITH_BUILDINFO
1261 /* TODO(sergey): Add branch name to file as well? */
1264#else
1266 STRNCPY(fg.build_hash, "unknown");
1267#endif
1268 writestruct(wd, BLO_CODE_GLOB, FileGlobal, 1, &fg);
1269}
1270
1276static void write_thumb(WriteData *wd, const BlendThumbnail *thumb)
1277{
1278 if (thumb) {
1279 writedata(wd, BLO_CODE_TEST, BLEN_THUMB_MEMSIZE_FILE(thumb->width, thumb->height), thumb);
1280 }
1281}
1282
1284
1285/* -------------------------------------------------------------------- */
1288
1289BLO_Write_IDBuffer::BLO_Write_IDBuffer(ID &id, const bool is_undo, const bool is_placeholder)
1290 : buffer_(is_placeholder ? sizeof(ID) : BKE_idtype_get_info_from_id(&id)->struct_size,
1291 alignof(ID))
1292{
1293 const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(&id);
1294 ID *temp_id = static_cast<ID *>(buffer_.buffer());
1295
1296 if (is_undo) {
1297 /* Record the changes that happened up to this undo push in
1298 * recalc_up_to_undo_push, and clear `recalc_after_undo_push` again
1299 * to start accumulating for the next undo push. */
1300 id.recalc_up_to_undo_push = id.recalc_after_undo_push;
1301 id.recalc_after_undo_push = 0;
1302 }
1303
1304 /* Copy ID data itself into buffer, to be able to freely modify it. */
1305
1306 if (is_placeholder) {
1307 /* For placeholders (references to linked data), zero-initialize, and only explicitely copy the
1308 * very small subset of required data. */
1309 *temp_id = ID{};
1310 temp_id->lib = id.lib;
1311 STRNCPY(temp_id->name, id.name);
1312 temp_id->flag = id.flag;
1313 temp_id->session_uid = id.session_uid;
1314 if (is_undo) {
1315 temp_id->recalc_up_to_undo_push = id.recalc_up_to_undo_push;
1316 temp_id->tag = id.tag & ID_TAG_KEEP_ON_UNDO;
1317 }
1318 return;
1319 }
1320
1321 /* Regular 'full' ID writing, copy everything, then clear some runtime data irrelevant in the
1322 * blendfile. */
1323 memcpy(temp_id, &id, id_type->struct_size);
1324
1325 /* Clear runtime data to reduce false detection of changed data in undo/redo context. */
1326 if (is_undo) {
1327 temp_id->tag &= ID_TAG_KEEP_ON_UNDO;
1328 }
1329 else {
1330 temp_id->tag = 0;
1331 }
1332 temp_id->us = 0;
1333 temp_id->icon_id = 0;
1334 /* Those listbase data change every time we add/remove an ID, and also often when
1335 * renaming one (due to re-sorting). This avoids generating a lot of false 'is changed'
1336 * detections between undo steps. */
1337 temp_id->prev = nullptr;
1338 temp_id->next = nullptr;
1339 /* Those runtime pointers should never be set during writing stage, but just in case clear
1340 * them too. */
1341 temp_id->orig_id = nullptr;
1342 temp_id->newid = nullptr;
1343 /* Even though in theory we could be able to preserve this python instance across undo even
1344 * when we need to re-read the ID into its original address, this is currently cleared in
1345 * #direct_link_id_common in `readfile.cc` anyway. */
1346 temp_id->py_instance = nullptr;
1347 /* Clear runtime data struct. */
1348 temp_id->runtime = ID_Runtime{};
1349}
1350
1355
1356/* Helper callback for checking linked IDs used by given ID (assumed local), to ensure directly
1357 * linked data is tagged accordingly. */
1359{
1360 ID *self_id = cb_data->self_id;
1361 ID *id = *cb_data->id_pointer;
1362 const LibraryForeachIDCallbackFlag cb_flag = cb_data->cb_flag;
1363
1364 if (id == nullptr || !ID_IS_LINKED(id)) {
1365 return IDWALK_RET_NOP;
1366 }
1367 BLI_assert(!ID_IS_LINKED(self_id));
1368 BLI_assert((cb_flag & IDWALK_CB_INDIRECT_USAGE) == 0);
1369
1370 if (self_id->tag & ID_TAG_RUNTIME) {
1371 return IDWALK_RET_NOP;
1372 }
1373
1375 /* Usages of unlinkable IDs (aka ShapeKeys and some UI IDs) should never cause them to be
1376 * considered as directly linked. This can often happen e.g. from UI data (the Outliner will
1377 * have links to most IDs).
1378 */
1379 return IDWALK_RET_NOP;
1380 }
1381
1382 if (cb_flag & IDWALK_CB_DIRECT_WEAK_LINK) {
1384 }
1385 else {
1386 id_lib_extern(id);
1387 }
1388
1389 return IDWALK_RET_NOP;
1390}
1391
1392static std::string get_blend_file_header()
1393{
1395 USER_EXPERIMENTAL_TEST(&U, write_large_blend_file_blocks))
1396 {
1397 const int header_size_in_bytes = SIZEOFBLENDERHEADER_VERSION_1;
1398
1399 /* New blend file header format. */
1400 std::stringstream ss;
1401 ss << "BLENDER";
1402 ss << header_size_in_bytes;
1403 ss << '-';
1404 ss << std::setfill('0') << std::setw(2) << BLEND_FILE_FORMAT_VERSION_1;
1405 ss << 'v';
1406 ss << std::setfill('0') << std::setw(4) << BLENDER_FILE_VERSION;
1407
1408 const std::string header = ss.str();
1409 BLI_assert(header.size() == header_size_in_bytes);
1410 return header;
1411 }
1412
1413 const char pointer_size_char = sizeof(void *) == 8 ? '-' : '_';
1414 const char endian_char = ENDIAN_ORDER == B_ENDIAN ? 'V' : 'v';
1415
1416 /* Legacy blend file header format. */
1417 std::stringstream ss;
1418 ss << "BLENDER";
1419 ss << pointer_size_char;
1420 ss << endian_char;
1422 const std::string header = ss.str();
1423 BLI_assert(header.size() == SIZEOFBLENDERHEADER_VERSION_0);
1424 return header;
1425}
1426
1428{
1429 const std::string header = get_blend_file_header();
1430 mywrite(wd, header.data(), header.size());
1431}
1432
1436static blender::Vector<ID *> gather_local_ids_to_write(Main *bmain, const bool is_undo)
1437{
1438 blender::Vector<ID *> local_ids_to_write;
1439 ID *id;
1440 FOREACH_MAIN_ID_BEGIN (bmain, id) {
1441 if (GS(id->name) == ID_LI) {
1442 /* Libraries are handled separately below. */
1443 continue;
1444 }
1445 if (ID_IS_LINKED(id)) {
1446 /* Linked data-blocks are handled separately below. */
1447 continue;
1448 }
1449 const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
1450 UNUSED_VARS_NDEBUG(id_type);
1451 /* We should never attempt to write non-regular IDs
1452 * (i.e. all kind of temp/runtime ones). */
1454 /* We only write unused IDs in undo case. */
1455 if (!is_undo) {
1456 /* NOTE: All 'never unused' local IDs (Scenes, WindowManagers, ...) should always be
1457 * written to disk, so their user-count should never be zero currently. Note that
1458 * libraries have already been skipped above, as they need a specific handling. */
1459 if (id->us == 0) {
1460 /* FIXME: #124857: Some old files seem to cause incorrect handling of their temp
1461 * screens.
1462 *
1463 * See e.g. file attached to #124777 (from 2.79.1).
1464 *
1465 * For now ignore, issue is not obvious to track down (`temp` bScreen ID from read data
1466 * _does_ have the proper `temp` tag), and seems anecdotal at worst. */
1467 BLI_assert((id_type->flags & IDTYPE_FLAGS_NEVER_UNUSED) == 0);
1468 continue;
1469 }
1470
1471 /* XXX Special handling for ShapeKeys, as having unused shapekeys is not a good thing
1472 * (and reported as error by e.g. `BLO_main_validate_shapekeys`), skip writing shapekeys
1473 * when their 'owner' is not written.
1474 *
1475 * NOTE: Since ShapeKeys are conceptually embedded IDs (like root node trees e.g.), this
1476 * behavior actually makes sense anyway. This remains more of a temp hack until topic of
1477 * how to handle unused data on save is properly tackled. */
1478 if (GS(id->name) == ID_KE) {
1479 Key *shape_key = reinterpret_cast<Key *>(id);
1480 /* NOTE: Here we are accessing the real owner ID data, not it's 'proxy' shallow copy
1481 * generated for its file-writing. This is not expected to be an issue, but is worth
1482 * noting. */
1483 if (shape_key->from == nullptr || shape_key->from->us == 0) {
1484 continue;
1485 }
1486 }
1487 }
1488
1489 if ((id->tag & ID_TAG_RUNTIME) != 0 && !is_undo) {
1490 /* Runtime IDs are never written to .blend files, and they should not influence
1491 * (in)direct status of linked IDs they may use. */
1492 continue;
1493 }
1494
1495 local_ids_to_write.append(id);
1496 }
1498 return local_ids_to_write;
1499}
1500
1507static bool write_file_handle(Main *mainvar,
1508 WriteWrap *ww,
1509 MemFile *compare,
1510 MemFile *current,
1511 const int write_flags,
1512 const bool use_userdef,
1513 const BlendThumbnail *thumb,
1514 std::ostream *debug_dst)
1515{
1516 WriteData *wd;
1517
1518 wd = mywrite_begin(ww, compare, current);
1519 wd->debug_dst = debug_dst;
1520 BlendWriter writer = {wd};
1521
1522 /* Clear 'directly linked' flag for all linked data, these are not necessarily valid/up-to-date
1523 * info, they will be re-generated while write code is processing local IDs below. */
1524 if (!wd->use_memfile) {
1525 ID *id_iter;
1526 FOREACH_MAIN_ID_BEGIN (mainvar, id_iter) {
1527 if (ID_IS_LINKED(id_iter) && BKE_idtype_idcode_is_linkable(GS(id_iter->name))) {
1528 if (USER_EXPERIMENTAL_TEST(&U, use_all_linked_data_direct)) {
1529 /* Forces all linked data to be considered as directly linked.
1530 * FIXME: Workaround some BAT tool limitations for Heist production, should be removed
1531 * asap afterward. */
1532 id_lib_extern(id_iter);
1533 }
1534 else if (GS(id_iter->name) == ID_SCE) {
1535 /* For scenes, do not force them into 'indirectly linked' status.
1536 * The main reason is that scenes typically have no users, so most linked scene would be
1537 * systematically 'lost' on file save.
1538 *
1539 * While this change re-introduces the 'no-more-used data laying around in files for
1540 * ever' issue when it comes to scenes, this solution seems to be the most sensible one
1541 * for the time being, considering that:
1542 * - Scene are a top-level container.
1543 * - Linked scenes are typically explicitly linked by the user.
1544 * - Cases where scenes would be indirectly linked by other data (e.g. when linking a
1545 * collection or material) can be considered at the very least as not following sane
1546 * practice in data dependencies.
1547 * - There are typically not hundreds of scenes in a file, and they are always very
1548 * easily discoverable and browsable from the main UI. */
1549 }
1550 else {
1551 id_iter->tag |= ID_TAG_INDIRECT;
1552 id_iter->tag &= ~ID_TAG_EXTERN;
1553 }
1554 }
1555 }
1557 }
1558
1559 /* Recompute all ID user-counts if requested. Allows to avoid skipping writing of IDs wrongly
1560 * detected as unused due to invalid user-count. */
1561 if (!wd->use_memfile) {
1562 if (USER_EXPERIMENTAL_TEST(&U, use_recompute_usercount_on_save_debug)) {
1563 BKE_main_id_refcount_recompute(mainvar, false);
1564 }
1565 }
1566
1568 write_renderinfo(wd, mainvar);
1569 write_thumb(wd, thumb);
1570 write_global(wd, write_flags, mainvar);
1571
1572 /* The window-manager and screen often change,
1573 * avoid thumbnail detecting changes because of this. */
1574 mywrite_flush(wd);
1575
1576 const bool is_undo = wd->use_memfile;
1577 blender::Vector<ID *> local_ids_to_write = gather_local_ids_to_write(mainvar, is_undo);
1578
1579 if (!is_undo) {
1580 /* If not writing undo data, properly set directly linked IDs as `ID_TAG_EXTERN`. */
1581 for (ID *id : local_ids_to_write) {
1583 id,
1585 nullptr,
1587 }
1588
1589 /* Forcefully ensure we know about all needed override operations. */
1590 for (ID *id : local_ids_to_write) {
1592 BKE_lib_override_library_operations_create(mainvar, id, nullptr);
1593 }
1594 }
1595 }
1596
1597 /* Actually write local data-blocks to the file. */
1598 for (ID *id : local_ids_to_write) {
1599 write_id(wd, id);
1600 }
1601
1602 /* Write libraries about libraries and linked data-blocks. */
1603 write_libraries(wd, mainvar);
1604
1605 /* So changes above don't cause a 'DNA1' to be detected as changed on undo. */
1606 mywrite_flush(wd);
1607
1608 if (use_userdef) {
1609 write_userdef(&writer, &U);
1610 }
1611
1612 /* Write DNA last, because (to be implemented) test for which structs are written.
1613 *
1614 * Note that we *borrow* the pointer to 'DNAstr',
1615 * so writing each time uses the same address and doesn't cause unnecessary undo overhead. */
1616 writedata(wd, BLO_CODE_DNA1, size_t(wd->sdna->data_size), wd->sdna->data);
1617
1618 /* End of file. */
1619 BHead bhead{};
1620 bhead.code = BLO_CODE_ENDB;
1621 write_bhead(wd, bhead);
1622
1623 return mywrite_end(wd);
1624}
1625
1630static bool do_history(const char *filepath, ReportList *reports)
1631{
1632 /* Add 2 because version number maximum is double-digits. */
1633 char filepath_tmp1[FILE_MAX + 2], filepath_tmp2[FILE_MAX + 2];
1634 int version_number = min_ii(99, U.versions);
1635
1636 if (version_number == 0) {
1637 return true;
1638 }
1639
1640 if (strlen(filepath) < 2) {
1641 BKE_report(reports, RPT_ERROR, "Unable to make version backup: filename too short");
1642 return false;
1643 }
1644
1645 while (version_number > 1) {
1646 SNPRINTF(filepath_tmp1, "%s%d", filepath, version_number - 1);
1647 if (BLI_exists(filepath_tmp1)) {
1648 SNPRINTF(filepath_tmp2, "%s%d", filepath, version_number);
1649
1650 if (BLI_rename_overwrite(filepath_tmp1, filepath_tmp2)) {
1651 BKE_report(reports, RPT_ERROR, "Unable to make version backup");
1652 return false;
1653 }
1654 }
1655 version_number--;
1656 }
1657
1658 /* Needed when `version_number == 1`. */
1659 if (BLI_exists(filepath)) {
1660 SNPRINTF(filepath_tmp1, "%s%d", filepath, version_number);
1661
1662 if (BLI_rename_overwrite(filepath, filepath_tmp1)) {
1663 BKE_report(reports, RPT_ERROR, "Unable to make version backup");
1664 return false;
1665 }
1666 }
1667
1668 return true;
1669}
1670
1672{
1673 if (!bmain->lock) {
1674 return;
1675 }
1676
1677 if (G.debug & G_DEBUG_IO) {
1678 BKE_report(
1679 reports, RPT_DEBUG, "Checking validity of current .blend file *BEFORE* save to disk");
1680 }
1681
1683 if (!BKE_main_namemap_validate_and_fix(*bmain)) {
1685 RPT_ERROR,
1686 "Critical data corruption: Conflicts and/or otherwise invalid data-blocks names "
1687 "(see console for details)");
1688 }
1689
1690 if (G.debug & G_DEBUG_IO) {
1692 }
1693}
1694
1696{
1697 if (!bmain->lock) {
1698 return;
1699 }
1700
1701 if (G.debug & G_DEBUG_IO) {
1702 BKE_report(
1703 reports, RPT_DEBUG, "Checking validity of current .blend file *BEFORE* save to disk");
1705 }
1706}
1707
1708static bool BLO_write_file_impl(Main *mainvar,
1709 const char *filepath,
1710 const int write_flags,
1713 WriteWrap &ww)
1714{
1715 BLI_assert(!BLI_path_is_rel(filepath));
1717
1718 char tempname[FILE_MAX + 1];
1719
1720 eBLO_WritePathRemap remap_mode = params->remap_mode;
1721 const bool use_save_versions = params->use_save_versions;
1722 const bool use_save_as_copy = params->use_save_as_copy;
1723 const bool use_userdef = params->use_userdef;
1724 const BlendThumbnail *thumb = params->thumb;
1725 const bool relbase_valid = (mainvar->filepath[0] != '\0');
1726
1727 /* Extra protection: Never save a non asset file as asset file. Otherwise a normal file is turned
1728 * into an asset file, which can result in data loss because the asset system will allow editing
1729 * this file from the UI, regenerating its content with just the asset and it dependencies. */
1730 if ((write_flags & G_FILE_ASSET_EDIT_FILE) && !mainvar->is_asset_edit_file) {
1731 BKE_reportf(reports, RPT_ERROR, "Cannot save normal file (%s) as asset system file", tempname);
1732 return false;
1733 }
1734
1735 /* Path backup/restore. */
1736 void *path_list_backup = nullptr;
1739
1741
1742 /* Open temporary file, so we preserve the original in case we crash. */
1743 SNPRINTF(tempname, "%s@", filepath);
1744
1745 if (ww.open(tempname) == false) {
1747 reports, RPT_ERROR, "Cannot open file %s for writing: %s", tempname, strerror(errno));
1748 return false;
1749 }
1750
1751 if (remap_mode == BLO_WRITE_PATH_REMAP_ABSOLUTE) {
1752 /* Paths will already be absolute, no remapping to do. */
1753 if (relbase_valid == false) {
1754 remap_mode = BLO_WRITE_PATH_REMAP_NONE;
1755 }
1756 }
1757
1758 /* Remapping of relative paths to new file location. */
1759 if (remap_mode != BLO_WRITE_PATH_REMAP_NONE) {
1760 if (remap_mode == BLO_WRITE_PATH_REMAP_RELATIVE) {
1761 /* Make all relative as none of the existing paths can be relative in an unsaved document. */
1762 if (relbase_valid == false) {
1764 }
1765 }
1766
1767 /* The source path only makes sense to set if the file was saved (`relbase_valid`). */
1768 char dir_src[FILE_MAX];
1769 char dir_dst[FILE_MAX];
1770
1771 /* Normalize the paths in case there is some subtle difference (so they can be compared). */
1772 if (relbase_valid) {
1773 BLI_path_split_dir_part(mainvar->filepath, dir_src, sizeof(dir_src));
1774 BLI_path_normalize(dir_src);
1775 }
1776 else {
1777 dir_src[0] = '\0';
1778 }
1779 BLI_path_split_dir_part(filepath, dir_dst, sizeof(dir_dst));
1780 BLI_path_normalize(dir_dst);
1781
1782 /* Only for relative, not relative-all, as this means making existing paths relative. */
1783 if (remap_mode == BLO_WRITE_PATH_REMAP_RELATIVE) {
1784 if (relbase_valid && (BLI_path_cmp(dir_dst, dir_src) == 0)) {
1785 /* Saved to same path. Nothing to do. */
1786 remap_mode = BLO_WRITE_PATH_REMAP_NONE;
1787 }
1788 }
1789 else if (remap_mode == BLO_WRITE_PATH_REMAP_ABSOLUTE) {
1790 if (relbase_valid == false) {
1791 /* Unsaved, all paths are absolute.Even if the user manages to set a relative path,
1792 * there is no base-path that can be used to make it absolute. */
1793 remap_mode = BLO_WRITE_PATH_REMAP_NONE;
1794 }
1795 }
1796
1797 if (remap_mode != BLO_WRITE_PATH_REMAP_NONE) {
1798 /* Some path processing (e.g. with libraries) may use the current `main->filepath`, if this
1799 * is not matching the path currently used for saving, unexpected paths corruptions can
1800 * happen. See #98201. */
1801 char mainvar_filepath_orig[FILE_MAX];
1802 STRNCPY(mainvar_filepath_orig, mainvar->filepath);
1803 STRNCPY(mainvar->filepath, filepath);
1804
1805 /* Check if we need to backup and restore paths. */
1806 if (UNLIKELY(use_save_as_copy)) {
1807 path_list_backup = BKE_bpath_list_backup(mainvar, path_list_flag);
1808 }
1809
1810 switch (remap_mode) {
1812 /* Saved, make relative paths relative to new location (if possible). */
1813 BLI_assert(relbase_valid);
1814 BKE_bpath_relative_rebase(mainvar, dir_src, dir_dst, nullptr);
1815 break;
1817 /* Make all relative (when requested or unsaved). */
1818 BKE_bpath_relative_convert(mainvar, dir_dst, nullptr);
1819 break;
1821 /* Make all absolute (when requested or unsaved). */
1822 BLI_assert(relbase_valid);
1823 BKE_bpath_absolute_convert(mainvar, dir_src, nullptr);
1824 break;
1826 BLI_assert_unreachable(); /* Unreachable. */
1827 break;
1828 }
1829
1830 STRNCPY(mainvar->filepath, mainvar_filepath_orig);
1831 }
1832 }
1833
1834#if GENERATE_DEBUG_BLEND_FILE
1835 std::string debug_dst_path = blender::StringRef(filepath) + DEBUG_BLEND_FILE_SUFFIX;
1836 blender::fstream debug_dst_file(debug_dst_path, std::ios::out);
1837 std::ostream *debug_dst = &debug_dst_file;
1838#else
1839 std::ostream *debug_dst = nullptr;
1840#endif
1841
1842 /* Actual file writing. */
1843 const bool err = write_file_handle(
1844 mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb, debug_dst);
1845
1846 ww.close();
1847
1848 if (UNLIKELY(path_list_backup)) {
1849 BKE_bpath_list_restore(mainvar, path_list_flag, path_list_backup);
1850 BKE_bpath_list_free(path_list_backup);
1851 }
1852
1853 if (err) {
1854 BKE_report(reports, RPT_ERROR, strerror(errno));
1855 remove(tempname);
1856
1857 return false;
1858 }
1859
1860 /* File save to temporary file was successful, now do reverse file history
1861 * (move `.blend1` -> `.blend2`, `.blend` -> `.blend1` .. etc). */
1862 if (use_save_versions) {
1863 if (!do_history(filepath, reports)) {
1864 BKE_report(reports, RPT_ERROR, "Version backup failed (file saved with @)");
1865 return false;
1866 }
1867 }
1868
1869 if (BLI_rename_overwrite(tempname, filepath) != 0) {
1870 BKE_report(reports, RPT_ERROR, "Cannot change old file (file saved with @)");
1871 return false;
1872 }
1873
1875 if (mainvar->is_global_main && !params->use_save_as_copy) {
1876 /* It is used to reload Blender after a crash on Windows OS. */
1877 STRNCPY(G.filepath_last_blend, filepath);
1878 }
1879 return true;
1880}
1881
1883
1884/* -------------------------------------------------------------------- */
1887
1888bool BLO_write_file(Main *mainvar,
1889 const char *filepath,
1890 const int write_flags,
1893{
1894 RawWriteWrap raw_wrap;
1895
1896 if (write_flags & G_FILE_COMPRESS) {
1897 ZstdWriteWrap zstd_wrap(raw_wrap);
1898 return BLO_write_file_impl(mainvar, filepath, write_flags, params, reports, zstd_wrap);
1899 }
1900
1901 return BLO_write_file_impl(mainvar, filepath, write_flags, params, reports, raw_wrap);
1902}
1903
1904bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, const int write_flags)
1905{
1906 bool use_userdef = false;
1907
1908 const bool err = write_file_handle(
1909 mainvar, nullptr, compare, current, write_flags, use_userdef, nullptr, nullptr);
1910
1911 return (err == 0);
1912}
1913
1914/*
1915 * API to write chunks of data.
1916 */
1917
1918void BLO_write_raw(BlendWriter *writer, const size_t size_in_bytes, const void *data_ptr)
1919{
1920 writedata(writer->wd, BLO_CODE_DATA, size_in_bytes, data_ptr);
1921}
1922
1923void BLO_write_struct_by_name(BlendWriter *writer, const char *struct_name, const void *data_ptr)
1924{
1925 BLO_write_struct_array_by_name(writer, struct_name, 1, data_ptr);
1926}
1927
1929 const char *struct_name,
1930 const int64_t array_size,
1931 const void *data_ptr)
1932{
1933 int struct_id = BLO_get_struct_id_by_name(writer, struct_name);
1934 if (UNLIKELY(struct_id == -1)) {
1935 CLOG_ERROR(&LOG, "Can't find SDNA code <%s>", struct_name);
1936 return;
1937 }
1938 BLO_write_struct_array_by_id(writer, struct_id, array_size, data_ptr);
1939}
1940
1941void BLO_write_struct_by_id(BlendWriter *writer, const int struct_id, const void *data_ptr)
1942{
1943 writestruct_nr(writer->wd, BLO_CODE_DATA, struct_id, 1, data_ptr);
1944}
1945
1947 const int struct_id,
1948 const void *address,
1949 const void *data_ptr)
1950{
1952 writer, BLO_CODE_DATA, struct_id, address, data_ptr);
1953}
1954
1956 const int filecode,
1957 const int struct_id,
1958 const void *address,
1959 const void *data_ptr)
1960{
1961 writestruct_at_address_nr(writer->wd, filecode, struct_id, 1, address, data_ptr);
1962}
1963
1965 const int struct_id,
1966 const int64_t array_size,
1967 const void *data_ptr)
1968{
1969 writestruct_nr(writer->wd, BLO_CODE_DATA, struct_id, array_size, data_ptr);
1970}
1971
1973 const int struct_id,
1974 const int64_t array_size,
1975 const void *address,
1976 const void *data_ptr)
1977{
1978 writestruct_at_address_nr(writer->wd, BLO_CODE_DATA, struct_id, array_size, address, data_ptr);
1979}
1980
1981void BLO_write_struct_list_by_id(BlendWriter *writer, const int struct_id, const ListBase *list)
1982{
1983 writelist_nr(writer->wd, BLO_CODE_DATA, struct_id, list);
1984}
1985
1986void BLO_write_struct_list_by_name(BlendWriter *writer, const char *struct_name, ListBase *list)
1987{
1988 int struct_id = BLO_get_struct_id_by_name(writer, struct_name);
1989 if (UNLIKELY(struct_id == -1)) {
1990 CLOG_ERROR(&LOG, "Can't find SDNA code <%s>", struct_name);
1991 return;
1992 }
1993 BLO_write_struct_list_by_id(writer, struct_id, list);
1994}
1995
1997 const int struct_id,
1998 const void *id_address,
1999 const ID *id)
2000{
2001 writestruct_at_address_nr(writer->wd, GS(id->name), struct_id, 1, id_address, id);
2002}
2003
2004int BLO_get_struct_id_by_name(const BlendWriter *writer, const char *struct_name)
2005{
2006 int struct_id = DNA_struct_find_with_alias(writer->wd->sdna, struct_name);
2007 return struct_id;
2008}
2009
2010void BLO_write_char_array(BlendWriter *writer, const int64_t num, const char *data_ptr)
2011{
2012 BLO_write_raw(writer, sizeof(char) * size_t(num), data_ptr);
2013}
2014
2015void BLO_write_int8_array(BlendWriter *writer, const int64_t num, const int8_t *data_ptr)
2016{
2017 BLO_write_raw(writer, sizeof(int8_t) * size_t(num), data_ptr);
2018}
2019
2020void BLO_write_int16_array(BlendWriter *writer, const int64_t num, const int16_t *data_ptr)
2021{
2022 BLO_write_raw(writer, sizeof(int16_t) * size_t(num), data_ptr);
2023}
2024
2025void BLO_write_uint8_array(BlendWriter *writer, const int64_t num, const uint8_t *data_ptr)
2026{
2027 BLO_write_raw(writer, sizeof(uint8_t) * size_t(num), data_ptr);
2028}
2029
2030void BLO_write_int32_array(BlendWriter *writer, const int64_t num, const int32_t *data_ptr)
2031{
2032 BLO_write_raw(writer, sizeof(int32_t) * size_t(num), data_ptr);
2033}
2034
2035void BLO_write_uint32_array(BlendWriter *writer, const int64_t num, const uint32_t *data_ptr)
2036{
2037 BLO_write_raw(writer, sizeof(uint32_t) * size_t(num), data_ptr);
2038}
2039
2040void BLO_write_float_array(BlendWriter *writer, const int64_t num, const float *data_ptr)
2041{
2042 BLO_write_raw(writer, sizeof(float) * size_t(num), data_ptr);
2043}
2044
2045void BLO_write_double_array(BlendWriter *writer, const int64_t num, const double *data_ptr)
2046{
2047 BLO_write_raw(writer, sizeof(double) * size_t(num), data_ptr);
2048}
2049
2050void BLO_write_pointer_array(BlendWriter *writer, const int64_t num, const void *data_ptr)
2051{
2052 BLO_write_raw(writer, sizeof(void *) * size_t(num), data_ptr);
2053}
2054
2055void BLO_write_float3_array(BlendWriter *writer, const int64_t num, const float *data_ptr)
2056{
2057 BLO_write_raw(writer, sizeof(float[3]) * size_t(num), data_ptr);
2058}
2059
2060void BLO_write_string(BlendWriter *writer, const char *data_ptr)
2061{
2062 if (data_ptr != nullptr) {
2063 BLO_write_raw(writer, strlen(data_ptr) + 1, data_ptr);
2064 }
2065}
2066
2068 const void *data,
2069 const size_t approximate_size_in_bytes,
2070 const blender::ImplicitSharingInfo *sharing_info,
2071 const blender::FunctionRef<void()> write_fn)
2072{
2073 if (data == nullptr) {
2074 return;
2075 }
2076 if (BLO_write_is_undo(writer)) {
2077 MemFile &memfile = *writer->wd->mem.written_memfile;
2078 if (sharing_info != nullptr) {
2079 if (memfile.shared_storage == nullptr) {
2080 memfile.shared_storage = MEM_new<MemFileSharedStorage>(__func__);
2081 }
2082 if (memfile.shared_storage->map.add(data, sharing_info)) {
2083 /* The undo-step takes (shared) ownership of the data, which also makes it immutable. */
2084 sharing_info->add_user();
2085 /* This size is an estimate, but good enough to count data with many users less. */
2086 memfile.size += approximate_size_in_bytes / sharing_info->strong_users();
2087 return;
2088 }
2089 }
2090 }
2091 if (sharing_info != nullptr) {
2092 if (!writer->wd->per_id_written_shared_addresses.add(data)) {
2093 /* Was written already. */
2094 return;
2095 }
2096 }
2097 write_fn();
2098}
2099
2101{
2102 return writer->wd->use_memfile;
2103}
2104
void BKE_asset_catalog_path_list_blend_write(BlendWriter *writer, const ListBase &catalog_path_list)
#define BLENDER_FILE_SUBVERSION
#define BLENDER_FILE_MIN_VERSION
#define BLENDER_FILE_VERSION
#define BLENDER_FILE_MIN_SUBVERSION
void BKE_bpath_relative_convert(Main *bmain, const char *basedir, ReportList *reports, BPathSummary *r_summary=nullptr)
Definition bpath.cc:617
void * BKE_bpath_list_backup(Main *bmain, eBPathForeachFlag flag)
Definition bpath.cc:691
void BKE_bpath_absolute_convert(Main *bmain, const char *basedir, ReportList *reports, BPathSummary *r_summary=nullptr)
Definition bpath.cc:626
void BKE_bpath_list_restore(Main *bmain, eBPathForeachFlag flag, void *path_list_handle)
Definition bpath.cc:705
void BKE_bpath_relative_rebase(Main *bmain, const char *basedir_src, const char *basedir_dst, ReportList *reports, BPathSummary *r_summary=nullptr)
Definition bpath.cc:477
eBPathForeachFlag
Definition BKE_bpath.hh:27
@ BKE_BPATH_FOREACH_PATH_SKIP_LINKED
Definition BKE_bpath.hh:35
@ BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE
Definition BKE_bpath.hh:61
void BKE_bpath_list_free(void *path_list_handle)
Definition bpath.cc:717
#define G_FILE_FLAG_ALL_RUNTIME
@ G_DEBUG_IO
@ G_FILE_ASSET_EDIT_FILE
@ G_FILE_RECOVER_WRITE
@ G_FILE_COMPRESS
void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop)
Definition idprop.cc:1453
@ IDTYPE_FLAGS_NEVER_UNUSED
Definition BKE_idtype.hh:69
const IDTypeInfo * BKE_idtype_get_info_from_id(const ID *id)
Definition idtype.cc:147
bool BKE_idtype_idcode_is_linkable(short idcode)
Definition idtype.cc:198
ViewLayer * BKE_view_layer_find(const Scene *scene, const char *layer_name)
void id_lib_extern(ID *id)
Definition lib_id.cc:285
#define MAIN_ID_SESSION_UID_UNSET
void id_lib_indirect_weak_link(ID *id)
Definition lib_id.cc:298
void BKE_main_id_refcount_recompute(Main *bmain, bool do_linked_only)
Definition lib_id.cc:2015
void BKE_lib_override_library_operations_create(Main *bmain, ID *local, int *r_report_flags)
LibraryForeachIDCallbackFlag
@ IDWALK_CB_DIRECT_WEAK_LINK
@ IDWALK_CB_INDIRECT_USAGE
void BKE_library_foreach_ID_link(Main *bmain, ID *id, blender::FunctionRef< LibraryIDLinkCallback > callback, void *user_data, LibraryForeachIDFlag flag)
Definition lib_query.cc:431
@ IDWALK_RET_NOP
@ IDWALK_INCLUDE_UI
@ IDWALK_READONLY
#define FOREACH_MAIN_ID_END
Definition BKE_main.hh:563
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:557
bool BKE_main_namemap_validate_and_fix(Main &bmain)
void BKE_preferences_extension_repo_write_data(struct BlendWriter *writer, const bUserExtensionRepo *repo)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
bScreen * BKE_workspace_active_screen_get(const WorkSpaceInstanceHook *hook) GETTER_ATTRS
Definition workspace.cc:612
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:83
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define B_ENDIAN
#define ENDIAN_ORDER
BLI_INLINE void BLI_endian_switch_uint32(unsigned int *val) ATTR_NONNULL(1)
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:373
#define O_BINARY
int BLI_rename_overwrite(const char *from, const char *to) ATTR_NONNULL()
Definition fileops_c.cc:505
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
File and directory operations.
#define LISTBASE_FOREACH(type, var, list)
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
MINLINE int min_ii(int a, int b)
MINLINE int max_ii(int a, int b)
ATTR_WARN_UNUSED_RESULT const size_t num
#define FILE_MAX
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
bool BLI_path_is_abs_from_cwd(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
bool BLI_path_is_rel(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
#define BLI_path_cmp
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
unsigned char uchar
unsigned long ulong
void BLI_condition_notify_all(ThreadCondition *cond)
Definition threads.cc:595
void BLI_mutex_end(ThreadMutex *mutex)
Definition threads.cc:360
void BLI_threadpool_remove(struct ListBase *threadbase, void *callerdata)
Definition threads.cc:197
void BLI_condition_wait(ThreadCondition *cond, ThreadMutex *mutex)
Definition threads.cc:580
void BLI_threadpool_init(struct ListBase *threadbase, void *(*do_thread)(void *), int tot)
Definition threads.cc:121
void BLI_mutex_init(ThreadMutex *mutex)
Definition threads.cc:340
pthread_cond_t ThreadCondition
void BLI_condition_end(ThreadCondition *cond)
Definition threads.cc:600
int BLI_system_thread_count(void)
Definition threads.cc:253
void BLI_threadpool_end(struct ListBase *threadbase)
Definition threads.cc:234
void BLI_condition_init(ThreadCondition *cond)
Definition threads.cc:575
void BLI_mutex_lock(ThreadMutex *mutex)
Definition threads.cc:345
void BLI_mutex_unlock(ThreadMutex *mutex)
Definition threads.cc:350
int BLI_available_threads(struct ListBase *threadbase)
Definition threads.cc:146
void BLI_threadpool_insert(struct ListBase *threadbase, void *callerdata)
Definition threads.cc:184
pthread_mutex_t ThreadMutex
Definition BLI_threads.h:79
#define UNUSED_VARS_NDEBUG(...)
#define UNLIKELY(x)
Compatibility-like things for windows.
Utilities ensuring .blend file (i.e. Main) is in valid state during write and/or read process.
bool BLO_main_validate_libraries(Main *bmain, ReportList *reports)
bool BLO_main_validate_shapekeys(Main *bmain, ReportList *reports)
@ BLO_CODE_ENDB
@ BLO_CODE_REND
@ BLO_CODE_TEST
@ BLO_CODE_GLOB
@ BLO_CODE_DATA
@ BLO_CODE_DNA1
@ BLO_CODE_USER
#define SIZEOFBLENDERHEADER_VERSION_0
#define SIZEOFBLENDERHEADER_VERSION_1
#define BLEND_FILE_FORMAT_VERSION_1
#define SYSTEM_SUPPORTS_WRITING_FILE_VERSION_1
#define BLO_write_struct(writer, struct_name, data_ptr)
bool BLO_write_is_undo(BlendWriter *writer)
external readfile function prototypes.
#define BLEN_THUMB_MEMSIZE_FILE(_x, _y)
void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
Definition undofile.cc:128
void BLO_memfile_write_init(MemFileWriteData *mem_data, MemFile *written_memfile, MemFile *reference_memfile)
Definition undofile.cc:97
void BLO_memfile_write_finalize(MemFileWriteData *mem_data)
Definition undofile.cc:123
external writefile.cc function prototypes.
eBLO_WritePathRemap
@ BLO_WRITE_PATH_REMAP_NONE
@ BLO_WRITE_PATH_REMAP_RELATIVE_ALL
@ BLO_WRITE_PATH_REMAP_ABSOLUTE
@ BLO_WRITE_PATH_REMAP_RELATIVE
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
@ ID_FLAG_INDIRECT_WEAK_LINK
Definition DNA_ID.h:693
@ ID_TAG_NO_USER_REFCOUNT
Definition DNA_ID.h:926
@ ID_TAG_INDIRECT
Definition DNA_ID.h:756
@ ID_TAG_RUNTIME
Definition DNA_ID.h:767
@ ID_TAG_EXTERN
Definition DNA_ID.h:750
@ ID_TAG_NOT_ALLOCATED
Definition DNA_ID.h:933
@ ID_TAG_NO_MAIN
Definition DNA_ID.h:886
@ ID_LI
@ ID_KE
@ ID_SCE
blenloader genfile private function prototypes
const struct SDNA * DNA_sdna_current_get(void)
int DNA_struct_find_with_alias(const struct SDNA *sdna, const char *str)
int DNA_struct_size(const struct SDNA *sdna, int struct_index)
@ R_BG_RENDER
#define SDNA_RAW_DATA_STRUCT_INDEX
#define USER_EXPERIMENTAL_TEST(userdef, member)
@ USER_MENU_TYPE_OPERATOR
@ USER_MENU_TYPE_PROP
@ USER_MENU_TYPE_MENU
Read Guarded memory(de)allocation.
ReportList * reports
Definition WM_types.hh:1025
#define U
BMesh const char void * data
char build_hash[]
Definition bpy_app.cc:67
ulong build_commit_timestamp
Definition bpy_app.cc:64
long long int int64_t
unsigned long long int uint64_t
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
bool write(const void *buf, size_t buf_len) override
Definition writefile.cc:212
bool open(const char *filepath) override
Definition writefile.cc:195
bool close() override
Definition writefile.cc:208
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:595
void clear()
Definition BLI_set.hh:551
virtual bool open(const char *filepath)=0
virtual bool write(const void *buf, size_t buf_len)=0
bool use_buf
Definition writefile.cc:182
virtual bool close()=0
ZstdWriteWrap(WriteWrap &base_wrap)
Definition writefile.cc:232
bool open(const char *filepath) override
Definition writefile.cc:298
bool close() override
Definition writefile.cc:354
bool write(const void *buf, size_t buf_len) override
Definition writefile.cc:368
Span< Value > lookup(const Key &key) const
void add(const Key &key, const Value &value)
void append(const T &value)
bool is_empty() const
#define INT32_MAX
ThreadMutex mutex
#define sizeof
#define printf(...)
#define ID_IS_OVERRIDE_LIBRARY_VIRTUAL(_id)
#define ID_IS_OVERRIDE_LIBRARY_REAL(_id)
#define ID_IS_LINKED(_id)
#define MAX_ID_NAME
#define ID_LINK_PLACEHOLDER
#define GS(a)
#define ID_TAG_KEEP_ON_UNDO
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define LOG(severity)
Definition log.h:32
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
#define G(x, y, z)
void print_structs_at_address(const SDNA &sdna, int struct_id, const void *data, const void *address, int64_t element_num, std::ostream &stream)
int sdna_struct_id_get_max()
int64_t len
int64_t nr
const void * old
BLO_Write_IDBuffer(ID &id, bool is_undo, bool is_placeholder)
WriteData * wd
Definition writefile.cc:468
struct ViewLayer * cur_view_layer
struct Scene * curscene
uint64_t build_commit_timestamp
struct bScreen * curscreen
char filepath[1024]
IDTypeBlendWriteFunction blend_write
uint32_t flags
size_t struct_size
Definition DNA_ID.h:404
void * py_instance
Definition DNA_ID.h:483
int tag
Definition DNA_ID.h:424
struct Library * lib
Definition DNA_ID.h:410
int us
Definition DNA_ID.h:425
int icon_id
Definition DNA_ID.h:426
struct ID * newid
Definition DNA_ID.h:408
void * prev
Definition DNA_ID.h:407
struct ID * orig_id
Definition DNA_ID.h:466
short flag
Definition DNA_ID.h:420
void * next
Definition DNA_ID.h:407
char name[66]
Definition DNA_ID.h:415
unsigned int recalc_up_to_undo_push
Definition DNA_ID.h:437
unsigned int session_uid
Definition DNA_ID.h:444
struct ID_Runtime runtime
Definition DNA_ID.h:493
ID * from
LibraryForeachIDCallbackFlag cb_flag
struct PackedFile * packedfile
Definition DNA_ID.h:509
ID id
Definition DNA_ID.h:505
LibraryRuntimeHandle * runtime
Definition DNA_ID.h:516
void * first
ListBase scenes
Definition BKE_main.hh:245
ListBase wm
Definition BKE_main.hh:276
bool is_asset_edit_file
Definition BKE_main.hh:171
char filepath[1024]
Definition BKE_main.hh:155
ListBase libraries
Definition BKE_main.hh:246
MainLock * lock
Definition BKE_main.hh:312
bool is_global_main
Definition BKE_main.hh:211
blender::Map< const void *, const blender::ImplicitSharingInfo * > map
blender::Map< uint, MemFileChunk * > id_session_uid_mapping
MemFile * written_memfile
MemFileChunk * reference_current_chunk
MemFileSharedStorage * shared_storage
size_t size
char scene_name[MAX_ID_NAME - 2]
Definition writefile.cc:979
const char * data
int data_size
struct ListBase addons
ListBase script_directories
struct ListBase uistyles
struct ListBase asset_shelves_settings
struct ListBase user_keymaps
struct ListBase themes
struct ListBase autoexec_paths
struct ListBase user_keyconfig_prefs
struct ListBase extension_repos
struct ListBase user_menus
struct ListBase asset_libraries
blender::Set< const void * > per_id_addresses_set
Definition writefile.cc:445
const SDNA * sdna
Definition writefile.cc:411
WriteWrap * ww
Definition writefile.cc:464
uchar * buf
Definition writefile.cc:416
bool critical_error
Definition writefile.cc:440
bool use_memfile
Definition writefile.cc:457
blender::Set< const void * > per_id_written_shared_addresses
Definition writefile.cc:452
bool is_writing_id
Definition writefile.cc:432
struct WriteData::@167240152353153150004376024347372131061376204212 validation_data
std::ostream * debug_dst
Definition writefile.cc:412
MemFileWriteData mem
Definition writefile.cc:455
struct WriteData::@322233131303130151342011105257030221107160165374 buffer
size_t chunk_size
Definition writefile.cc:423
size_t max_size
Definition writefile.cc:421
size_t used_len
Definition writefile.cc:418
uint32_t uncompressed_size
Definition writefile.cc:172
uint32_t compressed_size
Definition writefile.cc:171
ZstdFrame * next
Definition writefile.cc:169
ZstdFrame * prev
Definition writefile.cc:169
static void * write_task(void *userdata)
Definition writefile.cc:252
struct IDProperty * prop
struct Scene * scene
struct wmWindow * next
struct WorkSpaceInstanceHook * workspace_hook
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
uint len
void BLO_write_double_array(BlendWriter *writer, const int64_t num, const double *data_ptr)
void BLO_write_uint32_array(BlendWriter *writer, const int64_t num, const uint32_t *data_ptr)
static void writedata_do_write(WriteData *wd, const void *mem, const size_t memlen)
Definition writefile.cc:494
static void writestruct_at_address_nr(WriteData *wd, const int filecode, const int struct_nr, const int64_t nr, const void *adr, const void *data)
Definition writefile.cc:768
void blo_write_id_struct(BlendWriter *writer, const int struct_id, const void *id_address, const ID *id)
int BLO_get_struct_id_by_name(const BlendWriter *writer, const char *struct_name)
static void mywrite_id_begin(WriteData *wd, ID *id)
Definition writefile.cc:647
#define ZSTD_COMPRESSION_LEVEL
Definition writefile.cc:157
static void write_thumb(WriteData *wd, const BlendThumbnail *thumb)
#define writestruct(wd, filecode, struct_id, nr, adr)
Definition writefile.cc:923
static void current_screen_compat(Main *mainvar, const bool use_active_win, bScreen **r_screen, Scene **r_scene, ViewLayer **r_view_layer)
Definition writefile.cc:938
void BLO_write_pointer_array(BlendWriter *writer, const int64_t num, const void *data_ptr)
void BLO_write_struct_array_by_name(BlendWriter *writer, const char *struct_name, const int64_t array_size, const void *data_ptr)
void BLO_write_struct_at_address_by_id(BlendWriter *writer, const int struct_id, const void *address, const void *data_ptr)
static bool write_file_handle(Main *mainvar, WriteWrap *ww, MemFile *compare, MemFile *current, const int write_flags, const bool use_userdef, const BlendThumbnail *thumb, std::ostream *debug_dst)
static void writelist_nr(WriteData *wd, const int filecode, const int struct_nr, const ListBase *lb)
Definition writefile.cc:886
static std::string get_blend_file_header()
void BLO_write_int32_array(BlendWriter *writer, const int64_t num, const int32_t *data_ptr)
void BLO_write_struct_by_name(BlendWriter *writer, const char *struct_name, const void *data_ptr)
static void writedata_free(WriteData *wd)
Definition writefile.cc:520
#define DEBUG_BLEND_FILE_SUFFIX
Definition writefile.cc:146
static void write_keymapitem(BlendWriter *writer, const wmKeyMapItem *kmi)
static void write_id(WriteData *wd, ID *id)
#define ZSTD_CHUNK_SIZE
Definition writefile.cc:155
static bool do_history(const char *filepath, ReportList *reports)
static void write_renderinfo(WriteData *wd, Main *mainvar)
Definition writefile.cc:988
void BLO_write_struct_list_by_id(BlendWriter *writer, const int struct_id, const ListBase *list)
static void write_userdef(BlendWriter *writer, const UserDef *userdef)
#define writestruct_at_address(wd, filecode, struct_id, nr, adr, data)
Definition writefile.cc:919
static void write_file_main_validate_pre(Main *bmain, ReportList *reports)
void BLO_write_float_array(BlendWriter *writer, const int64_t num, const float *data_ptr)
static void writestruct_nr(WriteData *wd, const int filecode, const int struct_nr, const int64_t nr, const void *adr)
Definition writefile.cc:814
static void write_file_main_validate_post(Main *bmain, ReportList *reports)
void BLO_write_struct_by_id(BlendWriter *writer, const int struct_id, const void *data_ptr)
#define MEM_CHUNK_SIZE
Definition writefile.cc:152
void BLO_write_struct_array_at_address_by_id(BlendWriter *writer, const int struct_id, const int64_t array_size, const void *address, const void *data_ptr)
static void writedata(WriteData *wd, const int filecode, const size_t len, const void *adr)
Definition writefile.cc:849
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
static WriteData * writedata_new(WriteWrap *ww)
Definition writefile.cc:471
void BLO_write_int8_array(BlendWriter *writer, const int64_t num, const int8_t *data_ptr)
void BLO_write_int16_array(BlendWriter *writer, const int64_t num, const int16_t *data_ptr)
void BLO_write_uint8_array(BlendWriter *writer, const int64_t num, const uint8_t *data_ptr)
static void write_libraries(WriteData *wd, Main *bmain)
static void write_global(WriteData *wd, const int fileflags, Main *mainvar)
static bool write_at_address_validate(WriteData *wd, const int filecode, const void *address)
Definition writefile.cc:713
void BLO_write_char_array(BlendWriter *writer, const int64_t num, const char *data_ptr)
static bool mywrite_end(WriteData *wd)
Definition writefile.cc:625
static void mywrite_flush(WriteData *wd)
Definition writefile.cc:538
static blender::Vector< ID * > gather_local_ids_to_write(Main *bmain, const bool is_undo)
static void mywrite_id_end(WriteData *wd, ID *)
Definition writefile.cc:684
static void write_blend_file_header(WriteData *wd)
#define ZSTD_BUFFER_SIZE
Definition writefile.cc:154
bool BLO_write_file(Main *mainvar, const char *filepath, const int write_flags, const BlendFileWriteParams *params, ReportList *reports)
static void write_raw_data_in_debug_file(WriteData *wd, const size_t len, const void *adr)
Definition writefile.cc:820
static void write_id_placeholder(WriteData *wd, ID *id)
void BLO_write_struct_array_by_id(BlendWriter *writer, const int struct_id, const int64_t array_size, const void *data_ptr)
bool BLO_write_is_undo(BlendWriter *writer)
#define MEM_BUFFER_SIZE
Definition writefile.cc:151
static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_data)
static void mywrite(WriteData *wd, const void *adr, size_t len)
Definition writefile.cc:551
void BLO_write_raw(BlendWriter *writer, const size_t size_in_bytes, const void *data_ptr)
bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, const int write_flags)
static WriteData * mywrite_begin(WriteWrap *ww, MemFile *compare, MemFile *current)
Definition writefile.cc:607
void BLO_write_struct_list_by_name(BlendWriter *writer, const char *struct_name, ListBase *list)
static void write_bhead(WriteData *wd, const BHead &bhead)
Definition writefile.cc:731
static bool BLO_write_file_impl(Main *mainvar, const char *filepath, const int write_flags, const BlendFileWriteParams *params, ReportList *reports, WriteWrap &ww)
void BLO_write_shared(BlendWriter *writer, const void *data, const size_t approximate_size_in_bytes, const blender::ImplicitSharingInfo *sharing_info, const blender::FunctionRef< void()> write_fn)
void BLO_write_struct_at_address_by_id_with_filecode(BlendWriter *writer, const int filecode, const int struct_id, const void *address, const void *data_ptr)
void BLO_write_float3_array(BlendWriter *writer, const int64_t num, const float *data_ptr)