From fd21113c7299253d3f049aed84a5e8ef6242a5cb Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 12:44:26 +0200 Subject: [PATCH 01/16] crn_decomp: make DXT block word access explicitly endian-independent Use explicit 16-bit types and byte-wise little-endian reconstruction for DXT1 color endpoints and DXT5 packed words. Also make the DXT5 selector accessor const-correct. This clarifies that these values are reconstructed from the serialized byte representation rather than relying on host endianness. --- inc/crn_decomp.h | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/inc/crn_decomp.h b/inc/crn_decomp.h index 1abb5558..09230454 100644 --- a/inc/crn_decomp.h +++ b/inc/crn_decomp.h @@ -1278,23 +1278,26 @@ struct dxt1_block { utils::zero_this(this); } - // These methods assume the in-memory rep is in LE byte order. - inline uint32 get_low_color() const { - return m_low_color[0] | (m_low_color[1] << 8U); + inline uint16 get_low_color() const { + return static_cast( + static_cast(m_low_color[0]) | + (static_cast(m_low_color[1]) << 8U)); } - - inline uint32 get_high_color() const { - return m_high_color[0] | (m_high_color[1] << 8U); + + inline uint16 get_high_color() const { + return static_cast( + static_cast(m_high_color[0]) | + (static_cast(m_high_color[1]) << 8U)); } - + inline void set_low_color(uint16 c) { - m_low_color[0] = static_cast(c & 0xFF); - m_low_color[1] = static_cast((c >> 8) & 0xFF); + m_low_color[0] = static_cast(c); + m_low_color[1] = static_cast(c >> 8); } - + inline void set_high_color(uint16 c) { - m_high_color[0] = static_cast(c & 0xFF); - m_high_color[1] = static_cast((c >> 8) & 0xFF); + m_high_color[0] = static_cast(c); + m_high_color[1] = static_cast(c >> 8); } inline uint32 get_selector(uint32 x, uint32 y) const { @@ -1364,11 +1367,18 @@ struct dxt5_block { m_endpoints[1] = static_cast(i); } - uint32 get_endpoints_as_word() const { return m_endpoints[0] | (m_endpoints[1] << 8); } - - uint32 get_selectors_as_word(uint32 index) { + uint16 get_endpoints_as_word() const { + return static_cast( + static_cast(m_endpoints[0]) | + (static_cast(m_endpoints[1]) << 8)); + } + + uint16 get_selectors_as_word(uint index) const { CRND_ASSERT(index < 3); - return m_selectors[index * 2] | (m_selectors[index * 2 + 1] << 8); + + return static_cast( + static_cast(m_selectors[index * 2]) | + (static_cast(m_selectors[index * 2 + 1]) << 8)); } inline uint32 get_selector(uint32 x, uint32 y) const { From 4be77f532c91950219ba0cb33c438c515c86ddf8 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 13:26:23 +0200 Subject: [PATCH 02/16] crn_dxt1: fix DXT1 color hashing on big-endian platforms Avoid treating color_quad_u8 as a native-endian uint32 when building DXT1 unique-color hashes and selectors. Construct the RGBA value explicitly in byte order instead. Also handle the DXT1 alpha threshold using the source pixel's alpha component directly. --- crnlib/crn_color.h | 7 +++++++ crnlib/crn_dxt1.cpp | 29 +++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/crnlib/crn_color.h b/crnlib/crn_color.h index 076b53f1..8d417b2c 100644 --- a/crnlib/crn_color.h +++ b/crnlib/crn_color.h @@ -176,6 +176,13 @@ class color_quad : public helpers::rel_opsm_enforce_selector = m_best_solution.m_enforce_selector; m_pResults->m_enforced_selector = m_best_solution.m_enforced_selector; m_pResults->m_reordered = invert_selectors; + if (invert_selectors) { m_pResults->m_low_color = m_best_solution.m_coords.m_high_color; m_pResults->m_high_color = m_best_solution.m_coords.m_low_color; @@ -234,17 +235,13 @@ void dxt1_endpoint_optimizer::return_solution() { if (invert_selectors) pInvert_table = m_best_solution.m_alpha_block ? g_invTableAlpha : g_invTableColor; - const uint alpha_thresh = m_pParams->m_pixels_have_alpha ? (m_pParams->m_dxt1a_alpha_threshold << 24U) : 0; - - const uint32* pSrc_pixels = reinterpret_cast(m_pParams->m_pPixels); + const color_quad_u8* pSrc_pixels = m_pParams->m_pPixels; uint8* pDst_selectors = m_pResults->m_pSelectors; if ((m_unique_colors.size() == 1) && (!m_pParams->m_pixels_have_alpha)) { - uint32 c = utils::read_le32(pSrc_pixels); - - CRNLIB_ASSERT(c >= alpha_thresh); + color_quad_u8 color(pSrc_pixels->r, pSrc_pixels->g, pSrc_pixels->b, 255); - c |= 0xFF000000U; + uint32 c = color.get_rgba_u32(); unique_color_hash_map::const_iterator it(m_unique_color_hash_map.find(c)); CRNLIB_ASSERT(it != m_unique_color_hash_map.end()); @@ -261,13 +258,15 @@ void dxt1_endpoint_optimizer::return_solution() { uint32 prev_color = 0; do { - uint32 c = utils::read_le32(pSrc_pixels); - pSrc_pixels++; - uint8 selector = 3; - if (c >= alpha_thresh) { - c |= 0xFF000000U; + uint8 alpha_thresh = pSrc_pixels->a; + + if (!m_pParams->m_pixels_have_alpha || (alpha_thresh >= m_pParams->m_dxt1a_alpha_threshold)) { + + color_quad_u8 color(pSrc_pixels->r, pSrc_pixels->g, pSrc_pixels->b, 255); + + uint32 c = color.get_rgba_u32(); if (c == prev_color) selector = prev_selector; @@ -285,6 +284,8 @@ void dxt1_endpoint_optimizer::return_solution() { } } + pSrc_pixels++; + *pDst_selectors++ = selector; } while (pDst_selectors != pDst_selectors_end); @@ -1708,8 +1709,8 @@ void dxt1_endpoint_optimizer::compute_internal(const params& p, results& r) { unique_color color(color_quad_u8(0), 1); for (uint i = 0; i < m_pParams->m_num_pixels; i++) { if (!m_pParams->m_pixels_have_alpha || m_pParams->m_pPixels[i].a >= m_pParams->m_dxt1a_alpha_threshold) { - color.m_color.m_u32 = m_pParams->m_pPixels[i].m_u32 | 0xFF000000; - unique_color_hash_map::insert_result ins_result(m_unique_color_hash_map.insert(color.m_color.m_u32, m_unique_colors.size())); + color.m_color = color_quad_u8(m_pParams->m_pPixels[i].r, m_pParams->m_pPixels[i].g, m_pParams->m_pPixels[i].b, 255); + unique_color_hash_map::insert_result ins_result(m_unique_color_hash_map.insert(color.m_color.get_rgba_u32(), m_unique_colors.size())); if (ins_result.second) { m_unique_colors.push_back(color); } else { From 5e8f75ca999cd63a5973cd70ba0aa69b6c07caf3 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 17:29:11 +0200 Subject: [PATCH 03/16] crn_core: detect endianness correctly on GCC targets The GCC configuration unconditionally defined CRNLIB_LITTLE_ENDIAN_CPU, which caused big-endian targets such as s390x to be treated as little-endian. Use the compiler-provided byte-order macros to set the appropriate endianness flag instead. This fixes serialization of little-endian formats such as DDS on big-endian platforms. --- crnlib/crn_core.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crnlib/crn_core.h b/crnlib/crn_core.h index c1f31b03..6f73f4ba 100644 --- a/crnlib/crn_core.h +++ b/crnlib/crn_core.h @@ -75,7 +75,13 @@ #define CRNLIB_USE_UNALIGNED_INT_LOADS 1 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define CRNLIB_LITTLE_ENDIAN_CPU 1 +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define CRNLIB_BIG_ENDIAN_CPU 1 +#else +#error "Unknown CPU endianness" +#endif #define CRNLIB_USE_PTHREADS_API 1 #define CRNLIB_USE_GCC_ATOMIC_BUILTINS 1 From 8cb6e73153c06415d308e782609188e70a8da5e2 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 17:52:50 +0200 Subject: [PATCH 04/16] crn_ktx_texture: add configurable endianness support - Add little-, big-, and native-endian KTX output modes. - Serialize KTX headers and size fields using the selected byte order. - Propagate KTX endianness through compression parameters and file output. - Expose -ktxBigEndian and -ktxNativeEndian in crunch. - Preserve the selected KTX byte order when copying textures. --- crnlib/crn_ktx_texture.cpp | 44 +++++++++++++++---------------- crnlib/crn_ktx_texture.h | 4 +++ crnlib/crn_mipmapped_texture.cpp | 40 +++++++++++++++++++++++++--- crnlib/crn_mipmapped_texture.h | 4 +-- crnlib/crn_texture_conversion.cpp | 10 +++++-- crunch/crunch.cpp | 10 +++++++ inc/crnlib.h | 12 +++++++++ 7 files changed, 94 insertions(+), 30 deletions(-) diff --git a/crnlib/crn_ktx_texture.cpp b/crnlib/crn_ktx_texture.cpp index 5b398f63..804a759d 100644 --- a/crnlib/crn_ktx_texture.cpp +++ b/crnlib/crn_ktx_texture.cpp @@ -501,13 +501,16 @@ bool ktx_texture::read_from_stream(data_stream_serializer& serializer) { } bool ktx_texture::write_to_stream(data_stream_serializer& serializer, bool no_keyvalue_data) { + serializer.set_little_endian(!m_big_endian); + if (!consistency_check()) { CRNLIB_ASSERT(0); return false; } memcpy(m_header.m_identifier, s_ktx_file_id, sizeof(m_header.m_identifier)); - m_header.m_endianness = m_opposite_endianness ? KTX_OPPOSITE_ENDIAN : KTX_ENDIAN; + + m_header.m_endianness = KTX_ENDIAN; if (m_block_dim == 1) { m_header.m_glTypeSize = get_ogl_type_size(m_header.m_glType); @@ -522,13 +525,22 @@ bool ktx_texture::write_to_stream(data_stream_serializer& serializer, bool no_ke m_header.m_bytesOfKeyValueData += sizeof(uint32) + ((m_key_values[i].size() + 3) & ~3); } - if (m_opposite_endianness) - m_header.endian_swap(); - - bool success = (serializer.write(&m_header, sizeof(m_header), 1) == 1); - - if (m_opposite_endianness) - m_header.endian_swap(); + bool success = true; + + success &= serializer.write_chars((const char*)m_header.m_identifier, sizeof(m_header.m_identifier)); + success &= serializer.write_value(m_header.m_endianness); + success &= serializer.write_value(m_header.m_glType); + success &= serializer.write_value(m_header.m_glTypeSize); + success &= serializer.write_value(m_header.m_glFormat); + success &= serializer.write_value(m_header.m_glInternalFormat); + success &= serializer.write_value(m_header.m_glBaseInternalFormat); + success &= serializer.write_value(m_header.m_pixelWidth); + success &= serializer.write_value(m_header.m_pixelHeight); + success &= serializer.write_value(m_header.m_pixelDepth); + success &= serializer.write_value(m_header.m_numberOfArrayElements); + success &= serializer.write_value(m_header.m_numberOfFaces); + success &= serializer.write_value(m_header.m_numberOfMipmapLevels); + success &= serializer.write_value(m_header.m_bytesOfKeyValueData); if (!success) return success; @@ -540,15 +552,9 @@ bool ktx_texture::write_to_stream(data_stream_serializer& serializer, bool no_ke for (uint i = 0; i < m_key_values.size(); i++) { uint32 key_value_size = m_key_values[i].size(); - if (m_opposite_endianness) - key_value_size = utils::swap32(key_value_size); - - success = (serializer.write(&key_value_size, sizeof(key_value_size), 1) == 1); + success = serializer.write_value(key_value_size); total_key_value_bytes += sizeof(key_value_size); - if (m_opposite_endianness) - key_value_size = utils::swap32(key_value_size); - if (!success) return false; @@ -584,13 +590,7 @@ bool ktx_texture::write_to_stream(data_stream_serializer& serializer, bool no_ke if (!image_size) return false; - if (m_opposite_endianness) - image_size = utils::swap32(image_size); - - success = (serializer.write(&image_size, sizeof(image_size), 1) == 1); - - if (m_opposite_endianness) - image_size = utils::swap32(image_size); + success = serializer.write_value(image_size); if (!success) return false; diff --git a/crnlib/crn_ktx_texture.h b/crnlib/crn_ktx_texture.h index 65698796..b009e8ca 100644 --- a/crnlib/crn_ktx_texture.h +++ b/crnlib/crn_ktx_texture.h @@ -165,6 +165,7 @@ class ktx_texture { m_block_dim = rhs.m_block_dim; m_bytes_per_block = rhs.m_bytes_per_block; m_opposite_endianness = rhs.m_opposite_endianness; + m_big_endian = rhs.m_big_endian; return *this; } @@ -178,6 +179,7 @@ class ktx_texture { m_bytes_per_block = 0; m_opposite_endianness = false; + m_big_endian = false; } // High level methods @@ -215,6 +217,7 @@ class ktx_texture { bool get_opposite_endianness() const { return m_opposite_endianness; } void set_opposite_endianness(bool flag) { m_opposite_endianness = flag; } + void set_big_endian(bool flag) { m_big_endian = flag; } uint32 get_block_dim() const { return m_block_dim; } uint32 get_bytes_per_block() const { return m_bytes_per_block; } @@ -282,6 +285,7 @@ class ktx_texture { uint32 m_bytes_per_block; bool m_opposite_endianness; + bool m_big_endian; bool compute_pixel_info(); }; diff --git a/crnlib/crn_mipmapped_texture.cpp b/crnlib/crn_mipmapped_texture.cpp index 22b7078f..eaf9d699 100644 --- a/crnlib/crn_mipmapped_texture.cpp +++ b/crnlib/crn_mipmapped_texture.cpp @@ -1553,7 +1553,7 @@ bool mipmapped_texture::read_ktx(data_stream_serializer& serializer) { return true; } -bool mipmapped_texture::write_ktx(data_stream_serializer& serializer) const { +bool mipmapped_texture::write_ktx(data_stream_serializer& serializer, crn_ktx_endianness ktx_endianness) const { if (!m_width) { set_last_error("Nothing to write"); return false; @@ -1662,6 +1662,32 @@ bool mipmapped_texture::write_ktx(data_stream_serializer& serializer) const { if (!success) return false; + bool ktx_big_endian = false; + + switch (ktx_endianness) { + case cCRNKTXLittleEndian: + ktx_big_endian = false; + break; + + case cCRNKTXBigEndian: + ktx_big_endian = true; + break; + + case cCRNKTXNativeEndian: + ktx_big_endian = !c_crnlib_little_endian_platform; + break; + + default: + CRNLIB_ASSERT(0); + return false; + } + + const bool host_big_endian = !c_crnlib_little_endian_platform; + const bool opposite_endianness = (ktx_big_endian != host_big_endian); + + kt.set_opposite_endianness(opposite_endianness); + kt.set_big_endian(ktx_big_endian); + dynamic_string fourcc_str(cVarArg, "%c%c%c%c", m_format & 0xFF, (m_format >> 8) & 0xFF, (m_format >> 16) & 0xFF, (m_format >> 24) & 0xFF); kt.add_key_value("CRNLIB_FOURCC", fourcc_str.get_ptr()); @@ -2809,7 +2835,7 @@ bool mipmapped_texture::read_crn(data_stream_serializer& serializer) { bool mipmapped_texture::write_to_file( const char* pFilename, texture_file_types::format file_format, - crn_comp_params* pComp_params, + const crn_comp_params* pComp_params, uint32* pActual_quality_level, float* pActual_bitrate, uint32 image_write_flags) { if (pActual_quality_level) @@ -2840,7 +2866,7 @@ bool mipmapped_texture::write_to_file( } else if (!texture_file_types::supports_mipmaps(file_format)) { success = write_regular_image(pFilename, image_write_flags); } else { - if (pComp_params) { + if (pComp_params && (file_format != texture_file_types::cFormatKTX)) { console::warning("mipmapped_texture::write_to_file: Ignoring CRN compression parameters (currently unsupported for this file type)."); } @@ -2857,7 +2883,13 @@ bool mipmapped_texture::write_to_file( break; } case texture_file_types::cFormatKTX: { - success = write_ktx(serializer); + crn_ktx_endianness ktx_endianness = cCRNKTXLittleEndian; + + if (pComp_params) { + ktx_endianness = pComp_params->m_ktx_endianness; + } + + success = write_ktx(serializer, ktx_endianness); break; } default: { diff --git a/crnlib/crn_mipmapped_texture.h b/crnlib/crn_mipmapped_texture.h index 648faa20..9c8fdc8b 100644 --- a/crnlib/crn_mipmapped_texture.h +++ b/crnlib/crn_mipmapped_texture.h @@ -187,7 +187,7 @@ class mipmapped_texture { bool write_dds(data_stream_serializer& serializer) const; bool read_ktx(data_stream_serializer& serializer); - bool write_ktx(data_stream_serializer& serializer) const; + bool write_ktx(data_stream_serializer& serializer, crn_ktx_endianness ktx_endianness) const; bool read_crn(data_stream_serializer& serializer); bool read_crn_from_memory(const void* pData, uint data_size, const char* pFilename); @@ -199,7 +199,7 @@ class mipmapped_texture { bool write_to_file( const char* pFilename, texture_file_types::format file_format = texture_file_types::cFormatInvalid, - crn_comp_params* pComp_params = NULL, + const crn_comp_params* pComp_params = NULL, uint32* pActual_quality_level = NULL, float* pActual_bitrate = NULL, uint32 image_write_flags = 0); diff --git a/crnlib/crn_texture_conversion.cpp b/crnlib/crn_texture_conversion.cpp index d7f205b3..760575d8 100644 --- a/crnlib/crn_texture_conversion.cpp +++ b/crnlib/crn_texture_conversion.cpp @@ -447,6 +447,12 @@ static bool convert_and_write_normal_texture(mipmapped_texture& work_tex, conver console::info("Texture format conversion took %3.3fs", t); } + const crn_comp_params* pCompParams = NULL; + + if (params.m_dst_file_type == texture_file_types::cFormatKTX) { + pCompParams = &comp_params; + } + if (params.m_write_mipmaps_to_multiple_files) { for (uint f = 0; f < work_tex.get_num_faces(); f++) { for (uint l = 0; l < work_tex.get_num_levels(); l++) { @@ -469,14 +475,14 @@ static bool convert_and_write_normal_texture(mipmapped_texture& work_tex, conver console::info("Writing texture face %u mip level %u to file %s", f, l, filename.get_ptr()); - if (!new_tex.write_to_file(filename.get_ptr(), params.m_dst_file_type, NULL, NULL, NULL)) + if (!new_tex.write_to_file(filename.get_ptr(), params.m_dst_file_type, pCompParams, NULL, NULL)) return convert_error(params, "Failed writing output file!"); } } } else { console::message("Writing texture to file: \"%s\"", params.m_dst_filename.get_ptr()); - if (!work_tex.write_to_file(params.m_dst_filename.get_ptr(), params.m_dst_file_type, NULL, NULL, NULL)) + if (!work_tex.write_to_file(params.m_dst_filename.get_ptr(), params.m_dst_file_type, pCompParams, NULL, NULL)) return convert_error(params, "Failed writing output file!"); if (!params.m_no_stats) { diff --git a/crunch/crunch.cpp b/crunch/crunch.cpp index 03c4b2f3..444800db 100644 --- a/crunch/crunch.cpp +++ b/crunch/crunch.cpp @@ -168,6 +168,8 @@ class crunch { console::message("\nOutput pixel format options:"); console::printf("-usesourceformat - Use input file's format for output format (when possible)."); + console::printf("-ktxBigEndian - Write KTX files using big-endian byte order (default=little-endian)."); + console::printf("-ktxNativeEndian - Write KTX files using the native CPU byte order."); console::message("\nAll supported texture formats (Note: .CRN only supports DXTn pixel formats):"); for (uint32 i = 0; i < pixel_format_helpers::get_num_formats(); i++) { pixel_format fmt = pixel_format_helpers::get_pixel_format_by_index(i); @@ -242,6 +244,8 @@ class crunch { {"forceprimaryencoding", 0, false}, {"usetransparentindicesforblack", 0, false}, {"usesourceformat", 0, false}, + {"ktxBigEndian", 0, false}, + {"ktxNativeEndian", 0, false}, {"rescalemode", 1, false}, {"rescale", 2, false}, @@ -873,6 +877,12 @@ class crunch { else comp_params.set_flag(cCRNCompFlagUseTransparentIndicesForBlack, false); + if (m_params.get_value_as_bool("ktxBigendian")) { + comp_params.m_ktx_endianness = cCRNKTXBigEndian; + } else if (m_params.get_value_as_bool("ktxNativeEndian")) { + comp_params.m_ktx_endianness = cCRNKTXNativeEndian; + } + return true; } diff --git a/inc/crnlib.h b/inc/crnlib.h index 5ecbeb8a..3a335bce 100644 --- a/inc/crnlib.h +++ b/inc/crnlib.h @@ -186,6 +186,14 @@ enum crn_dxt_compressor_type { cCRNDXTCompressorForceDWORD = 0xFFFFFFFF }; +// KTX endianness. +enum crn_ktx_endianness { + cCRNKTXLittleEndian, + cCRNKTXBigEndian, + cCRNKTXNativeEndian, + cCRNKTXForceDWORD = 0xFFFFFFFF +}; + // Progress callback function. // Processing will stop prematurely (and fail) if the callback returns false. // phase_index, total_phases - high level progress @@ -216,6 +224,7 @@ struct crn_comp_params { m_dxt1a_alpha_threshold = 128; m_dxt_quality = cCRNDXTQualityUber; m_dxt_compressor_type = cCRNDXTCompressorCRN; + m_ktx_endianness = cCRNKTXLittleEndian; m_alpha_component = 3; m_crn_adaptive_tile_color_psnr_derating = 2.0f; @@ -333,6 +342,9 @@ struct crn_comp_params { crn_dxt_quality m_dxt_quality; crn_dxt_compressor_type m_dxt_compressor_type; + // KTX endianness parameters. + crn_ktx_endianness m_ktx_endianness; + // Alpha channel's component. Defaults to 3. crn_uint32 m_alpha_component; From a6b1a37da7edac8452d2ba0839fb9cc13509c8be Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 21:03:46 +0200 Subject: [PATCH 05/16] crn_ktx_texture: fix reading for cross-endian files Read the KTX endianness marker as raw bytes before interpreting the remaining header fields, then configure the serializer to match the file's byte order. Use endian-aware deserialization for key-value sizes and mip image sizes instead of manually swapping them. --- crnlib/crn_ktx_texture.cpp | 57 ++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/crnlib/crn_ktx_texture.cpp b/crnlib/crn_ktx_texture.cpp index 804a759d..df5acca8 100644 --- a/crnlib/crn_ktx_texture.cpp +++ b/crnlib/crn_ktx_texture.cpp @@ -279,25 +279,52 @@ bool ktx_texture::compute_pixel_info() { bool ktx_texture::read_from_stream(data_stream_serializer& serializer) { clear(); - // Read header - if (serializer.read(&m_header, 1, sizeof(m_header)) != sizeof(ktx_header)) + // Read the identifier first. It is endian-independent. + if (serializer.read(m_header.m_identifier, 1, sizeof(m_header.m_identifier)) != sizeof(m_header.m_identifier)) return false; - // Check header + // Check identifier. if (memcmp(s_ktx_file_id, m_header.m_identifier, sizeof(m_header.m_identifier))) return false; - if ((m_header.m_endianness != KTX_OPPOSITE_ENDIAN) && (m_header.m_endianness != KTX_ENDIAN)) + // Read the KTX endianness marker as raw bytes. We cannot interpret it + // until we know the file's byte order. + uint8 endianness_bytes[4]; + if (serializer.read(endianness_bytes, 1, sizeof(endianness_bytes)) != sizeof(endianness_bytes)) return false; - m_opposite_endianness = (m_header.m_endianness == KTX_OPPOSITE_ENDIAN); - if (m_opposite_endianness) { - m_header.endian_swap(); - - if ((m_header.m_glTypeSize != sizeof(uint8)) && (m_header.m_glTypeSize != sizeof(uint16)) && (m_header.m_glTypeSize != sizeof(uint32))) - return false; + static const uint8 little_endian_bytes[] = { 0x01, 0x02, 0x03, 0x04 }; + static const uint8 big_endian_bytes[] = { 0x04, 0x03, 0x02, 0x01 }; + + if (!memcmp(endianness_bytes, little_endian_bytes, sizeof(little_endian_bytes))) { + // File is little-endian. + m_opposite_endianness = !c_crnlib_little_endian_platform; + m_header.m_endianness = KTX_ENDIAN; + serializer.set_little_endian(true); + } else if (!memcmp(endianness_bytes, big_endian_bytes, sizeof(big_endian_bytes))) { + // File is big-endian. + m_opposite_endianness = c_crnlib_little_endian_platform; + m_header.m_endianness = KTX_OPPOSITE_ENDIAN; + serializer.set_little_endian(false); + } else { + return false; } + // Read the remaining header fields using the file's byte order. + if (!serializer.read_object(m_header.m_glType) || + !serializer.read_object(m_header.m_glTypeSize) || + !serializer.read_object(m_header.m_glFormat) || + !serializer.read_object(m_header.m_glInternalFormat) || + !serializer.read_object(m_header.m_glBaseInternalFormat) || + !serializer.read_object(m_header.m_pixelWidth) || + !serializer.read_object(m_header.m_pixelHeight) || + !serializer.read_object(m_header.m_pixelDepth) || + !serializer.read_object(m_header.m_numberOfArrayElements) || + !serializer.read_object(m_header.m_numberOfFaces) || + !serializer.read_object(m_header.m_numberOfMipmapLevels) || + !serializer.read_object(m_header.m_bytesOfKeyValueData)) + return false; + if (!check_header()) return false; @@ -313,14 +340,11 @@ bool ktx_texture::read_from_stream(data_stream_serializer& serializer) { return false; uint32 key_value_byte_size; - if (serializer.read(&key_value_byte_size, 1, sizeof(uint32)) != sizeof(uint32)) + if (!serializer.read_object(key_value_byte_size)) return false; num_key_value_bytes_remaining -= sizeof(uint32); - if (m_opposite_endianness) - key_value_byte_size = utils::swap32(key_value_byte_size); - if (key_value_byte_size > num_key_value_bytes_remaining) return false; @@ -426,11 +450,8 @@ bool ktx_texture::read_from_stream(data_stream_serializer& serializer) { if (!has_valid_image_size_fields) image_size = mip_depth * mip_row_blocks * mip_col_blocks * m_bytes_per_block * get_array_size() * get_num_faces(); else { - if (serializer.read(&image_size, 1, sizeof(image_size)) != sizeof(image_size)) + if (!serializer.read_object(image_size)) return false; - - if (m_opposite_endianness) - image_size = utils::swap32(image_size); } if (!image_size) From 727004fea8b4837ccca7d7fc6be8180a50d9f9a6 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 23:16:09 +0200 Subject: [PATCH 06/16] crn_decomp:: write DXT1 blocks in little-endian byte order Fix CRN DXT1 decompression on big-endian systems. --- inc/crn_decomp.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/inc/crn_decomp.h b/inc/crn_decomp.h index 09230454..c3da2280 100644 --- a/inc/crn_decomp.h +++ b/inc/crn_decomp.h @@ -3416,8 +3416,20 @@ class crn_unpacker { } uint32 color_selector_index = m_codec.decode(m_selector_delta_dm[0]); if (visible) { - pData[0] = m_color_endpoints[color_endpoint_index]; - pData[1] = m_color_selectors[color_selector_index]; + uint32 endpoint = m_color_endpoints[color_endpoint_index]; + uint32 selector = m_color_selectors[color_selector_index]; + + uint8* pBytes = reinterpret_cast(pData); + + pBytes[0] = static_cast(endpoint); + pBytes[1] = static_cast(endpoint >> 8); + pBytes[2] = static_cast(endpoint >> 16); + pBytes[3] = static_cast(endpoint >> 24); + + pBytes[4] = static_cast(selector); + pBytes[5] = static_cast(selector >> 8); + pBytes[6] = static_cast(selector >> 16); + pBytes[7] = static_cast(selector >> 24); } } } From 0387303f7fbac723ec9ecb0e94ad90b848b56338 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 23:46:48 +0200 Subject: [PATCH 07/16] crn_decomp:: write DXT5 blocks in little-endian byte order Fix CRN DXT5 decompression on big-endian systems. --- inc/crn_decomp.h | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/inc/crn_decomp.h b/inc/crn_decomp.h index c3da2280..ef4a0e7b 100644 --- a/inc/crn_decomp.h +++ b/inc/crn_decomp.h @@ -3489,10 +3489,33 @@ class crn_unpacker { uint32 alpha0_selector_index = m_codec.decode(m_selector_delta_dm[1]); if (visible) { const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index * 3]; - pData[0] = m_alpha_endpoints[alpha0_endpoint_index] | (pAlpha0_selectors[0] << 16); - pData[1] = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16); - pData[2] = m_color_endpoints[color_endpoint_index]; - pData[3] = m_color_selectors[color_selector_index]; + + uint32 block0 = m_alpha_endpoints[alpha0_endpoint_index] | (pAlpha0_selectors[0] << 16); + uint32 block1 = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16); + uint32 block2 = m_color_endpoints[color_endpoint_index]; + uint32 block3 = m_color_selectors[color_selector_index]; + + uint8* pBytes = reinterpret_cast(pData); + + pBytes[0] = static_cast(block0); + pBytes[1] = static_cast(block0 >> 8); + pBytes[2] = static_cast(block0 >> 16); + pBytes[3] = static_cast(block0 >> 24); + + pBytes[4] = static_cast(block1); + pBytes[5] = static_cast(block1 >> 8); + pBytes[6] = static_cast(block1 >> 16); + pBytes[7] = static_cast(block1 >> 24); + + pBytes[8] = static_cast(block2); + pBytes[9] = static_cast(block2 >> 8); + pBytes[10] = static_cast(block2 >> 16); + pBytes[11] = static_cast(block2 >> 24); + + pBytes[12] = static_cast(block3); + pBytes[13] = static_cast(block3 >> 8); + pBytes[14] = static_cast(block3 >> 16); + pBytes[15] = static_cast(block3 >> 24); } } } From dc4361a9e65ac65713fb5df7c45f71f84a6e28f9 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 8 Aug 2026 22:04:04 +0000 Subject: [PATCH 08/16] crn_decomp:: write DXN blocks in little-endian byte order Fix CRN DXN decompression on big-endian systems. --- inc/crn_decomp.h | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/inc/crn_decomp.h b/inc/crn_decomp.h index ef4a0e7b..71f20857 100644 --- a/inc/crn_decomp.h +++ b/inc/crn_decomp.h @@ -3575,10 +3575,33 @@ class crn_unpacker { if (visible) { const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index * 3]; const uint16* pAlpha1_selectors = &m_alpha_selectors[alpha1_selector_index * 3]; - pData[0] = m_alpha_endpoints[alpha0_endpoint_index] | (pAlpha0_selectors[0] << 16); - pData[1] = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16); - pData[2] = m_alpha_endpoints[alpha1_endpoint_index] | (pAlpha1_selectors[0] << 16); - pData[3] = pAlpha1_selectors[1] | (pAlpha1_selectors[2] << 16); + + uint32 block0 = m_alpha_endpoints[alpha0_endpoint_index] | (pAlpha0_selectors[0] << 16); + uint32 block1 = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16); + uint32 block2 = m_alpha_endpoints[alpha1_endpoint_index] | (pAlpha1_selectors[0] << 16); + uint32 block3 = pAlpha1_selectors[1] | (pAlpha1_selectors[2] << 16); + + uint8* pBytes = reinterpret_cast(pData); + + pBytes[0] = static_cast(block0); + pBytes[1] = static_cast(block0 >> 8); + pBytes[2] = static_cast(block0 >> 16); + pBytes[3] = static_cast(block0 >> 24); + + pBytes[4] = static_cast(block1); + pBytes[5] = static_cast(block1 >> 8); + pBytes[6] = static_cast(block1 >> 16); + pBytes[7] = static_cast(block1 >> 24); + + pBytes[8] = static_cast(block2); + pBytes[9] = static_cast(block2 >> 8); + pBytes[10] = static_cast(block2 >> 16); + pBytes[11] = static_cast(block2 >> 24); + + pBytes[12] = static_cast(block3); + pBytes[13] = static_cast(block3 >> 8); + pBytes[14] = static_cast(block3 >> 16); + pBytes[15] = static_cast(block3 >> 24); } } } From c8e110d9979b728d57489d8d5d175daff295963e Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 9 Aug 2026 00:50:12 +0200 Subject: [PATCH 09/16] crn_etc: use explicit byte order for ETC1 block packing Avoid relying on host byte order when packing ETC1 blocks. The ETC1 bitstream is defined in big-endian byte order, but the packer previously used the host-endian m_uint64 and m_u32 union members directly. Pack the RGB endpoint components explicitly and write the resulting 64-bit ETC1 block using the big-endian helper. --- crnlib/crn_etc.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crnlib/crn_etc.cpp b/crnlib/crn_etc.cpp index 37c5109b..802dd90f 100644 --- a/crnlib/crn_etc.cpp +++ b/crnlib/crn_etc.cpp @@ -1617,7 +1617,20 @@ uint64 pack_etc1s_block(etc1_block& dst_block, const color_quad_u8* pSrc_pixels, } } - dst_block.m_uint64 = (uint64)selector << 32 | results.m_block_inten_table << 29 | results.m_block_inten_table << 26 | 1 << 25 | (results.m_block_color_unscaled.m_u32 & 0xFFFFFF) << 3; + const uint32 color = + ((uint32)results.m_block_color_unscaled.r << 16) | + ((uint32)results.m_block_color_unscaled.g << 8) | + (uint32)results.m_block_color_unscaled.b; + + const uint64 packed = + (uint64)selector << 32 | + (uint64)results.m_block_inten_table << 29 | + (uint64)results.m_block_inten_table << 26 | + (uint64)1 << 25 | + (uint64)color << 3; + + utils::write_be64(&dst_block.m_uint64, packed); + return results.m_error; } From 1cf506e735443512d112e0a79c7ef4ad0acdfe52 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 9 Aug 2026 01:42:18 +0200 Subject: [PATCH 10/16] crn_dxt_hc: use explicit RGBA packing for ETC color endpoints The code was assigning color_quad_u8::m_u32 directly to an endpoint value. m_u32 depends on the host byte order, causing different CRN output on big-endian targets. Use get_rgba_u32() to explicitly define the RGBA byte layout. This is one of several endian-related conversions in the CRN path. --- crnlib/crn_dxt_hc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crnlib/crn_dxt_hc.cpp b/crnlib/crn_dxt_hc.cpp index 18c9c39c..35595730 100644 --- a/crnlib/crn_dxt_hc.cpp +++ b/crnlib/crn_dxt_hc.cpp @@ -616,7 +616,7 @@ void dxt_hc::determine_color_endpoint_codebook_task_etc(uint64 data, void*) { for (int c = 0; c < 3; c++) endpoint.c[c] = results.m_block_color_unscaled.c[c] << 3 | results.m_block_color_unscaled.c[c] >> 2; endpoint.c[3] = results.m_block_inten_table; - cluster.first_endpoint = endpoint.m_u32; + cluster.first_endpoint = endpoint.get_rgba_u32(); for (uint8 d0 = delta[endpoint.c[3]][0], d1 = delta[endpoint.c[3]][1], c = 0; c < 3; c++) { uint8 q = endpoint.c[c]; cluster.color_values[0].c[c] = q <= d1 ? 0 : q - d1; From a803e5b43f0d5e1358fffb3cf88d2179af65464c Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 9 Aug 2026 01:46:33 +0200 Subject: [PATCH 11/16] crn_comp: fix endian-dependent color endpoint unpacking The color endpoint optimization path was relying on m_u32 assignments when unpacking packed endpoints into color_quad_u8 values. This made the conversion depend on host byte order and produced different CRN output on big-endian architectures. Extract endpoint components explicitly to ensure identical output across little- and big-endian systems. --- crnlib/crn_comp.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crnlib/crn_comp.cpp b/crnlib/crn_comp.cpp index 7a37e47b..89d73e78 100644 --- a/crnlib/crn_comp.cpp +++ b/crnlib/crn_comp.cpp @@ -795,9 +795,26 @@ void crn_comp::optimize_color() { } } crnlib::vector unpacked_endpoints(n); + for (uint16 i = 0; i < n; i++) { - unpacked_endpoints[i].low.m_u32 = m_has_etc_color_blocks ? m_color_endpoints[i] & 0xFFFFFF : dxt1_block::unpack_color(m_color_endpoints[i] & 0xFFFF, true).m_u32; - unpacked_endpoints[i].high.m_u32 = m_has_etc_color_blocks ? m_color_endpoints[i] >> 24 : dxt1_block::unpack_color(m_color_endpoints[i] >> 16, true).m_u32; + uint32 endpoint = m_color_endpoints[i]; + + if (m_has_etc_color_blocks) { + unpacked_endpoints[i].low.r = uint8(endpoint); + unpacked_endpoints[i].low.g = uint8(endpoint >> 8); + unpacked_endpoints[i].low.b = uint8(endpoint >> 16); + unpacked_endpoints[i].low.a = 0; + + endpoint >>= 24; + unpacked_endpoints[i].high.r = uint8(endpoint); + unpacked_endpoints[i].high.g = 0; + unpacked_endpoints[i].high.b = 0; + unpacked_endpoints[i].high.a = 0; + } else { + unpacked_endpoints[i].low = dxt1_block::unpack_color(endpoint & 0xFFFF, true); + + unpacked_endpoints[i].high = dxt1_block::unpack_color(endpoint >> 16, true); + } } optimize_color_params::result remapping_trial[4]; From ea53e3467a237f144eb46d88b251a5c07a81683a Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 9 Aug 2026 02:06:58 +0200 Subject: [PATCH 12/16] crn_comp: avoid endian-dependent color endpoint unpacking Use color_quad::set_rgba_u32() instead of writing the m_u32 union member directly when unpacking ETC color endpoints. The m_u32 representation depends on host byte order, while color components have a fixed RGBA ordering. Explicit component assignment keeps endpoint reconstruction portable across little and big-endian architectures. --- crnlib/crn_color.h | 7 +++++++ crnlib/crn_comp.cpp | 19 ++++++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crnlib/crn_color.h b/crnlib/crn_color.h index 8d417b2c..309588b6 100644 --- a/crnlib/crn_color.h +++ b/crnlib/crn_color.h @@ -176,6 +176,13 @@ class color_quad : public helpers::rel_ops(v); + g = static_cast(v >> 8); + b = static_cast(v >> 16); + a = static_cast(v >> 24); + } + inline uint32 get_rgba_u32() const { return uint32(r) | (uint32(g) << 8) | diff --git a/crnlib/crn_comp.cpp b/crnlib/crn_comp.cpp index 89d73e78..eb3824ae 100644 --- a/crnlib/crn_comp.cpp +++ b/crnlib/crn_comp.cpp @@ -800,20 +800,13 @@ void crn_comp::optimize_color() { uint32 endpoint = m_color_endpoints[i]; if (m_has_etc_color_blocks) { - unpacked_endpoints[i].low.r = uint8(endpoint); - unpacked_endpoints[i].low.g = uint8(endpoint >> 8); - unpacked_endpoints[i].low.b = uint8(endpoint >> 16); - unpacked_endpoints[i].low.a = 0; - - endpoint >>= 24; - unpacked_endpoints[i].high.r = uint8(endpoint); - unpacked_endpoints[i].high.g = 0; - unpacked_endpoints[i].high.b = 0; - unpacked_endpoints[i].high.a = 0; + unpacked_endpoints[i].low.set_rgba_u32(endpoint & 0xFFFFFF); + unpacked_endpoints[i].high.set_rgba_u32(endpoint >> 24); } else { - unpacked_endpoints[i].low = dxt1_block::unpack_color(endpoint & 0xFFFF, true); - - unpacked_endpoints[i].high = dxt1_block::unpack_color(endpoint >> 16, true); + unpacked_endpoints[i].low = + dxt1_block::unpack_color(endpoint & 0xFFFF, true); + unpacked_endpoints[i].high = + dxt1_block::unpack_color(endpoint >> 16, true); } } From 62c6b0b4f599256b8e518d3fcc78cdd9717e6936 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 9 Aug 2026 02:22:24 +0200 Subject: [PATCH 13/16] example2: write DDS output in little-endian format Avoid writing DDS metadata using the host byte order. This produced invalid DDS files on big-endian systems, with swapped signatures, FOURCC values, and header fields. Serialize the DDS header fields explicitly as little endian so the generated files are identical on little and big endian systems. --- example2/example2.cpp | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/example2/example2.cpp b/example2/example2.cpp index 910a9b4c..8aacaec1 100644 --- a/example2/example2.cpp +++ b/example2/example2.cpp @@ -78,6 +78,36 @@ static crn_uint8* read_file_into_buffer(const char* pFilename, crn_uint32& size) return pSrc_file_data; } +// Writes a 32-bit value in little-endian byte order. +// DDS files use little-endian fields regardless of the host CPU byte order. +// Do not write integer values or structures directly with fwrite(), +// because that would make the output dependent on the host endianness. +static void write_u32_le(FILE* pFile, crn_uint32 v) +{ + crn_uint8 buf[4]; + buf[0] = static_cast(v); + buf[1] = static_cast(v >> 8); + buf[2] = static_cast(v >> 16); + buf[3] = static_cast(v >> 24); + fwrite(buf, sizeof(buf), 1, pFile); +} + +// Writes a DDS header using the format's required little-endian byte order. +// DDSURFACEDESC2 consists of 32-bit fields. Serialize each field +// through the little endian writer instead of relying on the native +// memory layout of the structure. +// Do not fwrite() the structure directly: DDS files have a fixed byte order +// while the host CPU may be big-endian. +static void write_dds_header(FILE* pFile, const crnlib::DDSURFACEDESC2& h) +{ + static_assert(sizeof(crnlib::DDSURFACEDESC2) % sizeof(crn_uint32) == 0, + "DDS header must contain only 32-bit fields"); + + const crn_uint32* p = reinterpret_cast(&h); + for (size_t i = 0; i < sizeof(h) / sizeof(crn_uint32); i++) + write_u32_le(pFile, p[i]); +} + int main(int argc, char* argv[]) { printf("example2 - Version v%u.%02u Built " __DATE__ ", " __TIME__ "\n", CRNLIB_VERSION / 100, CRNLIB_VERSION % 100); @@ -166,7 +196,7 @@ int main(int argc, char* argv[]) { } // Write the 4-byte DDS signature (not endian safe, but whatever this is a sample). - fwrite(&crnlib::cDDSFileSignature, sizeof(crnlib::cDDSFileSignature), 1, pDDS_file); + write_u32_le(pDDS_file, crnlib::cDDSFileSignature); // Prepare the DDS header. crnlib::DDSURFACEDESC2 dds_desc; @@ -203,7 +233,7 @@ int main(int argc, char* argv[]) { dds_desc.dwFlags |= DDSD_LINEARSIZE; // Write the DDS header to the output file. - fwrite(&dds_desc, sizeof(dds_desc), 1, pDDS_file); + write_dds_header(pDDS_file, dds_desc); // Now transcode all face and mipmap levels into memory, one mip level at a time. void* pImages[cCRNMaxFaces][cCRNMaxLevels]; From 0459934b62730a68fefbde7e6b3b3362a91a291d Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 9 Aug 2026 02:42:03 +0200 Subject: [PATCH 14/16] example3: write DDS output in little-endian format Avoid writing DDS metadata using the host byte order. This produced invalid DDS files on big-endian systems, with swapped signatures, FOURCC values, and header fields. Serialize the DDS header fields explicitly as little endian so the generated files are identical on little and big endian systems. --- example3/example3.cpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/example3/example3.cpp b/example3/example3.cpp index 2149cbd0..4dc8175f 100644 --- a/example3/example3.cpp +++ b/example3/example3.cpp @@ -72,6 +72,36 @@ static int error(const char* pMsg, ...) { return EXIT_FAILURE; } +// Writes a 32-bit value in little-endian byte order. +// DDS files use little-endian fields regardless of the host CPU byte order. +// Do not write integer values or structures directly with fwrite(), +// because that would make the output dependent on the host endianness. +static void write_u32_le(FILE* pFile, crn_uint32 v) +{ + crn_uint8 buf[4]; + buf[0] = static_cast(v); + buf[1] = static_cast(v >> 8); + buf[2] = static_cast(v >> 16); + buf[3] = static_cast(v >> 24); + fwrite(buf, sizeof(buf), 1, pFile); +} + +// Writes a DDS header using the format's required little-endian byte order. +// DDSURFACEDESC2 consists of 32-bit fields. Serialize each field +// through the little endian writer instead of relying on the native +// memory layout of the structure. +// Do not fwrite() the structure directly: DDS files have a fixed byte order +// while the host CPU may be big-endian. +static void write_dds_header(FILE* pFile, const crnlib::DDSURFACEDESC2& h) +{ + static_assert(sizeof(crnlib::DDSURFACEDESC2) % sizeof(crn_uint32) == 0, + "DDS header must contain only 32-bit fields"); + + const crn_uint32* p = reinterpret_cast(&h); + for (size_t i = 0; i < sizeof(h) / sizeof(crn_uint32); i++) + write_u32_le(pFile, p[i]); +} + int main(int argc, char* argv[]) { printf("example3 - Version v%u.%02u Built " __DATE__ ", " __TIME__ "\n", CRNLIB_VERSION / 100, CRNLIB_VERSION % 100); @@ -241,8 +271,8 @@ int main(int argc, char* argv[]) { return error("Failed creating destination file!\n"); } - // Write the 4-byte DDS signature (not endian safe, but whatever this is a sample). - fwrite(&crnlib::cDDSFileSignature, sizeof(crnlib::cDDSFileSignature), 1, pDDS_file); + // Write the 4-byte DDS signature. + write_u32_le(pDDS_file, crnlib::cDDSFileSignature); // Prepare the DDS header. crnlib::DDSURFACEDESC2 dds_desc; @@ -263,7 +293,7 @@ int main(int argc, char* argv[]) { dds_desc.dwFlags |= DDSD_LINEARSIZE; // Write the DDS header to the output file. - fwrite(&dds_desc, sizeof(dds_desc), 1, pDDS_file); + write_dds_header(pDDS_file, dds_desc); // Write the image's compressed data to the output file. fwrite(pCompressed_data, total_compressed_size, 1, pDDS_file); From 7e34347eec940d72d7c84670ba1ec772d83c7559 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 10 Aug 2026 09:49:51 +0200 Subject: [PATCH 15/16] test: test renormalization, DXN layout, little and big endian KTX --- test/checksums.tsv | 58 +++++++++ ...sample-normal-ej01-floortrim02-128x128.png | Bin 0 -> 17015 bytes test/test.py | 114 ++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 test/sample-normal-ej01-floortrim02-128x128.png diff --git a/test/checksums.tsv b/test/checksums.tsv index 67f1b627..3b2291d6 100644 --- a/test/checksums.tsv +++ b/test/checksums.tsv @@ -469,6 +469,64 @@ build/test/tga-to-all-transparent/sample-storage-rle-rgb8-other8-color-transpare build/test/tga-to-all-transparent/sample-storage-rle-rgb8-other8-color-transparent-8x8.dds d13b620bd08777f9f958849ed99b96293182e8bc7ff29cad31181b38036e6c4d325cc8bf038b24bf6c17403eac66756f2e1946f3739640414eaa2098ba475db0 build/test/tga-to-all-transparent/sample-storage-rle-rgb8-other8-color-transparent-8x8.ktx db575597abf955bb613d3cf85a74c86ad6d3b341e6b68daba79886598d83f60eb1ef49eca84d0a917f2b1d6f8cc2da65867d535981d92176deca2ad5708ef08b build/test/tga-to-all-opaque/sample-storage-rle-rgb8-other8-color-transparent-8x8.jpg 39e66d8f5be9f9e35e61b5c229b88d07161e1ea8087f0be61a988490ac39316980d45c0c9db48a7a67080cfd3446820d7389400327664cb4e72de428663ff8da +build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.tga a1fc8bb9f3e45df2b6fd4fb1c24ecb788d24bf69b2e396218892f62106cc4f2b146e937872c7a7ba58d6526d1145fd8943184bd9e012dc55bbb416d532154019 +build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.bmp 15c90496350c2c4319959ca183606f5cea48532fe49258328f4fbafd176b2d857ed14bd98efd6033bbf21df355f84531bd87b35ade51a33ab55da0d2ab5ae549 +build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.png 2342a44dbd328b69d47c322ad15dbd68d553f2878213a9ac23eb282aada4177ceed6265a8dd55c161a12df7939371cb3d18983e316c794f5523265c06772c7df +build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.crn 9362f01a9eca9792f878815463cadb635eb66ad6dc67299450c4feb8cd40e55663098c56d4c2ed475b59133deaa3dc6c77a264986294c83167677d5493f82b7f +build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.dds b8c8b937cb03e0a20b9dc619d845d640c1ecc9c9dbcbe151527f365bb66c778382ad47adfa45c646c3cc4c9a89a34a5535e702777452222697ec4a9ec00c4f82 +build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.ktx 8a22d1b1aec6f9fb10d37ff86c66dd43757e80a68ad6b97fcd61bc5cb22dc90ec060be7c69887f39fe2eeb37133e57f774e7d78b2a3fc656973c86f1e5c7b0fd +build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.jpg 6b6c5296c6ab3c0470a894f46ff94883f77bcd44dc3698c06c2cd1be9ac7e169ee5ee77c7b97e1cd3ae442a2a2968f211b589eaf9e699347c9a9e075292c629f +build/test/crunch-normal-renormalize-default-png-to-all/sample-normal-ej01-floortrim02-128x128.tga 47b5332b50ac03139586233ae1fdda6b84425a6bab806d8fb93aed5178c809b8e19b0470bd15f3c8fcca4dce2a4b46bfba11429cc855eed3164b6997e8761103 +build/test/crunch-normal-renormalize-default-png-to-all/sample-normal-ej01-floortrim02-128x128.bmp a3dab35c03a469ae837c743b6835fe90976c01779a1f1b1f546d1ac3017441a56e75ae9965e2a3f4e135115f859cd65c060c809129926ce316107bc5272440ac +build/test/crunch-normal-renormalize-default-png-to-all/sample-normal-ej01-floortrim02-128x128.png 6a07dec1cb2e306bdca19c2d9050fd21a6427b92dfa3d197213766a00cb05ac0d991adb9d54d5d839b22b8afdd5510fd2a104a961ec046fb28f9b0513d44309b +build/test/crunch-normal-renormalize-default-png-to-all/sample-normal-ej01-floortrim02-128x128.crn a6f7ef910eb3956254b650d41d80b353918b2fb838db0e0a58e8011dd6e330157a6e67682d0c5dc300630dc6d487b5d06aa49611a58bf43b72805f34045bf595 +build/test/crunch-normal-renormalize-default-png-to-all/sample-normal-ej01-floortrim02-128x128.dds df4b980c89b5d1b88a24915f68950563be601a8116640bd4db468a731ca227f8773aeee43652b5ca63bb4ab385aa6807d6d3aa2758069189888a4ddcab16c64f +build/test/crunch-normal-renormalize-default-png-to-all/sample-normal-ej01-floortrim02-128x128.ktx b4f067af5e1c5899f270fd0268ccc1a46e493f043a85f188aa905ba78654b00e53cc7cff8d27cc44a40ba68317941e04a2d8b97dc7e4d295585778ae9daa41e4 +build/test/crunch-normal-renormalize-default-png-to-all/sample-normal-ej01-floortrim02-128x128.jpg 85af5a41083e0fbd2b636b9443c360ca002d35f429edb212c4aa79b659700960b01e299fba7d16c0a2228c712eeafd326dfce3b47b25e28fda6475688c965a08 +build/test/crunch-normal-original-dxn-png-to-dxt/sample-normal-ej01-floortrim02-128x128.crn 305a8921e1b3c521da7791f6c83bb63d2369f60d52ba5559a21e848ca4b7c607e0b95d522b0eb95723dbe1fd22b51d7280633ac3ca739d87832c19be564b08cb +build/test/crunch-normal-original-dxn-png-to-dxt/sample-normal-ej01-floortrim02-128x128.dds 9ee4308a1be14dae40a8bd8f36aad218ce76371a5e7c98d0aca483a03cd1e4111a3c70b2bff817344f1afc5d7ee67bcc0c51d5336f593c9cdaea9dad0a7b41ec +build/test/crunch-normal-original-dxn-png-to-dxt/sample-normal-ej01-floortrim02-128x128.ktx 802801053a974ba2112535b3c83ff1019e393ce0bd5b8ca9790783cb4819e874f667c054eac6287549c3fd506525490c25e66db78a41b6d5fe9a58e16ca71da8 +build/test/crunch-normal-renormalize-dxn-png-to-dxt/sample-normal-ej01-floortrim02-128x128.crn 6ea08ed891e47f430e343826435b9046c3766c3f5fd410986b4661a10a4206edfe155140a8435afd71c4d06f8d25d46cdfdd4082dee07fb74eba0f6c10503f9e +build/test/crunch-normal-renormalize-dxn-png-to-dxt/sample-normal-ej01-floortrim02-128x128.dds e58aba7bce435032a8ba63da7f29470c7499a9f8023009f826b66b20ee70ea3a77a4c10a9b8b88c36f65af9aad683ba6e976f6900d2fa9e59e58600180149c2a +build/test/crunch-normal-renormalize-dxn-png-to-dxt/sample-normal-ej01-floortrim02-128x128.ktx c653da4a1cbb3b32854ae57a96bf23e7e584a52777391ca737bb450fdb6861b0cab240290a93239c90e40f2ab0b6b4b0d03a5a25b30645af57ef5313cd58c1db +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-tga.ktx e92023cdc9abfa7f3c65989f65e1f45059f0f4d67967ef6b1cb1b0254c69801ab1419a72b35c10b9d3b90608aebe3ba07770182595fbaf5c1802951247c98984 +build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-tga.ktx 8e470892e19ebdca38a834f2a1d880f87800b88d81491863d0422c33087acbc908715044d1734c4330a316da29f3d11e3cbf3dfa22a67ef191fec0704bdf2728 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.tga 41e904dd50624d5c6363c9ade546c315bc43b45d7e7ea13e20846bcdfa5b32bc88b1fa18dc307fd5256238dca5fc725f8e1dee67928a04b47663a446ee7e1f33 +build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.tga e02ffd43206da4bac86bfe936e9eb49b2d652e513bfee2b36db63ce716e03642707c592624f36b899d4dcec06491aa16a9983778b7e76c10b1ba6b2abd1ca796 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.bmp 1dcc4576aeddfb003da8858b3f49f1512d74a35ec6e1d7dc79a4190b30d8a5fa05da6e606ed3b59512c74a0666f1fb99f33be14cfe8501bf51d0ebc181d5a8f6 +build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.bmp 5aeb9549fc4b8476f9c3718d4ed117f69198dcb9dd41b8b4c6c86bf2d2cad83d106aa4cf0c2083b39fd5acd3e072a49e9755a6f30e7f52c8279bbf2a671b52e6 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.png a1d080ee0d02d8cc4b0ce2e32559127fe014057bdbe1aa71e26a89ea3d965b3d8c634aa3713da5fdb0485ea60263c91704451bc0e8bdb9ef4aed1ae019f46f6f +build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.png c6639a99f31c4e6e6461805f1fa418c5a68bbba4b4e4addb32d7097fa329c7f32b6da51219e6b73401972826dd1a891584c4358bb824bd1e499231288a471c45 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.crn b43299f299d412d5779402ee65ba771d229084dfb704014fc6a86ce531ae3e896f9e5f0993595aaaf2b5fba100267b25b0490b0791d6d0e8f12d084bb7beaed2 +build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.crn 0431d7bdd5da69d7326ba878afd20a1aea21792ae86a18f14ed012f6b162e9800fc27bd90705af27e4dfe8fd7573e75601e19586f532f01355439887a49a8519 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.dds f550c5186abd109c157314eff19b9f9920a6a6392327b2360ed4d3ef58fdc40db439086db789ac1f039754a1a2e150b5451e33acea7d9f0127fdd2b67cd885e0 +build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.dds 985f251485d0735bf631eb4153969d18dc7f421befd5f7fade308f79437a3c0fed58f70ed7d3ed9942a0102b76374e9c0eff175695d138f7f5d95d42264913ed +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.ktx 4303a268f79dfe3afbed1e2966028519f4034311d52d1d76c8369f48e74bb5eb84491a535efe517ca5f5df3c9bca721f44e613527e09cd18822f5c81634a0e87 +build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.ktx 233e5d029b08cc45f8b3e9853edc6dc5dd5cab49cb0f95e870b9b6db917b2ddceaa2756d55aec1785c147375bc7a37030e3a8cde77d364f73d86043e99a7969a +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.jpg 30423144edff0f7435e5775ebf2c143a92ca8715e6f120b45af2bf697b06672c6dbfe354c4ffef7d659d515346440ba30cdb452f2a951da6a9874f17dd535243 +build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.jpg cf17541cf476b59dab8218a3ec79abeed4b7fd6f20d2232d77b004d95a3a600fded9549fbe3f2097ee7852dd735e87ada3101ff4e28db0e630e1c98ba89263fe +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-bmp.ktx e92023cdc9abfa7f3c65989f65e1f45059f0f4d67967ef6b1cb1b0254c69801ab1419a72b35c10b9d3b90608aebe3ba07770182595fbaf5c1802951247c98984 +build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-bmp.ktx 8e470892e19ebdca38a834f2a1d880f87800b88d81491863d0422c33087acbc908715044d1734c4330a316da29f3d11e3cbf3dfa22a67ef191fec0704bdf2728 +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-png.ktx e92023cdc9abfa7f3c65989f65e1f45059f0f4d67967ef6b1cb1b0254c69801ab1419a72b35c10b9d3b90608aebe3ba07770182595fbaf5c1802951247c98984 +build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-png.ktx 8e470892e19ebdca38a834f2a1d880f87800b88d81491863d0422c33087acbc908715044d1734c4330a316da29f3d11e3cbf3dfa22a67ef191fec0704bdf2728 +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-crn.ktx 74b9a4c02624305621ecfc20e72ae8099826eaf30df7bc4feb3262b41a760f2895c798925bd02480d47eacb85636a4bb3f05f0207135cd95967ed64603cefdf3 +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-dxn-from-crn.ktx 9bf035bcd1a5165a7f65262f41f3ee9a273eb98f9283d2f5a2eddea0d23d3d0a8679fc8b6f49241c0742658a6ef5d08124f8978f235d54ea0f88eb88a1705c4c +build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-crn.ktx eb3c7f36851cf3fb1392505d2515c2305d44717f9c2a1f73aba2a886506fb7b4417dafb1cad5e7939b08d4683f795f68631d7a32b56a177f484a1db6cbe700d6 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.tga ac17bbd83f3561fc5bf0d71f920419c569f4f350881d7b489b4e8615984df8033f3fcacbe85959d2c2ec3565aa18443150878459e06eb02ff9ce1d2dc8d32819 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.bmp 41528963fa76e4512a76b949fc115163e871b673f38f6199cc3f7ddbb4db8e5bed4473f4f21c91bf9e00c4357d55cce3163ec2fe0a49af9b9fa46a6463851c59 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.png 7fde6023bd5289dddccf070d6bb4f36cdcf5ef873b27c736745d138f12ecfe748be27b45f84e1b0754b5ab7e371d31e8dc70f755cbbe70016443e739fe74a09e +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.crn 14a27e95f77bd9f138760478afbe93db16981c1cb2f65dbccdba6cc6c4601493f31ab8269354b2e291d31d2856f8e759b0fcf45cb9ec7609e17efd319c50f763 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.dds 9ee4308a1be14dae40a8bd8f36aad218ce76371a5e7c98d0aca483a03cd1e4111a3c70b2bff817344f1afc5d7ee67bcc0c51d5336f593c9cdaea9dad0a7b41ec +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.ktx 802801053a974ba2112535b3c83ff1019e393ce0bd5b8ca9790783cb4819e874f667c054eac6287549c3fd506525490c25e66db78a41b6d5fe9a58e16ca71da8 +build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.jpg 698f7867e9b6d29656be30f6b0b0b0f48103212c58bafe9f8d4b1f548bfe665ae532961dce467790fd14b03b1bc49561d64d5e03fa7368446586a4ed6d1042ea +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-dds.ktx bac591d3074cd7b454f01900f6281355dcd55856744af47e35451d5b65cb45b89042475894fec4a011759fdb77744913425302bc6ddfb8295199c6fa20f5cbf6 +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-dxn-from-dds.ktx 96d92ada468e92e1907c2c660c7ed9c07780237a99231fce08a1326cafc1454cdaa15ab5f8e8d0d0e7c4f5e8cc8f98a7a049df4cf7c37980d2be3cf2175ee1c1 +build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-dds.ktx 895509ad26f36cf14adeb8464ecb0f28b9e2e992ab5f718663c155295cce8893b10926eb41fb216b622ffaec61d936dcac74578be0b05555757906838106eec0 +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-ktx.ktx e92023cdc9abfa7f3c65989f65e1f45059f0f4d67967ef6b1cb1b0254c69801ab1419a72b35c10b9d3b90608aebe3ba07770182595fbaf5c1802951247c98984 +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.ktx 96d92ada468e92e1907c2c660c7ed9c07780237a99231fce08a1326cafc1454cdaa15ab5f8e8d0d0e7c4f5e8cc8f98a7a049df4cf7c37980d2be3cf2175ee1c1 +build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-ktx.ktx 8e470892e19ebdca38a834f2a1d880f87800b88d81491863d0422c33087acbc908715044d1734c4330a316da29f3d11e3cbf3dfa22a67ef191fec0704bdf2728 +build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-jpg.ktx 66cb0b7970d8f7a779e20426704c97297a2854686c5458679cea13f0e35bf1f916e0f189e3ee207e0cd6261aa5e85df33cb1acee0e52e70256e54f59efee9ff3 +build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-jpg.ktx 6f0f507cc9155980fc2d1098831c16c588b3471bf1bdba5ef5c3a34239da49bb460f69fc066f5ce837cd0da978e68d79d03d69a5fe435013932d4bef3fe4fc71 build/test/example1-icon-png-to-dds/sample-icon-unvanquished-64x64.dds ce98b21c574b4883e4aff4a0b92fd0f533f22e00ab753686f353111fa7503287c2c4dba25f2c2670508630d79b59464578e094461bad81ad803e1d58ed5e69f8 build/test/example1-icon-png-to-crn/sample-icon-unvanquished-64x64.crn a3b526e95f44b6a4c919bc8eb79f4debf592f0bccc163c900cd1b6f9add0ce58c3db9fc8dac1828d350bbcd915823d7743a0c114a533fbc08c163fcb4846d2f5 build/test/example1-icon-crn-to-dds/sample-icon-unvanquished-64x64.dds 2aae5c5bd8c6a82d1cf3fc9d9da12fe3fc5be8651261ff1cb359e754308fd81e93bf304aeb55ada0bad50794d6e97d3e48084e5a4f48fa7caec4ed954152bae9 diff --git a/test/sample-normal-ej01-floortrim02-128x128.png b/test/sample-normal-ej01-floortrim02-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce81276147ea615063b9c6aaf5bd20f07602c2d GIT binary patch literal 17015 zcmW(+dpy(s_jk=Dml@?g*VWuFkxO%%g{Cqi%r&%V$}KCDOBqrwt+w%rFmjpu{gz8E zp((7~Qdw@%lxyyn{d|9ayx;FX-{|E0ULk0)Y;{LKZD=RUc<57&#fcgMj{YYS80VXz#=d?P2 z2v6iPP_pa|(LQr?CNAQnsV-b@NA+RFXC2z*u;eqnpV)LYIO~WU@eRZ)+kctN$?w~^ zY!+jCHe{}YwXpAeL}twWwb6&U08jeq)E2wWmxzLyzXJkjRd^%^2R}ay&B3p&o?f{ocfcL9*U3)1sSWgVq z{eH8(T|_iRR%|fJguZqU_2krC88&J*h{R?h2|qO_)SH zc@9L};Dd}_6EF(ltfEI zL~XlN%@@NRb4;Vw0-;`QvMX55bIY&*e^Os$>B5?3_H3DLf-B1ZvW8+Y8*wBdV-Y2I z`OXtZk&@fWH}8Dv$4WX};6P!JDZ1SB3@nLjsw-5$z4^3tEI?9Lu^VNg3qP5fEFj`K zgrG*8HB1n!dw!DL@6K)X{vbMSb1Uz1-Mzm!MbfCPgi+*;@A){N8Ber_Hmdw*4G@rm z7ip;(RwzW+1Dtq0+T?qBtR|2M1$$#Dhm`h3|9JSM-WYM?KS!W)O2A}l<|;9 zQnEx@ikjP^F5yNs2_==iKw&g-w#xT@2KUVZbHuM>{#?T+D!)oy<0L`Q%X{RQg*)mNL!1<>zCr&;AH$J*eL6_B~8@ME^JOatZ-KXo> z{gsFF=}|kk%z8G4c0S2zSJsrtviI_yG?Kde@ZVJBK`LvX)faS>w{iq##dZANj~Vil zb8y4q=d8eeLp|d z7GAAGMWG~-)C8S3u9AFDh6(HyIqUiQaQIV-6iidZn$fUX2&UcwTVq3e7!^GQF$y$?nqU?(scx znpOYfay4memb%s;B&tFg3@Igv=LO05{V27(5?<-dn7e3I`2wI?46LFQ8atM7)44B` zDw0y2R7DU<0*m}zK*a#Val4MaQMt{RF@xhv4?32IqJQOh4Q=WIxx;z&TzRT<*5eM0 zc3YgxhJsczTl-&4Qh?1*eVa_W-kxdfz{;nh?~?Aaiu<+wMPem zg`^cvxvThE`*t~4`)W$`Hj`!j^kM1Dj7gfguWA5i9tCb5P-1`HGqvxmX>x%MR8417 zQtrz{VqZO_kZhMGx8uQBZ;T4x+uTbxn^o)h`%>IzTm{^dFI>@+ihkdnxU*h4;q`Ax zcxY`|i4VuDKDz$t*>*^!bkWT)lL#YS&m7bJJ8~Tx^t8waTIo8rl6eKFi4chN-H*6* zoux6je=){9ZA^XCUl};&$Y&uhZp_9-&xl8Iz)MI?q}kmo3pqP;d%fxPk<+^~1*nZ* zUkFkyc#T+Bce9d1M*$XJuzF$6%>D{d?m6FKDaV1H*?^D*^^U1ZjMG z>-|Z;8`K2kiIMK$aQ?=tt?{I38;x$$EZo4RCP+9Z>eGO=knDqxrNUmD#_dbiQ7btc ziP0a$Bl;d_3MWw1!FVv6Hy`f+07>)an@`au21fuAyqje}P_)oHT|5%O%T2i+KF!(j zDTtL(d6==Xhi2@5XkY;BlE@5KeBSKFoDcfM3d#J+($eej=avHVw6 z#^a7j^&#~wX=GXK; zmYxu06m}^Ak^8ROF-PF1!f;bx#AFDq-dczR9-Kg4-2J)awXs*d!pcATYxehBOjlp) zT0qAyahBatr`>Lw*YIqa}?!^=GUp3E9Tmvov08<8rc@19VeOo_)!bvZSOAY<82 z0zjLO?pI%DzTkB*0O763dyArsxq*(KT1Wb3TjPfZUYlvGn#zZjW;-vVx9#?pbGCo5 z%ow{Ho-?&U*ODD|fMa=5Nsi7~Yd|k&SLo}t)57b+xN%9!sEy22w&}ys4V5;!#3VL2_wBdYFb&5imuWM_9t$;d-_9X z?Inv)(%p}}{oP?D1ej7j1Y((FZNe!%-+4D{lP^d(i1E91Z{2K@u^SLQS9EY}?a-0Q zu@4;WrtN)_+blS?)@+vNNG)f53uA%;tKpqKAjFiyB6LCg3a#am)C=p+i`=cOofr?d z_C5RVA2rGC-EBYU_B1@$>ev!KI=0koZJ%Y+XYIAMJ~-l(-7YdTTTPp{7@z*Kv)F{I zqFE`CB(k|KLrae{LH<(81E|kCGOr7~L`E}Z`tdd~me`cV>jo)b|F9PQ{kUFiUzq>Y z$P$fNtK2=VG8-&k)c(2&t)idG0$u1X4NoZ zf`%YABl6Uix;MX;uI!62HW=%F&1OzQcgthu(^*5&M?pK|9X~tIdg-qGz-ol|_^oa3 zq~fgkee!XY=UnC1REj|L-X1Uu&e~w3V}y72 zRHfYRBZZ2`5V+9IuS;R8F9qc>q%Y%|v1iamYnRnNE-L6Oo54z%yU2;URN6amL5<)PlotdqTUilYU(HAVnoj#i!wC)OCa|yygefxUK%2l)y%Fm{0gT@mU zjdjLB7pBInH}9#RMSQTSN90xO3%I4trc8GhypeBM4tKPSchg#HPTYSUP-&?|e;nX- z(0M;HVsqYWedVC?a3W@A8g|Yc;bDP!PvA3Ei7kyH{noxS94uB8;m*Q*XtfmvT zX>-aqH3)Q+l(Qp#)VQ>Agk$WNnzA^vy;eUN)Sma8EeOn-pVaAVGu`I)+EdugYQHpZ z2)#t?`;z1*_Cy=QJzH_#cii{KtYvGZ#buX)q_gO>34bt(S~WZTN~iRRcfzD`)0Sbe zwoc#5!|sEpY3*Il+&xU-s(%nh?MI2wj$c0*>t?L+qv@cXrIp=;p{UOC+U(2R`6QeA z^J1Wg=}+Y{wGFUDSiZjU!z2SQ*_ds!^`nJ{%PafeSH5b6mnYxHjgLthEpM(h?#&4A z%n0x9e0vza2C19^tp|^d4X6eQYi+t`Eb00-NvxP{$Y=@68LqUl1dNS)a$OUp%X_@l zMhW%$)z%eA-C_O{MxbIlQ(H?z99~;@zxj-PoF=)lVD#8D-KRd1lMxtgaie=Ct;H$gNu+ zHLUD`bE5xb=DaL2g-#f(v@Z^rEk28xu=Wb|irSE_@eFVOGBkH<(FGr$+Bf;g^TD6+ z=JmWBmF@R$&8n(JPP^EAH^<%*C?{u3yz*y1DP+)PWpvUqAYf;4XH_Hm&~Be5yf@Ra z`rd16dHc_^vVH7SbEWBFVGyhPXeVd;{mRA`Py5k{ysX)X*GemMv{7#ySHWs*Wb)YT z>+vGL*2;N@^Pc{aFlrwUW4+V4qmQvKx7(hxacDN{*1^p53LZb}wYn>}e#dOit)sg= z@~}l(HZ78DAe?+*q)It3SL@8)9CG8=rSOP_h_+VAY-nF{dQ0U~O9inv&j|sNPcg(^ zbfXj{K*<*(eRgmX+t$h~Z4BBBI;yKY^7cBwF!l!8=S!zj&2~I;9(Ja&z>JMq-S*vm z(}=q?ZxzBKi8LXBC;3C5(59M(^S|Ot!-ca$DV)JX_ne3CqZZPmKL7V!Q&C5Nq6o;&rcNrwal98^N}3P`%_|H{Q^p^bbU#|4E9nQ3(`Rc9M^!C~ zU-Z-LL%o$sC6baI)zpiSw77_n%Ca_H&-P`JO2*81%;%M(*r0t(4okd!gXdt)Z2nvO z7lU?Ygh{GVB-(0`q>zhHBS75Vb-yGex)15Eb-5{n)Wn1ld^fz6#us9~EiD{=_gZD_ zo5cKi){gQze0qPA&fIJ{4_eVj^f{_sHW{}{9UEv_GrlTN83gK+^tq(384=k$)hlU$ zs+~$OEVad#Q%Fn#mXC6p=$%McBh+NmH=p#nkMqcgn@9HI{r4<;I=+P5x5 z=2k*4n{Q6?Kzi0T4*$-WgM}c#*3ZxMDi=F(^=UPy*6y={P zbbi|eTfN6Em+_5}+tm9yIpO;w&@2B`^q_(Xlhp;(BEdokz=Xxp`^)(5LeL8iAqh1)H)pB3`|WXWtd3a_4RuDMEmyVb8fwdURks#Z!2 z4y&)x1;A~J0j|jV0#OLun-=u|Rduj$ujkop0&mH+N;~g;YF~}xX;>nG4giP!5MP)k8bs-j-<@;Ns?0vs&~i0tf#4f1 zu#`%jZ*8gEyXoVFkE%gQqm6PKP!Mfh@^vmt3?C&yNbf1Z=7*Xf|J<2gUM+L{%A7Vl13njZmuQtsb#~DtF(%?vnJmTjZ(EtdoYhgT*Mk#2N1jezOZ6q6&CgdB+xw|a^wtv~l@_>{ zYZGwR3Dv$ILO-50b`&=ryr4SWPxr@cW)Kn|Cp!d*!#WiU%LMy|Yfuuy#_0yVRtOae zmq1wkF@SbTZ4|%z@umJO^x+dU`l3ph*mxCtX>qcwLO%V}o_1n^c9#S4_%<+LO%IC# z_dG%Y9%-fa@3*97i4G~Ca{o-w)hopnZW}i>Pqo|(Zz33IDhY3A73M~t<6HyxLnI6Eq>4p zmrUpTL##8dAr?_x{!fMh$mt6A3WZZoPf5L}!SZ~ueWp#IYT*losO?)$AlJMCJmN_z z53S#(xvaO$k#XzevuL={s=vgHl}!`SMXEyPy)F^vUwr)!qAh0aUR~g28I73H{l^&_B|Vn(e!fP^ z2@)|$%CJ`tn0-Fb&%|Kv=m8a$Dg<(^V~3i@I)q{6D33k24At5yH%;_3(^gAp@MzW;F@ZL ziDNMQYQjdM(KQjC)?4A=5o>5iVx@oR= zf}1y2t<%~Rq@uHCwlz4r)QBhVL`b2S1WYEsi86)tl@ z;C9x8RN2{cHRnO}dS}&B$9WSiC_h?y3SrL4SL|ff#h)jP^6_A_yn z!vur3Qs)Y{E>YPm?-;_ZQyo)Now{ix{qcyJhpR|cwRke1~Cdst@n$~b6^On1JSt9g>XrxK+8w$Qly{+#mH2#G{=qseMG!G7plCBQ?$k|$qZ0Uez_vjf}Qh3KD?j_^zq|pTx~4ZdO>i)4sG!p;K^dH^sFd_-yUxoy}2&Qv!CJ z_|sBoiyw4x@7~$f++Qfqf1--h-l=0rj>p&>U192R#&hiM=Ax?Kj{umML~RW`V8Bq# z_PW*5iNQ&N4UWV{8in!mI*F7xm(+#gRKn`Jh9OSaKAG*&VbZ8G8ic>b^|Zzj`&AyNqK%ORkTN^;qSguv+t-^rF5HhHboTQRE$Nx_*3tjyIY{<4;Mz7PP?fpWx|$ z6H<~hC)`!mo;3D%!}|oabOjRg-vJW}63{*Y=_J*1$3#nLE)wpO->1^|a@)>iIS#cD z2QrbVH>=2DTj9pv2el1k zzBz>e|GyGku8X{AFh<=XSKd1b{wQCERGs_X=AR9Ym~3TxgPL0V@TaWhrvFEzhpb{e z84S{Rq&t9&nhk#V@7LJ5hin7S4&kQy^QsleQIQYqX>n1rDTxs;y}!?2DX}alltJxxh26gZ$SzAAe(e2e z+BX}PuXYR{-vXrKkZCf=1=Rg35<`&@k;&uR_u4M}RI1d^p{WTA*~^&fAK232rq)R zPyh4{nmv3_xw7f2mB?#rl?Ht>T%@7Ag`hr|%a{aYqo|*GwaH>Aqnkg9a*3~MhyWjm zO)h~~Qji@_e&q@)DKKu$$QPFM4+~>6DTGSOI)ID$>U7tiSF>>@@+d4nlkpJUw&4me zn_nl+d&}){(A1nkQ;MXb1zhSOXT+-zz~ZEV=s8_iuz`bY@woR7%v`5xYR~-3G?Tt1 z+C+E8gk>19jO=|q-w?C{EhQp#ldTkeIaNldUvoXL=Y~`hP8s*$DN+jc1f&&*Ri3$^ zqB@5M6)DeUMF~+l1uPeE7MlIg9_wt>LiCT9kNjcG2~NxxmW^0kjN|p`O|cdX3P=2% zTwC^i>!`}D#$EiG1RfdjpaR%#&jAn;fF!qhJTL5@3cFf-0`^qARlh%V7zhKiDV%=P z>~339nyx>9;>2|^YltGcWTQanz^YGLax5Og*Uc;tD78bs-IDr+AaPS)B%Vti@O`*- zW_*8^4C$|~Ch;Ga@;nR}FuX|9*Hb3}ygx9plucdGQl?xMUF3X1m5yz_yN1p*_6ZrL z#->QmAP{^xSN_|(&Br;5YI~e9;g3%-BJvFQx!+9^Iwtm};nr(J_8aQWFiT}uYQ@{G zWT609Co+#1CzV_QR0nfO@e#G{T@hg_qikIAQx&_9cTbnI+2TH`%1XL46w1J;dGbVT zCzDxC=%3)p+1|0~n)a55^GPR2jno1wcs$=RY4eX!eAjxC1PS)Eq?FWL$HWZ(H!Y}? z<4z4r1R*4msR%>?VQp}B~$~j)^c5CvZ~5Yk=Rm#&8YysB=WpJ28TXTCS631 z_=0>dE%!$1BFPHifh?~eqGA!($FJkSNc?05QgCiUXPSd;-khI9Lqf&Y`{W5W{ix7? z3f5nZNBaTCKw6Vm{vASPg%>vlR{D-BDZOl7U(=P~Ur_clqx1=UCbe&olPo$Uo|@)V znX-RXQ8^a{y8)3%DyVCzL5$10mngukkxnI)Uy}jmR?e1Btj6S@f~(>kq>-K2_`Fr^ z)(?hYeu02R`@%EOTsuK_FuC1$wA5NoUFlkn=fa{nLY+VEU8ujFV?f{2wjcJ* zn?qpcRfp)}ns#0D2&0~|iqwr6$-AE>^_7TLPU)knNy9a+*fGt`pFWr?m>&o##&eo; z!6Cxf&Q{o)>(ST`IbsWQzCQcQd487r$k8Ci_#G;)zJ;c_OH*xk>3HCo+T zYBB`^sZ&n?!RD`vJRXRV6z~Amu(}!)hlQX9FR4CY%rQ;KoXsDHMshbJF1>l|FBYE& z1hm~Oe((g-hZY_LPJKfdMsi!{+a;AzBV42wT7(r&CVl3oA>Y%@)m&_H)e}J$r?Mj3 zU*i6Qs3`Np))eVrl!oQ>>YT0sW%QNJcXm30)VDaf=zkf_`yM0Vkg$jvvr;XPDwn^h z(#J_Q2aWV5-}L}eJuV335AY|xhI>`>S;_+}WYIrv&J)x+e@$FHCt+w}O^;yUHPMX@2(oN+~^uu#GPLRT(-?91#t8{(61mIPVo3B-6FG@m^)K79mFeh{h z@Ja=Uam7`Dl~V=WlA7ue#T2>OsdG$LsnZR-dqby+Wt+FA#T<|8Kh4p<9?x%nq5%jg zS9H9@u3gCA)2q%KrhgqL07=z2g;4#Ye_bVUA=btP!YxTgu-Wj=yM$7copSCecO{+H z4yH<`$RwFKe$c7^7tcwdAe+RDiD#@Fr%G@pkbgDms(8X~&{Oogbf>>7vkpk-J}%%VIzR4Q9q zQ2gFGdnd2}Smz_fvJ@FARJ{TKSGw#{%inW37;iF)OS;A4N<%I3Ako>_qdwA-ORl zGo@qv^AaC{=bEn6fUGyUpJ30=Ml5eG^N{8(kta}2KhnK^X?_xMMJ0a5{L&LtSI^xi zlt<{s$jh6zXcL5t;E(?Q3|GP-=kAFq0AaZVPG+hRs9U>`47%{x-%ut$xYKC39`m|j z3KBImf904xwiHvd_RU~oKhgYg++6lO&x^`rd+}Im9ty0$&-Ijti(L=TXStD<98)7y0rO)Rz>VV>E>CiJeL= z-|yVGWVmj1Oc26PdVdH96+!DcMn+PeX8^a`KC2NZWeGMK;D8AxV(yl(fOJM_z7nZG zDlL2rT_EDf*{!RGx;VG^Y7*z`WQdW{Su&+>^71y*9Gkw0osd$oBL zuvo9d(=f;{?I9|w^62!>8Z%t>bi2;|J>=cR^8@Gh&_=awB#a;a~j+HY2be?Ys0jnWu!G+PsPlk_d zOok}xKqJZomApynxXSxWe+{#iBqNS#mOK(dKS36m^3$JhY4^E#|0~QqRl(7^8lQ;P z>`Tq-a>{m0O)l4kK*e-nm()dT$pR2=b_|3&oj@qqLR=;~`Sfa>OuCTksN`KwwiGcx z?IM*UCKjIEu`67fq?q4HR@zW~y!}ou+ zaFeP6Ut4Z|U;{b<#w?3H71>Wo6-9zw$$%suG)>pomC6--0#TyusyQo@?#Rpo7T`+K zcP0%EcXBpGV@6`uz6{>qjXCIRpO2aTcBIvQ1U~DUSKm64a<{1l?3G;_mBgOYGPIlvZM99)3 zTtsqSPbfl9eGQOM3aOJ}+7HnKRF7EPo!=@emZCs&OVQce(su{zYt#SM%dB`L>QNhS zU6LwNxp*;N5dgWUQ6O((0gtz(UGB&<1PB(8U_xbuYw%}8@)Pm2}62p?i?s71H7&aOQC+_wTv3aCF{ zi~n4Gm7_e+`tRSUzNCrq3j&#Zl0@<0tK*a&|QecU% z&_wqwZ;@Iw(Y443^RXerI6dpBM*Jti+X60KME-KebgK#!d879(=>3By`{~dYqp9&{ z#JFiU-t{w5LSEQ+74#v#Q252*HO&(NiOlgp5{N`L|i>HGDOKa9YvL1s{8NG zGPorv3vqr(FS44a3hQ93d)qMm_WlU_PwneQjlL4K7~9vIi=Ik}^6Kw@-T0TPG3uaJ zMtB$D`mN*~(CTW6rlcbMF#z)#-stqMrv$ed9DZ-1dD{cjy4~OMRo{IeJj^(Mws`4x zG&FMN!!7dX*pLKQ=aj&=g-&Ie=a$LniuWL@yNe;eb56sN6@?dxJUD4iLd4VELwKH9y^R@{N>6*s{ z?lbRu0F2A`T4_Cq%UjPKI{#(c^Du(-(}cCG<+kEF&z>}mulL)&US9k2sjuM zlwgxYRv;Q*M;q#&mKCC%_cnwW_-H7SR2M(IK%jL8Gd<>^<#bCWP8-S_S2ctXU;cfB zMRQ6~YvjiG5usykC1Sc`J*eaF>kGVszCI4o;d^(r_SS8WdMm?cI|tIdFOsRmJE}$3 z-wbNxf0LAamV7RB(S>gFL+ieCGX8mfhcOWA03mGokbc8Qpo09P`+$ zEz1eIH6K5JiPv=rt+h=`6^jI1MQ6Fz7CUBGPWWPbeL|ket0U@NXJpiF|9C7lHX&mW zWV*h_SWA!jdD!u5FT{jmDK9WxbFlg!YcpmmF=kGwGNan^N?DCz!EMCla=LMP)GJlA zlUvch>@t(=o(QRAWjSbgM`9qkDZ|6pUYoT!8_+AO&?Y-;b*Ei8y}seH}OuF)`cA zVTt^K%B#L=MK&tu6&$rG9xD-_+Fpv8ikzAETC2=C9yvo`I2WW7UIF}g?N^%?D>>)L zD5wKm^uiy4?+tb~xLJn9XMevDqBAS;BjJ68JBD{c;LREJ-kUI4aba=cqO)1^#EU9F zSMjA;XMnvj=kJo<7oubn7O5`qt}Bhri*d8e@AoiIlCE6@2fn%YT?lOX@de^a!DEP; z=|3`K=hCrO3704({Ms;!dmHF4;?=zv^D_>!QNq8J`2MeTh$r8nYUrX4t7 z`WNOq9p-d(NnR{BS0MjUZ~1b=(x=VN>Z}2f%DbAMSA{N?&)+ldbsd~O6UKK{6=R>^ zI&diL!NA?0Sa}Prx~vEDz2o6ZO+9XDLp1%B0j?UCsp(UVL04hDVnjOs=~KXk#aq`b zab>T*yj`(n&x6>*X}Y(!3J34}2>U3C-uhs8>+hG{_WkXDM*IK0EozaBwUK~@mFdvG zv@a?(n-7q$NF=I-HM@njX!PAmcqS{`dx>Tt0Ujv%7PmdMHgl{BD*A5v$Bji(W30~! zF8FTSsXL-cbPc+WZ{Kqs8|9WaXMm~#rP}r8R+ru+->ojsL_3wyb!=f^V}SZn*qH0- za%#Lo<4B3yAMilSrSEd7JoojzPd09QU5Hmwa*>QXtQ2Rd^S*(POmo4Q+QtWdba76M%az8M+&<9 zg{e}56Zr!r*|%#kc#C4V+}j2Er@wWl6N^?_OgA4nN3Gg=?%5Bu-C%Co?WV|WHZxW~ zFv7M+YK67=#dLwxz=crvQ;A4jE};q6xN2YR+ls!gTP_=VH?^;{-H+O-jsEA?wr8}x z60xmeRF=6fywiOwB3yX5y4`lLpB*#scW^TzVe;}HjiPTgrL)k>Wtpk)1OWCu{G=cS z+4IZYNx?-*ji^)oPQ&T-Ky0$MxI?BoHg9zPhDlRac3|d`gRn3ts;4jd^FYq#9A^B> z^S|+OO)-N(hujQSmfT@W+QG?m@sAr>-^-AeT@3*qS!EOi2Q$`N9XM`W^(Oh}b)&1N z#qH#>?nloI?T_SyHHWWUaX^n{%)4*>o(41aRgU$9!mLNahuPLak;R%+H;=A7n2(S1 zNh=A!nMiGznuPzG+tg&M-(;rCs5zh7Q_O2uZ*nilzYLqy>toVn+OYI>l*elG10(m9)Pc02#f*8Euir3XT1I08>BjEsM#~9`IAh(*{ zTlmty7@^{X*GCdT`!8cY^_l*i_G)P_A4sh|gSyLKYxu)+{YPc^(ihKN*26`UW#Y(l z-m~b?V|Ati0Ckp%JS`}atmvfQ1)!BEa=1>nxM=3bzo31_>Z?mSSi8#^$+=gUu00k$ z)EVmdbFamtyxjL{W(^IM{lLL%)jH;b+3w5eRec&y#7hk~J8z~Tx<){d7YDH)bJ4M7 zmgVc-U~R2@KiFA1T8o+6)cSA|)OLsQEhO*sdw;#H->=2Xa@J|@OKNr30_~DrxI`j7O|C^wv#5pXRzs%5Rl(x!$BwrI9{e^p zc;o5X9<}zYBk-`oZsSA8axLp+&>^_vFlX;b+$6a#=$mt%7|HcxXrR_@qq^r4IA0Qm z6!|0tqHRYi^Vt=ho_xL*4EJ107D% zQTx?r?<(;=l&6ng+%#0IBAJ+-|CDGY>mt^d)pbPv!% zc84r{;F(`N?zFbX4+ga!Y+MM0PWyUot>tX24KdFJy7GaB!Q-4AaL0c7!I;3#Rh`Su=79lZ(}kwJyidAA46ktx7o-U8(l_!s6DY_5E49Oc?-NVsRFXG>$E5Zh3kHtKv zdQ?iSCY|nWpU9JOM@ZB=r8cm-H#Km9*W^>^>OsKG{iF0K)=AI91UuF%=Ob!b)Occa z=K_m-eCI(&*BJY)IM<-aE|H;F$P8+T+3SI(aWH#Rsd#e;`?01dcLdIX?0!0o^yj(; zFHtTWVFwlr1PAwCSex!%6G3~j3WFYgYK;Es#@Kv!^5MFg>FzbT!@T-77THrn-QxPG z$Nu^psTu4tAd!N34K*!1LD)J>Xa4K^nlDRF3)ZVy$Z%cYJznTM?BHyNlYF^BMo>LHny( zI|k7of>?f2`?ppYcS#WRO{3->%fBH`iFaJix~YLt88#5GYf|pbkN0|runYumF4rgk zfw@!{D1w4K3-*DUwr=;Zb_0$Wl@W&*K;a+HcFdgf+9YVX4AeGeoqcwJTHEE*N2i;566Hvg{`lIh69u?yVKV&>v4xX&1|MS1-QR|p5ejQ8iJC;{M zeeDwzzC(DrISkHY%q2Lw0I`p2Bxp#O3PlXhA;yN$;S#&fEB*6P%tyD@|NVNja&Jf6 zgSL{sSjn>j$nkC%qB!rdMCiu% zfM2U^#~TVKJ$DPuj&LhSg*?k_{9VWscp!q0+nmo__FZ-0wnM;^B*(AQZ&C%SLF$87 zs6DgRgMU>PMt_`3x;TJ>`=nSDOH#A4SrvhmQB#>_znXK_=R9wPxlyDZ6xX8qPfNW; zT;hr!gDLZmNTpm~diRuycYPN8xZj_Pi#;A+6NZ0oJS$MWTipkW8hmLz74_}A`qOHJ z9;{)kr(#53^DJ~b>)W(s)ap)WGT_AYbt5h*4PQdY{e=|9-9te$spBP_UO#B$b0$zA zm**tPN;Uu6F-rOM^?j|Dq0oiEHv$7bX-hS{e#HQwC>04;9fhS|>n%x$iDB>Elf?k_+d8jSS9hu;tq_Xt%BL;4o%!fYp`rY>07i zxDd6UznpE{R}DEEV*XvTD(LZpj_4JysINIkB}HpgxxN1$bQlzlHMP0x9`8H1`Q+dgiJh}E7}+ZCIY(DDei&4{^@1Ae>c1P_SJ>VD}Cy!Y?66wY(xNW z3SKvO>QBKy1cD^HuYIjY(?xgdcf%)dug#lUti>qCZb-~8#y`)8zf8t?STj>1Z*TJQ z%px0buHBE@i*jD()J|P%z3y4tK2#VBzokIbgz2Y-l-~WOU#}gQC>!u`tg3$NR-OV% zab6h?pr*pp^@D$G&E##@3{xUtNqBxtyvBg3ph}VF6Q@S~tEmnj z&d&jix7r+k?;m6wl(iSxdJ)a$^i6Jj_zY~w&#dk$9DKF*MR+-;>q5>Z2ji&1i^bTS ziq6K?RppJ+UQpnr)r)6lbPQZNjLfDs%CyamTRYzn-$=P9|sz*{In{(J_Q)Fr8=Dc%-gtU2pfQQ@^d z=m+&;9C3DR@!XUBhIaA(X_G5v%lz$|6ZK_-HC+2eZuEY1x3pRCcGfk&Vz|!pVA^LS zUzUXA*>ud0-=CUs&B4u*j@@|(E2Jp-8u$qjTbzWeRn+GpRfGQ1arSYhEyjUbG>mH? z%3``w=+l;$ipeg|=+$}EhHB1gQj0Enw5MY&Mt1v2`co-Gt-s@A7Qg=9-23LWJthpj zuVdi7=%XXVX`=7gaJSiHjV9N0m_2TkZaw6tU*Kq&)SEX@#nBD(=B~iqub)Y_?!K1Z z{IdUwnz?y>Xg;i4<>MH4dS)o7$4GPZ%bf?%tB&dFYGX~Ow*dU*7r*LXmYGe~EX5;8 zk^%ooIZ>*O&|r1*1nB|Kih+o`GB+byS`T;zgCg!gZ`+ZZ`<1}@@wXExgY)L@M%dm% zB_sD4moW%<)a4p25A3LBi+z3bw@+DU%bksW({kxFk93o*PjEE?lb1V19U+r7`u+;w zZpLKM-M4PP{{8#!?X%kulh*^DV=DRV^2D*HlILzGICMcgP$fk*{$-fgXSOm#TgvVT zj-LGPbH@+!r^!=hV7~LtgMa-kO|L=ACM~sOVy0$dRXJVfn`uEyr&HH+Uh_+J@{Z=# z%5vVZ^bz}o#~w?11u(U0awj<$i@UHm4V4-CryZi%EnA))Se@N?HSp;dsk|cf zXSH2VIk@#S?}GvdC%@t9;FjEaQYG|B@dX#1XRsjPkDg%aMq&NT_pNjHi~O`MtjvX_ zEG(XDbg%|oO^hquxFb6elA83k=@>7oP~f(BCu|kh{{`3qC;o`CN10`lrNaoKB~VvQViZ97_Xgk!m%{aY{`j8as}w_wJmhb#ITJ9=5wXx6@qhgAZ|W%)30Zzr-Us zm9WMwgz6HIni439HB^_7(P$(@Yn{3@u&RhvrGbNo(Ph_+yX73`wic|@)T&kIBdv8a zSX#=GP$zeF-dwl6O*o~LZG{mU4P}IrO%ZEMHP%wfVx4B>&`@P?*fnF~LgQiVXf%v0 zEt{=3``(TTQO*kMaDB?}*e^QzJ%8pe`_g~lUxR{B7AIJg5K1U0Wf4#UN+=;9l!9Rc zgp&(5t{ecjZ_OQ7%HmP_K6lL!5K1VE#eFPRm9jYj5C((-loBRdJAxW^+_-?v30eUs tC6urzKo~+85KuxW;oxrjoBtu>{|7nvr;Xxy)8+sG002ovPDHLkV1gJr*cbo+ literal 0 HcmV?d00001 diff --git a/test/test.py b/test/test.py index 4b44c548..720c5491 100755 --- a/test/test.py +++ b/test/test.py @@ -530,6 +530,120 @@ def start_from(items, value): mkdir(out_dir) crunch(in_path, out_path, clone_name) +for sample_name in [ + "sample-normal-ej01-floortrim02-128x128", +]: + layout_dict_list = [ + { + "name": "default", + "option": [], + }, + { + "name": "dxn", + "option": ["-dxn"], + }, + ] + + for layout in layout_dict_list: + layout_name = layout["name"] + layout_option = layout["option"] + + normal_dict_list = [ + { + "name": "original", + "option": [], + }, + { + "name": "renormalize", + "option": ["-renormalize", "-rtopmip"], + }, + ] + + for normal in normal_dict_list: + normal_name = normal["name"] + normal_option = normal["option"] + + for out_format in all_format_list: + if out_format not in dxt_format_list and layout_name == "dxn": + continue + + in_format = "png" + in_path = f"test/{sample_name}.{in_format}" + + if layout_name == "dxn": + out_dir_format = "dxt" + else: + out_dir_format = "all" + + out_dir = f"build/test/crunch-normal-{normal_name}-{layout_name}-{in_format}-to-{out_dir_format}" + out_path = f"{out_dir}/{sample_name}.{out_format}" + + option_list = [] + option_list.extend(normal_option) + option_list.extend(layout_option) + + mkdir(out_dir) + crunch(in_path, out_path, None, options=option_list) + + endian_dict_list = [ + { + "name": "little-to-big", + "option": ["-ktxBigEndian"] + }, + { + "name": "big-to-little", + "option": [], + }, + ] + +for in_format in all_format_list: + mkdir("build/test/crunch-endian-original-default-little-to-big") + + crunch( + f"build/test/crunch-normal-original-default-png-to-all/sample-normal-ej01-floortrim02-128x128.{in_format}", + f"build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-{in_format}.ktx", + None, + options=["-ktxBigEndian"], + ) + + if in_format in dxt_format_list: + crunch( + f"build/test/crunch-normal-original-dxn-png-to-dxt/sample-normal-ej01-floortrim02-128x128.{in_format}", + f"build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-dxn-from-{in_format}.ktx", + None, + options=["-ktxBigEndian", "-dxn"], + ) + + crunch( + f"build/test/crunch-icon-png-to-all/sample-icon-unvanquished-64x64.{in_format}", + f"build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-{in_format}.ktx", + None, + options=["-ktxBigEndian"], + ) + + mkdir("build/test/crunch-endian-original-default-big-to-little") + + for out_format in all_format_list: + crunch( + f"build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-default-from-{in_format}.ktx", + f"build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-default-from-ktx.{out_format}", + None, + ) + + if in_format in dxt_format_list: + crunch( + f"build/test/crunch-endian-original-default-little-to-big/sample-normal-ej01-floortrim02-128x128-dxn-from-{in_format}.ktx", + f"build/test/crunch-endian-original-default-big-to-little/sample-normal-ej01-floortrim02-128x128-dxn-from-ktx.{out_format}", + None, + options=["-dxn"], + ) + + crunch( + f"build/test/crunch-endian-original-default-little-to-big/sample-icon-unvanquished-64x64-from-{in_format}.ktx", + f"build/test/crunch-endian-original-default-big-to-little/sample-icon-unvanquished-64x64-from-ktx.{out_format}", + None, + ) + example(1, "test/sample-icon-unvanquished-64x64.png", None, None, options=["i"]) mkdir("build/test/example1-icon-png-to-dds") From c8c3fb607a305ed6c7ed321cad4db896195bf328 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 9 Aug 2026 15:28:30 +0200 Subject: [PATCH 16/16] ci: add many more platforms including some big endian ones - Big Engian Debian platforms: * ppc64 * s390x - Modern Debian platforms: * ppc64el * loong64 * riscv64 - Modern Windows platforms (using MSYS2 on Windows): * arm64 Also upgradee the Linux distributions running MinGW, Wine and QEMU. --- .azure-pipelines.yml | 70 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 0b3ad6cf..9d1bc57e 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -37,41 +37,77 @@ strategy: CXX_COMPILER: i686-linux-gnu-g++ COMPILER_FLAGS: -mfpmath=sse -msse Linux arm64 GCC: - VM_IMAGE: 'ubuntu-22.04' + VM_IMAGE: 'ubuntu-26.04' APT_PACKAGES: ninja-build g++-aarch64-linux-gnu qemu-user C_COMPILER: aarch64-linux-gnu-gcc CXX_COMPILER: aarch64-linux-gnu-g++ CRUNCH_EXE_RUNNER: qemu-aarch64 -L /usr/aarch64-linux-gnu Linux armhf GCC: - # There is an IO bug in qemu-arm from ubuntu-22.04. - VM_IMAGE: 'ubuntu-24.04' + VM_IMAGE: 'ubuntu-26.04' APT_PACKAGES: ninja-build g++-arm-linux-gnueabihf qemu-user C_COMPILER: arm-linux-gnueabihf-gcc CXX_COMPILER: arm-linux-gnueabihf-g++ CRUNCH_EXE_RUNNER: qemu-arm -L /usr/arm-linux-gnueabihf + Linux ppc64 GCC: + VM_IMAGE: 'ubuntu-26.04' + APT_PACKAGES: ninja-build g++-powerpc64-linux-gnu qemu-user + C_COMPILER: powerpc64-linux-gnu-gcc + CXX_COMPILER: powerpc64-linux-gnu-g++ + CRUNCH_EXE_RUNNER: qemu-ppc64 -L /usr/powerpc64-linux-gnu + Linux ppc64el GCC: + VM_IMAGE: 'ubuntu-26.04' + APT_PACKAGES: ninja-build g++-powerpc64le-linux-gnu qemu-user + C_COMPILER: powerpc64le-linux-gnu-gcc + CXX_COMPILER: powerpc64le-linux-gnu-g++ + CRUNCH_EXE_RUNNER: qemu-ppc64le -L /usr/powerpc64le-linux-gnu + Linux riscv64 GCC: + VM_IMAGE: 'ubuntu-26.04' + APT_PACKAGES: ninja-build g++-riscv64-linux-gnu qemu-user + C_COMPILER: riscv64-linux-gnu-gcc + CXX_COMPILER: riscv64-linux-gnu-g++ + CRUNCH_EXE_RUNNER: qemu-riscv64 -L /usr/riscv64-linux-gnu + Linux loong64 GCC: + VM_IMAGE: 'ubuntu-26.04' + APT_PACKAGES: ninja-build g++-loongarch64-linux-gnu qemu-user + C_COMPILER: loongarch64-linux-gnu-gcc + CXX_COMPILER: loongarch64-linux-gnu-g++ + CRUNCH_EXE_RUNNER: qemu-loongarch64 -L /usr/loongarch64-linux-gnu + Linux s390x GCC: + VM_IMAGE: 'ubuntu-26.04' + APT_PACKAGES: ninja-build g++-s390x-linux-gnu qemu-user + C_COMPILER: s390x-linux-gnu-gcc + CXX_COMPILER: s390x-linux-gnu-g++ + CRUNCH_EXE_RUNNER: qemu-s390x -L /usr/s390x-linux-gnu Linux amd64 Clang: VM_IMAGE: 'ubuntu-22.04' APT_PACKAGES: ninja-build C_COMPILER: clang CXX_COMPILER: clang++ Windows amd64 MinGW: - VM_IMAGE: 'ubuntu-22.04' - APT_PACKAGES: ninja-build g++-mingw-w64-x86-64 mingw-w64-x86-64-dev gcc-mingw-w64-x86-64-posix-runtime wine + VM_IMAGE: 'ubuntu-26.04' + APT_PACKAGES: ninja-build g++-mingw-w64-x86-64 mingw-w64-x86-64-dev gcc-mingw-w64-x86-64-posix-runtime wine64 SETUP_COMMANDS: sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix TOOLCHAIN_FILE: cmake/cross-toolchain-mingw64.cmake + RUNTIME_FILES: /usr/lib/gcc/x86_64-w64-mingw32/13-posix/libgcc_s_seh-1.dll /usr/lib/gcc/x86_64-w64-mingw32/13-posix/libstdc++-6.dll /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll CRUNCH_EXE_RUNNER: wine - CRUNCH_EXTENSION: .exe - RUNTIME_FILES: /usr/lib/gcc/x86_64-w64-mingw32/10-posix/libgcc_s_seh-1.dll /usr/lib/gcc/x86_64-w64-mingw32/10-posix/libstdc++-6.dll /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll + CRUNCH_EXE_EXTENSION: .exe Windows i686 MinGW: - VM_IMAGE: 'ubuntu-22.04' + VM_IMAGE: 'ubuntu-26.04' APT_ARCHITECTURE: i386 - APT_PACKAGES: ninja-build g++-mingw-w64-i686 mingw-w64-i686-dev gcc-mingw-w64-i686-posix-runtime wine wine32 + APT_PACKAGES: ninja-build g++-mingw-w64-i686 mingw-w64-i686-dev gcc-mingw-w64-i686-posix-runtime wine64 SETUP_COMMANDS: sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix TOOLCHAIN_FILE: cmake/cross-toolchain-mingw32.cmake COMPILER_FLAGS: -mfpmath=sse -msse + RUNTIME_FILES: /usr/lib/gcc/i686-w64-mingw32/13-posix/libgcc_s_dw2-1.dll /usr/lib/gcc/i686-w64-mingw32/13-posix/libstdc++-6.dll /usr/i686-w64-mingw32/lib/libwinpthread-1.dll CRUNCH_EXE_RUNNER: wine - CRUNCH_EXTENSION: .exe - RUNTIME_FILES: /usr/lib/gcc/i686-w64-mingw32/10-posix/libgcc_s_dw2-1.dll /usr/lib/gcc/i686-w64-mingw32/10-posix/libstdc++-6.dll /usr/i686-w64-mingw32/lib/libwinpthread-1.dll + CRUNCH_EXE_EXTENSION: .exe + Windows arm64 MinGW: + VM_IMAGE: 'windows-2025' + PACMAN_PACKAGES: mingw-w64-cross-mingwarm64-gcc + C_COMPILER: aarch64-w64-mingw32-gcc + CXX_COMPILER: aarch64-w64-mingw32-g++ + CRUNCH_EXE_EXTENSION: .exe + RUN_TESTS: false macOS amd64 AppleClang: VM_IMAGE: 'macOS-14' PIP_PACKAGES: colorama @@ -104,11 +140,19 @@ steps: if [ -n "${APT_PACKAGES:-}" ]; then sudo apt-get update && sudo apt-get -y -q --no-install-recommends install ${APT_PACKAGES} fi + if [ -n "${PACMAN_PACKAGES:-}" ]; then + 'C:\msys64\usr\bin\bash.exe' -lc "pacman -Sy --noconfirm && pacman -S --noconfirm ${PACMAN_PACKAGES}" + echo '##vso[task.prependpath]C:\msys64\usr\bin' + echo '##vso[task.prependpath]C:\msys64\opt\bin' + fi if [ -n "${PIP_PACKAGES:-}" ]; then pip install ${PIP_PACKAGES} fi if [ -n "${SETUP_COMMANDS:-}" ]; then - $(SETUP_COMMANDS) + $(SETUP_COMMANDS) + fi + if [ -x '/usr/lib/x86_64-linux-gnu/wine/wineserver' ]; then + echo '##vso[task.prependpath]/usr/lib/x86_64-linux-gnu/wine' fi displayName: 'Setup' - bash: | @@ -138,7 +182,7 @@ steps: set -xue EXECUTABLES="${EXECUTABLES:-crunch example1 example2 example3}" for exe_file in ${EXECUTABLES}; do - file "build/${exe_file}${CRUNCH_EXTENSION:-}" + file "build/${exe_file}${CRUNCH_EXE_EXTENSION:-}" done if [ -n "${RUNTIME_FILES:-}" ]; then cp -av ${RUNTIME_FILES} build/