/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup blenloader */ #include /* for isdigit. */ #include #include #include /* for va_start/end. */ #include /* for offsetof. */ #include /* for atoi. */ #include /* for gmtime. */ #include /* for open flags (O_BINARY, O_RDONLY). */ #include "BLI_utildefines.h" #ifndef WIN32 # include /* for read close */ #else # include "BLI_winstuff.h" # include "winsock2.h" # include /* 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 : ""; } /* -------------------------------------------------------------------- */ /** \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( MEM_reallocN(onm->entries, sizeof(*onm->entries) * ENTRIES_CAPACITY(onm))); onm->map = static_cast(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( MEM_malloc_arrayN(ENTRIES_CAPACITY(onm), sizeof(*onm->entries), "OldNewMap.entries")); onm->map = static_cast( MEM_malloc_arrayN(MAP_CAPACITY(onm), sizeof(*onm->map), "OldNewMap.map")); oldnewmap_clear_map(onm); } static OldNewMap *oldnewmap_new() { OldNewMap *onm = static_cast(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(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
(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(lb_src->first), *idnext; id; id = idnext) { idnext = static_cast(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
( MEM_malloc_arrayN(lib_main_array_len, sizeof(*lib_main_array), __func__)); int i = 0; for (Library *lib = static_cast(main->libraries.first); lib; lib = static_cast(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(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(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
(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(BKE_libblock_alloc( static_cast
(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(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( 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(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( 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(&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(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(mem))) { file = BLI_filereader_new_gzip(mem_file); } else if (BLI_file_magic_is_zstd(static_cast(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 "); 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(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(newpackedadr(fd, ima->packedfile)); LISTBASE_FOREACH (ImagePackedFile *, imapf, &ima->packedfiles) { imapf->packedfile = static_cast(newpackedadr(fd, imapf->packedfile)); } } LISTBASE_FOREACH (VFont *, vfont, &oldmain->fonts) { vfont->packedfile = static_cast(newpackedadr(fd, vfont->packedfile)); } LISTBASE_FOREACH (bSound *, sound, &oldmain->sounds) { sound->packedfile = static_cast(newpackedadr(fd, sound->packedfile)); } LISTBASE_FOREACH (Library *, lib, &oldmain->libraries) { lib->packedfile = static_cast(newpackedadr(fd, lib->packedfile)); } LISTBASE_FOREACH (Volume *, volume, &oldmain->volumes) { volume->packedfile = static_cast(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(cache_storage_v); BLI_assert(!BLI_ghash_haskey(cache_storage->cache_map, key)); IDCacheKey *storage_key = static_cast( BLI_memarena_alloc(cache_storage->memarena, sizeof(*storage_key))); *storage_key = *key; BLOCacheStorageValue *storage_value = static_cast( 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(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( 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(cache_storage_v); BLOCacheStorageValue *storage_value = static_cast( 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( 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(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(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(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(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( 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(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(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(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(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(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(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(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(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(elem); typed_elem->id = static_cast( 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( restore_pointer_by_name(id_map, (ID *)v3d->camera, USER_REAL)); v3d->ob_center = static_cast( 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( restore_pointer_by_name(id_map, (ID *)ads->source, USER_REAL)); if (ads->filter_grp) { ads->filter_grp = static_cast( 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( 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( restore_pointer_by_name(id_map, (ID *)saction->action, USER_REAL)); saction->ads.source = static_cast( restore_pointer_by_name(id_map, (ID *)saction->ads.source, USER_REAL)); if (saction->ads.filter_grp) { saction->ads.filter_grp = static_cast( 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( 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( restore_pointer_by_name(id_map, (ID *)sima->gpd, USER_REAL)); sima->mask_info.mask = static_cast( 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( 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( restore_pointer_by_name(id_map, (ID *)ads->source, USER_REAL)); if (ads->filter_grp) { ads->filter_grp = static_cast( 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( restore_pointer_by_name(id_map, (ID *)st->text, USER_IGNORE)); if (st->text == nullptr) { st->text = static_cast(newmain->texts.first); } } else if (sl->spacetype == SPACE_SCRIPT) { SpaceScript *scpt = (SpaceScript *)sl; scpt->script = static_cast