Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenloader/intern/readfile.cc')
-rw-r--r--source/blender/blenloader/intern/readfile.cc5213
1 files changed, 5213 insertions, 0 deletions
diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc
new file mode 100644
index 00000000000..569798048f6
--- /dev/null
+++ b/source/blender/blenloader/intern/readfile.cc
@@ -0,0 +1,5213 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2001-2002 NaN Holding BV. All rights reserved. */
+
+/** \file
+ * \ingroup blenloader
+ */
+
+#include <cctype> /* for isdigit. */
+#include <cerrno>
+#include <climits>
+#include <cstdarg> /* for va_start/end. */
+#include <cstddef> /* for offsetof. */
+#include <cstdlib> /* for atoi. */
+#include <ctime> /* for gmtime. */
+#include <fcntl.h> /* for open flags (O_BINARY, O_RDONLY). */
+
+#include "BLI_utildefines.h"
+#ifndef WIN32
+# include <unistd.h> /* for read close */
+#else
+# include "BLI_winstuff.h"
+# include "winsock2.h"
+# include <io.h> /* for open close read */
+#endif
+
+#include "CLG_log.h"
+
+/* allow readfile to use deprecated functionality */
+#define DNA_DEPRECATED_ALLOW
+
+#include "DNA_anim_types.h"
+#include "DNA_asset_types.h"
+#include "DNA_cachefile_types.h"
+#include "DNA_collection_types.h"
+#include "DNA_fileglobal_types.h"
+#include "DNA_genfile.h"
+#include "DNA_key_types.h"
+#include "DNA_layer_types.h"
+#include "DNA_node_types.h"
+#include "DNA_packedFile_types.h"
+#include "DNA_sdna_types.h"
+#include "DNA_sound_types.h"
+#include "DNA_vfont_types.h"
+#include "DNA_volume_types.h"
+#include "DNA_workspace_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_endian_defines.h"
+#include "BLI_endian_switch.h"
+#include "BLI_ghash.h"
+#include "BLI_linklist.h"
+#include "BLI_math.h"
+#include "BLI_memarena.h"
+#include "BLI_mempool.h"
+#include "BLI_threads.h"
+
+#include "PIL_time.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_anim_data.h"
+#include "BKE_animsys.h"
+#include "BKE_asset.h"
+#include "BKE_collection.h"
+#include "BKE_global.h" /* for G */
+#include "BKE_idprop.h"
+#include "BKE_idtype.h"
+#include "BKE_layer.h"
+#include "BKE_lib_id.h"
+#include "BKE_lib_override.h"
+#include "BKE_lib_query.h"
+#include "BKE_main.h" /* for Main */
+#include "BKE_main_idmap.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_node.h" /* for tree type defines */
+#include "BKE_object.h"
+#include "BKE_packedFile.h"
+#include "BKE_report.h"
+#include "BKE_scene.h"
+#include "BKE_screen.h"
+#include "BKE_undo_system.h"
+#include "BKE_workspace.h"
+
+#include "DRW_engine.h"
+
+#include "DEG_depsgraph.h"
+
+#include "BLO_blend_defs.h"
+#include "BLO_blend_validate.h"
+#include "BLO_read_write.h"
+#include "BLO_readfile.h"
+#include "BLO_undofile.h"
+
+#include "SEQ_clipboard.h"
+#include "SEQ_iterator.h"
+#include "SEQ_modifier.h"
+#include "SEQ_sequencer.h"
+#include "SEQ_utils.h"
+
+#include "readfile.h"
+
+/* Make preferences read-only. */
+#define U (*((const UserDef *)&U))
+
+/**
+ * READ
+ * ====
+ *
+ * - Existing Library (#Main) push or free
+ * - allocate new #Main
+ * - load file
+ * - read #SDNA
+ * - for each LibBlock
+ * - read LibBlock
+ * - if a Library
+ * - make a new #Main
+ * - attach ID's to it
+ * - else
+ * - read associated 'direct data'
+ * - link direct data (internal and to LibBlock)
+ * - read #FileGlobal
+ * - read #USER data, only when indicated (file is `~/.config/blender/X.XX/config/userpref.blend`)
+ * - free file
+ * - per Library (per #Main)
+ * - read file
+ * - read #SDNA
+ * - find LibBlocks and attach #ID's to #Main
+ * - if external LibBlock
+ * - search all #Main's
+ * - or it's already read,
+ * - or not read yet
+ * - or make new #Main
+ * - per LibBlock
+ * - read recursive
+ * - read associated direct data
+ * - link direct data (internal and to LibBlock)
+ * - free file
+ * - per Library with unread LibBlocks
+ * - read file
+ * - read #SDNA
+ * - per LibBlock
+ * - read recursive
+ * - read associated direct data
+ * - link direct data (internal and to LibBlock)
+ * - free file
+ * - join all #Main's
+ * - link all LibBlocks and indirect pointers to libblocks
+ * - initialize #FileGlobal and copy pointers to #Global
+ *
+ * \note Still a weak point is the new-address function, that doesn't solve reading from
+ * multiple files at the same time.
+ * (added remark: oh, i thought that was solved? will look at that... (ton).
+ */
+
+/**
+ * Delay reading blocks we might not use (especially applies to library linking).
+ * which keeps large arrays in memory from data-blocks we may not even use.
+ *
+ * \note This is disabled when using compression,
+ * while ZLIB supports seek it's unusably slow, see: T61880.
+ */
+#define USE_BHEAD_READ_ON_DEMAND
+
+/** Use #GHash for #BHead name-based lookups (speeds up linking). */
+#define USE_GHASH_BHEAD
+
+/** Use #GHash for restoring pointers by name. */
+#define USE_GHASH_RESTORE_POINTER
+
+static CLG_LogRef LOG = {"blo.readfile"};
+static CLG_LogRef LOG_UNDO = {"blo.readfile.undo"};
+
+/* local prototypes */
+static void read_libraries(FileData *basefd, ListBase *mainlist);
+static void *read_struct(FileData *fd, BHead *bh, const char *blockname);
+static BHead *find_bhead_from_code_name(FileData *fd, const short idcode, const char *name);
+static BHead *find_bhead_from_idname(FileData *fd, const char *idname);
+
+struct BHeadN {
+ struct BHeadN *next, *prev;
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ /** Use to read the data from the file directly into memory as needed. */
+ off64_t file_offset;
+ /** When set, the remainder of this allocation is the data, otherwise it needs to be read. */
+ bool has_data;
+#endif
+ bool is_memchunk_identical;
+ struct BHead bhead;
+};
+
+#define BHEADN_FROM_BHEAD(bh) ((BHeadN *)POINTER_OFFSET(bh, -int(offsetof(BHeadN, bhead))))
+
+/**
+ * We could change this in the future, for now it's simplest if only data is delayed
+ * because ID names are used in lookup tables.
+ */
+#define BHEAD_USE_READ_ON_DEMAND(bhead) ((bhead)->code == DATA)
+
+void BLO_reportf_wrap(BlendFileReadReport *reports, eReportType type, const char *format, ...)
+{
+ char fixed_buf[1024]; /* should be long enough */
+
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(fixed_buf, sizeof(fixed_buf), format, args);
+ va_end(args);
+
+ fixed_buf[sizeof(fixed_buf) - 1] = '\0';
+
+ BKE_report(reports->reports, type, fixed_buf);
+
+ if (G.background == 0) {
+ printf("%s: %s\n", BKE_report_type_str(type), fixed_buf);
+ }
+}
+
+/* for reporting linking messages */
+static const char *library_parent_filepath(Library *lib)
+{
+ return lib->parent ? lib->parent->filepath_abs : "<direct>";
+}
+
+/* -------------------------------------------------------------------- */
+/** \name OldNewMap API
+ * \{ */
+
+struct OldNew {
+ const void *oldp;
+ void *newp;
+ /* `nr` is "user count" for data, and ID code for libdata. */
+ int nr;
+};
+
+struct OldNewMap {
+ /* Array that stores the actual entries. */
+ OldNew *entries;
+ int nentries;
+ /* Hash-map that stores indices into the `entries` array. */
+ int32_t *map;
+
+ int capacity_exp;
+};
+
+#define ENTRIES_CAPACITY(onm) (1ll << (onm)->capacity_exp)
+#define MAP_CAPACITY(onm) (1ll << ((onm)->capacity_exp + 1))
+#define SLOT_MASK(onm) (MAP_CAPACITY(onm) - 1)
+#define DEFAULT_SIZE_EXP 6
+#define PERTURB_SHIFT 5
+
+/* based on the probing algorithm used in Python dicts. */
+#define ITER_SLOTS(onm, KEY, SLOT_NAME, INDEX_NAME) \
+ uint32_t hash = BLI_ghashutil_ptrhash(KEY); \
+ uint32_t mask = SLOT_MASK(onm); \
+ uint perturb = hash; \
+ int SLOT_NAME = mask & hash; \
+ int INDEX_NAME = onm->map[SLOT_NAME]; \
+ for (;; SLOT_NAME = mask & ((5 * SLOT_NAME) + 1 + perturb), \
+ perturb >>= PERTURB_SHIFT, \
+ INDEX_NAME = onm->map[SLOT_NAME])
+
+static void oldnewmap_insert_index_in_map(OldNewMap *onm, const void *ptr, int index)
+{
+ ITER_SLOTS (onm, ptr, slot, stored_index) {
+ if (stored_index == -1) {
+ onm->map[slot] = index;
+ break;
+ }
+ }
+}
+
+static void oldnewmap_insert_or_replace(OldNewMap *onm, OldNew entry)
+{
+ ITER_SLOTS (onm, entry.oldp, slot, index) {
+ if (index == -1) {
+ onm->entries[onm->nentries] = entry;
+ onm->map[slot] = onm->nentries;
+ onm->nentries++;
+ break;
+ }
+ if (onm->entries[index].oldp == entry.oldp) {
+ onm->entries[index] = entry;
+ break;
+ }
+ }
+}
+
+static OldNew *oldnewmap_lookup_entry(const OldNewMap *onm, const void *addr)
+{
+ ITER_SLOTS (onm, addr, slot, index) {
+ if (index >= 0) {
+ OldNew *entry = &onm->entries[index];
+ if (entry->oldp == addr) {
+ return entry;
+ }
+ }
+ else {
+ return nullptr;
+ }
+ }
+}
+
+static void oldnewmap_clear_map(OldNewMap *onm)
+{
+ memset(onm->map, 0xFF, MAP_CAPACITY(onm) * sizeof(*onm->map));
+}
+
+static void oldnewmap_increase_size(OldNewMap *onm)
+{
+ onm->capacity_exp++;
+ onm->entries = static_cast<OldNew *>(
+ MEM_reallocN(onm->entries, sizeof(*onm->entries) * ENTRIES_CAPACITY(onm)));
+ onm->map = static_cast<int32_t *>(MEM_reallocN(onm->map, sizeof(*onm->map) * MAP_CAPACITY(onm)));
+ oldnewmap_clear_map(onm);
+ for (int i = 0; i < onm->nentries; i++) {
+ oldnewmap_insert_index_in_map(onm, onm->entries[i].oldp, i);
+ }
+}
+
+/* Public OldNewMap API */
+
+static void oldnewmap_init_data(OldNewMap *onm, const int capacity_exp)
+{
+ memset(onm, 0x0, sizeof(*onm));
+
+ onm->capacity_exp = capacity_exp;
+ onm->entries = static_cast<OldNew *>(
+ MEM_malloc_arrayN(ENTRIES_CAPACITY(onm), sizeof(*onm->entries), "OldNewMap.entries"));
+ onm->map = static_cast<int32_t *>(
+ MEM_malloc_arrayN(MAP_CAPACITY(onm), sizeof(*onm->map), "OldNewMap.map"));
+ oldnewmap_clear_map(onm);
+}
+
+static OldNewMap *oldnewmap_new()
+{
+ OldNewMap *onm = static_cast<OldNewMap *>(MEM_mallocN(sizeof(*onm), "OldNewMap"));
+
+ oldnewmap_init_data(onm, DEFAULT_SIZE_EXP);
+
+ return onm;
+}
+
+static void oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void *newaddr, int nr)
+{
+ if (oldaddr == nullptr || newaddr == nullptr) {
+ return;
+ }
+
+ if (UNLIKELY(onm->nentries == ENTRIES_CAPACITY(onm))) {
+ oldnewmap_increase_size(onm);
+ }
+
+ OldNew entry;
+ entry.oldp = oldaddr;
+ entry.newp = newaddr;
+ entry.nr = nr;
+ oldnewmap_insert_or_replace(onm, entry);
+}
+
+static void oldnewmap_lib_insert(FileData *fd, const void *oldaddr, ID *newaddr, int nr)
+{
+ oldnewmap_insert(fd->libmap, oldaddr, newaddr, nr);
+}
+
+void blo_do_versions_oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void *newaddr, int nr)
+{
+ oldnewmap_insert(onm, oldaddr, newaddr, nr);
+}
+
+static void *oldnewmap_lookup_and_inc(OldNewMap *onm, const void *addr, bool increase_users)
+{
+ OldNew *entry = oldnewmap_lookup_entry(onm, addr);
+ if (entry == nullptr) {
+ return nullptr;
+ }
+ if (increase_users) {
+ entry->nr++;
+ }
+ return entry->newp;
+}
+
+/* for libdata, OldNew.nr has ID code, no increment */
+static void *oldnewmap_liblookup(OldNewMap *onm, const void *addr, const void *lib)
+{
+ if (addr == nullptr) {
+ return nullptr;
+ }
+
+ ID *id = static_cast<ID *>(oldnewmap_lookup_and_inc(onm, addr, false));
+ if (id == nullptr) {
+ return nullptr;
+ }
+ if (!lib || id->lib) {
+ return id;
+ }
+ return nullptr;
+}
+
+static void oldnewmap_clear(OldNewMap *onm)
+{
+ /* Free unused data. */
+ for (int i = 0; i < onm->nentries; i++) {
+ OldNew *entry = &onm->entries[i];
+ if (entry->nr == 0) {
+ MEM_freeN(entry->newp);
+ entry->newp = nullptr;
+ }
+ }
+
+ MEM_freeN(onm->entries);
+ MEM_freeN(onm->map);
+
+ oldnewmap_init_data(onm, DEFAULT_SIZE_EXP);
+}
+
+static void oldnewmap_free(OldNewMap *onm)
+{
+ MEM_freeN(onm->entries);
+ MEM_freeN(onm->map);
+ MEM_freeN(onm);
+}
+
+#undef ENTRIES_CAPACITY
+#undef MAP_CAPACITY
+#undef SLOT_MASK
+#undef DEFAULT_SIZE_EXP
+#undef PERTURB_SHIFT
+#undef ITER_SLOTS
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Helper Functions
+ * \{ */
+
+static void add_main_to_main(Main *mainvar, Main *from)
+{
+ ListBase *lbarray[INDEX_ID_MAX], *fromarray[INDEX_ID_MAX];
+ int a;
+
+ set_listbasepointers(mainvar, lbarray);
+ a = set_listbasepointers(from, fromarray);
+ while (a--) {
+ BLI_movelisttolist(lbarray[a], fromarray[a]);
+ }
+}
+
+void blo_join_main(ListBase *mainlist)
+{
+ Main *tojoin, *mainl;
+
+ mainl = static_cast<Main *>(mainlist->first);
+
+ if (mainl->id_map != nullptr) {
+ /* Cannot keep this since we add some IDs from joined mains. */
+ BKE_main_idmap_destroy(mainl->id_map);
+ mainl->id_map = nullptr;
+ }
+
+ while ((tojoin = mainl->next)) {
+ add_main_to_main(mainl, tojoin);
+ BLI_remlink(mainlist, tojoin);
+ BKE_main_free(tojoin);
+ }
+}
+
+static void split_libdata(ListBase *lb_src, Main **lib_main_array, const uint lib_main_array_len)
+{
+ for (ID *id = static_cast<ID *>(lb_src->first), *idnext; id; id = idnext) {
+ idnext = static_cast<ID *>(id->next);
+
+ if (id->lib) {
+ if ((uint(id->lib->temp_index) < lib_main_array_len) &&
+ /* this check should never fail, just in case 'id->lib' is a dangling pointer. */
+ (lib_main_array[id->lib->temp_index]->curlib == id->lib)) {
+ Main *mainvar = lib_main_array[id->lib->temp_index];
+ ListBase *lb_dst = which_libbase(mainvar, GS(id->name));
+ BLI_remlink(lb_src, id);
+ BLI_addtail(lb_dst, id);
+ }
+ else {
+ CLOG_ERROR(&LOG, "Invalid library for '%s'", id->name);
+ }
+ }
+ }
+}
+
+void blo_split_main(ListBase *mainlist, Main *main)
+{
+ mainlist->first = mainlist->last = main;
+ main->next = nullptr;
+
+ if (BLI_listbase_is_empty(&main->libraries)) {
+ return;
+ }
+
+ if (main->id_map != nullptr) {
+ /* Cannot keep this since we remove some IDs from given main. */
+ BKE_main_idmap_destroy(main->id_map);
+ main->id_map = nullptr;
+ }
+
+ /* (Library.temp_index -> Main), lookup table */
+ const uint lib_main_array_len = BLI_listbase_count(&main->libraries);
+ Main **lib_main_array = static_cast<Main **>(
+ MEM_malloc_arrayN(lib_main_array_len, sizeof(*lib_main_array), __func__));
+
+ int i = 0;
+ for (Library *lib = static_cast<Library *>(main->libraries.first); lib;
+ lib = static_cast<Library *>(lib->id.next), i++) {
+ Main *libmain = BKE_main_new();
+ libmain->curlib = lib;
+ libmain->versionfile = lib->versionfile;
+ libmain->subversionfile = lib->subversionfile;
+ BLI_addtail(mainlist, libmain);
+ lib->temp_index = i;
+ lib_main_array[i] = libmain;
+ }
+
+ ListBase *lbarray[INDEX_ID_MAX];
+ i = set_listbasepointers(main, lbarray);
+ while (i--) {
+ ID *id = static_cast<ID *>(lbarray[i]->first);
+ if (id == nullptr || GS(id->name) == ID_LI) {
+ /* No ID_LI data-block should ever be linked anyway, but just in case, better be explicit. */
+ continue;
+ }
+ split_libdata(lbarray[i], lib_main_array, lib_main_array_len);
+ }
+
+ MEM_freeN(lib_main_array);
+}
+
+static void read_file_version(FileData *fd, Main *main)
+{
+ BHead *bhead;
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ if (bhead->code == GLOB) {
+ FileGlobal *fg = static_cast<FileGlobal *>(read_struct(fd, bhead, "Global"));
+ if (fg) {
+ main->subversionfile = fg->subversion;
+ main->minversionfile = fg->minversion;
+ main->minsubversionfile = fg->minsubversion;
+ MEM_freeN(fg);
+ }
+ else if (bhead->code == ENDB) {
+ break;
+ }
+ }
+ }
+ if (main->curlib) {
+ main->curlib->versionfile = main->versionfile;
+ main->curlib->subversionfile = main->subversionfile;
+ }
+}
+
+static bool blo_bhead_is_id(const BHead *bhead)
+{
+ /* BHead codes are four bytes (like 'ENDB', 'TEST', etc.), but if the two most-significant bytes
+ * are zero, the values actually indicate an ID type. */
+ return bhead->code <= 0xFFFF;
+}
+
+static bool blo_bhead_is_id_valid_type(const BHead *bhead)
+{
+ if (!blo_bhead_is_id(bhead)) {
+ return false;
+ }
+
+ const short id_type_code = bhead->code & 0xFFFF;
+ return BKE_idtype_idcode_is_valid(id_type_code);
+}
+
+#ifdef USE_GHASH_BHEAD
+static void read_file_bhead_idname_map_create(FileData *fd)
+{
+ BHead *bhead;
+
+ /* dummy values */
+ bool is_link = false;
+ int code_prev = ENDB;
+ uint reserve = 0;
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ if (code_prev != bhead->code) {
+ code_prev = bhead->code;
+ is_link = blo_bhead_is_id_valid_type(bhead) ?
+ BKE_idtype_idcode_is_linkable(short(code_prev)) :
+ false;
+ }
+
+ if (is_link) {
+ reserve += 1;
+ }
+ }
+
+ BLI_assert(fd->bhead_idname_hash == nullptr);
+
+ fd->bhead_idname_hash = BLI_ghash_str_new_ex(__func__, reserve);
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ if (code_prev != bhead->code) {
+ code_prev = bhead->code;
+ is_link = blo_bhead_is_id_valid_type(bhead) ?
+ BKE_idtype_idcode_is_linkable(short(code_prev)) :
+ false;
+ }
+
+ if (is_link) {
+ BLI_ghash_insert(fd->bhead_idname_hash, (void *)blo_bhead_id_name(fd, bhead), bhead);
+ }
+ }
+}
+#endif
+
+static Main *blo_find_main(FileData *fd, const char *filepath, const char *relabase)
+{
+ ListBase *mainlist = fd->mainlist;
+ Main *m;
+ Library *lib;
+ char name1[FILE_MAX];
+
+ BLI_strncpy(name1, filepath, sizeof(name1));
+ BLI_path_normalize(relabase, name1);
+
+ // printf("blo_find_main: relabase %s\n", relabase);
+ // printf("blo_find_main: original in %s\n", filepath);
+ // printf("blo_find_main: converted to %s\n", name1);
+
+ for (m = static_cast<Main *>(mainlist->first); m; m = m->next) {
+ const char *libname = (m->curlib) ? m->curlib->filepath_abs : m->filepath;
+
+ if (BLI_path_cmp(name1, libname) == 0) {
+ if (G.debug & G_DEBUG) {
+ CLOG_INFO(&LOG, 3, "Found library %s", libname);
+ }
+ return m;
+ }
+ }
+
+ m = BKE_main_new();
+ BLI_addtail(mainlist, m);
+
+ /* Add library data-block itself to 'main' Main, since libraries are **never** linked data.
+ * Fixes bug where you could end with all ID_LI data-blocks having the same name... */
+ lib = static_cast<Library *>(BKE_libblock_alloc(
+ static_cast<Main *>(mainlist->first), ID_LI, BLI_path_basename(filepath), 0));
+
+ /* Important, consistency with main ID reading code from read_libblock(). */
+ lib->id.us = ID_FAKE_USERS(lib);
+
+ /* Matches direct_link_library(). */
+ id_us_ensure_real(&lib->id);
+
+ BLI_strncpy(lib->filepath, filepath, sizeof(lib->filepath));
+ BLI_strncpy(lib->filepath_abs, name1, sizeof(lib->filepath_abs));
+
+ m->curlib = lib;
+
+ read_file_version(fd, m);
+
+ if (G.debug & G_DEBUG) {
+ CLOG_INFO(&LOG, 3, "Added new lib %s", filepath);
+ }
+ return m;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name File Parsing
+ * \{ */
+
+struct BlendDataReader {
+ FileData *fd;
+};
+
+struct BlendLibReader {
+ FileData *fd;
+ Main *main;
+};
+
+struct BlendExpander {
+ FileData *fd;
+ Main *main;
+};
+
+static void switch_endian_bh4(BHead4 *bhead)
+{
+ /* the ID_.. codes */
+ if ((bhead->code & 0xFFFF) == 0) {
+ bhead->code >>= 16;
+ }
+
+ if (bhead->code != ENDB) {
+ BLI_endian_switch_int32(&bhead->len);
+ BLI_endian_switch_int32(&bhead->SDNAnr);
+ BLI_endian_switch_int32(&bhead->nr);
+ }
+}
+
+static void switch_endian_bh8(BHead8 *bhead)
+{
+ /* the ID_.. codes */
+ if ((bhead->code & 0xFFFF) == 0) {
+ bhead->code >>= 16;
+ }
+
+ if (bhead->code != ENDB) {
+ BLI_endian_switch_int32(&bhead->len);
+ BLI_endian_switch_int32(&bhead->SDNAnr);
+ BLI_endian_switch_int32(&bhead->nr);
+ }
+}
+
+static void bh4_from_bh8(BHead *bhead, BHead8 *bhead8, bool do_endian_swap)
+{
+ BHead4 *bhead4 = (BHead4 *)bhead;
+ int64_t old;
+
+ bhead4->code = bhead8->code;
+ bhead4->len = bhead8->len;
+
+ if (bhead4->code != ENDB) {
+ /* perform a endian swap on 64bit pointers, otherwise the pointer might map to zero
+ * 0x0000000000000000000012345678 would become 0x12345678000000000000000000000000
+ */
+ if (do_endian_swap) {
+ BLI_endian_switch_uint64(&bhead8->old);
+ }
+
+ /* this patch is to avoid `intptr_t` being read from not-eight aligned positions
+ * is necessary on any modern 64bit architecture) */
+ memcpy(&old, &bhead8->old, 8);
+ bhead4->old = int(old >> 3);
+
+ bhead4->SDNAnr = bhead8->SDNAnr;
+ bhead4->nr = bhead8->nr;
+ }
+}
+
+static void bh8_from_bh4(BHead *bhead, BHead4 *bhead4)
+{
+ BHead8 *bhead8 = (BHead8 *)bhead;
+
+ bhead8->code = bhead4->code;
+ bhead8->len = bhead4->len;
+
+ if (bhead8->code != ENDB) {
+ bhead8->old = bhead4->old;
+ bhead8->SDNAnr = bhead4->SDNAnr;
+ bhead8->nr = bhead4->nr;
+ }
+}
+
+static BHeadN *get_bhead(FileData *fd)
+{
+ BHeadN *new_bhead = nullptr;
+ ssize_t readsize;
+
+ if (fd) {
+ if (!fd->is_eof) {
+ /* initializing to zero isn't strictly needed but shuts valgrind up
+ * since uninitialized memory gets compared */
+ BHead8 bhead8 = {0};
+ BHead4 bhead4 = {0};
+ BHead bhead = {0};
+
+ /* First read the bhead structure.
+ * Depending on the platform the file was written on this can
+ * be a big or little endian BHead4 or BHead8 structure.
+ *
+ * As usual 'ENDB' (the last *partial* bhead of the file)
+ * needs some special handling. We don't want to EOF just yet.
+ */
+ if (fd->flags & FD_FLAGS_FILE_POINTSIZE_IS_4) {
+ bhead4.code = DATA;
+ readsize = fd->file->read(fd->file, &bhead4, sizeof(bhead4));
+
+ if (readsize == sizeof(bhead4) || bhead4.code == ENDB) {
+ if (fd->flags & FD_FLAGS_SWITCH_ENDIAN) {
+ switch_endian_bh4(&bhead4);
+ }
+
+ if (fd->flags & FD_FLAGS_POINTSIZE_DIFFERS) {
+ bh8_from_bh4(&bhead, &bhead4);
+ }
+ else {
+ /* MIN2 is only to quiet '-Warray-bounds' compiler warning. */
+ BLI_assert(sizeof(bhead) == sizeof(bhead4));
+ memcpy(&bhead, &bhead4, MIN2(sizeof(bhead), sizeof(bhead4)));
+ }
+ }
+ else {
+ fd->is_eof = true;
+ bhead.len = 0;
+ }
+ }
+ else {
+ bhead8.code = DATA;
+ readsize = fd->file->read(fd->file, &bhead8, sizeof(bhead8));
+
+ if (readsize == sizeof(bhead8) || bhead8.code == ENDB) {
+ if (fd->flags & FD_FLAGS_SWITCH_ENDIAN) {
+ switch_endian_bh8(&bhead8);
+ }
+
+ if (fd->flags & FD_FLAGS_POINTSIZE_DIFFERS) {
+ bh4_from_bh8(&bhead, &bhead8, (fd->flags & FD_FLAGS_SWITCH_ENDIAN) != 0);
+ }
+ else {
+ /* MIN2 is only to quiet '-Warray-bounds' compiler warning. */
+ BLI_assert(sizeof(bhead) == sizeof(bhead8));
+ memcpy(&bhead, &bhead8, MIN2(sizeof(bhead), sizeof(bhead8)));
+ }
+ }
+ else {
+ fd->is_eof = true;
+ bhead.len = 0;
+ }
+ }
+
+ /* make sure people are not trying to pass bad blend files */
+ if (bhead.len < 0) {
+ fd->is_eof = true;
+ }
+
+ /* bhead now contains the (converted) bhead structure. Now read
+ * the associated data and put everything in a BHeadN (creative naming !)
+ */
+ if (fd->is_eof) {
+ /* pass */
+ }
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ else if (fd->file->seek != nullptr && BHEAD_USE_READ_ON_DEMAND(&bhead)) {
+ /* Delay reading bhead content. */
+ new_bhead = static_cast<BHeadN *>(MEM_mallocN(sizeof(BHeadN), "new_bhead"));
+ if (new_bhead) {
+ new_bhead->next = new_bhead->prev = nullptr;
+ new_bhead->file_offset = fd->file->offset;
+ new_bhead->has_data = false;
+ new_bhead->is_memchunk_identical = false;
+ new_bhead->bhead = bhead;
+ off64_t seek_new = fd->file->seek(fd->file, bhead.len, SEEK_CUR);
+ if (seek_new == -1) {
+ fd->is_eof = true;
+ MEM_freeN(new_bhead);
+ new_bhead = nullptr;
+ }
+ BLI_assert(fd->file->offset == seek_new);
+ }
+ else {
+ fd->is_eof = true;
+ }
+ }
+#endif
+ else {
+ new_bhead = static_cast<BHeadN *>(
+ MEM_mallocN(sizeof(BHeadN) + size_t(bhead.len), "new_bhead"));
+ if (new_bhead) {
+ new_bhead->next = new_bhead->prev = nullptr;
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ new_bhead->file_offset = 0; /* don't seek. */
+ new_bhead->has_data = true;
+#endif
+ new_bhead->is_memchunk_identical = false;
+ new_bhead->bhead = bhead;
+
+ readsize = fd->file->read(fd->file, new_bhead + 1, size_t(bhead.len));
+
+ if (readsize != bhead.len) {
+ fd->is_eof = true;
+ MEM_freeN(new_bhead);
+ new_bhead = nullptr;
+ }
+
+ if (fd->flags & FD_FLAGS_IS_MEMFILE) {
+ new_bhead->is_memchunk_identical = ((UndoReader *)fd->file)->memchunk_identical;
+ }
+ }
+ else {
+ fd->is_eof = true;
+ }
+ }
+ }
+ }
+
+ /* We've read a new block. Now add it to the list
+ * of blocks.
+ */
+ if (new_bhead) {
+ BLI_addtail(&fd->bhead_list, new_bhead);
+ }
+
+ return new_bhead;
+}
+
+BHead *blo_bhead_first(FileData *fd)
+{
+ BHeadN *new_bhead;
+ BHead *bhead = nullptr;
+
+ /* Rewind the file
+ * Read in a new block if necessary
+ */
+ new_bhead = static_cast<BHeadN *>(fd->bhead_list.first);
+ if (new_bhead == nullptr) {
+ new_bhead = get_bhead(fd);
+ }
+
+ if (new_bhead) {
+ bhead = &new_bhead->bhead;
+ }
+
+ return bhead;
+}
+
+BHead *blo_bhead_prev(FileData * /*fd*/, BHead *thisblock)
+{
+ BHeadN *bheadn = BHEADN_FROM_BHEAD(thisblock);
+ BHeadN *prev = bheadn->prev;
+
+ return (prev) ? &prev->bhead : nullptr;
+}
+
+BHead *blo_bhead_next(FileData *fd, BHead *thisblock)
+{
+ BHeadN *new_bhead = nullptr;
+ BHead *bhead = nullptr;
+
+ if (thisblock) {
+ /* bhead is actually a sub part of BHeadN
+ * We calculate the BHeadN pointer from the BHead pointer below */
+ new_bhead = BHEADN_FROM_BHEAD(thisblock);
+
+ /* get the next BHeadN. If it doesn't exist we read in the next one */
+ new_bhead = new_bhead->next;
+ if (new_bhead == nullptr) {
+ new_bhead = get_bhead(fd);
+ }
+ }
+
+ if (new_bhead) {
+ /* here we do the reverse:
+ * go from the BHeadN pointer to the BHead pointer */
+ bhead = &new_bhead->bhead;
+ }
+
+ return bhead;
+}
+
+#ifdef USE_BHEAD_READ_ON_DEMAND
+static bool blo_bhead_read_data(FileData *fd, BHead *thisblock, void *buf)
+{
+ bool success = true;
+ BHeadN *new_bhead = BHEADN_FROM_BHEAD(thisblock);
+ BLI_assert(new_bhead->has_data == false && new_bhead->file_offset != 0);
+ off64_t offset_backup = fd->file->offset;
+ if (UNLIKELY(fd->file->seek(fd->file, new_bhead->file_offset, SEEK_SET) == -1)) {
+ success = false;
+ }
+ else {
+ if (fd->file->read(fd->file, buf, size_t(new_bhead->bhead.len)) != new_bhead->bhead.len) {
+ success = false;
+ }
+ if (fd->flags & FD_FLAGS_IS_MEMFILE) {
+ new_bhead->is_memchunk_identical = ((UndoReader *)fd->file)->memchunk_identical;
+ }
+ }
+ if (fd->file->seek(fd->file, offset_backup, SEEK_SET) == -1) {
+ success = false;
+ }
+ return success;
+}
+
+static BHead *blo_bhead_read_full(FileData *fd, BHead *thisblock)
+{
+ BHeadN *new_bhead = BHEADN_FROM_BHEAD(thisblock);
+ BHeadN *new_bhead_data = static_cast<BHeadN *>(
+ MEM_mallocN(sizeof(BHeadN) + new_bhead->bhead.len, "new_bhead"));
+ new_bhead_data->bhead = new_bhead->bhead;
+ new_bhead_data->file_offset = new_bhead->file_offset;
+ new_bhead_data->has_data = true;
+ new_bhead_data->is_memchunk_identical = false;
+ if (!blo_bhead_read_data(fd, thisblock, new_bhead_data + 1)) {
+ MEM_freeN(new_bhead_data);
+ return nullptr;
+ }
+ return &new_bhead_data->bhead;
+}
+#endif /* USE_BHEAD_READ_ON_DEMAND */
+
+const char *blo_bhead_id_name(const FileData *fd, const BHead *bhead)
+{
+ return (const char *)POINTER_OFFSET(bhead, sizeof(*bhead) + fd->id_name_offset);
+}
+
+AssetMetaData *blo_bhead_id_asset_data_address(const FileData *fd, const BHead *bhead)
+{
+ BLI_assert(blo_bhead_is_id_valid_type(bhead));
+ return (fd->id_asset_data_offset >= 0) ?
+ *(AssetMetaData **)POINTER_OFFSET(bhead, sizeof(*bhead) + fd->id_asset_data_offset) :
+ nullptr;
+}
+
+static void decode_blender_header(FileData *fd)
+{
+ char header[SIZEOFBLENDERHEADER], num[4];
+ ssize_t readsize;
+
+ /* read in the header data */
+ readsize = fd->file->read(fd->file, header, sizeof(header));
+
+ if (readsize == sizeof(header) && STREQLEN(header, "BLENDER", 7) && ELEM(header[7], '_', '-') &&
+ ELEM(header[8], 'v', 'V') &&
+ (isdigit(header[9]) && isdigit(header[10]) && isdigit(header[11]))) {
+ fd->flags |= FD_FLAGS_FILE_OK;
+
+ /* what size are pointers in the file ? */
+ if (header[7] == '_') {
+ fd->flags |= FD_FLAGS_FILE_POINTSIZE_IS_4;
+ if (sizeof(void *) != 4) {
+ fd->flags |= FD_FLAGS_POINTSIZE_DIFFERS;
+ }
+ }
+ else {
+ if (sizeof(void *) != 8) {
+ fd->flags |= FD_FLAGS_POINTSIZE_DIFFERS;
+ }
+ }
+
+ /* is the file saved in a different endian
+ * than we need ?
+ */
+ if (((header[8] == 'v') ? L_ENDIAN : B_ENDIAN) != ENDIAN_ORDER) {
+ fd->flags |= FD_FLAGS_SWITCH_ENDIAN;
+ }
+
+ /* get the version number */
+ memcpy(num, header + 9, 3);
+ num[3] = 0;
+ fd->fileversion = atoi(num);
+ }
+}
+
+/**
+ * \return Success if the file is read correctly, else set \a r_error_message.
+ */
+static bool read_file_dna(FileData *fd, const char **r_error_message)
+{
+ BHead *bhead;
+ int subversion = 0;
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ if (bhead->code == GLOB) {
+ /* Before this, the subversion didn't exist in 'FileGlobal' so the subversion
+ * value isn't accessible for the purpose of DNA versioning in this case. */
+ if (fd->fileversion <= 242) {
+ continue;
+ }
+ /* We can't use read_global because this needs 'DNA1' to be decoded,
+ * however the first 4 chars are _always_ the subversion. */
+ FileGlobal *fg = reinterpret_cast<FileGlobal *>(&bhead[1]);
+ BLI_STATIC_ASSERT(offsetof(FileGlobal, subvstr) == 0, "Must be first: subvstr")
+ char num[5];
+ memcpy(num, fg->subvstr, 4);
+ num[4] = 0;
+ subversion = atoi(num);
+ }
+ else if (bhead->code == DNA1) {
+ const bool do_endian_swap = (fd->flags & FD_FLAGS_SWITCH_ENDIAN) != 0;
+
+ fd->filesdna = DNA_sdna_from_data(
+ &bhead[1], bhead->len, do_endian_swap, true, r_error_message);
+ if (fd->filesdna) {
+ blo_do_versions_dna(fd->filesdna, fd->fileversion, subversion);
+ fd->compflags = DNA_struct_get_compareflags(fd->filesdna, fd->memsdna);
+ fd->reconstruct_info = DNA_reconstruct_info_create(
+ fd->filesdna, fd->memsdna, fd->compflags);
+ /* used to retrieve ID names from (bhead+1) */
+ fd->id_name_offset = DNA_elem_offset(fd->filesdna, "ID", "char", "name[]");
+ BLI_assert(fd->id_name_offset != -1);
+ fd->id_asset_data_offset = DNA_elem_offset(
+ fd->filesdna, "ID", "AssetMetaData", "*asset_data");
+
+ return true;
+ }
+
+ return false;
+ }
+ else if (bhead->code == ENDB) {
+ break;
+ }
+ }
+
+ *r_error_message = "Missing DNA block";
+ return false;
+}
+
+static int *read_file_thumbnail(FileData *fd)
+{
+ BHead *bhead;
+ int *blend_thumb = nullptr;
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ if (bhead->code == TEST) {
+ const bool do_endian_swap = (fd->flags & FD_FLAGS_SWITCH_ENDIAN) != 0;
+ int *data = (int *)(bhead + 1);
+
+ if (bhead->len < sizeof(int[2])) {
+ break;
+ }
+
+ if (do_endian_swap) {
+ BLI_endian_switch_int32(&data[0]);
+ BLI_endian_switch_int32(&data[1]);
+ }
+
+ const int width = data[0];
+ const int height = data[1];
+ if (!BLEN_THUMB_MEMSIZE_IS_VALID(width, height)) {
+ break;
+ }
+ if (bhead->len < BLEN_THUMB_MEMSIZE_FILE(width, height)) {
+ break;
+ }
+
+ blend_thumb = data;
+ break;
+ }
+ if (bhead->code != REND) {
+ /* Thumbnail is stored in TEST immediately after first REND... */
+ break;
+ }
+ }
+
+ return blend_thumb;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name File Data API
+ * \{ */
+
+static FileData *filedata_new(BlendFileReadReport *reports)
+{
+ BLI_assert(reports != nullptr);
+
+ FileData *fd = static_cast<FileData *>(MEM_callocN(sizeof(FileData), "FileData"));
+
+ fd->memsdna = DNA_sdna_current_get();
+
+ fd->datamap = oldnewmap_new();
+ fd->globmap = oldnewmap_new();
+ fd->libmap = oldnewmap_new();
+
+ fd->reports = reports;
+
+ return fd;
+}
+
+static FileData *blo_decode_and_check(FileData *fd, ReportList *reports)
+{
+ decode_blender_header(fd);
+
+ if (fd->flags & FD_FLAGS_FILE_OK) {
+ const char *error_message = nullptr;
+ if (read_file_dna(fd, &error_message) == false) {
+ BKE_reportf(
+ reports, RPT_ERROR, "Failed to read blend file '%s': %s", fd->relabase, error_message);
+ blo_filedata_free(fd);
+ fd = nullptr;
+ }
+ }
+ else {
+ BKE_reportf(
+ reports, RPT_ERROR, "Failed to read blend file '%s', not a blend file", fd->relabase);
+ blo_filedata_free(fd);
+ fd = nullptr;
+ }
+
+ return fd;
+}
+
+static FileData *blo_filedata_from_file_descriptor(const char *filepath,
+ BlendFileReadReport *reports,
+ int filedes)
+{
+ char header[7];
+ FileReader *rawfile = BLI_filereader_new_file(filedes);
+ FileReader *file = nullptr;
+
+ errno = 0;
+ /* If opening the file failed or we can't read the header, give up. */
+ if (rawfile == nullptr || rawfile->read(rawfile, header, sizeof(header)) != sizeof(header)) {
+ BKE_reportf(reports->reports,
+ RPT_WARNING,
+ "Unable to read '%s': %s",
+ filepath,
+ errno ? strerror(errno) : TIP_("insufficient content"));
+ if (rawfile) {
+ rawfile->close(rawfile);
+ }
+ else {
+ close(filedes);
+ }
+ return nullptr;
+ }
+
+ /* Rewind the file after reading the header. */
+ rawfile->seek(rawfile, 0, SEEK_SET);
+
+ /* Check if we have a regular file. */
+ if (memcmp(header, "BLENDER", sizeof(header)) == 0) {
+ /* Try opening the file with memory-mapped IO. */
+ file = BLI_filereader_new_mmap(filedes);
+ if (file == nullptr) {
+ /* mmap failed, so just keep using rawfile. */
+ file = rawfile;
+ rawfile = nullptr;
+ }
+ }
+ else if (BLI_file_magic_is_gzip(header)) {
+ file = BLI_filereader_new_gzip(rawfile);
+ if (file != nullptr) {
+ rawfile = nullptr; /* The `Gzip` #FileReader takes ownership of `rawfile`. */
+ }
+ }
+ else if (BLI_file_magic_is_zstd(header)) {
+ file = BLI_filereader_new_zstd(rawfile);
+ if (file != nullptr) {
+ rawfile = nullptr; /* The `Zstd` #FileReader takes ownership of `rawfile`. */
+ }
+ }
+
+ /* Clean up `rawfile` if it wasn't taken over. */
+ if (rawfile != nullptr) {
+ rawfile->close(rawfile);
+ }
+ if (file == nullptr) {
+ BKE_reportf(reports->reports, RPT_WARNING, "Unrecognized file format '%s'", filepath);
+ return nullptr;
+ }
+
+ FileData *fd = filedata_new(reports);
+ fd->file = file;
+
+ return fd;
+}
+
+static FileData *blo_filedata_from_file_open(const char *filepath, BlendFileReadReport *reports)
+{
+ errno = 0;
+ const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
+ if (file == -1) {
+ BKE_reportf(reports->reports,
+ RPT_WARNING,
+ "Unable to open '%s': %s",
+ filepath,
+ errno ? strerror(errno) : TIP_("unknown error reading file"));
+ return nullptr;
+ }
+ return blo_filedata_from_file_descriptor(filepath, reports, file);
+}
+
+FileData *blo_filedata_from_file(const char *filepath, BlendFileReadReport *reports)
+{
+ FileData *fd = blo_filedata_from_file_open(filepath, reports);
+ if (fd != nullptr) {
+ /* needed for library_append and read_libraries */
+ BLI_strncpy(fd->relabase, filepath, sizeof(fd->relabase));
+
+ return blo_decode_and_check(fd, reports->reports);
+ }
+ return nullptr;
+}
+
+/**
+ * Same as blo_filedata_from_file(), but does not reads DNA data, only header.
+ * Use it for light access (e.g. thumbnail reading).
+ */
+static FileData *blo_filedata_from_file_minimal(const char *filepath)
+{
+ BlendFileReadReport read_report{};
+ FileData *fd = blo_filedata_from_file_open(filepath, &read_report);
+ if (fd != nullptr) {
+ decode_blender_header(fd);
+ if (fd->flags & FD_FLAGS_FILE_OK) {
+ return fd;
+ }
+ blo_filedata_free(fd);
+ }
+ return nullptr;
+}
+
+FileData *blo_filedata_from_memory(const void *mem, int memsize, BlendFileReadReport *reports)
+{
+ if (!mem || memsize < SIZEOFBLENDERHEADER) {
+ BKE_report(
+ reports->reports, RPT_WARNING, (mem) ? TIP_("Unable to read") : TIP_("Unable to open"));
+ return nullptr;
+ }
+
+ FileReader *mem_file = BLI_filereader_new_memory(mem, memsize);
+ FileReader *file = mem_file;
+
+ if (BLI_file_magic_is_gzip(static_cast<const char *>(mem))) {
+ file = BLI_filereader_new_gzip(mem_file);
+ }
+ else if (BLI_file_magic_is_zstd(static_cast<const char *>(mem))) {
+ file = BLI_filereader_new_zstd(mem_file);
+ }
+
+ if (file == nullptr) {
+ /* Compression initialization failed. */
+ mem_file->close(mem_file);
+ return nullptr;
+ }
+
+ FileData *fd = filedata_new(reports);
+ fd->file = file;
+
+ return blo_decode_and_check(fd, reports->reports);
+}
+
+FileData *blo_filedata_from_memfile(MemFile *memfile,
+ const BlendFileReadParams *params,
+ BlendFileReadReport *reports)
+{
+ if (!memfile) {
+ BKE_report(reports->reports, RPT_WARNING, "Unable to open blend <memory>");
+ return nullptr;
+ }
+
+ FileData *fd = filedata_new(reports);
+ fd->file = BLO_memfile_new_filereader(memfile, params->undo_direction);
+ fd->undo_direction = params->undo_direction;
+ fd->flags |= FD_FLAGS_IS_MEMFILE;
+
+ return blo_decode_and_check(fd, reports->reports);
+}
+
+void blo_filedata_free(FileData *fd)
+{
+ if (fd) {
+
+ /* Free all BHeadN data blocks */
+#ifndef NDEBUG
+ BLI_freelistN(&fd->bhead_list);
+#else
+ /* Sanity check we're not keeping memory we don't need. */
+ LISTBASE_FOREACH_MUTABLE (BHeadN *, new_bhead, &fd->bhead_list) {
+ if (fd->file->seek != nullptr && BHEAD_USE_READ_ON_DEMAND(&new_bhead->bhead)) {
+ BLI_assert(new_bhead->has_data == 0);
+ }
+ MEM_freeN(new_bhead);
+ }
+#endif
+ fd->file->close(fd->file);
+
+ if (fd->filesdna) {
+ DNA_sdna_free(fd->filesdna);
+ }
+ if (fd->compflags) {
+ MEM_freeN((void *)fd->compflags);
+ }
+ if (fd->reconstruct_info) {
+ DNA_reconstruct_info_free(fd->reconstruct_info);
+ }
+
+ if (fd->datamap) {
+ oldnewmap_free(fd->datamap);
+ }
+ if (fd->globmap) {
+ oldnewmap_free(fd->globmap);
+ }
+ if (fd->packedmap) {
+ oldnewmap_free(fd->packedmap);
+ }
+ if (fd->libmap && !(fd->flags & FD_FLAGS_NOT_MY_LIBMAP)) {
+ oldnewmap_free(fd->libmap);
+ }
+ if (fd->old_idmap != nullptr) {
+ BKE_main_idmap_destroy(fd->old_idmap);
+ }
+ blo_cache_storage_end(fd);
+ if (fd->bheadmap) {
+ MEM_freeN(fd->bheadmap);
+ }
+
+#ifdef USE_GHASH_BHEAD
+ if (fd->bhead_idname_hash) {
+ BLI_ghash_free(fd->bhead_idname_hash, nullptr, nullptr);
+ }
+#endif
+
+ MEM_freeN(fd);
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public Utilities
+ * \{ */
+
+bool BLO_has_bfile_extension(const char *str)
+{
+ const char *ext_test[4] = {".blend", ".ble", ".blend.gz", nullptr};
+ return BLI_path_extension_check_array(str, ext_test);
+}
+
+bool BLO_library_path_explode(const char *path, char *r_dir, char **r_group, char **r_name)
+{
+ /* We might get some data names with slashes,
+ * so we have to go up in path until we find blend file itself,
+ * then we know next path item is group, and everything else is data name. */
+ char *slash = nullptr, *prev_slash = nullptr, c = '\0';
+
+ r_dir[0] = '\0';
+ if (r_group) {
+ *r_group = nullptr;
+ }
+ if (r_name) {
+ *r_name = nullptr;
+ }
+
+ /* if path leads to an existing directory, we can be sure we're not (in) a library */
+ if (BLI_is_dir(path)) {
+ return false;
+ }
+
+ strcpy(r_dir, path);
+
+ while ((slash = (char *)BLI_path_slash_rfind(r_dir))) {
+ char tc = *slash;
+ *slash = '\0';
+ if (BLO_has_bfile_extension(r_dir) && BLI_is_file(r_dir)) {
+ break;
+ }
+ if (STREQ(r_dir, BLO_EMBEDDED_STARTUP_BLEND)) {
+ break;
+ }
+
+ if (prev_slash) {
+ *prev_slash = c;
+ }
+ prev_slash = slash;
+ c = tc;
+ }
+
+ if (!slash) {
+ return false;
+ }
+
+ if (slash[1] != '\0') {
+ BLI_assert(strlen(slash + 1) < BLO_GROUP_MAX);
+ if (r_group) {
+ *r_group = slash + 1;
+ }
+ }
+
+ if (prev_slash && (prev_slash[1] != '\0')) {
+ BLI_assert(strlen(prev_slash + 1) < MAX_ID_NAME - 2);
+ if (r_name) {
+ *r_name = prev_slash + 1;
+ }
+ }
+
+ return true;
+}
+
+BlendThumbnail *BLO_thumbnail_from_file(const char *filepath)
+{
+ FileData *fd;
+ BlendThumbnail *data = nullptr;
+ int *fd_data;
+
+ fd = blo_filedata_from_file_minimal(filepath);
+ fd_data = fd ? read_file_thumbnail(fd) : nullptr;
+
+ if (fd_data) {
+ const int width = fd_data[0];
+ const int height = fd_data[1];
+ if (BLEN_THUMB_MEMSIZE_IS_VALID(width, height)) {
+ const size_t data_size = BLEN_THUMB_MEMSIZE(width, height);
+ data = static_cast<BlendThumbnail *>(MEM_mallocN(data_size, __func__));
+ if (data) {
+ BLI_assert((data_size - sizeof(*data)) ==
+ (BLEN_THUMB_MEMSIZE_FILE(width, height) - (sizeof(*fd_data) * 2)));
+ data->width = width;
+ data->height = height;
+ memcpy(data->rect, &fd_data[2], data_size - sizeof(*data));
+ }
+ }
+ }
+
+ blo_filedata_free(fd);
+
+ return data;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Old/New Pointer Map
+ * \{ */
+
+/* Only direct data-blocks. */
+static void *newdataadr(FileData *fd, const void *adr)
+{
+ return oldnewmap_lookup_and_inc(fd->datamap, adr, true);
+}
+
+/* Only direct data-blocks. */
+static void *newdataadr_no_us(FileData *fd, const void *adr)
+{
+ return oldnewmap_lookup_and_inc(fd->datamap, adr, false);
+}
+
+void *blo_read_get_new_globaldata_address(FileData *fd, const void *adr)
+{
+ return oldnewmap_lookup_and_inc(fd->globmap, adr, true);
+}
+
+/* Used to restore packed data after undo. */
+static void *newpackedadr(FileData *fd, const void *adr)
+{
+ if (fd->packedmap && adr) {
+ return oldnewmap_lookup_and_inc(fd->packedmap, adr, true);
+ }
+
+ return oldnewmap_lookup_and_inc(fd->datamap, adr, true);
+}
+
+/* only lib data */
+static void *newlibadr(FileData *fd, const void *lib, const void *adr)
+{
+ return oldnewmap_liblookup(fd->libmap, adr, lib);
+}
+
+void *blo_do_versions_newlibadr(FileData *fd, const void *lib, const void *adr)
+{
+ return newlibadr(fd, lib, adr);
+}
+
+/* increases user number */
+static void change_link_placeholder_to_real_ID_pointer_fd(FileData *fd,
+ const void *old,
+ void *newp)
+{
+ for (int i = 0; i < fd->libmap->nentries; i++) {
+ OldNew *entry = &fd->libmap->entries[i];
+
+ if (old == entry->newp && entry->nr == ID_LINK_PLACEHOLDER) {
+ entry->newp = newp;
+ if (newp) {
+ entry->nr = GS(((ID *)newp)->name);
+ }
+ }
+ }
+}
+
+static void change_link_placeholder_to_real_ID_pointer(ListBase *mainlist,
+ FileData *basefd,
+ void *old,
+ void *newp)
+{
+ LISTBASE_FOREACH (Main *, mainptr, mainlist) {
+ FileData *fd;
+
+ if (mainptr->curlib) {
+ fd = mainptr->curlib->filedata;
+ }
+ else {
+ fd = basefd;
+ }
+
+ if (fd) {
+ change_link_placeholder_to_real_ID_pointer_fd(fd, old, newp);
+ }
+ }
+}
+
+/* XXX disabled this feature - packed files also belong in temp saves and quit.blend,
+ * to make restore work. */
+
+static void insert_packedmap(FileData *fd, PackedFile *pf)
+{
+ oldnewmap_insert(fd->packedmap, pf, pf, 0);
+ oldnewmap_insert(fd->packedmap, pf->data, pf->data, 0);
+}
+
+void blo_make_packed_pointer_map(FileData *fd, Main *oldmain)
+{
+ fd->packedmap = oldnewmap_new();
+
+ LISTBASE_FOREACH (Image *, ima, &oldmain->images) {
+ if (ima->packedfile) {
+ insert_packedmap(fd, ima->packedfile);
+ }
+
+ LISTBASE_FOREACH (ImagePackedFile *, imapf, &ima->packedfiles) {
+ if (imapf->packedfile) {
+ insert_packedmap(fd, imapf->packedfile);
+ }
+ }
+ }
+
+ LISTBASE_FOREACH (VFont *, vfont, &oldmain->fonts) {
+ if (vfont->packedfile) {
+ insert_packedmap(fd, vfont->packedfile);
+ }
+ }
+
+ LISTBASE_FOREACH (bSound *, sound, &oldmain->sounds) {
+ if (sound->packedfile) {
+ insert_packedmap(fd, sound->packedfile);
+ }
+ }
+
+ LISTBASE_FOREACH (Volume *, volume, &oldmain->volumes) {
+ if (volume->packedfile) {
+ insert_packedmap(fd, volume->packedfile);
+ }
+ }
+
+ LISTBASE_FOREACH (Library *, lib, &oldmain->libraries) {
+ if (lib->packedfile) {
+ insert_packedmap(fd, lib->packedfile);
+ }
+ }
+}
+
+void blo_end_packed_pointer_map(FileData *fd, Main *oldmain)
+{
+ OldNew *entry = fd->packedmap->entries;
+
+ /* used entries were restored, so we put them to zero */
+ for (int i = 0; i < fd->packedmap->nentries; i++, entry++) {
+ if (entry->nr > 0) {
+ entry->newp = nullptr;
+ }
+ }
+
+ LISTBASE_FOREACH (Image *, ima, &oldmain->images) {
+ ima->packedfile = static_cast<PackedFile *>(newpackedadr(fd, ima->packedfile));
+
+ LISTBASE_FOREACH (ImagePackedFile *, imapf, &ima->packedfiles) {
+ imapf->packedfile = static_cast<PackedFile *>(newpackedadr(fd, imapf->packedfile));
+ }
+ }
+
+ LISTBASE_FOREACH (VFont *, vfont, &oldmain->fonts) {
+ vfont->packedfile = static_cast<PackedFile *>(newpackedadr(fd, vfont->packedfile));
+ }
+
+ LISTBASE_FOREACH (bSound *, sound, &oldmain->sounds) {
+ sound->packedfile = static_cast<PackedFile *>(newpackedadr(fd, sound->packedfile));
+ }
+
+ LISTBASE_FOREACH (Library *, lib, &oldmain->libraries) {
+ lib->packedfile = static_cast<PackedFile *>(newpackedadr(fd, lib->packedfile));
+ }
+
+ LISTBASE_FOREACH (Volume *, volume, &oldmain->volumes) {
+ volume->packedfile = static_cast<PackedFile *>(newpackedadr(fd, volume->packedfile));
+ }
+}
+
+void blo_add_library_pointer_map(ListBase *old_mainlist, FileData *fd)
+{
+ ListBase *lbarray[INDEX_ID_MAX];
+
+ LISTBASE_FOREACH (Main *, ptr, old_mainlist) {
+ int i = set_listbasepointers(ptr, lbarray);
+ while (i--) {
+ LISTBASE_FOREACH (ID *, id, lbarray[i]) {
+ oldnewmap_lib_insert(fd, id, id, GS(id->name));
+ }
+ }
+ }
+
+ fd->old_mainlist = old_mainlist;
+}
+
+void blo_make_old_idmap_from_main(FileData *fd, Main *bmain)
+{
+ if (fd->old_idmap != nullptr) {
+ BKE_main_idmap_destroy(fd->old_idmap);
+ }
+ fd->old_idmap = BKE_main_idmap_create(bmain, false, nullptr, MAIN_IDMAP_TYPE_UUID);
+}
+
+struct BLOCacheStorage {
+ GHash *cache_map;
+ MemArena *memarena;
+};
+
+struct BLOCacheStorageValue {
+ void *cache_v;
+ uint new_usage_count;
+};
+
+/** Register a cache data entry to be preserved when reading some undo memfile. */
+static void blo_cache_storage_entry_register(
+ ID *id, const IDCacheKey *key, void **cache_p, uint /*flags*/, void *cache_storage_v)
+{
+ BLI_assert(key->id_session_uuid == id->session_uuid);
+ UNUSED_VARS_NDEBUG(id);
+
+ BLOCacheStorage *cache_storage = static_cast<BLOCacheStorage *>(cache_storage_v);
+ BLI_assert(!BLI_ghash_haskey(cache_storage->cache_map, key));
+
+ IDCacheKey *storage_key = static_cast<IDCacheKey *>(
+ BLI_memarena_alloc(cache_storage->memarena, sizeof(*storage_key)));
+ *storage_key = *key;
+ BLOCacheStorageValue *storage_value = static_cast<BLOCacheStorageValue *>(
+ BLI_memarena_alloc(cache_storage->memarena, sizeof(*storage_value)));
+ storage_value->cache_v = *cache_p;
+ storage_value->new_usage_count = 0;
+ BLI_ghash_insert(cache_storage->cache_map, storage_key, storage_value);
+}
+
+/** Restore a cache data entry from old ID into new one, when reading some undo memfile. */
+static void blo_cache_storage_entry_restore_in_new(
+ ID * /*id*/, const IDCacheKey *key, void **cache_p, uint flags, void *cache_storage_v)
+{
+ BLOCacheStorage *cache_storage = static_cast<BLOCacheStorage *>(cache_storage_v);
+
+ if (cache_storage == nullptr) {
+ /* In non-undo case, only clear the pointer if it is a purely runtime one.
+ * If it may be stored in a persistent way in the .blend file, direct_link code is responsible
+ * to properly deal with it. */
+ if ((flags & IDTYPE_CACHE_CB_FLAGS_PERSISTENT) == 0) {
+ *cache_p = nullptr;
+ }
+ return;
+ }
+
+ BLOCacheStorageValue *storage_value = static_cast<BLOCacheStorageValue *>(
+ BLI_ghash_lookup(cache_storage->cache_map, key));
+ if (storage_value == nullptr) {
+ *cache_p = nullptr;
+ return;
+ }
+ storage_value->new_usage_count++;
+ *cache_p = storage_value->cache_v;
+}
+
+/** Clear as needed a cache data entry from old ID, when reading some undo memfile. */
+static void blo_cache_storage_entry_clear_in_old(
+ ID * /*id*/, const IDCacheKey *key, void **cache_p, uint /*flags*/, void *cache_storage_v)
+{
+ BLOCacheStorage *cache_storage = static_cast<BLOCacheStorage *>(cache_storage_v);
+
+ BLOCacheStorageValue *storage_value = static_cast<BLOCacheStorageValue *>(
+ BLI_ghash_lookup(cache_storage->cache_map, key));
+ if (storage_value == nullptr) {
+ *cache_p = nullptr;
+ return;
+ }
+ /* If that cache has been restored into some new ID, we want to remove it from old one, otherwise
+ * keep it there so that it gets properly freed together with its ID. */
+ if (storage_value->new_usage_count != 0) {
+ *cache_p = nullptr;
+ }
+ else {
+ BLI_assert(*cache_p == storage_value->cache_v);
+ }
+}
+
+void blo_cache_storage_init(FileData *fd, Main *bmain)
+{
+ if (fd->flags & FD_FLAGS_IS_MEMFILE) {
+ BLI_assert(fd->cache_storage == nullptr);
+ fd->cache_storage = static_cast<BLOCacheStorage *>(
+ MEM_mallocN(sizeof(*fd->cache_storage), __func__));
+ fd->cache_storage->memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
+ fd->cache_storage->cache_map = BLI_ghash_new(
+ BKE_idtype_cache_key_hash, BKE_idtype_cache_key_cmp, __func__);
+
+ ListBase *lb;
+ FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
+ ID *id = static_cast<ID *>(lb->first);
+ if (id == nullptr) {
+ continue;
+ }
+
+ const IDTypeInfo *type_info = BKE_idtype_get_info_from_id(id);
+ if (type_info->foreach_cache == nullptr) {
+ continue;
+ }
+
+ FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
+ if (ID_IS_LINKED(id)) {
+ continue;
+ }
+ BKE_idtype_id_foreach_cache(id, blo_cache_storage_entry_register, fd->cache_storage);
+ }
+ FOREACH_MAIN_LISTBASE_ID_END;
+ }
+ FOREACH_MAIN_LISTBASE_END;
+ }
+ else {
+ fd->cache_storage = nullptr;
+ }
+}
+
+void blo_cache_storage_old_bmain_clear(FileData *fd, Main *bmain_old)
+{
+ if (fd->cache_storage != nullptr) {
+ ListBase *lb;
+ FOREACH_MAIN_LISTBASE_BEGIN (bmain_old, lb) {
+ ID *id = static_cast<ID *>(lb->first);
+ if (id == nullptr) {
+ continue;
+ }
+
+ const IDTypeInfo *type_info = BKE_idtype_get_info_from_id(id);
+ if (type_info->foreach_cache == nullptr) {
+ continue;
+ }
+
+ FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
+ if (ID_IS_LINKED(id)) {
+ continue;
+ }
+ BKE_idtype_id_foreach_cache(id, blo_cache_storage_entry_clear_in_old, fd->cache_storage);
+ }
+ FOREACH_MAIN_LISTBASE_ID_END;
+ }
+ FOREACH_MAIN_LISTBASE_END;
+ }
+}
+
+void blo_cache_storage_end(FileData *fd)
+{
+ if (fd->cache_storage != nullptr) {
+ BLI_ghash_free(fd->cache_storage->cache_map, nullptr, nullptr);
+ BLI_memarena_free(fd->cache_storage->memarena);
+ MEM_freeN(fd->cache_storage);
+ fd->cache_storage = nullptr;
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name DNA Struct Loading
+ * \{ */
+
+static void switch_endian_structs(const SDNA *filesdna, BHead *bhead)
+{
+ int blocksize, nblocks;
+ char *data;
+
+ data = (char *)(bhead + 1);
+ blocksize = filesdna->types_size[filesdna->structs[bhead->SDNAnr]->type];
+
+ nblocks = bhead->nr;
+ while (nblocks--) {
+ DNA_struct_switch_endian(filesdna, bhead->SDNAnr, data);
+
+ data += blocksize;
+ }
+}
+
+static void *read_struct(FileData *fd, BHead *bh, const char *blockname)
+{
+ void *temp = nullptr;
+
+ if (bh->len) {
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ BHead *bh_orig = bh;
+#endif
+
+ /* switch is based on file dna */
+ if (bh->SDNAnr && (fd->flags & FD_FLAGS_SWITCH_ENDIAN)) {
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ if (BHEADN_FROM_BHEAD(bh)->has_data == false) {
+ bh = blo_bhead_read_full(fd, bh);
+ if (UNLIKELY(bh == nullptr)) {
+ fd->flags &= ~FD_FLAGS_FILE_OK;
+ return nullptr;
+ }
+ }
+#endif
+ switch_endian_structs(fd->filesdna, bh);
+ }
+
+ if (fd->compflags[bh->SDNAnr] != SDNA_CMP_REMOVED) {
+ if (fd->compflags[bh->SDNAnr] == SDNA_CMP_NOT_EQUAL) {
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ if (BHEADN_FROM_BHEAD(bh)->has_data == false) {
+ bh = blo_bhead_read_full(fd, bh);
+ if (UNLIKELY(bh == nullptr)) {
+ fd->flags &= ~FD_FLAGS_FILE_OK;
+ return nullptr;
+ }
+ }
+#endif
+ temp = DNA_struct_reconstruct(fd->reconstruct_info, bh->SDNAnr, bh->nr, (bh + 1));
+ }
+ else {
+ /* SDNA_CMP_EQUAL */
+ temp = MEM_mallocN(bh->len, blockname);
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ if (BHEADN_FROM_BHEAD(bh)->has_data) {
+ memcpy(temp, (bh + 1), bh->len);
+ }
+ else {
+ /* Instead of allocating the bhead, then copying it,
+ * read the data from the file directly into the memory. */
+ if (UNLIKELY(!blo_bhead_read_data(fd, bh, temp))) {
+ fd->flags &= ~FD_FLAGS_FILE_OK;
+ MEM_freeN(temp);
+ temp = nullptr;
+ }
+ }
+#else
+ memcpy(temp, (bh + 1), bh->len);
+#endif
+ }
+ }
+
+#ifdef USE_BHEAD_READ_ON_DEMAND
+ if (bh_orig != bh) {
+ MEM_freeN(BHEADN_FROM_BHEAD(bh));
+ }
+#endif
+ }
+
+ return temp;
+}
+
+/* Like read_struct, but gets a pointer without allocating. Only works for
+ * undo since DNA must match. */
+static const void *peek_struct_undo(FileData *fd, BHead *bhead)
+{
+ BLI_assert(fd->flags & FD_FLAGS_IS_MEMFILE);
+ UNUSED_VARS_NDEBUG(fd);
+ return (bhead->len) ? (const void *)(bhead + 1) : nullptr;
+}
+
+static void link_glob_list(FileData *fd, ListBase *lb) /* for glob data */
+{
+ Link *ln, *prev;
+ void *poin;
+
+ if (BLI_listbase_is_empty(lb)) {
+ return;
+ }
+ poin = newdataadr(fd, lb->first);
+ if (lb->first) {
+ oldnewmap_insert(fd->globmap, lb->first, poin, 0);
+ }
+ lb->first = poin;
+
+ ln = static_cast<Link *>(lb->first);
+ prev = nullptr;
+ while (ln) {
+ poin = newdataadr(fd, ln->next);
+ if (ln->next) {
+ oldnewmap_insert(fd->globmap, ln->next, poin, 0);
+ }
+ ln->next = static_cast<Link *>(poin);
+ ln->prev = prev;
+ prev = ln;
+ ln = ln->next;
+ }
+ lb->last = prev;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read ID
+ * \{ */
+
+static void lib_link_id(BlendLibReader *reader, ID *id);
+
+static void lib_link_id_embedded_id(BlendLibReader *reader, ID *id)
+{
+
+ /* Handle 'private IDs'. */
+ bNodeTree *nodetree = ntreeFromID(id);
+ if (nodetree != nullptr) {
+ lib_link_id(reader, &nodetree->id);
+ ntreeBlendReadLib(reader, nodetree);
+ }
+
+ if (GS(id->name) == ID_SCE) {
+ Scene *scene = (Scene *)id;
+ if (scene->master_collection != nullptr) {
+ lib_link_id(reader, &scene->master_collection->id);
+ BKE_collection_blend_read_lib(reader, scene->master_collection);
+ }
+ }
+}
+
+static void lib_link_id(BlendLibReader *reader, ID *id)
+{
+ /* NOTE: WM IDProperties are never written to file, hence they should always be nullptr here. */
+ BLI_assert((GS(id->name) != ID_WM) || id->properties == nullptr);
+ IDP_BlendReadLib(reader, id->lib, id->properties);
+
+ AnimData *adt = BKE_animdata_from_id(id);
+ if (adt != nullptr) {
+ BKE_animdata_blend_read_lib(reader, id, adt);
+ }
+
+ if (id->override_library) {
+ BLO_read_id_address(reader, id->lib, &id->override_library->reference);
+ BLO_read_id_address(reader, id->lib, &id->override_library->storage);
+ BLO_read_id_address(reader, id->lib, &id->override_library->hierarchy_root);
+ }
+
+ lib_link_id_embedded_id(reader, id);
+}
+
+static void direct_link_id_override_property_operation_cb(BlendDataReader *reader, void *data)
+{
+ IDOverrideLibraryPropertyOperation *opop = static_cast<IDOverrideLibraryPropertyOperation *>(
+ data);
+
+ BLO_read_data_address(reader, &opop->subitem_reference_name);
+ BLO_read_data_address(reader, &opop->subitem_local_name);
+
+ opop->tag = 0; /* Runtime only. */
+}
+
+static void direct_link_id_override_property_cb(BlendDataReader *reader, void *data)
+{
+ IDOverrideLibraryProperty *op = static_cast<IDOverrideLibraryProperty *>(data);
+
+ BLO_read_data_address(reader, &op->rna_path);
+
+ op->tag = 0; /* Runtime only. */
+
+ BLO_read_list_cb(reader, &op->operations, direct_link_id_override_property_operation_cb);
+}
+
+static void direct_link_id_common(
+ BlendDataReader *reader, Library *current_library, ID *id, ID *id_old, const int tag);
+
+static void direct_link_id_embedded_id(BlendDataReader *reader,
+ Library *current_library,
+ ID *id,
+ ID *id_old)
+{
+ /* Handle 'private IDs'. */
+ bNodeTree **nodetree = BKE_ntree_ptr_from_id(id);
+ if (nodetree != nullptr && *nodetree != nullptr) {
+ BLO_read_data_address(reader, nodetree);
+ direct_link_id_common(reader,
+ current_library,
+ (ID *)*nodetree,
+ id_old != nullptr ? (ID *)ntreeFromID(id_old) : nullptr,
+ 0);
+ ntreeBlendReadData(reader, id, *nodetree);
+ }
+
+ if (GS(id->name) == ID_SCE) {
+ Scene *scene = (Scene *)id;
+ if (scene->master_collection != nullptr) {
+ BLO_read_data_address(reader, &scene->master_collection);
+ direct_link_id_common(reader,
+ current_library,
+ &scene->master_collection->id,
+ id_old != nullptr ? &((Scene *)id_old)->master_collection->id :
+ nullptr,
+ 0);
+ BKE_collection_blend_read_data(reader, scene->master_collection, &scene->id);
+ }
+ }
+}
+
+static int direct_link_id_restore_recalc_exceptions(const ID *id_current)
+{
+ /* Exception for armature objects, where the pose has direct points to the
+ * armature data-block. */
+ if (GS(id_current->name) == ID_OB && ((Object *)id_current)->pose) {
+ return ID_RECALC_GEOMETRY;
+ }
+
+ return 0;
+}
+
+static int direct_link_id_restore_recalc(const FileData *fd,
+ const ID *id_target,
+ const ID *id_current,
+ const bool is_identical)
+{
+ /* These are the evaluations that had not been performed yet at the time the
+ * target undo state was written. These need to be done again, since they may
+ * flush back changes to the original datablock. */
+ int recalc = id_target->recalc;
+
+ if (id_current == nullptr) {
+ /* ID does not currently exist in the database, so also will not exist in
+ * the dependency graphs. That means it will be newly created and as a
+ * result also fully re-evaluated regardless of the recalc flag set here. */
+ recalc |= ID_RECALC_ALL;
+ }
+ else {
+ /* If the contents datablock changed, the depsgraph needs to copy the
+ * datablock again to ensure it matches the original datablock. */
+ if (!is_identical) {
+ recalc |= ID_RECALC_COPY_ON_WRITE;
+ }
+
+ /* Special exceptions. */
+ recalc |= direct_link_id_restore_recalc_exceptions(id_current);
+
+ /* Evaluations for the current state that have not been performed yet
+ * by the time we are performing this undo step. */
+ recalc |= id_current->recalc;
+
+ /* Tags that were set between the target state and the current state,
+ * that we need to perform again. */
+ if (fd->undo_direction == STEP_UNDO) {
+ /* Undo: tags from target to the current state. */
+ recalc |= id_current->recalc_up_to_undo_push;
+ }
+ else {
+ BLI_assert(fd->undo_direction == STEP_REDO);
+ /* Redo: tags from current to the target state. */
+ recalc |= id_target->recalc_up_to_undo_push;
+ }
+ }
+
+ return recalc;
+}
+
+static void direct_link_id_common(
+ BlendDataReader *reader, Library *current_library, ID *id, ID *id_old, const int tag)
+{
+ if (!BLO_read_data_is_undo(reader)) {
+ /* When actually reading a file, we do want to reset/re-generate session UUIDS.
+ * In undo case, we want to re-use existing ones. */
+ id->session_uuid = MAIN_ID_SESSION_UUID_UNSET;
+ }
+
+ if ((tag & LIB_TAG_TEMP_MAIN) == 0) {
+ BKE_lib_libblock_session_uuid_ensure(id);
+ }
+
+ id->lib = current_library;
+ id->us = ID_FAKE_USERS(id);
+ id->icon_id = 0;
+ id->newid = nullptr; /* Needed because .blend may have been saved with crap value here... */
+ id->orig_id = nullptr;
+ id->py_instance = nullptr;
+
+ /* Initialize with provided tag. */
+ id->tag = tag;
+
+ if (ID_IS_LINKED(id)) {
+ id->library_weak_reference = nullptr;
+ }
+ else {
+ BLO_read_data_address(reader, &id->library_weak_reference);
+ }
+
+ if (tag & LIB_TAG_ID_LINK_PLACEHOLDER) {
+ /* For placeholder we only need to set the tag and properly initialize generic ID fields above,
+ * no further data to read. */
+ return;
+ }
+
+ if (id->asset_data) {
+ BLO_read_data_address(reader, &id->asset_data);
+ BKE_asset_metadata_read(reader, id->asset_data);
+ /* Restore runtime asset type info. */
+ const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
+ id->asset_data->local_type_info = id_type->asset_type_info;
+ }
+
+ /* Link direct data of ID properties. */
+ if (id->properties) {
+ BLO_read_data_address(reader, &id->properties);
+ /* this case means the data was written incorrectly, it should not happen */
+ IDP_BlendDataRead(reader, &id->properties);
+ }
+
+ id->flag &= ~LIB_INDIRECT_WEAK_LINK;
+
+ /* NOTE: It is important to not clear the recalc flags for undo/redo.
+ * Preserving recalc flags on redo/undo is the only way to make dependency graph detect
+ * that animation is to be evaluated on undo/redo. If this is not enforced by the recalc
+ * flags dependency graph does not do animation update to avoid loss of unkeyed changes.,
+ * which conflicts with undo/redo of changes to animation data itself.
+ *
+ * But for regular file load we clear the flag, since the flags might have been changed since
+ * the version the file has been saved with. */
+ if (!BLO_read_data_is_undo(reader)) {
+ id->recalc = 0;
+ id->recalc_after_undo_push = 0;
+ }
+ else if ((reader->fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0) {
+ id->recalc = direct_link_id_restore_recalc(reader->fd, id, id_old, false);
+ id->recalc_after_undo_push = 0;
+ }
+
+ /* Link direct data of overrides. */
+ if (id->override_library) {
+ BLO_read_data_address(reader, &id->override_library);
+ /* Work around file corruption on writing, see T86853. */
+ if (id->override_library != nullptr) {
+ BLO_read_list_cb(
+ reader, &id->override_library->properties, direct_link_id_override_property_cb);
+ id->override_library->runtime = nullptr;
+ }
+ }
+
+ DrawDataList *drawdata = DRW_drawdatalist_from_id(id);
+ if (drawdata) {
+ BLI_listbase_clear((ListBase *)drawdata);
+ }
+
+ /* Handle 'private IDs'. */
+ direct_link_id_embedded_id(reader, current_library, id, id_old);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read Animation (legacy for version patching)
+ * \{ */
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read ID: Shape Keys
+ * \{ */
+
+void blo_do_versions_key_uidgen(Key *key)
+{
+ key->uidgen = 1;
+ LISTBASE_FOREACH (KeyBlock *, block, &key->block) {
+ block->uid = key->uidgen++;
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read ID: Scene
+ * \{ */
+
+#ifdef USE_SETSCENE_CHECK
+/**
+ * A version of #BKE_scene_validate_setscene with special checks for linked libraries.
+ */
+static bool scene_validate_setscene__liblink(Scene *sce, const int totscene)
+{
+ Scene *sce_iter;
+ int a;
+
+ if (sce->set == nullptr) {
+ return true;
+ }
+
+ for (a = 0, sce_iter = sce; sce_iter->set; sce_iter = sce_iter->set, a++) {
+ /* This runs per library (before each libraries #Main has been joined),
+ * so we can't step into other libraries since `totscene` is only for this library.
+ *
+ * Also, other libraries may not have been linked yet,
+ * while we could check #LIB_TAG_NEED_LINK the library pointer check is sufficient. */
+ if (sce->id.lib != sce_iter->id.lib) {
+ return true;
+ }
+ if (sce_iter->flag & SCE_READFILE_LIBLINK_NEED_SETSCENE_CHECK) {
+ return true;
+ }
+
+ if (a > totscene) {
+ sce->set = nullptr;
+ return false;
+ }
+ }
+
+ return true;
+}
+#endif
+
+static void lib_link_scenes_check_set(Main *bmain)
+{
+#ifdef USE_SETSCENE_CHECK
+ const int totscene = BLI_listbase_count(&bmain->scenes);
+ LISTBASE_FOREACH (Scene *, sce, &bmain->scenes) {
+ if (sce->flag & SCE_READFILE_LIBLINK_NEED_SETSCENE_CHECK) {
+ sce->flag &= ~SCE_READFILE_LIBLINK_NEED_SETSCENE_CHECK;
+ if (!scene_validate_setscene__liblink(sce, totscene)) {
+ CLOG_WARN(&LOG, "Found cyclic background scene when linking %s", sce->id.name + 2);
+ }
+ }
+ }
+#else
+ UNUSED_VARS(bmain, totscene);
+#endif
+}
+
+#undef USE_SETSCENE_CHECK
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read ID: Screen
+ * \{ */
+
+/* how to handle user count on pointer restore */
+enum ePointerUserMode {
+ USER_IGNORE = 0, /* ignore user count */
+ USER_REAL = 1, /* ensure at least one real user (fake user ignored) */
+};
+
+static void restore_pointer_user(ID *id, ID *newid, ePointerUserMode user)
+{
+ BLI_assert(STREQ(newid->name + 2, id->name + 2));
+ BLI_assert(newid->lib == id->lib);
+ UNUSED_VARS_NDEBUG(id);
+
+ if (user == USER_REAL) {
+ id_us_ensure_real(newid);
+ }
+}
+
+#ifndef USE_GHASH_RESTORE_POINTER
+/**
+ * A version of #restore_pointer_by_name that performs a full search (slow!).
+ * Use only for limited lookups, when the overhead of
+ * creating a #IDNameLib_Map for a single lookup isn't worthwhile.
+ */
+static void *restore_pointer_by_name_main(Main *mainp, ID *id, ePointerUserMode user)
+{
+ if (id) {
+ ListBase *lb = which_libbase(mainp, GS(id->name));
+ if (lb) { /* there's still risk of checking corrupt mem (freed Ids in oops) */
+ ID *idn = lb->first;
+ for (; idn; idn = idn->next) {
+ if (STREQ(idn->name + 2, id->name + 2)) {
+ if (idn->lib == id->lib) {
+ restore_pointer_user(id, idn, user);
+ break;
+ }
+ }
+ }
+ return idn;
+ }
+ }
+ return nullptr;
+}
+#endif
+
+/**
+ * Only for undo files, or to restore a screen after reading without UI...
+ *
+ * \param user:
+ * - USER_IGNORE: no user-count change.
+ * - USER_REAL: ensure a real user (even if a fake one is set).
+ * \param id_map: lookup table, use when performing many lookups.
+ * this could be made an optional argument (falling back to a full lookup),
+ * however at the moment it's always available.
+ */
+static void *restore_pointer_by_name(IDNameLib_Map *id_map, ID *id, ePointerUserMode user)
+{
+#ifdef USE_GHASH_RESTORE_POINTER
+ if (id) {
+ /* use fast lookup when available */
+ ID *idn = BKE_main_idmap_lookup_id(id_map, id);
+ if (idn) {
+ restore_pointer_user(id, idn, user);
+ }
+ return idn;
+ }
+ return nullptr;
+#else
+ Main *mainp = BKE_main_idmap_main_get(id_map);
+ return restore_pointer_by_name_main(mainp, id, user);
+#endif
+}
+
+static void lib_link_seq_clipboard_pt_restore(ID *id, IDNameLib_Map *id_map)
+{
+ if (id) {
+ /* clipboard must ensure this */
+ BLI_assert(id->newid != nullptr);
+ id->newid = static_cast<ID *>(restore_pointer_by_name(id_map, id->newid, USER_REAL));
+ }
+}
+static bool lib_link_seq_clipboard_cb(Sequence *seq, void *arg_pt)
+{
+ IDNameLib_Map *id_map = static_cast<IDNameLib_Map *>(arg_pt);
+
+ lib_link_seq_clipboard_pt_restore((ID *)seq->scene, id_map);
+ lib_link_seq_clipboard_pt_restore((ID *)seq->scene_camera, id_map);
+ lib_link_seq_clipboard_pt_restore((ID *)seq->clip, id_map);
+ lib_link_seq_clipboard_pt_restore((ID *)seq->mask, id_map);
+ lib_link_seq_clipboard_pt_restore((ID *)seq->sound, id_map);
+ return true;
+}
+
+static void lib_link_clipboard_restore(IDNameLib_Map *id_map)
+{
+ /* update IDs stored in sequencer clipboard */
+ SEQ_for_each_callback(&seqbase_clipboard, lib_link_seq_clipboard_cb, id_map);
+}
+
+static int lib_link_main_data_restore_cb(LibraryIDLinkCallbackData *cb_data)
+{
+ const int cb_flag = cb_data->cb_flag;
+ ID **id_pointer = cb_data->id_pointer;
+ if (cb_flag & IDWALK_CB_EMBEDDED || *id_pointer == nullptr) {
+ return IDWALK_RET_NOP;
+ }
+
+ /* Special ugly case here, thanks again for those non-IDs IDs... */
+ /* We probably need to add more cases here (hint: nodetrees),
+ * but will wait for changes from D5559 to get in first. */
+ if (GS((*id_pointer)->name) == ID_GR) {
+ Collection *collection = (Collection *)*id_pointer;
+ if (collection->flag & COLLECTION_IS_MASTER) {
+ /* We should never reach that point anymore, since master collection private ID should be
+ * properly tagged with IDWALK_CB_EMBEDDED. */
+ BLI_assert_unreachable();
+ return IDWALK_RET_NOP;
+ }
+ }
+
+ IDNameLib_Map *id_map = static_cast<IDNameLib_Map *>(cb_data->user_data);
+
+ /* NOTE: Handling of user-count here is really bad, defining its own system...
+ * Will have to be refactored at some point, but that is not top priority task for now.
+ * And all user-counts are properly recomputed at the end of the undo management code anyway. */
+ *id_pointer = static_cast<ID *>(restore_pointer_by_name(
+ id_map, *id_pointer, (cb_flag & IDWALK_CB_USER_ONE) ? USER_REAL : USER_IGNORE));
+
+ return IDWALK_RET_NOP;
+}
+
+static void lib_link_main_data_restore(IDNameLib_Map *id_map, Main *newmain)
+{
+ ID *id;
+ FOREACH_MAIN_ID_BEGIN (newmain, id) {
+ BKE_library_foreach_ID_link(newmain, id, lib_link_main_data_restore_cb, id_map, IDWALK_NOP);
+ }
+ FOREACH_MAIN_ID_END;
+}
+
+static void lib_link_wm_xr_data_restore(IDNameLib_Map *id_map, wmXrData *xr_data)
+{
+ xr_data->session_settings.base_pose_object = static_cast<Object *>(restore_pointer_by_name(
+ id_map, (ID *)xr_data->session_settings.base_pose_object, USER_REAL));
+}
+
+static void lib_link_window_scene_data_restore(wmWindow *win, Scene *scene, ViewLayer *view_layer)
+{
+ bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook);
+
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
+ if (sl->spacetype == SPACE_VIEW3D) {
+ View3D *v3d = (View3D *)sl;
+
+ if (v3d->camera == nullptr || v3d->scenelock) {
+ v3d->camera = scene->camera;
+ }
+
+ if (v3d->localvd) {
+ Base *base = nullptr;
+
+ v3d->localvd->camera = scene->camera;
+
+ /* Local-view can become invalid during undo/redo steps,
+ * so we exit it when no could be found. */
+ for (base = static_cast<Base *>(view_layer->object_bases.first); base;
+ base = base->next) {
+ if (base->local_view_bits & v3d->local_view_uuid) {
+ break;
+ }
+ }
+ if (base == nullptr) {
+ MEM_freeN(v3d->localvd);
+ v3d->localvd = nullptr;
+ v3d->local_view_uuid = 0;
+
+ /* Region-base storage is different depending if the space is active. */
+ ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase :
+ &sl->regionbase;
+ LISTBASE_FOREACH (ARegion *, region, regionbase) {
+ if (region->regiontype == RGN_TYPE_WINDOW) {
+ RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
+ if (rv3d->localvd) {
+ MEM_freeN(rv3d->localvd);
+ rv3d->localvd = nullptr;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void lib_link_restore_viewer_path(IDNameLib_Map *id_map, ViewerPath *viewer_path)
+{
+ LISTBASE_FOREACH (ViewerPathElem *, elem, &viewer_path->path) {
+ if (elem->type == VIEWER_PATH_ELEM_TYPE_ID) {
+ IDViewerPathElem *typed_elem = reinterpret_cast<IDViewerPathElem *>(elem);
+ typed_elem->id = static_cast<ID *>(
+ restore_pointer_by_name(id_map, (ID *)typed_elem->id, USER_IGNORE));
+ }
+ }
+}
+
+static void lib_link_workspace_layout_restore(IDNameLib_Map *id_map,
+ Main *newmain,
+ WorkSpaceLayout *layout)
+{
+ bScreen *screen = BKE_workspace_layout_screen_get(layout);
+
+ /* avoid conflicts with 2.8x branch */
+ {
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
+ if (sl->spacetype == SPACE_VIEW3D) {
+ View3D *v3d = (View3D *)sl;
+
+ v3d->camera = static_cast<Object *>(
+ restore_pointer_by_name(id_map, (ID *)v3d->camera, USER_REAL));
+ v3d->ob_center = static_cast<Object *>(
+ restore_pointer_by_name(id_map, (ID *)v3d->ob_center, USER_REAL));
+
+ lib_link_restore_viewer_path(id_map, &v3d->viewer_path);
+ }
+ else if (sl->spacetype == SPACE_GRAPH) {
+ SpaceGraph *sipo = (SpaceGraph *)sl;
+ bDopeSheet *ads = sipo->ads;
+
+ if (ads) {
+ ads->source = static_cast<ID *>(
+ restore_pointer_by_name(id_map, (ID *)ads->source, USER_REAL));
+
+ if (ads->filter_grp) {
+ ads->filter_grp = static_cast<Collection *>(
+ restore_pointer_by_name(id_map, (ID *)ads->filter_grp, USER_IGNORE));
+ }
+ }
+
+ /* force recalc of list of channels (i.e. includes calculating F-Curve colors)
+ * thus preventing the "black curves" problem post-undo
+ */
+ sipo->runtime.flag |= SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC_COLOR;
+ }
+ else if (sl->spacetype == SPACE_PROPERTIES) {
+ SpaceProperties *sbuts = (SpaceProperties *)sl;
+ sbuts->pinid = static_cast<ID *>(
+ restore_pointer_by_name(id_map, sbuts->pinid, USER_IGNORE));
+ if (sbuts->pinid == nullptr) {
+ sbuts->flag &= ~SB_PIN_CONTEXT;
+ }
+
+ /* TODO: restore path pointers: T40046
+ * (complicated because this contains data pointers too, not just ID). */
+ MEM_SAFE_FREE(sbuts->path);
+ }
+ else if (sl->spacetype == SPACE_FILE) {
+ SpaceFile *sfile = (SpaceFile *)sl;
+ sfile->op = nullptr;
+ sfile->tags = FILE_TAG_REBUILD_MAIN_FILES;
+ }
+ else if (sl->spacetype == SPACE_ACTION) {
+ SpaceAction *saction = (SpaceAction *)sl;
+
+ saction->action = static_cast<bAction *>(
+ restore_pointer_by_name(id_map, (ID *)saction->action, USER_REAL));
+ saction->ads.source = static_cast<ID *>(
+ restore_pointer_by_name(id_map, (ID *)saction->ads.source, USER_REAL));
+
+ if (saction->ads.filter_grp) {
+ saction->ads.filter_grp = static_cast<Collection *>(
+ restore_pointer_by_name(id_map, (ID *)saction->ads.filter_grp, USER_IGNORE));
+ }
+
+ /* force recalc of list of channels, potentially updating the active action
+ * while we're at it (as it can only be updated that way) T28962.
+ */
+ saction->runtime.flag |= SACTION_RUNTIME_FLAG_NEED_CHAN_SYNC;
+ }
+ else if (sl->spacetype == SPACE_IMAGE) {
+ SpaceImage *sima = (SpaceImage *)sl;
+
+ sima->image = static_cast<Image *>(
+ restore_pointer_by_name(id_map, (ID *)sima->image, USER_REAL));
+
+ /* this will be freed, not worth attempting to find same scene,
+ * since it gets initialized later */
+ sima->iuser.scene = nullptr;
+
+#if 0
+ /* Those are allocated and freed by space code, no need to handle them here. */
+ MEM_SAFE_FREE(sima->scopes.waveform_1);
+ MEM_SAFE_FREE(sima->scopes.waveform_2);
+ MEM_SAFE_FREE(sima->scopes.waveform_3);
+ MEM_SAFE_FREE(sima->scopes.vecscope);
+#endif
+ sima->scopes.ok = 0;
+
+ /* NOTE: pre-2.5, this was local data not lib data, but now we need this as lib data
+ * so assume that here we're doing for undo only...
+ */
+ sima->gpd = static_cast<bGPdata *>(
+ restore_pointer_by_name(id_map, (ID *)sima->gpd, USER_REAL));
+ sima->mask_info.mask = static_cast<Mask *>(
+ restore_pointer_by_name(id_map, (ID *)sima->mask_info.mask, USER_REAL));
+ }
+ else if (sl->spacetype == SPACE_SEQ) {
+ SpaceSeq *sseq = (SpaceSeq *)sl;
+
+ /* NOTE: pre-2.5, this was local data not lib data, but now we need this as lib data
+ * so assume that here we're doing for undo only...
+ */
+ sseq->gpd = static_cast<bGPdata *>(
+ restore_pointer_by_name(id_map, (ID *)sseq->gpd, USER_REAL));
+ }
+ else if (sl->spacetype == SPACE_NLA) {
+ SpaceNla *snla = (SpaceNla *)sl;
+ bDopeSheet *ads = snla->ads;
+
+ if (ads) {
+ ads->source = static_cast<ID *>(
+ restore_pointer_by_name(id_map, (ID *)ads->source, USER_REAL));
+
+ if (ads->filter_grp) {
+ ads->filter_grp = static_cast<Collection *>(
+ restore_pointer_by_name(id_map, (ID *)ads->filter_grp, USER_IGNORE));
+ }
+ }
+ }
+ else if (sl->spacetype == SPACE_TEXT) {
+ SpaceText *st = (SpaceText *)sl;
+
+ st->text = static_cast<Text *>(
+ restore_pointer_by_name(id_map, (ID *)st->text, USER_IGNORE));
+ if (st->text == nullptr) {
+ st->text = static_cast<Text *>(newmain->texts.first);
+ }
+ }
+ else if (sl->spacetype == SPACE_SCRIPT) {
+ SpaceScript *scpt = (SpaceScript *)sl;
+
+ scpt->script = static_cast<Script *>(
+ restore_pointer_by_name(id_map, (ID *)scpt->script, USER_REAL));
+
+ // screen->script = nullptr; /* 2.45 set to null, better re-run the script. */
+ if (scpt->script) {
+ SCRIPT_SET_NULL(scpt->script);
+ }
+ }
+ else if (sl->spacetype == SPACE_OUTLINER) {
+ SpaceOutliner *space_outliner = (SpaceOutliner *)sl;
+
+ if (space_outliner->treestore) {
+ TreeStoreElem *tselem;
+ BLI_mempool_iter iter;
+
+ BLI_mempool_iternew(space_outliner->treestore, &iter);
+ while ((tselem = static_cast<TreeStoreElem *>(BLI_mempool_iterstep(&iter)))) {
+ /* Do not try to restore pointers to drivers/sequence/etc.,
+ * can crash in undo case! */
+ if (TSE_IS_REAL_ID(tselem)) {
+ tselem->id = static_cast<ID *>(
+ restore_pointer_by_name(id_map, tselem->id, USER_IGNORE));
+ }
+ else {
+ tselem->id = nullptr;
+ }
+ }
+ /* rebuild hash table, because it depends on ids too */
+ space_outliner->storeflag |= SO_TREESTORE_REBUILD;
+ }
+ }
+ else if (sl->spacetype == SPACE_NODE) {
+ SpaceNode *snode = (SpaceNode *)sl;
+ bNodeTreePath *path, *path_next;
+ bNodeTree *ntree;
+
+ /* node tree can be stored locally in id too, link this first */
+ snode->id = static_cast<ID *>(restore_pointer_by_name(id_map, snode->id, USER_REAL));
+ snode->from = static_cast<ID *>(
+ restore_pointer_by_name(id_map, snode->from, USER_IGNORE));
+
+ ntree = snode->id ? ntreeFromID(snode->id) : nullptr;
+ snode->nodetree = ntree ? ntree :
+ static_cast<bNodeTree *>(restore_pointer_by_name(
+ id_map, (ID *)snode->nodetree, USER_REAL));
+
+ for (path = static_cast<bNodeTreePath *>(snode->treepath.first); path;
+ path = path->next) {
+ if (path == snode->treepath.first) {
+ /* first nodetree in path is same as snode->nodetree */
+ path->nodetree = snode->nodetree;
+ }
+ else {
+ path->nodetree = static_cast<bNodeTree *>(
+ restore_pointer_by_name(id_map, (ID *)path->nodetree, USER_REAL));
+ }
+
+ if (!path->nodetree) {
+ break;
+ }
+ }
+
+ /* remaining path entries are invalid, remove */
+ for (; path; path = path_next) {
+ path_next = path->next;
+
+ BLI_remlink(&snode->treepath, path);
+ MEM_freeN(path);
+ }
+
+ /* edittree is just the last in the path,
+ * set this directly since the path may have been shortened above */
+ if (snode->treepath.last) {
+ path = static_cast<bNodeTreePath *>(snode->treepath.last);
+ snode->edittree = path->nodetree;
+ }
+ else {
+ snode->edittree = nullptr;
+ }
+ }
+ else if (sl->spacetype == SPACE_CLIP) {
+ SpaceClip *sclip = (SpaceClip *)sl;
+
+ sclip->clip = static_cast<MovieClip *>(
+ restore_pointer_by_name(id_map, (ID *)sclip->clip, USER_REAL));
+ sclip->mask_info.mask = static_cast<Mask *>(
+ restore_pointer_by_name(id_map, (ID *)sclip->mask_info.mask, USER_REAL));
+
+ sclip->scopes.ok = 0;
+ }
+ else if (sl->spacetype == SPACE_SPREADSHEET) {
+ SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
+ lib_link_restore_viewer_path(id_map, &sspreadsheet->viewer_path);
+ }
+ }
+ }
+ }
+}
+
+void blo_lib_link_restore(Main *oldmain,
+ Main *newmain,
+ wmWindowManager *curwm,
+ Scene *curscene,
+ ViewLayer *cur_view_layer)
+{
+ IDNameLib_Map *id_map = BKE_main_idmap_create(newmain, true, oldmain, MAIN_IDMAP_TYPE_NAME);
+
+ LISTBASE_FOREACH (WorkSpace *, workspace, &newmain->workspaces) {
+ LISTBASE_FOREACH (WorkSpaceLayout *, layout, &workspace->layouts) {
+ lib_link_workspace_layout_restore(id_map, newmain, layout);
+ }
+ workspace->pin_scene = static_cast<Scene *>(
+ restore_pointer_by_name(id_map, (ID *)workspace->pin_scene, USER_IGNORE));
+ lib_link_restore_viewer_path(id_map, &workspace->viewer_path);
+ }
+
+ LISTBASE_FOREACH (wmWindow *, win, &curwm->windows) {
+ WorkSpace *workspace = BKE_workspace_active_get(win->workspace_hook);
+ ID *workspace_id = (ID *)workspace;
+ Scene *oldscene = win->scene;
+
+ workspace = static_cast<WorkSpace *>(restore_pointer_by_name(id_map, workspace_id, USER_REAL));
+ BKE_workspace_active_set(win->workspace_hook, workspace);
+ win->scene = static_cast<Scene *>(
+ restore_pointer_by_name(id_map, (ID *)win->scene, USER_REAL));
+ if (win->scene == nullptr) {
+ win->scene = curscene;
+ }
+ win->unpinned_scene = static_cast<Scene *>(
+ restore_pointer_by_name(id_map, (ID *)win->unpinned_scene, USER_IGNORE));
+ if (BKE_view_layer_find(win->scene, win->view_layer_name) == nullptr) {
+ STRNCPY(win->view_layer_name, cur_view_layer->name);
+ }
+ BKE_workspace_active_set(win->workspace_hook, workspace);
+
+ /* keep cursor location through undo */
+ memcpy(&win->scene->cursor, &oldscene->cursor, sizeof(win->scene->cursor));
+
+ /* NOTE: even though that function seems to redo part of what is done by
+ * `lib_link_workspace_layout_restore()` above, it seems to have a slightly different scope:
+ * while the former updates the whole UI pointers from Main db (going over all layouts of
+ * all workspaces), that one only focuses one current active screen, takes care of
+ * potential local view, and needs window's scene pointer to be final... */
+ lib_link_window_scene_data_restore(win, win->scene, cur_view_layer);
+
+ BLI_assert(win->screen == nullptr);
+ }
+
+ lib_link_wm_xr_data_restore(id_map, &curwm->xr);
+
+ /* Restore all ID pointers in Main database itself
+ * (especially IDProperties might point to some word-space of other 'weirdly unchanged' ID
+ * pointers, see T69146).
+ * Note that this will re-apply again a few pointers in workspaces or so,
+ * but since we are remapping final ones already set above,
+ * that is just some minor harmless double-processing. */
+ lib_link_main_data_restore(id_map, newmain);
+
+ /* update IDs stored in all possible clipboards */
+ lib_link_clipboard_restore(id_map);
+
+ BKE_main_idmap_destroy(id_map);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read ID: Library
+ * \{ */
+
+static void direct_link_library(FileData *fd, Library *lib, Main *main)
+{
+ Main *newmain;
+
+ /* check if the library was already read */
+ for (newmain = static_cast<Main *>(fd->mainlist->first); newmain; newmain = newmain->next) {
+ if (newmain->curlib) {
+ if (BLI_path_cmp(newmain->curlib->filepath_abs, lib->filepath_abs) == 0) {
+ BLO_reportf_wrap(fd->reports,
+ RPT_WARNING,
+ TIP_("Library '%s', '%s' had multiple instances, save and reload!"),
+ lib->filepath,
+ lib->filepath_abs);
+
+ change_link_placeholder_to_real_ID_pointer(fd->mainlist, fd, lib, newmain->curlib);
+ /* change_link_placeholder_to_real_ID_pointer_fd(fd, lib, newmain->curlib); */
+
+ BLI_remlink(&main->libraries, lib);
+ MEM_freeN(lib);
+
+ /* Now, since Blender always expect **latest** Main pointer from fd->mainlist
+ * to be the active library Main pointer,
+ * where to add all non-library data-blocks found in file next, we have to switch that
+ * 'dupli' found Main to latest position in the list!
+ * Otherwise, you get weird disappearing linked data on a rather inconsistent basis.
+ * See also T53977 for reproducible case. */
+ BLI_remlink(fd->mainlist, newmain);
+ BLI_addtail(fd->mainlist, newmain);
+
+ return;
+ }
+ }
+ }
+
+ /* Make sure we have full path in lib->filepath_abs */
+ BLI_strncpy(lib->filepath_abs, lib->filepath, sizeof(lib->filepath));
+ BLI_path_normalize(fd->relabase, lib->filepath_abs);
+
+ // printf("direct_link_library: filepath %s\n", lib->filepath);
+ // printf("direct_link_library: filepath_abs %s\n", lib->filepath_abs);
+
+ BlendDataReader reader = {fd};
+ BKE_packedfile_blend_read(&reader, &lib->packedfile);
+
+ /* new main */
+ newmain = BKE_main_new();
+ BLI_addtail(fd->mainlist, newmain);
+ newmain->curlib = lib;
+
+ lib->parent = nullptr;
+
+ id_us_ensure_real(&lib->id);
+}
+
+static void lib_link_library(BlendLibReader * /*reader*/, Library * /*lib*/)
+{
+}
+
+/* Always call this once you have loaded new library data to set the relative paths correctly
+ * in relation to the blend file. */
+static void fix_relpaths_library(const char *basepath, Main *main)
+{
+ /* #BLO_read_from_memory uses a blank file-path. */
+ if (basepath == nullptr || basepath[0] == '\0') {
+ LISTBASE_FOREACH (Library *, lib, &main->libraries) {
+ /* when loading a linked lib into a file which has not been saved,
+ * there is nothing we can be relative to, so instead we need to make
+ * it absolute. This can happen when appending an object with a relative
+ * link into an unsaved blend file. See T27405.
+ * The remap relative option will make it relative again on save - campbell */
+ if (BLI_path_is_rel(lib->filepath)) {
+ BLI_strncpy(lib->filepath, lib->filepath_abs, sizeof(lib->filepath));
+ }
+ }
+ }
+ else {
+ LISTBASE_FOREACH (Library *, lib, &main->libraries) {
+ /* Libraries store both relative and abs paths, recreate relative paths,
+ * relative to the blend file since indirectly linked libraries will be
+ * relative to their direct linked library. */
+ if (BLI_path_is_rel(lib->filepath)) { /* if this is relative to begin with? */
+ BLI_strncpy(lib->filepath, lib->filepath_abs, sizeof(lib->filepath));
+ BLI_path_rel(lib->filepath, basepath);
+ }
+ }
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read Library Data Block
+ * \{ */
+
+static ID *create_placeholder(Main *mainvar, const short idcode, const char *idname, const int tag)
+{
+ ListBase *lb = which_libbase(mainvar, idcode);
+ ID *ph_id = static_cast<ID *>(BKE_libblock_alloc_notest(idcode));
+
+ *((short *)ph_id->name) = idcode;
+ BLI_strncpy(ph_id->name + 2, idname, sizeof(ph_id->name) - 2);
+ BKE_libblock_init_empty(ph_id);
+ ph_id->lib = mainvar->curlib;
+ ph_id->tag = tag | LIB_TAG_MISSING;
+ ph_id->us = ID_FAKE_USERS(ph_id);
+ ph_id->icon_id = 0;
+
+ BLI_addtail(lb, ph_id);
+ id_sort_by_name(lb, ph_id, nullptr);
+
+ if (mainvar->id_map != nullptr) {
+ BKE_main_idmap_insert_id(mainvar->id_map, ph_id);
+ }
+
+ if ((tag & LIB_TAG_TEMP_MAIN) == 0) {
+ BKE_lib_libblock_session_uuid_ensure(ph_id);
+ }
+
+ return ph_id;
+}
+
+static void placeholders_ensure_valid(Main *bmain)
+{
+ /* Placeholder ObData IDs won't have any material, we have to update their objects for that,
+ * otherwise the inconsistency between both will lead to crashes (especially in Eevee?). */
+ LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
+ ID *obdata = static_cast<ID *>(ob->data);
+ if (obdata != nullptr && obdata->tag & LIB_TAG_MISSING) {
+ BKE_object_materials_test(bmain, ob, obdata);
+ }
+ }
+}
+
+static const char *dataname(short id_code)
+{
+ switch ((ID_Type)id_code) {
+ case ID_OB:
+ return "Data from OB";
+ case ID_ME:
+ return "Data from ME";
+ case ID_IP:
+ return "Data from IP";
+ case ID_SCE:
+ return "Data from SCE";
+ case ID_MA:
+ return "Data from MA";
+ case ID_TE:
+ return "Data from TE";
+ case ID_CU_LEGACY:
+ return "Data from CU";
+ case ID_GR:
+ return "Data from GR";
+ case ID_AR:
+ return "Data from AR";
+ case ID_AC:
+ return "Data from AC";
+ case ID_LI:
+ return "Data from LI";
+ case ID_MB:
+ return "Data from MB";
+ case ID_IM:
+ return "Data from IM";
+ case ID_LT:
+ return "Data from LT";
+ case ID_LA:
+ return "Data from LA";
+ case ID_CA:
+ return "Data from CA";
+ case ID_KE:
+ return "Data from KE";
+ case ID_WO:
+ return "Data from WO";
+ case ID_SCR:
+ return "Data from SCR";
+ case ID_VF:
+ return "Data from VF";
+ case ID_TXT:
+ return "Data from TXT";
+ case ID_SPK:
+ return "Data from SPK";
+ case ID_LP:
+ return "Data from LP";
+ case ID_SO:
+ return "Data from SO";
+ case ID_NT:
+ return "Data from NT";
+ case ID_BR:
+ return "Data from BR";
+ case ID_PA:
+ return "Data from PA";
+ case ID_PAL:
+ return "Data from PAL";
+ case ID_PC:
+ return "Data from PCRV";
+ case ID_GD:
+ return "Data from GD";
+ case ID_WM:
+ return "Data from WM";
+ case ID_MC:
+ return "Data from MC";
+ case ID_MSK:
+ return "Data from MSK";
+ case ID_LS:
+ return "Data from LS";
+ case ID_CF:
+ return "Data from CF";
+ case ID_WS:
+ return "Data from WS";
+ case ID_CV:
+ return "Data from HA";
+ case ID_PT:
+ return "Data from PT";
+ case ID_VO:
+ return "Data from VO";
+ case ID_SIM:
+ return "Data from SIM";
+ }
+ return "Data from Lib Block";
+}
+
+static bool direct_link_id(FileData *fd, Main *main, const int tag, ID *id, ID *id_old)
+{
+ BlendDataReader reader = {fd};
+
+ /* Read part of datablock that is common between real and embedded datablocks. */
+ direct_link_id_common(&reader, main->curlib, id, id_old, tag);
+
+ if (tag & LIB_TAG_ID_LINK_PLACEHOLDER) {
+ /* For placeholder we only need to set the tag, no further data to read. */
+ id->tag = tag;
+ return true;
+ }
+
+ const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
+ if (id_type->blend_read_data != nullptr) {
+ id_type->blend_read_data(&reader, id);
+ }
+
+ /* XXX Very weakly handled currently, see comment in read_libblock() before trying to
+ * use it for anything new. */
+ bool success = true;
+
+ switch (GS(id->name)) {
+ case ID_SCR:
+ success = BKE_screen_blend_read_data(&reader, (bScreen *)id);
+ break;
+ case ID_LI:
+ direct_link_library(fd, (Library *)id, main);
+ break;
+ default:
+ /* Do nothing. Handled by IDTypeInfo callback. */
+ break;
+ }
+
+ /* try to restore (when undoing) or clear ID's cache pointers. */
+ if (id_type->foreach_cache != nullptr) {
+ BKE_idtype_id_foreach_cache(
+ id, blo_cache_storage_entry_restore_in_new, reader.fd->cache_storage);
+ }
+
+ return success;
+}
+
+/* Read all data associated with a datablock into datamap. */
+static BHead *read_data_into_datamap(FileData *fd, BHead *bhead, const char *allocname)
+{
+ bhead = blo_bhead_next(fd, bhead);
+
+ while (bhead && bhead->code == DATA) {
+ /* The code below is useful for debugging leaks in data read from the blend file.
+ * Without this the messages only tell us what ID-type the memory came from,
+ * eg: `Data from OB len 64`, see #dataname.
+ * With the code below we get the struct-name to help tracking down the leak.
+ * This is kept disabled as the #malloc for the text always leaks memory. */
+#if 0
+ if (bhead->SDNAnr == 0) {
+ /* The data type here is unclear because #writedata sets SDNAnr to 0. */
+ allocname = "likely raw data";
+ }
+ else {
+ SDNA_Struct *sp = fd->filesdna->structs[bhead->SDNAnr];
+ allocname = fd->filesdna->types[sp->type];
+ size_t allocname_size = strlen(allocname) + 1;
+ char *allocname_buf = malloc(allocname_size);
+ memcpy(allocname_buf, allocname, allocname_size);
+ allocname = allocname_buf;
+ }
+#endif
+
+ void *data = read_struct(fd, bhead, allocname);
+ if (data) {
+ oldnewmap_insert(fd->datamap, bhead->old, data, 0);
+ }
+
+ bhead = blo_bhead_next(fd, bhead);
+ }
+
+ return bhead;
+}
+
+/* Verify if the datablock and all associated data is identical. */
+static bool read_libblock_is_identical(FileData *fd, BHead *bhead)
+{
+ /* Test ID itself. */
+ if (bhead->len && !BHEADN_FROM_BHEAD(bhead)->is_memchunk_identical) {
+ return false;
+ }
+
+ /* Test any other data that is part of ID (logic must match read_data_into_datamap). */
+ bhead = blo_bhead_next(fd, bhead);
+
+ while (bhead && bhead->code == DATA) {
+ if (bhead->len && !BHEADN_FROM_BHEAD(bhead)->is_memchunk_identical) {
+ return false;
+ }
+
+ bhead = blo_bhead_next(fd, bhead);
+ }
+
+ return true;
+}
+
+/* For undo, restore matching library datablock from the old main. */
+static bool read_libblock_undo_restore_library(FileData *fd, Main *main, const ID *id)
+{
+ /* In undo case, most libraries and linked data should be kept as is from previous state
+ * (see BLO_read_from_memfile).
+ * However, some needed by the snapshot being read may have been removed in previous one,
+ * and would go missing.
+ * This leads e.g. to disappearing objects in some undo/redo case, see T34446.
+ * That means we have to carefully check whether current lib or
+ * libdata already exits in old main, if it does we merely copy it over into new main area,
+ * otherwise we have to do a full read of that bhead... */
+ CLOG_INFO(&LOG_UNDO, 2, "UNDO: restore library %s", id->name);
+
+ Main *libmain = static_cast<Main *>(fd->old_mainlist->first);
+ /* Skip oldmain itself... */
+ for (libmain = libmain->next; libmain; libmain = libmain->next) {
+ if (libmain->curlib && STREQ(id->name, libmain->curlib->id.name)) {
+ Main *oldmain = static_cast<Main *>(fd->old_mainlist->first);
+ CLOG_INFO(&LOG_UNDO,
+ 2,
+ " compare with %s -> match",
+ libmain->curlib ? libmain->curlib->id.name : "<nullptr>");
+ /* In case of a library, we need to re-add its main to fd->mainlist,
+ * because if we have later a missing ID_LINK_PLACEHOLDER,
+ * we need to get the correct lib it is linked to!
+ * Order is crucial, we cannot bulk-add it in BLO_read_from_memfile()
+ * like it used to be. */
+ BLI_remlink(fd->old_mainlist, libmain);
+ BLI_remlink_safe(&oldmain->libraries, libmain->curlib);
+ BLI_addtail(fd->mainlist, libmain);
+ BLI_addtail(&main->libraries, libmain->curlib);
+ return true;
+ }
+ CLOG_INFO(&LOG_UNDO,
+ 2,
+ " compare with %s -> NO match",
+ libmain->curlib ? libmain->curlib->id.name : "<nullptr>");
+ }
+
+ return false;
+}
+
+/* For undo, restore existing linked datablock from the old main. */
+static bool read_libblock_undo_restore_linked(FileData *fd, Main *main, const ID *id, BHead *bhead)
+{
+ CLOG_INFO(&LOG_UNDO, 2, "UNDO: restore linked datablock %s", id->name);
+
+ ID *id_old = BKE_libblock_find_name(main, GS(id->name), id->name + 2);
+ if (id_old != nullptr) {
+ CLOG_INFO(&LOG_UNDO,
+ 2,
+ " from %s (%s): found",
+ main->curlib ? main->curlib->id.name : "<nullptr>",
+ main->curlib ? main->curlib->filepath : "<nullptr>");
+ /* Even though we found our linked ID, there is no guarantee its address
+ * is still the same. */
+ if (id_old != bhead->old) {
+ oldnewmap_lib_insert(fd, bhead->old, id_old, GS(id_old->name));
+ }
+
+ /* No need to do anything else for ID_LINK_PLACEHOLDER, it's assumed
+ * already present in its lib's main. */
+ return true;
+ }
+
+ CLOG_INFO(&LOG_UNDO,
+ 2,
+ " from %s (%s): NOT found",
+ main->curlib ? main->curlib->id.name : "<nullptr>",
+ main->curlib ? main->curlib->filepath : "<nullptr>");
+ return false;
+}
+
+/* For undo, restore unchanged datablock from old main. */
+static void read_libblock_undo_restore_identical(
+ FileData *fd, Main *main, const ID * /*id*/, ID *id_old, const int tag)
+{
+ BLI_assert((fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0);
+ BLI_assert(id_old != nullptr);
+
+ /* Some tags need to be preserved here. */
+ id_old->tag = tag | (id_old->tag & LIB_TAG_EXTRAUSER);
+ id_old->lib = main->curlib;
+ id_old->us = ID_FAKE_USERS(id_old);
+ /* Do not reset id->icon_id here, memory allocated for it remains valid. */
+ /* Needed because .blend may have been saved with crap value here... */
+ id_old->newid = nullptr;
+ id_old->orig_id = nullptr;
+
+ const short idcode = GS(id_old->name);
+ Main *old_bmain = static_cast<Main *>(fd->old_mainlist->first);
+ ListBase *old_lb = which_libbase(old_bmain, idcode);
+ ListBase *new_lb = which_libbase(main, idcode);
+ BLI_remlink(old_lb, id_old);
+ BLI_addtail(new_lb, id_old);
+
+ /* Recalc flags, mostly these just remain as they are. */
+ id_old->recalc |= direct_link_id_restore_recalc_exceptions(id_old);
+ id_old->recalc_after_undo_push = 0;
+
+ if (GS(id_old->name) == ID_OB) {
+ Object *ob = (Object *)id_old;
+ /* For undo we stay in object mode during undo presses, so keep editmode disabled for re-used
+ * data-blocks too. */
+ ob->mode &= ~OB_MODE_EDIT;
+ }
+}
+
+/* For undo, store changed datablock at old address. */
+static void read_libblock_undo_restore_at_old_address(FileData *fd, Main *main, ID *id, ID *id_old)
+{
+ /* During memfile undo, if an ID changed and we cannot directly re-use existing one from old
+ * bmain, we do a full read of the new id from the memfile, and then fully swap its content
+ * with the old id. This allows us to keep the same pointer even for modified data, which
+ * helps reducing further detected changes by the depsgraph (since unchanged IDs remain fully
+ * unchanged, even if they are using/pointing to a changed one). */
+ BLI_assert((fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0);
+ BLI_assert(id_old != nullptr);
+
+ const short idcode = GS(id->name);
+
+ Main *old_bmain = static_cast<Main *>(fd->old_mainlist->first);
+ ListBase *old_lb = which_libbase(old_bmain, idcode);
+ ListBase *new_lb = which_libbase(main, idcode);
+ BLI_remlink(old_lb, id_old);
+ BLI_remlink(new_lb, id);
+
+ /* We do not need any remapping from this call here, since no ID pointer is valid in the data
+ * currently (they are all pointing to old addresses, and need to go through `lib_link`
+ * process). So we can pass nullptr for the Main pointer parameter. */
+ BKE_lib_id_swap_full(nullptr, id, id_old);
+
+ /* Special temporary usage of this pointer, necessary for the `undo_preserve` call after
+ * lib-linking to restore some data that should never be affected by undo, e.g. the 3D cursor of
+ * #Scene. */
+ id_old->orig_id = id;
+
+ BLI_addtail(new_lb, id_old);
+ BLI_addtail(old_lb, id);
+}
+
+static bool read_libblock_undo_restore(
+ FileData *fd, Main *main, BHead *bhead, const int tag, ID **r_id_old)
+{
+ /* Get pointer to memory of new ID that we will be reading. */
+ const ID *id = static_cast<const ID *>(peek_struct_undo(fd, bhead));
+ const short idcode = GS(id->name);
+
+ if (bhead->code == ID_LI) {
+ /* Restore library datablock. */
+ if (read_libblock_undo_restore_library(fd, main, id)) {
+ return true;
+ }
+ }
+ else if (bhead->code == ID_LINK_PLACEHOLDER) {
+ /* Restore linked datablock. */
+ if (read_libblock_undo_restore_linked(fd, main, id, bhead)) {
+ return true;
+ }
+ }
+ else if (ELEM(idcode, ID_WM, ID_SCR, ID_WS)) {
+ /* Skip reading any UI datablocks, existing ones are kept. We don't
+ * support pointers from other datablocks to UI datablocks so those
+ * we also don't put UI datablocks in fd->libmap. */
+ return true;
+ }
+
+ /* Restore local datablocks. */
+ ID *id_old = nullptr;
+ const bool do_partial_undo = (fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0;
+ if (do_partial_undo && (bhead->code != ID_LINK_PLACEHOLDER)) {
+ /* This code should only ever be reached for local data-blocks. */
+ BLI_assert(main->curlib == nullptr);
+
+ /* Find the 'current' existing ID we want to reuse instead of the one we
+ * would read from the undo memfile. */
+ BLI_assert(fd->old_idmap != nullptr);
+ id_old = BKE_main_idmap_lookup_uuid(fd->old_idmap, id->session_uuid);
+ }
+
+ if (id_old != nullptr && read_libblock_is_identical(fd, bhead)) {
+ /* Local datablock was unchanged, restore from the old main. */
+ CLOG_INFO(&LOG_UNDO,
+ 2,
+ "UNDO: read %s (uuid %u) -> keep identical datablock",
+ id->name,
+ id->session_uuid);
+
+ /* Do not add LIB_TAG_NEW here, this should not be needed/used in undo case anyway (as
+ * this is only for do_version-like code), but for sake of consistency, and also because
+ * it will tell us which ID is re-used from old Main, and which one is actually new. */
+ /* Also do not add LIB_TAG_NEED_LINK, those IDs will never be re-liblinked, hence that tag will
+ * never be cleared, leading to critical issue in link/append code. */
+ const int id_tag = tag | LIB_TAG_UNDO_OLD_ID_REUSED;
+ read_libblock_undo_restore_identical(fd, main, id, id_old, id_tag);
+
+ /* Insert into library map for lookup by newly read datablocks (with pointer value bhead->old).
+ * Note that existing datablocks in memory (which pointer value would be id_old) are not
+ * remapped anymore, so no need to store this info here. */
+ oldnewmap_lib_insert(fd, bhead->old, id_old, bhead->code);
+
+ *r_id_old = id_old;
+ return true;
+ }
+ if (id_old != nullptr) {
+ /* Local datablock was changed. Restore at the address of the old datablock. */
+ CLOG_INFO(&LOG_UNDO,
+ 2,
+ "UNDO: read %s (uuid %u) -> read to old existing address",
+ id->name,
+ id->session_uuid);
+ *r_id_old = id_old;
+ return false;
+ }
+
+ /* Local datablock does not exist in the undo step, so read from scratch. */
+ CLOG_INFO(
+ &LOG_UNDO, 2, "UNDO: read %s (uuid %u) -> read at new address", id->name, id->session_uuid);
+ return false;
+}
+
+/* This routine reads a datablock and its direct data, and advances bhead to
+ * the next datablock. For library linked datablocks, only a placeholder will
+ * be generated, to be replaced in read_library_linked_ids.
+ *
+ * When reading for undo, libraries, linked datablocks and unchanged datablocks
+ * will be restored from the old database. Only new or changed datablocks will
+ * actually be read. */
+static BHead *read_libblock(FileData *fd,
+ Main *main,
+ BHead *bhead,
+ const int tag,
+ const bool placeholder_set_indirect_extern,
+ ID **r_id)
+{
+ /* First attempt to restore existing datablocks for undo.
+ * When datablocks are changed but still exist, we restore them at the old
+ * address and inherit recalc flags for the dependency graph. */
+ ID *id_old = nullptr;
+ if (fd->flags & FD_FLAGS_IS_MEMFILE) {
+ if (read_libblock_undo_restore(fd, main, bhead, tag, &id_old)) {
+ if (r_id) {
+ *r_id = id_old;
+ }
+ if (main->id_map != nullptr) {
+ BKE_main_idmap_insert_id(main->id_map, id_old);
+ }
+
+ return blo_bhead_next(fd, bhead);
+ }
+ }
+
+ /* Read libblock struct. */
+ ID *id = static_cast<ID *>(read_struct(fd, bhead, "lib block"));
+ if (id == nullptr) {
+ if (r_id) {
+ *r_id = nullptr;
+ }
+ return blo_bhead_next(fd, bhead);
+ }
+
+ /* Determine ID type and add to main database list. */
+ const short idcode = GS(id->name);
+ ListBase *lb = which_libbase(main, idcode);
+ if (lb == nullptr) {
+ /* Unknown ID type. */
+ CLOG_WARN(&LOG, "Unknown id code '%c%c'", (idcode & 0xff), (idcode >> 8));
+ MEM_freeN(id);
+ if (r_id) {
+ *r_id = nullptr;
+ }
+ return blo_bhead_next(fd, bhead);
+ }
+
+ /* NOTE: id must be added to the list before direct_link_id(), since
+ * direct_link_library() may remove it from there in case of duplicates. */
+ BLI_addtail(lb, id);
+
+ /* Insert into library map for lookup by newly read datablocks (with pointer value bhead->old).
+ * Note that existing datablocks in memory (which pointer value would be id_old) are not remapped
+ * remapped anymore, so no need to store this info here. */
+ ID *id_target = id_old ? id_old : id;
+ oldnewmap_lib_insert(fd, bhead->old, id_target, bhead->code);
+
+ if (r_id) {
+ *r_id = id_target;
+ }
+
+ /* Set tag for new datablock to indicate lib linking and versioning needs
+ * to be done still. */
+ int id_tag = tag | LIB_TAG_NEED_LINK | LIB_TAG_NEW;
+
+ if (bhead->code == ID_LINK_PLACEHOLDER) {
+ /* Read placeholder for linked datablock. */
+ id_tag |= LIB_TAG_ID_LINK_PLACEHOLDER;
+
+ if (placeholder_set_indirect_extern) {
+ if (id->flag & LIB_INDIRECT_WEAK_LINK) {
+ id_tag |= LIB_TAG_INDIRECT;
+ }
+ else {
+ id_tag |= LIB_TAG_EXTERN;
+ }
+ }
+
+ direct_link_id(fd, main, id_tag, id, id_old);
+
+ if (main->id_map != nullptr) {
+ BKE_main_idmap_insert_id(main->id_map, id);
+ }
+
+ return blo_bhead_next(fd, bhead);
+ }
+
+ /* Read datablock contents.
+ * Use convenient malloc name for debugging and better memory link prints. */
+ const char *allocname = dataname(idcode);
+ bhead = read_data_into_datamap(fd, bhead, allocname);
+ const bool success = direct_link_id(fd, main, id_tag, id, id_old);
+ oldnewmap_clear(fd->datamap);
+
+ if (!success) {
+ /* XXX This is probably working OK currently given the very limited scope of that flag.
+ * However, it is absolutely **not** handled correctly: it is freeing an ID pointer that has
+ * been added to the fd->libmap mapping, which in theory could lead to nice crashes...
+ * This should be properly solved at some point. */
+ BKE_id_free(main, id);
+ if (r_id != nullptr) {
+ *r_id = nullptr;
+ }
+ }
+ else if (id_old) {
+ /* For undo, store contents read into id at id_old. */
+ read_libblock_undo_restore_at_old_address(fd, main, id, id_old);
+
+ if (main->id_map != nullptr) {
+ BKE_main_idmap_insert_id(main->id_map, id_old);
+ }
+ }
+ else if (main->id_map != nullptr) {
+ BKE_main_idmap_insert_id(main->id_map, id);
+ }
+
+ return bhead;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read Asset Data
+ * \{ */
+
+BHead *blo_read_asset_data_block(FileData *fd, BHead *bhead, AssetMetaData **r_asset_data)
+{
+ BLI_assert(blo_bhead_is_id_valid_type(bhead));
+
+ bhead = read_data_into_datamap(fd, bhead, "asset-data read");
+
+ BlendDataReader reader = {fd};
+ BLO_read_data_address(&reader, r_asset_data);
+ BKE_asset_metadata_read(&reader, *r_asset_data);
+
+ oldnewmap_clear(fd->datamap);
+
+ return bhead;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read Global Data
+ * \{ */
+
+/* NOTE: this has to be kept for reading older files... */
+/* also version info is written here */
+static BHead *read_global(BlendFileData *bfd, FileData *fd, BHead *bhead)
+{
+ FileGlobal *fg = static_cast<FileGlobal *>(read_struct(fd, bhead, "Global"));
+
+ /* copy to bfd handle */
+ bfd->main->subversionfile = fg->subversion;
+ bfd->main->minversionfile = fg->minversion;
+ bfd->main->minsubversionfile = fg->minsubversion;
+ bfd->main->build_commit_timestamp = fg->build_commit_timestamp;
+ BLI_strncpy(bfd->main->build_hash, fg->build_hash, sizeof(bfd->main->build_hash));
+
+ bfd->fileflags = fg->fileflags;
+ bfd->globalf = fg->globalf;
+ STRNCPY(bfd->filepath, fg->filepath);
+
+ /* Error in 2.65 and older: `main->filepath` was not set if you save from startup
+ * (not after loading file). */
+ if (bfd->filepath[0] == 0) {
+ if (fd->fileversion < 265 || (fd->fileversion == 265 && fg->subversion < 1)) {
+ if ((G.fileflags & G_FILE_RECOVER_READ) == 0) {
+ STRNCPY(bfd->filepath, BKE_main_blendfile_path(bfd->main));
+ }
+ }
+
+ /* early 2.50 version patch - filepath not in FileGlobal struct at all */
+ if (fd->fileversion <= 250) {
+ STRNCPY(bfd->filepath, BKE_main_blendfile_path(bfd->main));
+ }
+ }
+
+ if (G.fileflags & G_FILE_RECOVER_READ) {
+ BLI_strncpy(fd->relabase, fg->filepath, sizeof(fd->relabase));
+ }
+
+ bfd->curscreen = fg->curscreen;
+ bfd->curscene = fg->curscene;
+ bfd->cur_view_layer = fg->cur_view_layer;
+
+ MEM_freeN(fg);
+
+ fd->globalf = bfd->globalf;
+ fd->fileflags = bfd->fileflags;
+
+ return blo_bhead_next(fd, bhead);
+}
+
+/* NOTE: this has to be kept for reading older files... */
+static void link_global(FileData *fd, BlendFileData *bfd)
+{
+ bfd->cur_view_layer = static_cast<ViewLayer *>(
+ blo_read_get_new_globaldata_address(fd, bfd->cur_view_layer));
+ bfd->curscreen = static_cast<bScreen *>(newlibadr(fd, nullptr, bfd->curscreen));
+ bfd->curscene = static_cast<Scene *>(newlibadr(fd, nullptr, bfd->curscene));
+ /* this happens in files older than 2.35 */
+ if (bfd->curscene == nullptr) {
+ if (bfd->curscreen) {
+ bfd->curscene = bfd->curscreen->scene;
+ }
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Versioning
+ * \{ */
+
+static void do_versions_userdef(FileData * /*fd*/, BlendFileData *bfd)
+{
+ UserDef *user = bfd->user;
+
+ if (user == nullptr) {
+ return;
+ }
+
+ blo_do_versions_userdef(user);
+}
+
+static void do_versions(FileData *fd, Library *lib, Main *main)
+{
+ /* WATCH IT!!!: pointers from libdata have not been converted */
+
+ /* Don't allow versioning to create new data-blocks. */
+ main->is_locked_for_linking = true;
+
+ if (G.debug & G_DEBUG) {
+ char build_commit_datetime[32];
+ time_t temp_time = main->build_commit_timestamp;
+ struct tm *tm = (temp_time) ? gmtime(&temp_time) : nullptr;
+ if (LIKELY(tm)) {
+ strftime(build_commit_datetime, sizeof(build_commit_datetime), "%Y-%m-%d %H:%M", tm);
+ }
+ else {
+ BLI_strncpy(build_commit_datetime, "unknown", sizeof(build_commit_datetime));
+ }
+
+ CLOG_INFO(&LOG, 0, "Read file %s", fd->relabase);
+ CLOG_INFO(&LOG,
+ 0,
+ " Version %d sub %d date %s hash %s",
+ main->versionfile,
+ main->subversionfile,
+ build_commit_datetime,
+ main->build_hash);
+ }
+
+ blo_do_versions_pre250(fd, lib, main);
+ blo_do_versions_250(fd, lib, main);
+ blo_do_versions_260(fd, lib, main);
+ blo_do_versions_270(fd, lib, main);
+ blo_do_versions_280(fd, lib, main);
+ blo_do_versions_290(fd, lib, main);
+ blo_do_versions_300(fd, lib, main);
+ blo_do_versions_400(fd, lib, main);
+ blo_do_versions_cycles(fd, lib, main);
+
+ /* WATCH IT!!!: pointers from libdata have not been converted yet here! */
+ /* WATCH IT 2!: Userdef struct init see do_versions_userdef() above! */
+
+ /* don't forget to set version number in BKE_blender_version.h! */
+
+ main->is_locked_for_linking = false;
+}
+
+static void do_versions_after_linking(Main *main, ReportList *reports)
+{
+ CLOG_INFO(&LOG,
+ 2,
+ "Processing %s (%s), %d.%d",
+ main->curlib ? main->curlib->filepath : main->filepath,
+ main->curlib ? "LIB" : "MAIN",
+ main->versionfile,
+ main->subversionfile);
+
+ /* Don't allow versioning to create new data-blocks. */
+ main->is_locked_for_linking = true;
+
+ do_versions_after_linking_250(main);
+ do_versions_after_linking_260(main);
+ do_versions_after_linking_270(main);
+ do_versions_after_linking_280(main, reports);
+ do_versions_after_linking_290(main, reports);
+ do_versions_after_linking_300(main, reports);
+ do_versions_after_linking_cycles(main);
+
+ main->is_locked_for_linking = false;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read Library Data Block (all)
+ * \{ */
+
+static void lib_link_all(FileData *fd, Main *bmain)
+{
+ const bool do_partial_undo = (fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0;
+
+ BlendLibReader reader = {fd, bmain};
+
+ ID *id;
+ FOREACH_MAIN_ID_BEGIN (bmain, id) {
+ if ((id->tag & LIB_TAG_NEED_LINK) == 0) {
+ /* This ID does not need liblink, just skip to next one. */
+ continue;
+ }
+
+ if ((fd->flags & FD_FLAGS_IS_MEMFILE) && GS(id->name) == ID_WM) {
+ /* No load UI for undo memfiles.
+ * Only WM currently, SCR needs it still (see below), and so does WS? */
+ continue;
+ }
+
+ if ((fd->flags & FD_FLAGS_IS_MEMFILE) && do_partial_undo &&
+ (id->tag & LIB_TAG_UNDO_OLD_ID_REUSED) != 0) {
+ /* This ID has been re-used from 'old' bmain. Since it was therefore unchanged across
+ * current undo step, and old IDs re-use their old memory address, we do not need to liblink
+ * it at all. */
+ continue;
+ }
+
+ lib_link_id(&reader, id);
+
+ const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
+ if (id_type->blend_read_lib != nullptr) {
+ id_type->blend_read_lib(&reader, id);
+ }
+
+ if (GS(id->name) == ID_LI) {
+ lib_link_library(&reader, (Library *)id); /* Only init users. */
+ }
+
+ id->tag &= ~LIB_TAG_NEED_LINK;
+
+ /* Some data that should be persistent, like the 3DCursor or the tool settings, are
+ * stored in IDs affected by undo, like Scene. So this requires some specific handling. */
+ if (id_type->blend_read_undo_preserve != nullptr && id->orig_id != nullptr) {
+ id_type->blend_read_undo_preserve(&reader, id, id->orig_id);
+ }
+ }
+ FOREACH_MAIN_ID_END;
+
+ /* Cleanup `ID.orig_id`, this is now reserved for depsgraph/COW usage only. */
+ FOREACH_MAIN_ID_BEGIN (bmain, id) {
+ id->orig_id = nullptr;
+ }
+ FOREACH_MAIN_ID_END;
+
+#ifndef NDEBUG
+ /* Double check we do not have any 'need link' tag remaining, this should never be the case once
+ * this function has run. */
+ FOREACH_MAIN_ID_BEGIN (bmain, id) {
+ BLI_assert((id->tag & LIB_TAG_NEED_LINK) == 0);
+ }
+ FOREACH_MAIN_ID_END;
+#endif
+}
+
+/**
+ * Checks to perform after `lib_link_all`.
+ * Those operations cannot perform properly in a split bmain case, since some data from other
+ * bmain's (aka libraries) may not have been processed yet.
+ */
+static void after_liblink_merged_bmain_process(Main *bmain)
+{
+ /* We only expect a merged Main here, not a split one. */
+ BLI_assert((bmain->prev == nullptr) && (bmain->next == nullptr));
+
+ /* Check for possible cycles in scenes' 'set' background property. */
+ lib_link_scenes_check_set(bmain);
+
+ /* We could integrate that to mesh/curve/lattice lib_link, but this is really cheap process,
+ * so simpler to just use it directly in this single call. */
+ BLO_main_validate_shapekeys(bmain, nullptr);
+
+ /* We have to rebuild that runtime information *after* all data-blocks have been properly linked.
+ */
+ BKE_main_collections_parent_relations_rebuild(bmain);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read User Preferences
+ * \{ */
+
+static void direct_link_keymapitem(BlendDataReader *reader, wmKeyMapItem *kmi)
+{
+ BLO_read_data_address(reader, &kmi->properties);
+ IDP_BlendDataRead(reader, &kmi->properties);
+ kmi->ptr = nullptr;
+ kmi->flag &= ~KMI_UPDATE;
+}
+
+static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead)
+{
+ UserDef *user;
+ bfd->user = user = static_cast<UserDef *>(read_struct(fd, bhead, "user def"));
+
+ /* User struct has separate do-version handling */
+ user->versionfile = bfd->main->versionfile;
+ user->subversionfile = bfd->main->subversionfile;
+
+ /* read all data into fd->datamap */
+ bhead = read_data_into_datamap(fd, bhead, "user def");
+
+ BlendDataReader reader_ = {fd};
+ BlendDataReader *reader = &reader_;
+
+ BLO_read_list(reader, &user->themes);
+ BLO_read_list(reader, &user->user_keymaps);
+ BLO_read_list(reader, &user->user_keyconfig_prefs);
+ BLO_read_list(reader, &user->user_menus);
+ BLO_read_list(reader, &user->addons);
+ BLO_read_list(reader, &user->autoexec_paths);
+ BLO_read_list(reader, &user->asset_libraries);
+
+ LISTBASE_FOREACH (wmKeyMap *, keymap, &user->user_keymaps) {
+ keymap->modal_items = nullptr;
+ keymap->poll = nullptr;
+ keymap->flag &= ~KEYMAP_UPDATE;
+
+ BLO_read_list(reader, &keymap->diff_items);
+ BLO_read_list(reader, &keymap->items);
+
+ LISTBASE_FOREACH (wmKeyMapDiffItem *, kmdi, &keymap->diff_items) {
+ BLO_read_data_address(reader, &kmdi->remove_item);
+ BLO_read_data_address(reader, &kmdi->add_item);
+
+ if (kmdi->remove_item) {
+ direct_link_keymapitem(reader, kmdi->remove_item);
+ }
+ if (kmdi->add_item) {
+ direct_link_keymapitem(reader, kmdi->add_item);
+ }
+ }
+
+ LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
+ direct_link_keymapitem(reader, kmi);
+ }
+ }
+
+ LISTBASE_FOREACH (wmKeyConfigPref *, kpt, &user->user_keyconfig_prefs) {
+ BLO_read_data_address(reader, &kpt->prop);
+ IDP_BlendDataRead(reader, &kpt->prop);
+ }
+
+ LISTBASE_FOREACH (bUserMenu *, um, &user->user_menus) {
+ BLO_read_list(reader, &um->items);
+ LISTBASE_FOREACH (bUserMenuItem *, umi, &um->items) {
+ if (umi->type == USER_MENU_TYPE_OPERATOR) {
+ bUserMenuItem_Op *umi_op = (bUserMenuItem_Op *)umi;
+ BLO_read_data_address(reader, &umi_op->prop);
+ IDP_BlendDataRead(reader, &umi_op->prop);
+ }
+ }
+ }
+
+ LISTBASE_FOREACH (bAddon *, addon, &user->addons) {
+ BLO_read_data_address(reader, &addon->prop);
+ IDP_BlendDataRead(reader, &addon->prop);
+ }
+
+ /* XXX */
+ user->uifonts.first = user->uifonts.last = nullptr;
+
+ BLO_read_list(reader, &user->uistyles);
+
+ /* Don't read the active app template, use the default one. */
+ user->app_template[0] = '\0';
+
+ /* Clear runtime data. */
+ user->runtime.is_dirty = false;
+ user->edit_studio_light = 0;
+
+ /* free fd->datamap again */
+ oldnewmap_clear(fd->datamap);
+
+ return bhead;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Read File (Internal)
+ * \{ */
+
+BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
+{
+ BHead *bhead = blo_bhead_first(fd);
+ BlendFileData *bfd;
+ ListBase mainlist = {nullptr, nullptr};
+
+ if (fd->flags & FD_FLAGS_IS_MEMFILE) {
+ CLOG_INFO(&LOG_UNDO, 2, "UNDO: read step");
+ }
+
+ bfd = static_cast<BlendFileData *>(MEM_callocN(sizeof(BlendFileData), "blendfiledata"));
+
+ bfd->main = BKE_main_new();
+ bfd->main->versionfile = fd->fileversion;
+
+ bfd->type = BLENFILETYPE_BLEND;
+
+ if ((fd->skip_flags & BLO_READ_SKIP_DATA) == 0) {
+ BLI_addtail(&mainlist, bfd->main);
+ fd->mainlist = &mainlist;
+ STRNCPY(bfd->main->filepath, filepath);
+ }
+
+ if (G.background) {
+ /* We only read & store .blend thumbnail in background mode
+ * (because we cannot re-generate it, no OpenGL available).
+ */
+ const int *data = read_file_thumbnail(fd);
+
+ if (data) {
+ const int width = data[0];
+ const int height = data[1];
+ if (BLEN_THUMB_MEMSIZE_IS_VALID(width, height)) {
+ const size_t data_size = BLEN_THUMB_MEMSIZE(width, height);
+ bfd->main->blen_thumb = static_cast<BlendThumbnail *>(MEM_mallocN(data_size, __func__));
+
+ BLI_assert((data_size - sizeof(*bfd->main->blen_thumb)) ==
+ (BLEN_THUMB_MEMSIZE_FILE(width, height) - (sizeof(*data) * 2)));
+ bfd->main->blen_thumb->width = width;
+ bfd->main->blen_thumb->height = height;
+ memcpy(bfd->main->blen_thumb->rect, &data[2], data_size - sizeof(*bfd->main->blen_thumb));
+ }
+ }
+ }
+
+ while (bhead) {
+ switch (bhead->code) {
+ case DATA:
+ case DNA1:
+ case TEST: /* used as preview since 2.5x */
+ case REND:
+ bhead = blo_bhead_next(fd, bhead);
+ break;
+ case GLOB:
+ bhead = read_global(bfd, fd, bhead);
+ break;
+ case USER:
+ if (fd->skip_flags & BLO_READ_SKIP_USERDEF) {
+ bhead = blo_bhead_next(fd, bhead);
+ }
+ else {
+ bhead = read_userdef(bfd, fd, bhead);
+ }
+ break;
+ case ENDB:
+ bhead = nullptr;
+ break;
+
+ case ID_LINK_PLACEHOLDER:
+ if (fd->skip_flags & BLO_READ_SKIP_DATA) {
+ bhead = blo_bhead_next(fd, bhead);
+ }
+ else {
+ /* Add link placeholder to the main of the library it belongs to.
+ * The library is the most recently loaded ID_LI block, according
+ * to the file format definition. So we can use the entry at the
+ * end of mainlist, added in direct_link_library. */
+ Main *libmain = static_cast<Main *>(mainlist.last);
+ bhead = read_libblock(fd, libmain, bhead, 0, true, nullptr);
+ }
+ break;
+ /* in 2.50+ files, the file identifier for screens is patched, forward compatibility */
+ case ID_SCRN:
+ bhead->code = ID_SCR;
+ /* pass on to default */
+ ATTR_FALLTHROUGH;
+ default:
+ if (fd->skip_flags & BLO_READ_SKIP_DATA) {
+ bhead = blo_bhead_next(fd, bhead);
+ }
+ else {
+ bhead = read_libblock(fd, bfd->main, bhead, LIB_TAG_LOCAL, false, nullptr);
+ }
+ }
+ }
+
+ /* do before read_libraries, but skip undo case */
+ if ((fd->flags & FD_FLAGS_IS_MEMFILE) == 0) {
+ if ((fd->skip_flags & BLO_READ_SKIP_DATA) == 0) {
+ do_versions(fd, nullptr, bfd->main);
+ }
+
+ if ((fd->skip_flags & BLO_READ_SKIP_USERDEF) == 0) {
+ do_versions_userdef(fd, bfd);
+ }
+ }
+
+ if ((fd->skip_flags & BLO_READ_SKIP_DATA) == 0) {
+ fd->reports->duration.libraries = PIL_check_seconds_timer();
+ read_libraries(fd, &mainlist);
+
+ blo_join_main(&mainlist);
+
+ lib_link_all(fd, bfd->main);
+ after_liblink_merged_bmain_process(bfd->main);
+
+ fd->reports->duration.libraries = PIL_check_seconds_timer() - fd->reports->duration.libraries;
+
+ /* Skip in undo case. */
+ if ((fd->flags & FD_FLAGS_IS_MEMFILE) == 0) {
+ /* Note that we can't recompute user-counts at this point in undo case, we play too much with
+ * IDs from different memory realms, and Main database is not in a fully valid state yet.
+ */
+ /* Some versioning code does expect some proper user-reference-counting, e.g. in conversion
+ * from groups to collections... We could optimize out that first call when we are reading a
+ * current version file, but again this is really not a bottle neck currently.
+ * So not worth it. */
+ BKE_main_id_refcount_recompute(bfd->main, false);
+
+ /* Yep, second splitting... but this is a very cheap operation, so no big deal. */
+ blo_split_main(&mainlist, bfd->main);
+ LISTBASE_FOREACH (Main *, mainvar, &mainlist) {
+ BLI_assert(mainvar->versionfile != 0);
+ do_versions_after_linking(mainvar, fd->reports->reports);
+ }
+ blo_join_main(&mainlist);
+
+ /* And we have to compute those user-reference-counts again, as `do_versions_after_linking()`
+ * does not always properly handle user counts, and/or that function does not take into
+ * account old, deprecated data. */
+ BKE_main_id_refcount_recompute(bfd->main, false);
+ }
+
+ /* After all data has been read and versioned, uses LIB_TAG_NEW. Theoretically this should
+ * not be calculated in the undo case, but it is currently needed even on undo to recalculate
+ * a cache. */
+ ntreeUpdateAllNew(bfd->main);
+
+ placeholders_ensure_valid(bfd->main);
+
+ BKE_main_id_tag_all(bfd->main, LIB_TAG_NEW, false);
+
+ /* Now that all our data-blocks are loaded,
+ * we can re-generate overrides from their references. */
+ if ((fd->flags & FD_FLAGS_IS_MEMFILE) == 0) {
+ /* Do not apply in undo case! */
+ fd->reports->duration.lib_overrides = PIL_check_seconds_timer();
+
+ BKE_lib_override_library_main_validate(bfd->main, fd->reports->reports);
+ BKE_lib_override_library_main_update(bfd->main);
+
+ fd->reports->duration.lib_overrides = PIL_check_seconds_timer() -
+ fd->reports->duration.lib_overrides;
+ }
+
+ BKE_collections_after_lib_link(bfd->main);
+
+ /* Make all relative paths, relative to the open blend file. */
+ fix_relpaths_library(fd->relabase, bfd->main);
+
+ link_global(fd, bfd); /* as last */
+ }
+
+ fd->mainlist = nullptr; /* Safety, this is local variable, shall not be used afterward. */
+
+ BLI_assert(bfd->main->id_map == nullptr);
+
+ return bfd;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Library Linking
+ *
+ * Also used for append.
+ * \{ */
+
+struct BHeadSort {
+ BHead *bhead;
+ const void *old;
+};
+
+static int verg_bheadsort(const void *v1, const void *v2)
+{
+ const BHeadSort *x1 = static_cast<const BHeadSort *>(v1),
+ *x2 = static_cast<const BHeadSort *>(v2);
+
+ if (x1->old > x2->old) {
+ return 1;
+ }
+ if (x1->old < x2->old) {
+ return -1;
+ }
+ return 0;
+}
+
+static void sort_bhead_old_map(FileData *fd)
+{
+ BHead *bhead;
+ BHeadSort *bhs;
+ int tot = 0;
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ tot++;
+ }
+
+ fd->tot_bheadmap = tot;
+ if (tot == 0) {
+ return;
+ }
+
+ bhs = fd->bheadmap = static_cast<BHeadSort *>(
+ MEM_malloc_arrayN(tot, sizeof(BHeadSort), "BHeadSort"));
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead), bhs++) {
+ bhs->bhead = bhead;
+ bhs->old = bhead->old;
+ }
+
+ qsort(fd->bheadmap, tot, sizeof(BHeadSort), verg_bheadsort);
+}
+
+static BHead *find_previous_lib(FileData *fd, BHead *bhead)
+{
+ /* Skip library data-blocks in undo, see comment in read_libblock. */
+ if (fd->flags & FD_FLAGS_IS_MEMFILE) {
+ return nullptr;
+ }
+
+ for (; bhead; bhead = blo_bhead_prev(fd, bhead)) {
+ if (bhead->code == ID_LI) {
+ break;
+ }
+ }
+
+ return bhead;
+}
+
+static BHead *find_bhead(FileData *fd, void *old)
+{
+#if 0
+ BHead* bhead;
+#endif
+ BHeadSort *bhs, bhs_s;
+
+ if (!old) {
+ return nullptr;
+ }
+
+ if (fd->bheadmap == nullptr) {
+ sort_bhead_old_map(fd);
+ }
+
+ bhs_s.old = old;
+ bhs = static_cast<BHeadSort *>(
+ bsearch(&bhs_s, fd->bheadmap, fd->tot_bheadmap, sizeof(BHeadSort), verg_bheadsort));
+
+ if (bhs) {
+ return bhs->bhead;
+ }
+
+#if 0
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ if (bhead->old == old) {
+ return bhead;
+ }
+ }
+#endif
+
+ return nullptr;
+}
+
+static BHead *find_bhead_from_code_name(FileData *fd, const short idcode, const char *name)
+{
+#ifdef USE_GHASH_BHEAD
+
+ char idname_full[MAX_ID_NAME];
+
+ *((short *)idname_full) = idcode;
+ BLI_strncpy(idname_full + 2, name, sizeof(idname_full) - 2);
+
+ return static_cast<BHead *>(BLI_ghash_lookup(fd->bhead_idname_hash, idname_full));
+
+#else
+ BHead *bhead;
+
+ for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
+ if (bhead->code == idcode) {
+ const char *idname_test = blo_bhead_id_name(fd, bhead);
+ if (STREQ(idname_test + 2, name)) {
+ return bhead;
+ }
+ }
+ else if (bhead->code == ENDB) {
+ break;
+ }
+ }
+
+ return nullptr;
+#endif
+}
+
+static BHead *find_bhead_from_idname(FileData *fd, const char *idname)
+{
+#ifdef USE_GHASH_BHEAD
+ return static_cast<BHead *>(BLI_ghash_lookup(fd->bhead_idname_hash, idname));
+#else
+ return find_bhead_from_code_name(fd, GS(idname), idname + 2);
+#endif
+}
+
+static ID *is_yet_read(FileData *fd, Main *mainvar, BHead *bhead)
+{
+ if (mainvar->id_map == nullptr) {
+ mainvar->id_map = BKE_main_idmap_create(mainvar, false, nullptr, MAIN_IDMAP_TYPE_NAME);
+ }
+ BLI_assert(BKE_main_idmap_main_get(mainvar->id_map) == mainvar);
+
+ const char *idname = blo_bhead_id_name(fd, bhead);
+
+ ID *id = BKE_main_idmap_lookup_name(mainvar->id_map, GS(idname), idname + 2, mainvar->curlib);
+ BLI_assert(id == BLI_findstring(which_libbase(mainvar, GS(idname)), idname, offsetof(ID, name)));
+ return id;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Library Linking (expand pointers)
+ * \{ */
+
+static void expand_doit_library(void *fdhandle, Main *mainvar, void *old)
+{
+ FileData *fd = static_cast<FileData *>(fdhandle);
+
+ BHead *bhead = find_bhead(fd, old);
+ if (bhead == nullptr) {
+ return;
+ }
+
+ if (bhead->code == ID_LINK_PLACEHOLDER) {
+ /* Placeholder link to data-block in another library. */
+ BHead *bheadlib = find_previous_lib(fd, bhead);
+ if (bheadlib == nullptr) {
+ return;
+ }
+
+ Library *lib = static_cast<Library *>(read_struct(fd, bheadlib, "Library"));
+ Main *libmain = blo_find_main(fd, lib->filepath, fd->relabase);
+
+ if (libmain->curlib == nullptr) {
+ const char *idname = blo_bhead_id_name(fd, bhead);
+
+ BLO_reportf_wrap(fd->reports,
+ RPT_WARNING,
+ TIP_("LIB: Data refers to main .blend file: '%s' from %s"),
+ idname,
+ mainvar->curlib->filepath_abs);
+ return;
+ }
+
+ ID *id = is_yet_read(fd, libmain, bhead);
+
+ if (id == nullptr) {
+ /* ID has not been read yet, add placeholder to the main of the
+ * library it belongs to, so that it will be read later. */
+ read_libblock(fd, libmain, bhead, fd->id_tag_extra | LIB_TAG_INDIRECT, false, &id);
+ BLI_assert(id != nullptr);
+ id_sort_by_name(which_libbase(libmain, GS(id->name)), id, static_cast<ID *>(id->prev));
+
+ /* commented because this can print way too much */
+ // if (G.debug & G_DEBUG) printf("expand_doit: other lib %s\n", lib->filepath);
+
+ /* for outliner dependency only */
+ libmain->curlib->parent = mainvar->curlib;
+ }
+ else {
+ /* Convert any previously read weak link to regular link
+ * to signal that we want to read this data-block. */
+ if (id->tag & LIB_TAG_ID_LINK_PLACEHOLDER) {
+ id->flag &= ~LIB_INDIRECT_WEAK_LINK;
+ }
+
+ /* "id" is either a placeholder or real ID that is already in the
+ * main of the library (A) it belongs to. However it might have been
+ * put there by another library (C) which only updated its own
+ * fd->libmap. In that case we also need to update the fd->libmap
+ * of the current library (B) so we can find it for lookups.
+ *
+ * An example of such a setup is:
+ * (A) tree.blend: contains Tree object.
+ * (B) forest.blend: contains Forest collection linking in Tree from tree.blend.
+ * (C) shot.blend: links in both Tree from tree.blend and Forest from forest.blend.
+ */
+ oldnewmap_lib_insert(fd, bhead->old, id, bhead->code);
+
+ /* If "id" is a real data-block and not a placeholder, we need to
+ * update fd->libmap to replace ID_LINK_PLACEHOLDER with the real
+ * ID_* code.
+ *
+ * When the real ID is read this replacement happens for all
+ * libraries read so far, but not for libraries that have not been
+ * read yet at that point. */
+ change_link_placeholder_to_real_ID_pointer_fd(fd, bhead->old, id);
+
+ /* Commented because this can print way too much. */
+#if 0
+ if (G.debug & G_DEBUG) {
+ printf("expand_doit: already linked: %s lib: %s\n", id->name, lib->filepath);
+ }
+#endif
+ }
+
+ MEM_freeN(lib);
+ }
+ else {
+ /* Data-block in same library. */
+ /* In 2.50+ file identifier for screens is patched, forward compatibility. */
+ if (bhead->code == ID_SCRN) {
+ bhead->code = ID_SCR;
+ }
+
+ ID *id = is_yet_read(fd, mainvar, bhead);
+ if (id == nullptr) {
+ read_libblock(fd,
+ mainvar,
+ bhead,
+ fd->id_tag_extra | LIB_TAG_NEED_EXPAND | LIB_TAG_INDIRECT,
+ false,
+ &id);
+ BLI_assert(id != nullptr);
+ id_sort_by_name(which_libbase(mainvar, GS(id->name)), id, static_cast<ID *>(id->prev));
+ }
+ else {
+ /* Convert any previously read weak link to regular link
+ * to signal that we want to read this data-block. */
+ if (id->tag & LIB_TAG_ID_LINK_PLACEHOLDER) {
+ id->flag &= ~LIB_INDIRECT_WEAK_LINK;
+ }
+
+ /* this is actually only needed on UI call? when ID was already read before,
+ * and another append happens which invokes same ID...
+ * in that case the lookup table needs this entry */
+ oldnewmap_lib_insert(fd, bhead->old, id, bhead->code);
+ /* commented because this can print way too much */
+ // if (G.debug & G_DEBUG) printf("expand: already read %s\n", id->name);
+ }
+ }
+}
+
+static BLOExpandDoitCallback expand_doit;
+
+static void expand_id(BlendExpander *expander, ID *id);
+
+static void expand_id_embedded_id(BlendExpander *expander, ID *id)
+{
+ /* Handle 'private IDs'. */
+ bNodeTree *nodetree = ntreeFromID(id);
+ if (nodetree != nullptr) {
+ expand_id(expander, &nodetree->id);
+ ntreeBlendReadExpand(expander, nodetree);
+ }
+
+ if (GS(id->name) == ID_SCE) {
+ Scene *scene = (Scene *)id;
+ if (scene->master_collection != nullptr) {
+ expand_id(expander, &scene->master_collection->id);
+ BKE_collection_blend_read_expand(expander, scene->master_collection);
+ }
+ }
+}
+
+static void expand_id(BlendExpander *expander, ID *id)
+{
+ IDP_BlendReadExpand(expander, id->properties);
+
+ if (id->override_library) {
+ BLO_expand(expander, id->override_library->reference);
+ BLO_expand(expander, id->override_library->storage);
+ }
+
+ AnimData *adt = BKE_animdata_from_id(id);
+ if (adt != nullptr) {
+ BKE_animdata_blend_read_expand(expander, adt);
+ }
+
+ expand_id_embedded_id(expander, id);
+}
+
+void BLO_main_expander(BLOExpandDoitCallback expand_doit_func)
+{
+ expand_doit = expand_doit_func;
+}
+
+void BLO_expand_main(void *fdhandle, Main *mainvar)
+{
+ ListBase *lbarray[INDEX_ID_MAX];
+ FileData *fd = static_cast<FileData *>(fdhandle);
+ ID *id;
+ int a;
+ bool do_it = true;
+
+ BlendExpander expander = {fd, mainvar};
+
+ while (do_it) {
+ do_it = false;
+
+ a = set_listbasepointers(mainvar, lbarray);
+ while (a--) {
+ id = static_cast<ID *>(lbarray[a]->first);
+ while (id) {
+ if (id->tag & LIB_TAG_NEED_EXPAND) {
+ expand_id(&expander, id);
+
+ const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
+ if (id_type->blend_read_expand != nullptr) {
+ id_type->blend_read_expand(&expander, id);
+ }
+
+ do_it = true;
+ id->tag &= ~LIB_TAG_NEED_EXPAND;
+ }
+ id = static_cast<ID *>(id->next);
+ }
+ }
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Library Linking (helper functions)
+ * \{ */
+
+/* returns true if the item was found
+ * but it may already have already been appended/linked */
+static ID *link_named_part(
+ Main *mainl, FileData *fd, const short idcode, const char *name, const int flag)
+{
+ BHead *bhead = find_bhead_from_code_name(fd, idcode, name);
+ ID *id;
+
+ const bool use_placeholders = (flag & BLO_LIBLINK_USE_PLACEHOLDERS) != 0;
+ const bool force_indirect = (flag & BLO_LIBLINK_FORCE_INDIRECT) != 0;
+
+ BLI_assert(BKE_idtype_idcode_is_linkable(idcode) && BKE_idtype_idcode_is_valid(idcode));
+
+ if (bhead) {
+ id = is_yet_read(fd, mainl, bhead);
+ if (id == nullptr) {
+ /* not read yet */
+ const int tag = ((force_indirect ? LIB_TAG_INDIRECT : LIB_TAG_EXTERN) | fd->id_tag_extra);
+ read_libblock(fd, mainl, bhead, tag | LIB_TAG_NEED_EXPAND, false, &id);
+
+ if (id) {
+ /* sort by name in list */
+ ListBase *lb = which_libbase(mainl, idcode);
+ id_sort_by_name(lb, id, nullptr);
+ }
+ }
+ else {
+ /* already linked */
+ CLOG_WARN(&LOG, "Append: ID '%s' is already linked", id->name);
+ oldnewmap_lib_insert(fd, bhead->old, id, bhead->code);
+ if (!force_indirect && (id->tag & LIB_TAG_INDIRECT)) {
+ id->tag &= ~LIB_TAG_INDIRECT;
+ id->flag &= ~LIB_INDIRECT_WEAK_LINK;
+ id->tag |= LIB_TAG_EXTERN;
+ }
+ }
+ }
+ else if (use_placeholders) {
+ /* XXX flag part is weak! */
+ id = create_placeholder(
+ mainl, idcode, name, force_indirect ? LIB_TAG_INDIRECT : LIB_TAG_EXTERN);
+ }
+ else {
+ id = nullptr;
+ }
+
+ /* if we found the id but the id is nullptr, this is really bad */
+ BLI_assert(!((bhead != nullptr) && (id == nullptr)));
+
+ return id;
+}
+
+ID *BLO_library_link_named_part(Main *mainl,
+ BlendHandle **bh,
+ const short idcode,
+ const char *name,
+ const LibraryLink_Params *params)
+{
+ FileData *fd = (FileData *)(*bh);
+ return link_named_part(mainl, fd, idcode, name, params->flag);
+}
+
+/* common routine to append/link something from a library */
+
+static Main *library_link_begin(Main *mainvar,
+ FileData **fd,
+ const char *filepath,
+ const int id_tag_extra)
+{
+ Main *mainl;
+
+ /* Only allow specific tags to be set as extra,
+ * otherwise this could conflict with library loading logic.
+ * Other flags can be added here, as long as they are safe. */
+ BLI_assert((id_tag_extra & ~LIB_TAG_TEMP_MAIN) == 0);
+
+ (*fd)->id_tag_extra = id_tag_extra;
+
+ (*fd)->mainlist = static_cast<ListBase *>(MEM_callocN(sizeof(ListBase), "FileData.mainlist"));
+
+ /* make mains */
+ blo_split_main((*fd)->mainlist, mainvar);
+
+ /* which one do we need? */
+ mainl = blo_find_main(*fd, filepath, BKE_main_blendfile_path(mainvar));
+
+ /* needed for do_version */
+ mainl->versionfile = (*fd)->fileversion;
+ read_file_version(*fd, mainl);
+#ifdef USE_GHASH_BHEAD
+ read_file_bhead_idname_map_create(*fd);
+#endif
+
+ return mainl;
+}
+
+void BLO_library_link_params_init(LibraryLink_Params *params,
+ Main *bmain,
+ const int flag,
+ const int id_tag_extra)
+{
+ memset(params, 0, sizeof(*params));
+ params->bmain = bmain;
+ params->flag = flag;
+ params->id_tag_extra = id_tag_extra;
+}
+
+void BLO_library_link_params_init_with_context(LibraryLink_Params *params,
+ Main *bmain,
+ const int flag,
+ const int id_tag_extra,
+ /* Context arguments. */
+ Scene *scene,
+ ViewLayer *view_layer,
+ const View3D *v3d)
+{
+ BLO_library_link_params_init(params, bmain, flag, id_tag_extra);
+ if (scene != nullptr) {
+ params->context.scene = scene;
+ params->context.view_layer = view_layer;
+ params->context.v3d = v3d;
+ }
+}
+
+Main *BLO_library_link_begin(BlendHandle **bh,
+ const char *filepath,
+ const LibraryLink_Params *params)
+{
+ FileData *fd = (FileData *)(*bh);
+ return library_link_begin(params->bmain, &fd, filepath, params->id_tag_extra);
+}
+
+static void split_main_newid(Main *mainptr, Main *main_newid)
+{
+ /* We only copy the necessary subset of data in this temp main. */
+ main_newid->versionfile = mainptr->versionfile;
+ main_newid->subversionfile = mainptr->subversionfile;
+ STRNCPY(main_newid->filepath, mainptr->filepath);
+ main_newid->curlib = mainptr->curlib;
+
+ ListBase *lbarray[INDEX_ID_MAX];
+ ListBase *lbarray_newid[INDEX_ID_MAX];
+ int i = set_listbasepointers(mainptr, lbarray);
+ set_listbasepointers(main_newid, lbarray_newid);
+ while (i--) {
+ BLI_listbase_clear(lbarray_newid[i]);
+
+ LISTBASE_FOREACH_MUTABLE (ID *, id, lbarray[i]) {
+ if (id->tag & LIB_TAG_NEW) {
+ BLI_remlink(lbarray[i], id);
+ BLI_addtail(lbarray_newid[i], id);
+ }
+ }
+ }
+}
+
+static void library_link_end(Main *mainl, FileData **fd, const int flag)
+{
+ Main *mainvar;
+ Library *curlib;
+
+ if (mainl->id_map == nullptr) {
+ mainl->id_map = BKE_main_idmap_create(mainl, false, nullptr, MAIN_IDMAP_TYPE_NAME);
+ }
+
+ /* expander now is callback function */
+ BLO_main_expander(expand_doit_library);
+
+ /* make main consistent */
+ BLO_expand_main(*fd, mainl);
+
+ /* Do this when expand found other libraries. */
+ read_libraries(*fd, (*fd)->mainlist);
+
+ curlib = mainl->curlib;
+
+ /* make the lib path relative if required */
+ if (flag & FILE_RELPATH) {
+ /* use the full path, this could have been read by other library even */
+ BLI_strncpy(curlib->filepath, curlib->filepath_abs, sizeof(curlib->filepath));
+
+ /* uses current .blend file as reference */
+ BLI_path_rel(curlib->filepath, BKE_main_blendfile_path_from_global());
+ }
+
+ blo_join_main((*fd)->mainlist);
+ mainvar = static_cast<Main *>((*fd)->mainlist->first);
+ mainl = nullptr; /* blo_join_main free's mainl, can't use anymore */
+
+ lib_link_all(*fd, mainvar);
+ after_liblink_merged_bmain_process(mainvar);
+
+ /* Some versioning code does expect some proper userrefcounting, e.g. in conversion from
+ * groups to collections... We could optimize out that first call when we are reading a
+ * current version file, but again this is really not a bottle neck currently. so not worth
+ * it. */
+ BKE_main_id_refcount_recompute(mainvar, false);
+
+ BKE_collections_after_lib_link(mainvar);
+
+ /* Yep, second splitting... but this is a very cheap operation, so no big deal. */
+ blo_split_main((*fd)->mainlist, mainvar);
+ Main *main_newid = BKE_main_new();
+ for (mainvar = ((Main *)(*fd)->mainlist->first)->next; mainvar; mainvar = mainvar->next) {
+ BLI_assert(mainvar->versionfile != 0);
+ /* We need to split out IDs already existing,
+ * or they will go again through do_versions - bad, very bad! */
+ split_main_newid(mainvar, main_newid);
+
+ do_versions_after_linking(main_newid, (*fd)->reports->reports);
+
+ add_main_to_main(mainvar, main_newid);
+ }
+
+ blo_join_main((*fd)->mainlist);
+ mainvar = static_cast<Main *>((*fd)->mainlist->first);
+ MEM_freeN((*fd)->mainlist);
+
+ /* This does not take into account old, deprecated data, so we also have to do it after
+ * `do_versions_after_linking()`. */
+ BKE_main_id_refcount_recompute(mainvar, false);
+
+ /* After all data has been read and versioned, uses LIB_TAG_NEW. */
+ ntreeUpdateAllNew(mainvar);
+
+ placeholders_ensure_valid(mainvar);
+
+ /* Apply overrides of newly linked data if needed. Already existing IDs need to split out, to
+ * avoid re-applying their own overrides. */
+ BLI_assert(BKE_main_is_empty(main_newid));
+ split_main_newid(mainvar, main_newid);
+ BKE_lib_override_library_main_validate(main_newid, (*fd)->reports->reports);
+ BKE_lib_override_library_main_update(main_newid);
+ add_main_to_main(mainvar, main_newid);
+ BKE_main_free(main_newid);
+
+ BKE_main_id_tag_all(mainvar, LIB_TAG_NEW, false);
+
+ /* Make all relative paths, relative to the open blend file. */
+ fix_relpaths_library(BKE_main_blendfile_path(mainvar), mainvar);
+
+ /* patch to prevent switch_endian happens twice */
+ if ((*fd)->flags & FD_FLAGS_SWITCH_ENDIAN) {
+ blo_filedata_free(*fd);
+ *fd = nullptr;
+ }
+}
+
+void BLO_library_link_end(Main *mainl, BlendHandle **bh, const LibraryLink_Params *params)
+{
+ FileData *fd = (FileData *)(*bh);
+ library_link_end(mainl, &fd, params->flag);
+ *bh = (BlendHandle *)fd;
+}
+
+void *BLO_library_read_struct(FileData *fd, BHead *bh, const char *blockname)
+{
+ return read_struct(fd, bh, blockname);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Library Reading
+ * \{ */
+
+static int has_linked_ids_to_read(Main *mainvar)
+{
+ ListBase *lbarray[INDEX_ID_MAX];
+ int a = set_listbasepointers(mainvar, lbarray);
+
+ while (a--) {
+ LISTBASE_FOREACH (ID *, id, lbarray[a]) {
+ if ((id->tag & LIB_TAG_ID_LINK_PLACEHOLDER) && !(id->flag & LIB_INDIRECT_WEAK_LINK)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static void read_library_linked_id(
+ FileData *basefd, FileData *fd, Main *mainvar, ID *id, ID **r_id)
+{
+ BHead *bhead = nullptr;
+ const bool is_valid = BKE_idtype_idcode_is_linkable(GS(id->name)) ||
+ ((id->tag & LIB_TAG_EXTERN) == 0);
+
+ if (fd) {
+ bhead = find_bhead_from_idname(fd, id->name);
+ }
+
+ if (!is_valid) {
+ BLO_reportf_wrap(basefd->reports,
+ RPT_ERROR,
+ TIP_("LIB: %s: '%s' is directly linked from '%s' (parent '%s'), but is a "
+ "non-linkable data type"),
+ BKE_idtype_idcode_to_name(GS(id->name)),
+ id->name + 2,
+ mainvar->curlib->filepath_abs,
+ library_parent_filepath(mainvar->curlib));
+ }
+
+ id->tag &= ~LIB_TAG_ID_LINK_PLACEHOLDER;
+ id->flag &= ~LIB_INDIRECT_WEAK_LINK;
+
+ if (bhead) {
+ id->tag |= LIB_TAG_NEED_EXPAND;
+ // printf("read lib block %s\n", id->name);
+ read_libblock(fd, mainvar, bhead, id->tag, false, r_id);
+ }
+ else {
+ BLO_reportf_wrap(basefd->reports,
+ RPT_INFO,
+ TIP_("LIB: %s: '%s' missing from '%s', parent '%s'"),
+ BKE_idtype_idcode_to_name(GS(id->name)),
+ id->name + 2,
+ mainvar->curlib->filepath_abs,
+ library_parent_filepath(mainvar->curlib));
+ basefd->reports->count.missing_linked_id++;
+
+ /* Generate a placeholder for this ID (simplified version of read_libblock actually...). */
+ if (r_id) {
+ *r_id = is_valid ? create_placeholder(mainvar, GS(id->name), id->name + 2, id->tag) :
+ nullptr;
+ }
+ }
+}
+
+static void read_library_linked_ids(FileData *basefd,
+ FileData *fd,
+ ListBase *mainlist,
+ Main *mainvar)
+{
+ GHash *loaded_ids = BLI_ghash_str_new(__func__);
+
+ ListBase *lbarray[INDEX_ID_MAX];
+ int a = set_listbasepointers(mainvar, lbarray);
+
+ while (a--) {
+ ID *id = static_cast<ID *>(lbarray[a]->first);
+ ListBase pending_free_ids = {nullptr};
+
+ while (id) {
+ ID *id_next = static_cast<ID *>(id->next);
+ if ((id->tag & LIB_TAG_ID_LINK_PLACEHOLDER) && !(id->flag & LIB_INDIRECT_WEAK_LINK)) {
+ BLI_remlink(lbarray[a], id);
+ if (mainvar->id_map != nullptr) {
+ BKE_main_idmap_remove_id(mainvar->id_map, id);
+ }
+
+ /* When playing with lib renaming and such, you may end with cases where
+ * you have more than one linked ID of the same data-block from same
+ * library. This is absolutely horrible, hence we use a ghash to ensure
+ * we go back to a single linked data when loading the file. */
+ ID **realid = nullptr;
+ if (!BLI_ghash_ensure_p(loaded_ids, id->name, (void ***)&realid)) {
+ read_library_linked_id(basefd, fd, mainvar, id, realid);
+ }
+
+ /* `realid` shall never be nullptr - unless some source file/lib is broken
+ * (known case: some directly linked shape-key from a missing lib...). */
+ // BLI_assert(*realid != nullptr);
+
+ /* Now that we have a real ID, replace all pointers to placeholders in
+ * fd->libmap with pointers to the real data-blocks. We do this for all
+ * libraries since multiple might be referencing this ID. */
+ change_link_placeholder_to_real_ID_pointer(mainlist, basefd, id, *realid);
+
+ /* We cannot free old lib-ref placeholder ID here anymore, since we use
+ * its name as key in loaded_ids hash. */
+ BLI_addtail(&pending_free_ids, id);
+ }
+ id = id_next;
+ }
+
+ /* Clear GHash and free link placeholder IDs of the current type. */
+ BLI_ghash_clear(loaded_ids, nullptr, nullptr);
+ BLI_freelistN(&pending_free_ids);
+ }
+
+ BLI_ghash_free(loaded_ids, nullptr, nullptr);
+}
+
+static void read_library_clear_weak_links(FileData *basefd, ListBase *mainlist, Main *mainvar)
+{
+ /* Any remaining weak links at this point have been lost, silently drop
+ * those by setting them to nullptr pointers. */
+ ListBase *lbarray[INDEX_ID_MAX];
+ int a = set_listbasepointers(mainvar, lbarray);
+
+ while (a--) {
+ ID *id = static_cast<ID *>(lbarray[a]->first);
+
+ while (id) {
+ ID *id_next = static_cast<ID *>(id->next);
+ if ((id->tag & LIB_TAG_ID_LINK_PLACEHOLDER) && (id->flag & LIB_INDIRECT_WEAK_LINK)) {
+ CLOG_INFO(&LOG, 3, "Dropping weak link to '%s'", id->name);
+ change_link_placeholder_to_real_ID_pointer(mainlist, basefd, id, nullptr);
+ BLI_freelinkN(lbarray[a], id);
+ }
+ id = id_next;
+ }
+ }
+}
+
+static FileData *read_library_file_data(FileData *basefd,
+ ListBase *mainlist,
+ Main *mainl,
+ Main *mainptr)
+{
+ FileData *fd = mainptr->curlib->filedata;
+
+ if (fd != nullptr) {
+ /* File already open. */
+ return fd;
+ }
+
+ if (mainptr->curlib->packedfile) {
+ /* Read packed file. */
+ PackedFile *pf = mainptr->curlib->packedfile;
+
+ BLO_reportf_wrap(basefd->reports,
+ RPT_INFO,
+ TIP_("Read packed library: '%s', parent '%s'"),
+ mainptr->curlib->filepath,
+ library_parent_filepath(mainptr->curlib));
+ fd = blo_filedata_from_memory(pf->data, pf->size, basefd->reports);
+
+ /* Needed for library_append and read_libraries. */
+ BLI_strncpy(fd->relabase, mainptr->curlib->filepath_abs, sizeof(fd->relabase));
+ }
+ else {
+ /* Read file on disk. */
+ BLO_reportf_wrap(basefd->reports,
+ RPT_INFO,
+ TIP_("Read library: '%s', '%s', parent '%s'"),
+ mainptr->curlib->filepath_abs,
+ mainptr->curlib->filepath,
+ library_parent_filepath(mainptr->curlib));
+ fd = blo_filedata_from_file(mainptr->curlib->filepath_abs, basefd->reports);
+ }
+
+ if (fd) {
+ /* Share the mainlist, so all libraries are added immediately in a
+ * single list. It used to be that all FileData's had their own list,
+ * but with indirectly linking this meant we didn't catch duplicate
+ * libraries properly. */
+ fd->mainlist = mainlist;
+
+ fd->reports = basefd->reports;
+
+ if (fd->libmap) {
+ oldnewmap_free(fd->libmap);
+ }
+
+ fd->libmap = oldnewmap_new();
+
+ mainptr->curlib->filedata = fd;
+ mainptr->versionfile = fd->fileversion;
+
+ /* subversion */
+ read_file_version(fd, mainptr);
+#ifdef USE_GHASH_BHEAD
+ read_file_bhead_idname_map_create(fd);
+#endif
+ }
+ else {
+ mainptr->curlib->filedata = nullptr;
+ mainptr->curlib->id.tag |= LIB_TAG_MISSING;
+ /* Set lib version to current main one... Makes assert later happy. */
+ mainptr->versionfile = mainptr->curlib->versionfile = mainl->versionfile;
+ mainptr->subversionfile = mainptr->curlib->subversionfile = mainl->subversionfile;
+ }
+
+ if (fd == nullptr) {
+ BLO_reportf_wrap(
+ basefd->reports, RPT_INFO, TIP_("Cannot find lib '%s'"), mainptr->curlib->filepath_abs);
+ basefd->reports->count.missing_libraries++;
+ }
+
+ return fd;
+}
+
+static void read_libraries(FileData *basefd, ListBase *mainlist)
+{
+ Main *mainl = static_cast<Main *>(mainlist->first);
+ bool do_it = true;
+
+ /* Expander is now callback function. */
+ BLO_main_expander(expand_doit_library);
+
+ /* At this point the base blend file has been read, and each library blend
+ * encountered so far has a main with placeholders for linked data-blocks.
+ *
+ * Now we will read the library blend files and replace the placeholders
+ * with actual data-blocks. We loop over library mains multiple times in
+ * case a library needs to link additional data-blocks from another library
+ * that had been read previously. */
+ while (do_it) {
+ do_it = false;
+
+ /* Loop over mains of all library blend files encountered so far. Note
+ * this list gets longer as more indirectly library blends are found. */
+ for (Main *mainptr = mainl->next; mainptr; mainptr = mainptr->next) {
+ /* Does this library have any more linked data-blocks we need to read? */
+ if (has_linked_ids_to_read(mainptr)) {
+ CLOG_INFO(&LOG,
+ 3,
+ "Reading linked data-blocks from %s (%s)",
+ mainptr->curlib->id.name,
+ mainptr->curlib->filepath);
+
+ /* Open file if it has not been done yet. */
+ FileData *fd = read_library_file_data(basefd, mainlist, mainl, mainptr);
+
+ if (fd) {
+ do_it = true;
+
+ if (mainptr->id_map == nullptr) {
+ mainptr->id_map = BKE_main_idmap_create(mainptr, false, nullptr, MAIN_IDMAP_TYPE_NAME);
+ }
+ }
+
+ /* Read linked data-blocks for each link placeholder, and replace
+ * the placeholder with the real data-block. */
+ read_library_linked_ids(basefd, fd, mainlist, mainptr);
+
+ /* Test if linked data-blocks need to read further linked data-blocks
+ * and create link placeholders for them. */
+ BLO_expand_main(fd, mainptr);
+ }
+ }
+ }
+
+ for (Main *mainptr = mainl->next; mainptr; mainptr = mainptr->next) {
+ /* Drop weak links for which no data-block was found.
+ * Since this can remap pointers in `libmap` of all libraries, it needs to be performed in its
+ * own loop, before any call to `lib_link_all` (and the freeing of the libraries' filedata). */
+ read_library_clear_weak_links(basefd, mainlist, mainptr);
+ }
+
+ Main *main_newid = BKE_main_new();
+ for (Main *mainptr = mainl->next; mainptr; mainptr = mainptr->next) {
+ /* Do versioning for newly added linked data-blocks. If no data-blocks
+ * were read from a library versionfile will still be zero and we can
+ * skip it. */
+ if (mainptr->versionfile) {
+ /* Split out already existing IDs to avoid them going through
+ * do_versions multiple times, which would have bad consequences. */
+ split_main_newid(mainptr, main_newid);
+
+ /* File data can be zero with link/append. */
+ if (mainptr->curlib->filedata) {
+ do_versions(mainptr->curlib->filedata, mainptr->curlib, main_newid);
+ }
+ else {
+ do_versions(basefd, nullptr, main_newid);
+ }
+
+ add_main_to_main(mainptr, main_newid);
+ }
+
+ /* Lib linking. */
+ if (mainptr->curlib->filedata) {
+ lib_link_all(mainptr->curlib->filedata, mainptr);
+ }
+
+ /* NOTE: No need to call #do_versions_after_linking() or #BKE_main_id_refcount_recompute()
+ * here, as this function is only called for library 'subset' data handling, as part of
+ * either full blendfile reading (#blo_read_file_internal()), or library-data linking
+ * (#library_link_end()). */
+
+ /* Free file data we no longer need. */
+ if (mainptr->curlib->filedata) {
+ blo_filedata_free(mainptr->curlib->filedata);
+ }
+ mainptr->curlib->filedata = nullptr;
+ }
+ BKE_main_free(main_newid);
+}
+
+void *BLO_read_get_new_data_address(BlendDataReader *reader, const void *old_address)
+{
+ return newdataadr(reader->fd, old_address);
+}
+
+void *BLO_read_get_new_data_address_no_us(BlendDataReader *reader, const void *old_address)
+{
+ return newdataadr_no_us(reader->fd, old_address);
+}
+
+void *BLO_read_get_new_packed_address(BlendDataReader *reader, const void *old_address)
+{
+ return newpackedadr(reader->fd, old_address);
+}
+
+ID *BLO_read_get_new_id_address(BlendLibReader *reader, Library *lib, ID *id)
+{
+ return static_cast<ID *>(newlibadr(reader->fd, lib, id));
+}
+
+int BLO_read_fileversion_get(BlendDataReader *reader)
+{
+ return reader->fd->fileversion;
+}
+
+bool BLO_read_requires_endian_switch(BlendDataReader *reader)
+{
+ return (reader->fd->flags & FD_FLAGS_SWITCH_ENDIAN) != 0;
+}
+
+void BLO_read_list_cb(BlendDataReader *reader, ListBase *list, BlendReadListFn callback)
+{
+ if (BLI_listbase_is_empty(list)) {
+ return;
+ }
+
+ BLO_read_data_address(reader, &list->first);
+ if (callback != nullptr) {
+ callback(reader, list->first);
+ }
+ Link *ln = static_cast<Link *>(list->first);
+ Link *prev = nullptr;
+ while (ln) {
+ BLO_read_data_address(reader, &ln->next);
+ if (ln->next != nullptr && callback != nullptr) {
+ callback(reader, ln->next);
+ }
+ ln->prev = prev;
+ prev = ln;
+ ln = ln->next;
+ }
+ list->last = prev;
+}
+
+void BLO_read_list(BlendDataReader *reader, ListBase *list)
+{
+ BLO_read_list_cb(reader, list, nullptr);
+}
+
+void BLO_read_int32_array(BlendDataReader *reader, int array_size, int32_t **ptr_p)
+{
+ BLO_read_data_address(reader, ptr_p);
+ if (BLO_read_requires_endian_switch(reader)) {
+ BLI_endian_switch_int32_array(*ptr_p, array_size);
+ }
+}
+
+void BLO_read_uint32_array(BlendDataReader *reader, int array_size, uint32_t **ptr_p)
+{
+ BLO_read_data_address(reader, ptr_p);
+ if (BLO_read_requires_endian_switch(reader)) {
+ BLI_endian_switch_uint32_array(*ptr_p, array_size);
+ }
+}
+
+void BLO_read_float_array(BlendDataReader *reader, int array_size, float **ptr_p)
+{
+ BLO_read_data_address(reader, ptr_p);
+ if (BLO_read_requires_endian_switch(reader)) {
+ BLI_endian_switch_float_array(*ptr_p, array_size);
+ }
+}
+
+void BLO_read_float3_array(BlendDataReader *reader, int array_size, float **ptr_p)
+{
+ BLO_read_float_array(reader, array_size * 3, ptr_p);
+}
+
+void BLO_read_double_array(BlendDataReader *reader, int array_size, double **ptr_p)
+{
+ BLO_read_data_address(reader, ptr_p);
+ if (BLO_read_requires_endian_switch(reader)) {
+ BLI_endian_switch_double_array(*ptr_p, array_size);
+ }
+}
+
+static void convert_pointer_array_64_to_32(BlendDataReader *reader,
+ uint array_size,
+ const uint64_t *src,
+ uint32_t *dst)
+{
+ /* Match pointer conversion rules from bh4_from_bh8 and cast_pointer. */
+ if (BLO_read_requires_endian_switch(reader)) {
+ for (int i = 0; i < array_size; i++) {
+ uint64_t ptr = src[i];
+ BLI_endian_switch_uint64(&ptr);
+ dst[i] = uint32_t(ptr >> 3);
+ }
+ }
+ else {
+ for (int i = 0; i < array_size; i++) {
+ dst[i] = uint32_t(src[i] >> 3);
+ }
+ }
+}
+
+static void convert_pointer_array_32_to_64(BlendDataReader * /*reader*/,
+ uint array_size,
+ const uint32_t *src,
+ uint64_t *dst)
+{
+ /* Match pointer conversion rules from bh8_from_bh4 and cast_pointer_32_to_64. */
+ for (int i = 0; i < array_size; i++) {
+ dst[i] = src[i];
+ }
+}
+
+void BLO_read_pointer_array(BlendDataReader *reader, void **ptr_p)
+{
+ FileData *fd = reader->fd;
+
+ void *orig_array = newdataadr(fd, *ptr_p);
+ if (orig_array == nullptr) {
+ *ptr_p = nullptr;
+ return;
+ }
+
+ int file_pointer_size = fd->filesdna->pointer_size;
+ int current_pointer_size = fd->memsdna->pointer_size;
+
+ /* Over-allocation is fine, but might be better to pass the length as parameter. */
+ int array_size = MEM_allocN_len(orig_array) / file_pointer_size;
+
+ void *final_array = nullptr;
+
+ if (file_pointer_size == current_pointer_size) {
+ /* No pointer conversion necessary. */
+ final_array = orig_array;
+ }
+ else if (file_pointer_size == 8 && current_pointer_size == 4) {
+ /* Convert pointers from 64 to 32 bit. */
+ final_array = MEM_malloc_arrayN(array_size, 4, "new pointer array");
+ convert_pointer_array_64_to_32(
+ reader, array_size, (uint64_t *)orig_array, (uint32_t *)final_array);
+ MEM_freeN(orig_array);
+ }
+ else if (file_pointer_size == 4 && current_pointer_size == 8) {
+ /* Convert pointers from 32 to 64 bit. */
+ final_array = MEM_malloc_arrayN(array_size, 8, "new pointer array");
+ convert_pointer_array_32_to_64(
+ reader, array_size, (uint32_t *)orig_array, (uint64_t *)final_array);
+ MEM_freeN(orig_array);
+ }
+ else {
+ BLI_assert(false);
+ }
+
+ *ptr_p = final_array;
+}
+
+bool BLO_read_data_is_undo(BlendDataReader *reader)
+{
+ return (reader->fd->flags & FD_FLAGS_IS_MEMFILE);
+}
+
+void BLO_read_data_globmap_add(BlendDataReader *reader, void *oldaddr, void *newaddr)
+{
+ oldnewmap_insert(reader->fd->globmap, oldaddr, newaddr, 0);
+}
+
+void BLO_read_glob_list(BlendDataReader *reader, ListBase *list)
+{
+ link_glob_list(reader->fd, list);
+}
+
+BlendFileReadReport *BLO_read_data_reports(BlendDataReader *reader)
+{
+ return reader->fd->reports;
+}
+
+bool BLO_read_lib_is_undo(BlendLibReader *reader)
+{
+ return (reader->fd->flags & FD_FLAGS_IS_MEMFILE);
+}
+
+Main *BLO_read_lib_get_main(BlendLibReader *reader)
+{
+ return reader->main;
+}
+
+BlendFileReadReport *BLO_read_lib_reports(BlendLibReader *reader)
+{
+ return reader->fd->reports;
+}
+
+void BLO_expand_id(BlendExpander *expander, ID *id)
+{
+ expand_doit(expander->fd, expander->main, id);
+}
+
+/** \} */