From 14d1a56c71b35397ebd989c31e45a3d5ce130073 Mon Sep 17 00:00:00 2001 From: "sheng.zhang" Date: Tue, 2 Jun 2026 14:14:24 +0800 Subject: [PATCH] Fix macOS FBX load failure from FreeType bundled-zlib symbol pollution On macOS, FBX 7.4 binary files whose property arrays are zlib-compressed (e.g. chess_set_2k.fbx) fail to load with a generic "cannot parse file format" error, while uncompressed FBX load fine. Root cause: FreeType 2.12.1 always embeds its own copy of zlib inside ftgzip.c.o (even with FT_DISABLE_ZLIB=TRUE) and exports inflate / inflateInit_ / zcalloc / adler32 etc. as global symbols. When BambuStudio is statically linked, the linker resolves Assimp's inflateInit_ to FreeType's bundled 1.2.12 copy while inflate still binds at runtime to the system libz (1.2.11). The z_stream/state ABI mismatch makes Assimp's FBX binary parser throw "CompressionFailure decompressing this file using gzip.", surfacing as a generic "cannot parse file format" in the UI. Changes: - Add a macOS-only, idempotent post-install step deps/FREETYPE/strip_libfreetype_zlib.sh (wired in through FREETYPE.cmake) that rewrites ftgzip.c.o inside libfreetype.a so the inlined zlib helpers become local symbols while FT_Gzip_Uncompress / FT_Stream_OpenGzip stay externally visible. Handles universal (x86_64 + arm64) archives via lipo per-slice and is idempotent. - Disable Assimp's bundled zlib on macOS (deps/Assimp/Assimp.cmake: ASSIMP_BUILD_ZLIB=OFF) so it builds against the system zlib. Assimp's vendored zlib fails to compile against the modern macOS SDK: its zutil.h takes the classic-Mac branch under TARGET_OS_MAC and does `#define fdopen(fd,mode) NULL`, clobbering the SDK's real fdopen prototype in . Windows/Linux keep the bundled zlib. Non-macOS platforms are otherwise unaffected. --- deps/Assimp/Assimp.cmake | 13 ++- deps/FREETYPE/FREETYPE.cmake | 21 ++++ deps/FREETYPE/strip_libfreetype_zlib.sh | 136 ++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100755 deps/FREETYPE/strip_libfreetype_zlib.sh diff --git a/deps/Assimp/Assimp.cmake b/deps/Assimp/Assimp.cmake index 56ea3079c9..037bf8cdc8 100644 --- a/deps/Assimp/Assimp.cmake +++ b/deps/Assimp/Assimp.cmake @@ -6,6 +6,17 @@ else() set(_assimp_hash "SHA256=66dfbaee288f2bc43172440a55d0235dfc7bf885dda6435c038e8000e79582cb") endif() +# Assimp's bundled zlib (contrib/zlib) is too old to compile against the modern +# macOS SDK: its zutil.h takes the classic-Mac branch under TARGET_OS_MAC and +# does `#define fdopen(fd,mode) NULL`, which then clobbers the SDK's real +# `fdopen` prototype in and breaks the build. On macOS use the system +# zlib (already found by find_package(ZLIB) in deps-unix-common) instead. +if(APPLE) + set(_assimp_build_zlib "-DASSIMP_BUILD_ZLIB=OFF") +else() + set(_assimp_build_zlib "-DASSIMP_BUILD_ZLIB=ON") +endif() + bambustudio_add_cmake_project(Assimp URL ${_assimp_url} URL_HASH ${_assimp_hash} @@ -19,7 +30,7 @@ bambustudio_add_cmake_project(Assimp -DASSIMP_BUILD_GLTF_IMPORTER=ON -DASSIMP_BUILD_OBJ_IMPORTER=ON -DASSIMP_BUILD_FBX_IMPORTER=ON - -DASSIMP_BUILD_ZLIB=ON + ${_assimp_build_zlib} -DASSIMP_WARNINGS_AS_ERRORS=OFF -DBUILD_WITH_STATIC_CRT=OFF ) diff --git a/deps/FREETYPE/FREETYPE.cmake b/deps/FREETYPE/FREETYPE.cmake index d4b5e0811b..8106b8d050 100644 --- a/deps/FREETYPE/FREETYPE.cmake +++ b/deps/FREETYPE/FREETYPE.cmake @@ -27,3 +27,24 @@ bambustudio_add_cmake_project(FREETYPE if(MSVC) add_debug_dep(dep_FREETYPE) endif() + +# On macOS the FT_DISABLE_ZLIB option above does NOT prevent FreeType from +# linking the bundled zlib code into ftgzip.c.o - the inline copies of +# inflate / inflateInit_ / zcalloc / adler32 etc. stay as global T symbols. +# When BambuStudio is statically linked, those globals satisfy Assimp's +# `inflateInit_` reference with FreeType's bundled 1.2.12 code while Assimp's +# `inflate` still binds dynamically to the macOS system libz (1.2.11). The +# resulting z_stream/state ABI mismatch makes Assimp's FBX 7.4 binary parser +# fail with "CompressionFailure decompressing this file using gzip." on any +# FBX that uses compressed property arrays. Strip the zlib externals after +# install so only FT_Gzip_Uncompress / FT_Stream_OpenGzip remain visible. +if(APPLE) + ExternalProject_Add_Step(dep_FREETYPE strip_zlib_globals + COMMENT "Stripping FreeType bundled-zlib globals from libfreetype.a (macOS)" + DEPENDEES install + ALWAYS 1 + COMMAND ${CMAKE_COMMAND} -E env bash + "${CMAKE_CURRENT_LIST_DIR}/strip_libfreetype_zlib.sh" + "${DESTDIR}/usr/local/lib/libfreetype.a" + ) +endif() diff --git a/deps/FREETYPE/strip_libfreetype_zlib.sh b/deps/FREETYPE/strip_libfreetype_zlib.sh new file mode 100755 index 0000000000..56d0539129 --- /dev/null +++ b/deps/FREETYPE/strip_libfreetype_zlib.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# Strip global zlib symbols out of FreeType's bundled gzip code on macOS. +# +# FreeType 2.12.1 always embeds its own copy of zlib inside ftgzip.c.o, even +# when configured with FT_DISABLE_ZLIB=TRUE, because some other FreeType TUs +# call FT_Gzip_Uncompress / FT_Stream_OpenGzip. Those zlib internals +# (_inflate, _inflateInit_, _zcalloc, _adler32, ...) are exported as global T +# symbols. When BambuStudio is statically linked, the linker resolves +# Assimp's `inflateInit_` reference to FreeType's bundled 1.2.12 copy while +# `inflate` still binds at runtime to the system libz.1.dylib (1.2.11). The +# resulting z_stream/state ABI mismatch makes Assimp's FBX 7.4 binary parser +# return CompressionFailure for any FBX that uses compressed property arrays +# (the user-visible "无法解析文件格式" for chess_set_2k.fbx / Dragon.fbx). +# +# To keep FT_Gzip_* functional while preventing the symbol pollution, this +# script rewrites ftgzip.c.o inside libfreetype.a so that all the zlib +# helpers it inlines become private (lowercase t) while the public +# FT_Gzip_Uncompress / FT_Stream_OpenGzip stay externally visible. +# +# The script is idempotent: re-running it on an already-patched archive is a +# no-op. + +set -euo pipefail + +LIBFREETYPE_PATH="${1:?usage: $0 }" + +if [[ ! -f "${LIBFREETYPE_PATH}" ]]; then + echo "[strip_libfreetype_zlib] libfreetype.a not found: ${LIBFREETYPE_PATH}" >&2 + exit 1 +fi + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "[strip_libfreetype_zlib] skipped on non-macOS" + exit 0 +fi + +# Symbols that must remain external (anything else in ftgzip.c.o gets hidden). +LOCALIZE_SYMBOLS=( + _crc32_combine + _crc32_combine64 + _crc32_combine_gen + _crc32_combine_gen64 + _crc32_combine_op + _crc32_z + _get_crc_table + _inflate + _inflateInit_ + _inflateInit2_ + _inflateEnd + _inflateReset + _inflateReset2 + _inflateResetKeep + _inflateSetDictionary + _inflateSyncPoint + _inflateUndermine + _inflateValidate + _inflate_fast + _inflate_table + _zError + _zcalloc + _zcfree + _zlibCompileFlags + _zlibVersion + _zmemcmp + _zmemcpy + _zmemzero + _adler32 + _adler32_combine + _adler32_combine64 + _adler32_z +) + +# Quick guard: if libfreetype.a no longer exports zlib globals, we're done. +if ! nm "${LIBFREETYPE_PATH}" 2>/dev/null | grep -qE "^[0-9a-f]+ T _(inflate|inflateInit_|zcalloc|zlibVersion)$"; then + echo "[strip_libfreetype_zlib] libfreetype.a already clean: ${LIBFREETYPE_PATH}" + exit 0 +fi + +WORK_DIR="$(mktemp -d -t libfreetype_strip.XXXXXX)" +trap 'rm -rf "${WORK_DIR}"' EXIT + +LOCALIZE_LIST="${WORK_DIR}/localize.list" +printf '%s\n' "${LOCALIZE_SYMBOLS[@]}" > "${LOCALIZE_LIST}" + +ARCHES=$(lipo -archs "${LIBFREETYPE_PATH}" 2>/dev/null || echo "") +if [[ -z "${ARCHES}" ]]; then + ARCHES="$(uname -m)" +fi + +PATCHED_SLICES=() +for ARCH in ${ARCHES}; do + SLICE_DIR="${WORK_DIR}/${ARCH}" + mkdir -p "${SLICE_DIR}" + SLICE_LIB="${SLICE_DIR}/libfreetype.a" + if [[ "${ARCHES}" == *" "* ]]; then + lipo "${LIBFREETYPE_PATH}" -thin "${ARCH}" -output "${SLICE_LIB}" + else + cp "${LIBFREETYPE_PATH}" "${SLICE_LIB}" + fi + + pushd "${SLICE_DIR}" >/dev/null + ar -x libfreetype.a ftgzip.c.o + # macOS' ld -r needs an explicit platform_version, otherwise it complains + # about missing LC_BUILD_VERSION. We mirror the toolchain default + # (10.15 → SDK 13.1) used by BambuStudio's CMake setup. + xcrun ld -r ftgzip.c.o \ + -unexported_symbols_list "${LOCALIZE_LIST}" \ + -arch "${ARCH}" \ + -platform_version macos 10.15 13.1 \ + -o ftgzip.clean.c.o + mv ftgzip.clean.c.o ftgzip.c.o + ar -d libfreetype.a ftgzip.c.o + ar -q libfreetype.a ftgzip.c.o + ranlib libfreetype.a 2>/dev/null || true + popd >/dev/null + + PATCHED_SLICES+=("${SLICE_LIB}") +done + +if (( ${#PATCHED_SLICES[@]} == 1 )); then + cp "${PATCHED_SLICES[0]}" "${LIBFREETYPE_PATH}" +else + lipo -create "${PATCHED_SLICES[@]}" -output "${LIBFREETYPE_PATH}" +fi + +if nm "${LIBFREETYPE_PATH}" 2>/dev/null | grep -qE "^[0-9a-f]+ T _(inflate|inflateInit_|zcalloc|zlibVersion)$"; then + echo "[strip_libfreetype_zlib] FAILED to hide zlib externals in ${LIBFREETYPE_PATH}" >&2 + exit 2 +fi + +if ! nm "${LIBFREETYPE_PATH}" 2>/dev/null | grep -qE "^[0-9a-f]+ T _FT_Gzip_Uncompress$"; then + echo "[strip_libfreetype_zlib] FAILED to preserve _FT_Gzip_Uncompress in ${LIBFREETYPE_PATH}" >&2 + exit 3 +fi + +echo "[strip_libfreetype_zlib] patched ${LIBFREETYPE_PATH} (arches: ${ARCHES})"