/* Crafter®.Asset Copyright (C) 2026 Catcrafts® catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ module; // Vendored GDeflate (Microsoft DirectStorage reference, Apache-2.0). Headers // pull libdeflate (MIT) via -Ilib/gdeflate/libdeflate. The C++ wrappers are // pulled inline because Crafter.Build's cFiles only handles .c TUs — folding // them into this module impl avoids adding a parallel cppFiles channel. #include "../lib/gdeflate/GDeflate.h" #include "../lib/gdeflate/GDeflateCompress.cpp" #include "../lib/gdeflate/GDeflateDecompress.cpp" module Crafter.Asset; import std; namespace Crafter::Compression { CompressedBlob CompressStreams(std::span> streams) { CompressedBlob blob; blob.regions.reserve(streams.size()); // First pass: compress each stream into its own buffer so we know the // exact final size. GDeflate::CompressBound is an upper bound; we'd // waste capacity if we appended the bound directly into a single // shared buffer. std::vector> compressed; compressed.reserve(streams.size()); std::uint64_t totalSize = 0; for (const std::span& stream : streams) { if (stream.empty()) { compressed.emplace_back(); continue; } std::size_t boundSize = GDeflate::CompressBound(stream.size()); std::vector out(boundSize); std::size_t actualSize = boundSize; bool ok = GDeflate::Compress( reinterpret_cast(out.data()), &actualSize, reinterpret_cast(stream.data()), stream.size(), GDeflate::MaximumCompressionLevel, 0); if (!ok) { throw std::runtime_error("GDeflate::Compress failed"); } out.resize(actualSize); totalSize += actualSize; compressed.push_back(std::move(out)); } // Second pass: concatenate and build the region table. blob.bytes.reserve(totalSize); for (std::size_t i = 0; i < streams.size(); ++i) { RegionMeta r { .srcOffset = blob.bytes.size(), .compressedSize = compressed[i].size(), .decompressedSize = streams[i].size(), }; blob.regions.push_back(r); blob.bytes.insert(blob.bytes.end(), compressed[i].begin(), compressed[i].end()); } return blob; } void WriteBlob(std::ostream& file, const CompressedBlob& blob) { std::uint32_t regionCount = static_cast(blob.regions.size()); file.write(reinterpret_cast(®ionCount), sizeof(regionCount)); if (regionCount > 0) { file.write(reinterpret_cast(blob.regions.data()), regionCount * sizeof(RegionMeta)); } std::uint64_t payloadSize = blob.bytes.size(); file.write(reinterpret_cast(&payloadSize), sizeof(payloadSize)); if (payloadSize > 0) { file.write(reinterpret_cast(blob.bytes.data()), payloadSize); } } CompressedBlob ReadBlob(std::istream& file) { CompressedBlob blob; std::uint32_t regionCount = 0; file.read(reinterpret_cast(®ionCount), sizeof(regionCount)); blob.regions.resize(regionCount); if (regionCount > 0) { file.read(reinterpret_cast(blob.regions.data()), regionCount * sizeof(RegionMeta)); } std::uint64_t payloadSize = 0; file.read(reinterpret_cast(&payloadSize), sizeof(payloadSize)); blob.bytes.resize(payloadSize); if (payloadSize > 0) { file.read(reinterpret_cast(blob.bytes.data()), payloadSize); } return blob; } void DecompressCPU(const CompressedBlob& blob, std::span> outputs) { if (outputs.size() != blob.regions.size()) { throw std::runtime_error("DecompressCPU: outputs.size() != regions.size()"); } for (std::size_t i = 0; i < blob.regions.size(); ++i) { const RegionMeta& r = blob.regions[i]; const std::span& out = outputs[i]; if (out.size() != r.decompressedSize) { throw std::runtime_error("DecompressCPU: output size mismatch"); } if (r.decompressedSize == 0) continue; bool ok = GDeflate::Decompress( reinterpret_cast(out.data()), r.decompressedSize, reinterpret_cast(blob.bytes.data() + r.srcOffset), r.compressedSize, /*numWorkers=*/1); if (!ok) { throw std::runtime_error("GDeflate::Decompress failed"); } } } }