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