From ed29ab303c5c87befb91f1e041cdf33bbb01bc98 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 7 May 2025 04:51:50 +0200 Subject: [PATCH] Blenloader: extract blenloader core library for use in blendthumb We recently started using blenloader code in blendthumb to avoid having to reimplement some parts of .blend file parsing. While this works, it has the side effect that on Windows referencing blenloader code increased the binary size of blendthumb from < 1MB to ~75 MB. That happens because this dependency drags along lots of other code which effectively is unused, but the compiler is unable to remove it. There didn't seem to be a simple solution to make msvc optimize the unused code away. This patch solves the issue by extracting the shared code into a separate `blenloader_core` module which does not depend on the rest of Blender (except blenlib). Therefore, using this new module in blendthumb does not drag along all the other dependencies, bring its file size back down. In the future, more code may be moved from blenloader to blenloader_core, but for now I extracted these two headers: * `BLO_core_bhead.hh`: Various `BHead` types and related parsing functions. * `BLO_core_blend_header.hh`: Parsing of the header at the beginning of .blend files. Pull Request: https://projects.blender.org/blender/blender/pulls/138371 --- source/blender/CMakeLists.txt | 1 + source/blender/blendthumb/CMakeLists.txt | 8 +- .../blendthumb/src/blendthumb_extract.cc | 3 +- source/blender/blenlib/intern/BLI_mempool.cc | 2 +- source/blender/blenloader/BLO_blend_defs.hh | 96 ------- source/blender/blenloader/BLO_readfile.hh | 34 +-- source/blender/blenloader/CMakeLists.txt | 2 +- .../blenloader/intern/readblenentry.cc | 2 - source/blender/blenloader/intern/readfile.cc | 260 ------------------ source/blender/blenloader/intern/readfile.hh | 10 +- source/blender/blenloader/intern/writefile.cc | 3 +- .../blender/blenloader_core/BLO_core_bhead.hh | 133 +++++++++ .../blenloader_core/BLO_core_blend_header.hh | 80 ++++++ source/blender/blenloader_core/CMakeLists.txt | 27 ++ .../blenloader_core/intern/blo_core_bhead.cc | 137 +++++++++ .../intern/blo_core_blend_header.cc | 125 +++++++++ source/blender/makesdna/DNA_sdna_types.h | 51 ---- 17 files changed, 515 insertions(+), 459 deletions(-) delete mode 100644 source/blender/blenloader/BLO_blend_defs.hh create mode 100644 source/blender/blenloader_core/BLO_core_bhead.hh create mode 100644 source/blender/blenloader_core/BLO_core_blend_header.hh create mode 100644 source/blender/blenloader_core/CMakeLists.txt create mode 100644 source/blender/blenloader_core/intern/blo_core_bhead.cc create mode 100644 source/blender/blenloader_core/intern/blo_core_blend_header.cc diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index 699100ce1ca..00798c38a73 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -157,6 +157,7 @@ add_subdirectory(render) add_subdirectory(blenfont) add_subdirectory(blentranslation) add_subdirectory(blenloader) +add_subdirectory(blenloader_core) add_subdirectory(depsgraph) add_subdirectory(ikplugin) add_subdirectory(simulation) diff --git a/source/blender/blendthumb/CMakeLists.txt b/source/blender/blendthumb/CMakeLists.txt index 1adcb74f051..a4419cdadd6 100644 --- a/source/blender/blendthumb/CMakeLists.txt +++ b/source/blender/blendthumb/CMakeLists.txt @@ -7,7 +7,7 @@ include_directories( ../blenlib - ../blenloader + ../blenloader_core ../makesdna ../../../intern/guardedalloc ) @@ -47,7 +47,7 @@ if(WIN32) setup_platform_linker_flags(BlendThumb) setup_platform_linker_libs(BlendThumb) - target_link_libraries(BlendThumb PRIVATE bf_blenlib bf_blenloader dbghelp.lib Version.lib Comctl32.lib) + target_link_libraries(BlendThumb PRIVATE bf_blenlib bf_blenloader_core dbghelp.lib Version.lib Comctl32.lib) # `blenlib` drags in a whole bunch of dependencies on shared libraries, none of which are used # by `blenthumb`, but will cause load issues since the debug linker will not eliminate them. # Link with /OPT:ref to force elimination of those unused dependencies this is already @@ -69,7 +69,7 @@ elseif(APPLE) setup_platform_linker_libs(blender-thumbnailer) target_link_libraries(blender-thumbnailer bf_blenlib - bf_blenloader + bf_blenloader_core # Avoid linker error about undefined _main symbol. "-e _NSExtensionMain" "-framework QuickLookThumbnailing" @@ -102,7 +102,7 @@ elseif(UNIX) add_executable(blender-thumbnailer ${SRC} ${SRC_CMD}) setup_platform_linker_flags(blender-thumbnailer) setup_platform_linker_libs(blender-thumbnailer) - target_link_libraries(blender-thumbnailer PRIVATE bf_blenlib bf_blenloader) + target_link_libraries(blender-thumbnailer PRIVATE bf_blenlib bf_blenloader_core) if(DEFINED PTHREADS_LIBRARIES) target_link_libraries(blender-thumbnailer PRIVATE ${PTHREADS_LIBRARIES}) endif() diff --git a/source/blender/blendthumb/src/blendthumb_extract.cc b/source/blender/blendthumb/src/blendthumb_extract.cc index 2597c818e31..74acf23fb4a 100644 --- a/source/blender/blendthumb/src/blendthumb_extract.cc +++ b/source/blender/blendthumb/src/blendthumb_extract.cc @@ -19,7 +19,8 @@ #include "BLI_filereader.h" #include "BLI_string.h" -#include "BLO_readfile.hh" +#include "BLO_core_bhead.hh" +#include "BLO_core_blend_header.hh" #include "blendthumb.hh" diff --git a/source/blender/blenlib/intern/BLI_mempool.cc b/source/blender/blenlib/intern/BLI_mempool.cc index 0dfe64ff42c..89c3aa76064 100644 --- a/source/blender/blenlib/intern/BLI_mempool.cc +++ b/source/blender/blenlib/intern/BLI_mempool.cc @@ -45,7 +45,7 @@ # define POISON_REDZONE_SIZE 0 #endif -/* NOTE: copied from BLO_blend_defs.hh, don't use here because we're in BLI. */ +/* NOTE: copied from BLO_core_bhead.hh, don't use here because we're in BLI. */ #ifdef __BIG_ENDIAN__ /* Big Endian */ # define MAKE_ID(a, b, c, d) ((int)(a) << 24 | (int)(b) << 16 | (c) << 8 | (d)) diff --git a/source/blender/blenloader/BLO_blend_defs.hh b/source/blender/blenloader/BLO_blend_defs.hh deleted file mode 100644 index 0df321d7481..00000000000 --- a/source/blender/blenloader/BLO_blend_defs.hh +++ /dev/null @@ -1,96 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma once - -/** \file - * \ingroup blenloader - * \brief defines for blend-file codes. - */ - -/* INTEGER CODES */ -#ifdef __BIG_ENDIAN__ -/* Big Endian */ -# define BLEND_MAKE_ID(a, b, c, d) ((int)(a) << 24 | (int)(b) << 16 | (c) << 8 | (d)) -#else -/* Little Endian */ -# define BLEND_MAKE_ID(a, b, c, d) ((int)(d) << 24 | (int)(c) << 16 | (b) << 8 | (a)) -#endif - -/** - * Codes used for #BHead.code. - * - * These coexist with ID codes such as #ID_OB, #ID_SCE ... etc. - */ -enum { - /** - * Arbitrary allocated memory - * (typically owned by #ID's, will be freed when there are no users). - */ - BLO_CODE_DATA = BLEND_MAKE_ID('D', 'A', 'T', 'A'), - /** - * Used for #Global struct. - */ - BLO_CODE_GLOB = BLEND_MAKE_ID('G', 'L', 'O', 'B'), - /** - * Used for storing the encoded SDNA string - * (decoded into an #SDNA on load). - */ - BLO_CODE_DNA1 = BLEND_MAKE_ID('D', 'N', 'A', '1'), - /** - * Used to store thumbnail previews, written between #REND and #GLOB blocks, - * (ignored for regular file reading). - */ - BLO_CODE_TEST = BLEND_MAKE_ID('T', 'E', 'S', 'T'), - /** - * Used for #RenderInfo, basic Scene and frame range info, - * can be easily read by other applications without writing a full blend file parser. - */ - BLO_CODE_REND = BLEND_MAKE_ID('R', 'E', 'N', 'D'), - /** - * Used for #UserDef, (user-preferences data). - * (written to #BLENDER_STARTUP_FILE & #BLENDER_USERPREF_FILE). - */ - BLO_CODE_USER = BLEND_MAKE_ID('U', 'S', 'E', 'R'), - /** - * Terminate reading (no data). - */ - BLO_CODE_ENDB = BLEND_MAKE_ID('E', 'N', 'D', 'B'), -}; - -#define BLEN_THUMB_MEMSIZE_FILE(_x, _y) (sizeof(int) * (2 + (size_t)(_x) * (size_t)(_y))) - -/** - * A low level blend file version number. Also see #decode_blender_header for how the first few - * bytes of a .blend file are structured. - * - * 0: Uses #BHead4 or #SmallBHead8 for block headers depending on a .blend file header byte. - * 1: Uses #LargeBHead8 for block headers. - */ - -/** - * Low level version 0: the header is 12 bytes long. - * 0-6: 'BLENDER' - * 7: '-' for 8-byte pointers (#SmallBHead8) or '_' for 4-byte pointers (#BHead4) - * 8: 'v' for little endian or 'V' for big endian - * 9-11: 3 ASCII digits encoding #BLENDER_FILE_VERSION (e.g. '305' for Blender 3.5) - */ -#define BLEND_FILE_FORMAT_VERSION_0 0 -/** - * Lower level version 1: the header is 17 bytes long. - * 0-6: 'BLENDER' - * 7-8: size of the header in bytes encoded as ASCII digits (always '17' currently) - * 9: always '-' - * 10-11: File version format as ASCII digits (always '01' currently) - * 12: always 'v' - * 13-16: 4 ASCII digits encoding #BLENDER_FILE_VERSION (e.g. '0405' for Blender 4.5) - * - * With this header, #LargeBHead8 is always used. - */ -#define BLEND_FILE_FORMAT_VERSION_1 1 - -/** - * Only "modern" systems support writing files with #LargeBHead8 headers. Other systems are - * deprecated. This reduces the amount of variation we have to deal with when reading .blend files. - */ -#define SYSTEM_SUPPORTS_WRITING_FILE_VERSION_1 (ENDIAN_ORDER == L_ENDIAN && sizeof(void *) == 8) diff --git a/source/blender/blenloader/BLO_readfile.hh b/source/blender/blenloader/BLO_readfile.hh index f9b9f1dad30..da951e33bba 100644 --- a/source/blender/blenloader/BLO_readfile.hh +++ b/source/blender/blenloader/BLO_readfile.hh @@ -3,11 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include -#include - #include "DNA_listBase.h" -#include "DNA_sdna_types.h" #include "BLI_compiler_attrs.h" #include "BLI_sys_types.h" @@ -611,32 +607,4 @@ void BLO_readfile_id_runtime_data_free_all(Main &bmain); */ void BLO_readfile_id_runtime_data_free(ID &id); -/** A header that has been parsed successfully. */ -struct BlenderHeader { - /** 4 or 8. */ - int pointer_size; - /** L_ENDIAN or B_ENDIAN. */ - int endian; - /** #BLENDER_FILE_VERSION. */ - int file_version; - /** #BLEND_FILE_FORMAT_VERSION. */ - int file_format_version; - - BHeadType bhead_type() const; -}; - -/** The file is detected to be a Blender file, but it could not be decoded successfully. */ -struct BlenderHeaderUnknown {}; - -/** The file is not a Blender file. */ -struct BlenderHeaderInvalid {}; - -using BlenderHeaderVariant = - std::variant; - -BlenderHeaderVariant BLO_readfile_blender_header_decode(FileReader *file); - -/** Returns #std::nullopt if the file is exhausted. */ -std::optional BLO_readfile_read_bhead(FileReader *file, - BHeadType type, - bool do_endian_swap); +#define BLEN_THUMB_MEMSIZE_FILE(_x, _y) (sizeof(int) * (2 + (size_t)(_x) * (size_t)(_y))) diff --git a/source/blender/blenloader/CMakeLists.txt b/source/blender/blenloader/CMakeLists.txt index 18526168f21..39075a48e3e 100644 --- a/source/blender/blenloader/CMakeLists.txt +++ b/source/blender/blenloader/CMakeLists.txt @@ -36,7 +36,6 @@ set(SRC intern/versioning_userdef.cc intern/writefile.cc - BLO_blend_defs.hh BLO_blend_validate.hh BLO_read_write.hh BLO_readfile.hh @@ -51,6 +50,7 @@ set(LIB PRIVATE bf::animrig PRIVATE bf::blenkernel PRIVATE bf::blenlib + PUBLIC bf::blenloader_core PRIVATE bf::blentranslation PRIVATE bf::bmesh PRIVATE bf::depsgraph diff --git a/source/blender/blenloader/intern/readblenentry.cc b/source/blender/blenloader/intern/readblenentry.cc index c897c569ec7..d645538d04a 100644 --- a/source/blender/blenloader/intern/readblenentry.cc +++ b/source/blender/blenloader/intern/readblenentry.cc @@ -20,14 +20,12 @@ #include "BLI_utildefines.h" #include "DNA_genfile.h" -#include "DNA_sdna_types.h" #include "BKE_asset.hh" #include "BKE_idtype.hh" #include "BKE_main.hh" #include "BKE_preview_image.hh" -#include "BLO_blend_defs.hh" #include "BLO_readfile.hh" #include "readfile.hh" diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index a9c4ebf239b..8a0e3554b3d 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -94,7 +94,6 @@ #include "DEG_depsgraph.hh" -#include "BLO_blend_defs.hh" #include "BLO_blend_validate.hh" #include "BLO_read_write.hh" #include "BLO_readfile.hh" @@ -603,153 +602,6 @@ struct BlendLibReader { Main *main; }; -static void switch_endian_bh4(BHead4 *bhead) -{ - /* the ID_.. codes */ - if ((bhead->code & 0xFFFF) == 0) { - bhead->code >>= 16; - } - - if (bhead->code != BLO_CODE_ENDB) { - BLI_endian_switch_int32(&bhead->len); - BLI_endian_switch_int32(&bhead->SDNAnr); - BLI_endian_switch_int32(&bhead->nr); - } -} - -static void switch_endian_small_bh8(SmallBHead8 *bhead) -{ - /* The ID_* codes. */ - if ((bhead->code & 0xFFFF) == 0) { - bhead->code >>= 16; - } - - if (bhead->code != BLO_CODE_ENDB) { - BLI_endian_switch_int32(&bhead->len); - BLI_endian_switch_int32(&bhead->SDNAnr); - BLI_endian_switch_int32(&bhead->nr); - } -} - -static void switch_endian_large_bh8(LargeBHead8 *bhead) -{ - /* The ID_* codes. */ - if ((bhead->code & 0xFFFF) == 0) { - bhead->code >>= 16; - } - - if (bhead->code != BLO_CODE_ENDB) { - BLI_endian_switch_int64(&bhead->len); - BLI_endian_switch_int32(&bhead->SDNAnr); - BLI_endian_switch_int64(&bhead->nr); - } -} - -static BHead bhead_from_bhead4(const BHead4 &bhead4) -{ - BHead bhead; - bhead.code = bhead4.code; - bhead.len = bhead4.len; - bhead.old = reinterpret_cast(uintptr_t(bhead4.old)); - bhead.SDNAnr = bhead4.SDNAnr; - bhead.nr = bhead4.nr; - return bhead; -} - -/** - * Converts a BHead.old pointer from 64 to 32 bit. This can't work in the general case, but only - * when the lower 32 bits of all relevant 64 bit pointers are different. Otherwise two different - * pointers will map to the same, which will break things later on. There is no way to check for - * that here unfortunately. - */ -static uint32_t uint32_from_uint64_ptr(uint64_t ptr, const bool use_endian_swap) -{ - if (use_endian_swap) { - /* Do endian switch so that the resulting pointer is not all 0. */ - BLI_endian_switch_uint64(&ptr); - } - /* Behavior has to match #cast_pointer_64_to_32. */ - ptr >>= 3; - return uint32_t(ptr); -} - -static const void *old_ptr_from_uint64_ptr(const uint64_t ptr, const bool use_endian_swap) -{ - if constexpr (sizeof(void *) == 8) { - return reinterpret_cast(ptr); - } - else { - return reinterpret_cast(uintptr_t(uint32_from_uint64_ptr(ptr, use_endian_swap))); - } -} - -static BHead bhead_from_small_bhead8(const SmallBHead8 &small_bhead8, const bool use_endian_swap) -{ - BHead bhead; - bhead.code = small_bhead8.code; - bhead.len = small_bhead8.len; - bhead.old = old_ptr_from_uint64_ptr(small_bhead8.old, use_endian_swap); - bhead.SDNAnr = small_bhead8.SDNAnr; - bhead.nr = small_bhead8.nr; - return bhead; -} - -static BHead bhead_from_large_bhead8(const LargeBHead8 &large_bhead8, const bool use_endian_swap) -{ - BHead bhead; - bhead.code = large_bhead8.code; - bhead.len = large_bhead8.len; - bhead.old = old_ptr_from_uint64_ptr(large_bhead8.old, use_endian_swap); - bhead.SDNAnr = large_bhead8.SDNAnr; - bhead.nr = large_bhead8.nr; - return bhead; -} - -std::optional BLO_readfile_read_bhead(FileReader *file, - const BHeadType type, - const bool do_endian_swap) -{ - switch (type) { - case BHeadType::BHead4: { - BHead4 bhead4{}; - bhead4.code = BLO_CODE_DATA; - const int64_t readsize = file->read(file, &bhead4, sizeof(bhead4)); - if (readsize == sizeof(bhead4) || bhead4.code == BLO_CODE_ENDB) { - if (do_endian_swap) { - switch_endian_bh4(&bhead4); - } - return bhead_from_bhead4(bhead4); - } - break; - } - case BHeadType::SmallBHead8: { - SmallBHead8 small_bhead8{}; - small_bhead8.code = BLO_CODE_DATA; - const int64_t readsize = file->read(file, &small_bhead8, sizeof(small_bhead8)); - if (readsize == sizeof(small_bhead8) || small_bhead8.code == BLO_CODE_ENDB) { - if (do_endian_swap) { - switch_endian_small_bh8(&small_bhead8); - } - return bhead_from_small_bhead8(small_bhead8, do_endian_swap); - } - break; - } - case BHeadType::LargeBHead8: { - LargeBHead8 large_bhead8{}; - large_bhead8.code = BLO_CODE_DATA; - const int64_t readsize = file->read(file, &large_bhead8, sizeof(large_bhead8)); - if (readsize == sizeof(large_bhead8) || large_bhead8.code == BLO_CODE_ENDB) { - if (do_endian_swap) { - switch_endian_large_bh8(&large_bhead8); - } - return bhead_from_large_bhead8(large_bhead8, do_endian_swap); - } - break; - } - } - return std::nullopt; -} - static BHeadN *get_bhead(FileData *fd) { BHeadN *new_bhead = nullptr; @@ -956,118 +808,6 @@ AssetMetaData *blo_bhead_id_asset_data_address(const FileData *fd, const BHead * nullptr; } -BHeadType BlenderHeader::bhead_type() const -{ - if (this->pointer_size == 4) { - return BHeadType::BHead4; - } - if (this->file_format_version == BLEND_FILE_FORMAT_VERSION_0) { - return BHeadType::SmallBHead8; - } - BLI_assert(this->file_format_version == BLEND_FILE_FORMAT_VERSION_1); - return BHeadType::LargeBHead8; -} - -BlenderHeaderVariant BLO_readfile_blender_header_decode(FileReader *file) -{ - char header_bytes[MAX_SIZEOFBLENDERHEADER]; - /* We read the minimal number of header bytes first. If necessary, the remaining bytes are read - * below. */ - int64_t readsize = file->read(file, header_bytes, MIN_SIZEOFBLENDERHEADER); - if (readsize != MIN_SIZEOFBLENDERHEADER) { - return BlenderHeaderInvalid{}; - } - if (!STREQLEN(header_bytes, "BLENDER", 7)) { - return BlenderHeaderInvalid{}; - } - /* If the first 7 bytes are BLENDER, it is very likely that this is a newer version of the - * blendfile format. If the rest of the decode fails, we can still report that this was a Blender - * file of a potentially future version. */ - - BlenderHeader header; - /* In the old header format, the next bytes indicate the pointer size. In the new format a - * version number comes next. */ - const bool is_legacy_header = ELEM(header_bytes[7], '_', '-'); - - if (is_legacy_header) { - header.file_format_version = 0; - switch (header_bytes[7]) { - case '_': - header.pointer_size = 4; - break; - case '-': - header.pointer_size = 8; - break; - default: - return BlenderHeaderUnknown{}; - } - switch (header_bytes[8]) { - case 'v': - header.endian = L_ENDIAN; - break; - case 'V': - header.endian = B_ENDIAN; - break; - default: - return BlenderHeaderUnknown{}; - } - if (!isdigit(header_bytes[9]) || !isdigit(header_bytes[10]) || !isdigit(header_bytes[11])) { - return BlenderHeaderUnknown{}; - } - char version_str[4]; - memcpy(version_str, header_bytes + 9, 3); - version_str[3] = '\0'; - header.file_version = atoi(version_str); - return header; - } - - if (!isdigit(header_bytes[7]) || !isdigit(header_bytes[8])) { - return BlenderHeaderUnknown{}; - } - char header_size_str[3]; - memcpy(header_size_str, header_bytes + 7, 2); - header_size_str[2] = '\0'; - const int header_size = atoi(header_size_str); - if (header_size != MAX_SIZEOFBLENDERHEADER) { - return BlenderHeaderUnknown{}; - } - - /* Read remaining header bytes. */ - const int64_t remaining_bytes_to_read = header_size - MIN_SIZEOFBLENDERHEADER; - readsize = file->read(file, header_bytes + MIN_SIZEOFBLENDERHEADER, remaining_bytes_to_read); - if (readsize != remaining_bytes_to_read) { - return BlenderHeaderUnknown{}; - } - if (header_bytes[9] != '-') { - return BlenderHeaderUnknown{}; - } - header.pointer_size = 8; - if (!isdigit(header_bytes[10]) || !isdigit(header_bytes[11])) { - return BlenderHeaderUnknown{}; - } - char blend_file_version_format_str[3]; - memcpy(blend_file_version_format_str, header_bytes + 10, 2); - blend_file_version_format_str[2] = '\0'; - header.file_format_version = atoi(blend_file_version_format_str); - if (header.file_format_version != 1) { - return BlenderHeaderUnknown{}; - } - if (header_bytes[12] != 'v') { - return BlenderHeaderUnknown{}; - } - header.endian = L_ENDIAN; - if (!isdigit(header_bytes[13]) || !isdigit(header_bytes[14]) || !isdigit(header_bytes[15]) || - !isdigit(header_bytes[16])) - { - return BlenderHeaderUnknown{}; - } - char version_str[5]; - memcpy(version_str, header_bytes + 13, 4); - version_str[4] = '\0'; - header.file_version = std::atoi(version_str); - return header; -} - static void read_blender_header(FileData *fd) { const BlenderHeaderVariant header_variant = BLO_readfile_blender_header_decode(fd->file); diff --git a/source/blender/blenloader/intern/readfile.hh b/source/blender/blenloader/intern/readfile.hh index 645c7f78900..f0a9ba0f2e0 100644 --- a/source/blender/blenloader/intern/readfile.hh +++ b/source/blender/blenloader/intern/readfile.hh @@ -22,6 +22,8 @@ #include "DNA_sdna_types.h" #include "DNA_space_types.h" +#include "BLO_core_bhead.hh" +#include "BLO_core_blend_header.hh" #include "BLO_readfile.hh" struct BlendFileData; @@ -158,14 +160,6 @@ struct FileData { void *storage_handle = nullptr; }; -#define MIN_SIZEOFBLENDERHEADER 12 -#define MAX_SIZEOFBLENDERHEADER 17 - -/** See #BLEND_FILE_FORMAT_VERSION_0 for the structure. */ -#define SIZEOFBLENDERHEADER_VERSION_0 12 -/** See #BLEND_FILE_FORMAT_VERSION_1 for the structure. */ -#define SIZEOFBLENDERHEADER_VERSION_1 17 - /***/ void blo_join_main(ListBase *mainlist); void blo_split_main(ListBase *mainlist, Main *main); diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index 9107f9cc250..bb3298542a9 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -21,7 +21,7 @@ * * data-blocks: (also see struct #BHead). *
- * `bh.code`       `char[4]` see `BLO_blend_defs.hh` for a list of known types.
+ * `bh.code`       `char[4]` see `BLO_core_bhead.hh` for a list of known types.
  * `bh.len`        `int32` length data after #BHead in bytes.
  * `bh.old`        `void *` old pointer (the address at the time of writing the file).
  * `bh.SDNAnr`     `int32` struct index of structs stored in #DNA1 data.
@@ -125,7 +125,6 @@
 
 #include "DRW_engine.hh"
 
-#include "BLO_blend_defs.hh"
 #include "BLO_blend_validate.hh"
 #include "BLO_read_write.hh"
 #include "BLO_readfile.hh"
diff --git a/source/blender/blenloader_core/BLO_core_bhead.hh b/source/blender/blenloader_core/BLO_core_bhead.hh
new file mode 100644
index 00000000000..b15966b87dc
--- /dev/null
+++ b/source/blender/blenloader_core/BLO_core_bhead.hh
@@ -0,0 +1,133 @@
+/* SPDX-FileCopyrightText: 2025 Blender Authors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+#include 
+
+#include "BLI_endian_switch.h"
+#include "BLI_sys_types.h"
+
+struct FileReader;
+
+struct BHead {
+  /** Identifier for this #BHead. Can be any of BLO_CODE_* or an ID code like ID_OB.  */
+  int code;
+  /** Identifier of the struct type that is stored in this block. */
+  int SDNAnr;
+  /**
+   * Identifier the block had when it was written. This is used to remap memory blocks on load.
+   * Typically, this is the pointer that the memory had when it was written.
+   * This should be unique across the whole blend-file, except for `BLEND_DATA` blocks, which
+   * should be unique within a same ID.
+   */
+  const void *old;
+  /** Number of bytes in the block. */
+  int64_t len;
+  /** Number of structs in the array (1 for simple structs). */
+  int64_t nr;
+};
+
+struct BHead4 {
+  int code, len;
+  uint old;
+  int SDNAnr, nr;
+};
+
+struct SmallBHead8 {
+  int code, len;
+  uint64_t old;
+  int SDNAnr, nr;
+};
+
+struct LargeBHead8 {
+  int code;
+  int SDNAnr;
+  uint64_t old;
+  int64_t len;
+  int64_t nr;
+};
+
+enum class BHeadType {
+  BHead4,
+  SmallBHead8,
+  LargeBHead8,
+};
+
+/** Make #BHead.code from 4 chars. */
+#ifdef __BIG_ENDIAN__
+/* Big Endian */
+#  define BLEND_MAKE_ID(a, b, c, d) ((int)(a) << 24 | (int)(b) << 16 | (c) << 8 | (d))
+#else
+/* Little Endian */
+#  define BLEND_MAKE_ID(a, b, c, d) ((int)(d) << 24 | (int)(c) << 16 | (b) << 8 | (a))
+#endif
+
+/**
+ * Codes used for #BHead.code.
+ *
+ * These coexist with ID codes such as #ID_OB, #ID_SCE ... etc.
+ */
+enum {
+  /**
+   * Arbitrary allocated memory
+   * (typically owned by #ID's, will be freed when there are no users).
+   */
+  BLO_CODE_DATA = BLEND_MAKE_ID('D', 'A', 'T', 'A'),
+  /**
+   * Used for #Global struct.
+   */
+  BLO_CODE_GLOB = BLEND_MAKE_ID('G', 'L', 'O', 'B'),
+  /**
+   * Used for storing the encoded SDNA string
+   * (decoded into an #SDNA on load).
+   */
+  BLO_CODE_DNA1 = BLEND_MAKE_ID('D', 'N', 'A', '1'),
+  /**
+   * Used to store thumbnail previews, written between #REND and #GLOB blocks,
+   * (ignored for regular file reading).
+   */
+  BLO_CODE_TEST = BLEND_MAKE_ID('T', 'E', 'S', 'T'),
+  /**
+   * Used for #RenderInfo, basic Scene and frame range info,
+   * can be easily read by other applications without writing a full blend file parser.
+   */
+  BLO_CODE_REND = BLEND_MAKE_ID('R', 'E', 'N', 'D'),
+  /**
+   * Used for #UserDef, (user-preferences data).
+   * (written to #BLENDER_STARTUP_FILE & #BLENDER_USERPREF_FILE).
+   */
+  BLO_CODE_USER = BLEND_MAKE_ID('U', 'S', 'E', 'R'),
+  /**
+   * Terminate reading (no data).
+   */
+  BLO_CODE_ENDB = BLEND_MAKE_ID('E', 'N', 'D', 'B'),
+};
+
+/**
+ * Parse the next #BHead in the file, increasing the file reader to after the #BHead.
+ * This automaticaly converts the stored BHead (one of #BHeadType) to the runtime #BHead type.
+ *
+ * \return The next #BHEad or #std::nullopt if the file is exhausted.
+ */
+std::optional BLO_readfile_read_bhead(FileReader *file,
+                                             BHeadType type,
+                                             bool do_endian_swap);
+
+/**
+ * Converts a BHead.old pointer from 64 to 32 bit. This can't work in the general case, but only
+ * when the lower 32 bits of all relevant 64 bit pointers are different. Otherwise two different
+ * pointers will map to the same, which will break things later on. There is no way to check for
+ * that here unfortunately.
+ */
+inline uint32_t uint32_from_uint64_ptr(uint64_t ptr, const bool use_endian_swap)
+{
+  if (use_endian_swap) {
+    /* Do endian switch so that the resulting pointer is not all 0. */
+    BLI_endian_switch_uint64(&ptr);
+  }
+  /* Behavior has to match #cast_pointer_64_to_32. */
+  ptr >>= 3;
+  return uint32_t(ptr);
+}
diff --git a/source/blender/blenloader_core/BLO_core_blend_header.hh b/source/blender/blenloader_core/BLO_core_blend_header.hh
new file mode 100644
index 00000000000..cc7f6ef77af
--- /dev/null
+++ b/source/blender/blenloader_core/BLO_core_blend_header.hh
@@ -0,0 +1,80 @@
+/* SPDX-FileCopyrightText: 2025 Blender Authors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+#include 
+
+#include "BLO_core_bhead.hh"
+
+/**
+ * A low level blend file version number. Also see #decode_blender_header for how the first few
+ * bytes of a .blend file are structured.
+ *
+ * 0: Uses #BHead4 or #SmallBHead8 for block headers depending on a .blend file header byte.
+ * 1: Uses #LargeBHead8 for block headers.
+ */
+
+/**
+ * Low level version 0: the header is 12 bytes long.
+ * 0-6:  'BLENDER'
+ * 7:    '-' for 8-byte pointers (#SmallBHead8) or '_' for 4-byte pointers (#BHead4)
+ * 8:    'v' for little endian or 'V' for big endian
+ * 9-11: 3 ASCII digits encoding #BLENDER_FILE_VERSION (e.g. '305' for Blender 3.5)
+ */
+#define BLEND_FILE_FORMAT_VERSION_0 0
+/**
+ * Lower level version 1: the header is 17 bytes long.
+ * 0-6:   'BLENDER'
+ * 7-8:   size of the header in bytes encoded as ASCII digits (always '17' currently)
+ * 9:     always '-'
+ * 10-11: File version format as ASCII digits (always '01' currently)
+ * 12:    always 'v'
+ * 13-16: 4 ASCII digits encoding #BLENDER_FILE_VERSION (e.g. '0405' for Blender 4.5)
+ *
+ * With this header, #LargeBHead8 is always used.
+ */
+#define BLEND_FILE_FORMAT_VERSION_1 1
+
+/**
+ * Only "modern" systems support writing files with #LargeBHead8 headers. Other systems are
+ * deprecated. This reduces the amount of variation we have to deal with when reading .blend files.
+ */
+#define SYSTEM_SUPPORTS_WRITING_FILE_VERSION_1 (ENDIAN_ORDER == L_ENDIAN && sizeof(void *) == 8)
+
+#define MIN_SIZEOFBLENDERHEADER 12
+#define MAX_SIZEOFBLENDERHEADER 17
+
+/** See #BLEND_FILE_FORMAT_VERSION_0 for the structure. */
+#define SIZEOFBLENDERHEADER_VERSION_0 12
+/** See #BLEND_FILE_FORMAT_VERSION_1 for the structure. */
+#define SIZEOFBLENDERHEADER_VERSION_1 17
+
+/** A header that has been parsed successfully. */
+struct BlenderHeader {
+  /** 4 or 8. */
+  int pointer_size;
+  /** L_ENDIAN or B_ENDIAN. */
+  int endian;
+  /** #BLENDER_FILE_VERSION. */
+  int file_version;
+  /** #BLEND_FILE_FORMAT_VERSION. */
+  int file_format_version;
+
+  BHeadType bhead_type() const;
+};
+
+/** The file is detected to be a Blender file, but it could not be decoded successfully. */
+struct BlenderHeaderUnknown {};
+
+/** The file is not a Blender file. */
+struct BlenderHeaderInvalid {};
+
+using BlenderHeaderVariant =
+    std::variant;
+
+/**
+ * Reads the header at the beginning of a .blend file and decodes it.
+ */
+BlenderHeaderVariant BLO_readfile_blender_header_decode(FileReader *file);
diff --git a/source/blender/blenloader_core/CMakeLists.txt b/source/blender/blenloader_core/CMakeLists.txt
new file mode 100644
index 00000000000..23e1cde894f
--- /dev/null
+++ b/source/blender/blenloader_core/CMakeLists.txt
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: 2025 Blender Authors
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(INC
+  PUBLIC .
+)
+
+
+set(SRC
+  intern/blo_core_bhead.cc
+  intern/blo_core_blend_header.cc
+
+  BLO_core_bhead.hh
+  BLO_core_blend_header.hh
+)
+
+set(LIB
+  PRIVATE bf::blenlib
+)
+
+if(WIN32)
+  add_definitions(-DNOMINMAX)
+endif()
+
+blender_add_lib(bf_blenloader_core "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+add_library(bf::blenloader_core ALIAS bf_blenloader_core)
diff --git a/source/blender/blenloader_core/intern/blo_core_bhead.cc b/source/blender/blenloader_core/intern/blo_core_bhead.cc
new file mode 100644
index 00000000000..a380dc95f43
--- /dev/null
+++ b/source/blender/blenloader_core/intern/blo_core_bhead.cc
@@ -0,0 +1,137 @@
+/* SPDX-FileCopyrightText: 2025 Blender Authors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_filereader.h"
+
+#include "BLO_core_bhead.hh"
+
+static void switch_endian_bh4(BHead4 *bhead)
+{
+  /* the ID_.. codes */
+  if ((bhead->code & 0xFFFF) == 0) {
+    bhead->code >>= 16;
+  }
+
+  if (bhead->code != BLO_CODE_ENDB) {
+    BLI_endian_switch_int32(&bhead->len);
+    BLI_endian_switch_int32(&bhead->SDNAnr);
+    BLI_endian_switch_int32(&bhead->nr);
+  }
+}
+
+static void switch_endian_small_bh8(SmallBHead8 *bhead)
+{
+  /* The ID_* codes. */
+  if ((bhead->code & 0xFFFF) == 0) {
+    bhead->code >>= 16;
+  }
+
+  if (bhead->code != BLO_CODE_ENDB) {
+    BLI_endian_switch_int32(&bhead->len);
+    BLI_endian_switch_int32(&bhead->SDNAnr);
+    BLI_endian_switch_int32(&bhead->nr);
+  }
+}
+
+static void switch_endian_large_bh8(LargeBHead8 *bhead)
+{
+  /* The ID_* codes. */
+  if ((bhead->code & 0xFFFF) == 0) {
+    bhead->code >>= 16;
+  }
+
+  if (bhead->code != BLO_CODE_ENDB) {
+    BLI_endian_switch_int64(&bhead->len);
+    BLI_endian_switch_int32(&bhead->SDNAnr);
+    BLI_endian_switch_int64(&bhead->nr);
+  }
+}
+
+static BHead bhead_from_bhead4(const BHead4 &bhead4)
+{
+  BHead bhead;
+  bhead.code = bhead4.code;
+  bhead.len = bhead4.len;
+  bhead.old = reinterpret_cast(uintptr_t(bhead4.old));
+  bhead.SDNAnr = bhead4.SDNAnr;
+  bhead.nr = bhead4.nr;
+  return bhead;
+}
+
+static const void *old_ptr_from_uint64_ptr(const uint64_t ptr, const bool use_endian_swap)
+{
+  if constexpr (sizeof(void *) == 8) {
+    return reinterpret_cast(ptr);
+  }
+  else {
+    return reinterpret_cast(uintptr_t(uint32_from_uint64_ptr(ptr, use_endian_swap)));
+  }
+}
+
+static BHead bhead_from_small_bhead8(const SmallBHead8 &small_bhead8, const bool use_endian_swap)
+{
+  BHead bhead;
+  bhead.code = small_bhead8.code;
+  bhead.len = small_bhead8.len;
+  bhead.old = old_ptr_from_uint64_ptr(small_bhead8.old, use_endian_swap);
+  bhead.SDNAnr = small_bhead8.SDNAnr;
+  bhead.nr = small_bhead8.nr;
+  return bhead;
+}
+
+static BHead bhead_from_large_bhead8(const LargeBHead8 &large_bhead8, const bool use_endian_swap)
+{
+  BHead bhead;
+  bhead.code = large_bhead8.code;
+  bhead.len = large_bhead8.len;
+  bhead.old = old_ptr_from_uint64_ptr(large_bhead8.old, use_endian_swap);
+  bhead.SDNAnr = large_bhead8.SDNAnr;
+  bhead.nr = large_bhead8.nr;
+  return bhead;
+}
+
+std::optional BLO_readfile_read_bhead(FileReader *file,
+                                             const BHeadType type,
+                                             const bool do_endian_swap)
+{
+  switch (type) {
+    case BHeadType::BHead4: {
+      BHead4 bhead4{};
+      bhead4.code = BLO_CODE_DATA;
+      const int64_t readsize = file->read(file, &bhead4, sizeof(bhead4));
+      if (readsize == sizeof(bhead4) || bhead4.code == BLO_CODE_ENDB) {
+        if (do_endian_swap) {
+          switch_endian_bh4(&bhead4);
+        }
+        return bhead_from_bhead4(bhead4);
+      }
+      break;
+    }
+    case BHeadType::SmallBHead8: {
+      SmallBHead8 small_bhead8{};
+      small_bhead8.code = BLO_CODE_DATA;
+      const int64_t readsize = file->read(file, &small_bhead8, sizeof(small_bhead8));
+      if (readsize == sizeof(small_bhead8) || small_bhead8.code == BLO_CODE_ENDB) {
+        if (do_endian_swap) {
+          switch_endian_small_bh8(&small_bhead8);
+        }
+        return bhead_from_small_bhead8(small_bhead8, do_endian_swap);
+      }
+      break;
+    }
+    case BHeadType::LargeBHead8: {
+      LargeBHead8 large_bhead8{};
+      large_bhead8.code = BLO_CODE_DATA;
+      const int64_t readsize = file->read(file, &large_bhead8, sizeof(large_bhead8));
+      if (readsize == sizeof(large_bhead8) || large_bhead8.code == BLO_CODE_ENDB) {
+        if (do_endian_swap) {
+          switch_endian_large_bh8(&large_bhead8);
+        }
+        return bhead_from_large_bhead8(large_bhead8, do_endian_swap);
+      }
+      break;
+    }
+  }
+  return std::nullopt;
+}
diff --git a/source/blender/blenloader_core/intern/blo_core_blend_header.cc b/source/blender/blenloader_core/intern/blo_core_blend_header.cc
new file mode 100644
index 00000000000..255d514fa06
--- /dev/null
+++ b/source/blender/blenloader_core/intern/blo_core_blend_header.cc
@@ -0,0 +1,125 @@
+/* SPDX-FileCopyrightText: 2025 Blender Authors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include 
+#include 
+#include 
+
+#include "BLI_endian_defines.h"
+#include "BLI_filereader.h"
+
+#include "BLO_core_bhead.hh"
+#include "BLO_core_blend_header.hh"
+
+BHeadType BlenderHeader::bhead_type() const
+{
+  if (this->pointer_size == 4) {
+    return BHeadType::BHead4;
+  }
+  if (this->file_format_version == BLEND_FILE_FORMAT_VERSION_0) {
+    return BHeadType::SmallBHead8;
+  }
+  BLI_assert(this->file_format_version == BLEND_FILE_FORMAT_VERSION_1);
+  return BHeadType::LargeBHead8;
+}
+
+BlenderHeaderVariant BLO_readfile_blender_header_decode(FileReader *file)
+{
+  char header_bytes[MAX_SIZEOFBLENDERHEADER];
+  /* We read the minimal number of header bytes first. If necessary, the remaining bytes are read
+   * below. */
+  int64_t readsize = file->read(file, header_bytes, MIN_SIZEOFBLENDERHEADER);
+  if (readsize != MIN_SIZEOFBLENDERHEADER) {
+    return BlenderHeaderInvalid{};
+  }
+  if (!STREQLEN(header_bytes, "BLENDER", 7)) {
+    return BlenderHeaderInvalid{};
+  }
+  /* If the first 7 bytes are BLENDER, it is very likely that this is a newer version of the
+   * blendfile format. If the rest of the decode fails, we can still report that this was a Blender
+   * file of a potentially future version. */
+
+  BlenderHeader header;
+  /* In the old header format, the next bytes indicate the pointer size. In the new format a
+   * version number comes next. */
+  const bool is_legacy_header = ELEM(header_bytes[7], '_', '-');
+
+  if (is_legacy_header) {
+    header.file_format_version = 0;
+    switch (header_bytes[7]) {
+      case '_':
+        header.pointer_size = 4;
+        break;
+      case '-':
+        header.pointer_size = 8;
+        break;
+      default:
+        return BlenderHeaderUnknown{};
+    }
+    switch (header_bytes[8]) {
+      case 'v':
+        header.endian = L_ENDIAN;
+        break;
+      case 'V':
+        header.endian = B_ENDIAN;
+        break;
+      default:
+        return BlenderHeaderUnknown{};
+    }
+    if (!isdigit(header_bytes[9]) || !isdigit(header_bytes[10]) || !isdigit(header_bytes[11])) {
+      return BlenderHeaderUnknown{};
+    }
+    char version_str[4];
+    memcpy(version_str, header_bytes + 9, 3);
+    version_str[3] = '\0';
+    header.file_version = atoi(version_str);
+    return header;
+  }
+
+  if (!isdigit(header_bytes[7]) || !isdigit(header_bytes[8])) {
+    return BlenderHeaderUnknown{};
+  }
+  char header_size_str[3];
+  memcpy(header_size_str, header_bytes + 7, 2);
+  header_size_str[2] = '\0';
+  const int header_size = atoi(header_size_str);
+  if (header_size != MAX_SIZEOFBLENDERHEADER) {
+    return BlenderHeaderUnknown{};
+  }
+
+  /* Read remaining header bytes. */
+  const int64_t remaining_bytes_to_read = header_size - MIN_SIZEOFBLENDERHEADER;
+  readsize = file->read(file, header_bytes + MIN_SIZEOFBLENDERHEADER, remaining_bytes_to_read);
+  if (readsize != remaining_bytes_to_read) {
+    return BlenderHeaderUnknown{};
+  }
+  if (header_bytes[9] != '-') {
+    return BlenderHeaderUnknown{};
+  }
+  header.pointer_size = 8;
+  if (!isdigit(header_bytes[10]) || !isdigit(header_bytes[11])) {
+    return BlenderHeaderUnknown{};
+  }
+  char blend_file_version_format_str[3];
+  memcpy(blend_file_version_format_str, header_bytes + 10, 2);
+  blend_file_version_format_str[2] = '\0';
+  header.file_format_version = atoi(blend_file_version_format_str);
+  if (header.file_format_version != 1) {
+    return BlenderHeaderUnknown{};
+  }
+  if (header_bytes[12] != 'v') {
+    return BlenderHeaderUnknown{};
+  }
+  header.endian = L_ENDIAN;
+  if (!isdigit(header_bytes[13]) || !isdigit(header_bytes[14]) || !isdigit(header_bytes[15]) ||
+      !isdigit(header_bytes[16]))
+  {
+    return BlenderHeaderUnknown{};
+  }
+  char version_str[5];
+  memcpy(version_str, header_bytes + 13, 4);
+  version_str[4] = '\0';
+  header.file_version = std::atoi(version_str);
+  return header;
+}
diff --git a/source/blender/makesdna/DNA_sdna_types.h b/source/blender/makesdna/DNA_sdna_types.h
index 1f189aaf394..1c9e73fe9bb 100644
--- a/source/blender/makesdna/DNA_sdna_types.h
+++ b/source/blender/makesdna/DNA_sdna_types.h
@@ -141,54 +141,3 @@ typedef struct SDNA {
  * code would assume that the `0` value was raw data, so keep it at this value.
  */
 #define SDNA_RAW_DATA_STRUCT_INDEX 0
-
-#
-#
-typedef struct BHead {
-  /** Identifier for this #BHead. Can be any of BLO_CODE_* or an ID code like ID_OB.  */
-  int code;
-  /** Identifier of the struct type that is stored in this block. */
-  int SDNAnr;
-  /**
-   * Identifier the block had when it was written. This is used to remap memory blocks on load.
-   * Typically, this is the pointer that the memory had when it was written.
-   * This should be unique across the whole blend-file, except for `BLEND_DATA` blocks, which
-   * should be unique within a same ID.
-   */
-  const void *old;
-  /** Number of bytes in the block. */
-  int64_t len;
-  /** Number of structs in the array (1 for simple structs). */
-  int64_t nr;
-} BHead;
-#
-#
-typedef struct BHead4 {
-  int code, len;
-  uint old;
-  int SDNAnr, nr;
-} BHead4;
-#
-#
-typedef struct SmallBHead8 {
-  int code, len;
-  uint64_t old;
-  int SDNAnr, nr;
-} SmallBHead8;
-#
-#
-typedef struct LargeBHead8 {
-  int code;
-  int SDNAnr;
-  uint64_t old;
-  int64_t len;
-  int64_t nr;
-} LargeBHead8;
-
-#ifdef __cplusplus
-enum class BHeadType {
-  BHead4,
-  SmallBHead8,
-  LargeBHead8,
-};
-#endif