chore: Remove miniz and modularise inflation logic (#1073)
## Summary * Remove miniz and move completely to uzlib * Move uzlib interfacing to InflateReader to better modularise inflation code --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? Yes, Claude helped with the extraction and refactor
This commit is contained in:
@@ -1,14 +1,11 @@
|
|||||||
#include "FontDecompressor.h"
|
#include "FontDecompressor.h"
|
||||||
|
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <uzlib.h>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
bool FontDecompressor::init() {
|
bool FontDecompressor::init() {
|
||||||
clearCache();
|
clearCache();
|
||||||
memset(&decomp, 0, sizeof(decomp));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,20 +79,10 @@ bool FontDecompressor::decompressGroup(const EpdFontData* fontData, uint16_t gro
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decompress using uzlib
|
inflateReader.init(false);
|
||||||
const uint8_t* inputBuf = &fontData->bitmap[group.compressedOffset];
|
inflateReader.setSource(&fontData->bitmap[group.compressedOffset], group.compressedSize);
|
||||||
|
if (!inflateReader.read(outBuf, group.uncompressedSize)) {
|
||||||
uzlib_uncompress_init(&decomp, NULL, 0);
|
LOG_ERR("FDC", "Decompression failed for group %u", groupIndex);
|
||||||
decomp.source = inputBuf;
|
|
||||||
decomp.source_limit = inputBuf + group.compressedSize;
|
|
||||||
decomp.dest_start = outBuf;
|
|
||||||
decomp.dest = outBuf;
|
|
||||||
decomp.dest_limit = outBuf + group.uncompressedSize;
|
|
||||||
|
|
||||||
int res = uzlib_uncompress(&decomp);
|
|
||||||
|
|
||||||
if (res < 0 || decomp.dest != decomp.dest_limit) {
|
|
||||||
LOG_ERR("FDC", "Decompression failed for group %u (status %d)", groupIndex, res);
|
|
||||||
free(outBuf);
|
free(outBuf);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <uzlib.h>
|
#include <InflateReader.h>
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ class FontDecompressor {
|
|||||||
bool valid = false;
|
bool valid = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct uzlib_uncomp decomp = {};
|
InflateReader inflateReader;
|
||||||
CacheEntry cache[CACHE_SLOTS] = {};
|
CacheEntry cache[CACHE_SLOTS] = {};
|
||||||
uint32_t accessCounter = 0;
|
uint32_t accessCounter = 0;
|
||||||
|
|
||||||
|
|||||||
78
lib/InflateReader/InflateReader.cpp
Normal file
78
lib/InflateReader/InflateReader.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include "InflateReader.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr size_t INFLATE_DICT_SIZE = 32768;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guarantee the cast pattern in the header comment is valid.
|
||||||
|
static_assert(std::is_standard_layout<InflateReader>::value,
|
||||||
|
"InflateReader must be standard-layout for the uzlib callback cast to work");
|
||||||
|
|
||||||
|
InflateReader::~InflateReader() { deinit(); }
|
||||||
|
|
||||||
|
bool InflateReader::init(const bool streaming) {
|
||||||
|
deinit(); // free any previously allocated ring buffer and reset state
|
||||||
|
|
||||||
|
if (streaming) {
|
||||||
|
ringBuffer = static_cast<uint8_t*>(malloc(INFLATE_DICT_SIZE));
|
||||||
|
if (!ringBuffer) return false;
|
||||||
|
memset(ringBuffer, 0, INFLATE_DICT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uzlib_uncompress_init(&decomp, ringBuffer, ringBuffer ? INFLATE_DICT_SIZE : 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InflateReader::deinit() {
|
||||||
|
if (ringBuffer) {
|
||||||
|
free(ringBuffer);
|
||||||
|
ringBuffer = nullptr;
|
||||||
|
}
|
||||||
|
memset(&decomp, 0, sizeof(decomp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InflateReader::setSource(const uint8_t* src, size_t len) {
|
||||||
|
decomp.source = src;
|
||||||
|
decomp.source_limit = src + len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InflateReader::setReadCallback(int (*cb)(struct uzlib_uncomp*)) { decomp.source_read_cb = cb; }
|
||||||
|
|
||||||
|
void InflateReader::skipZlibHeader() {
|
||||||
|
uzlib_get_byte(&decomp);
|
||||||
|
uzlib_get_byte(&decomp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InflateReader::read(uint8_t* dest, size_t len) {
|
||||||
|
if (!ringBuffer) {
|
||||||
|
// One-shot mode: back-references use absolute offset from dest_start.
|
||||||
|
// Valid only when read() is called once with the full output buffer.
|
||||||
|
decomp.dest_start = dest;
|
||||||
|
}
|
||||||
|
decomp.dest = dest;
|
||||||
|
decomp.dest_limit = dest + len;
|
||||||
|
|
||||||
|
const int res = uzlib_uncompress(&decomp);
|
||||||
|
if (res < 0) return false;
|
||||||
|
return decomp.dest == decomp.dest_limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
InflateStatus InflateReader::readAtMost(uint8_t* dest, size_t maxLen, size_t* produced) {
|
||||||
|
if (!ringBuffer) {
|
||||||
|
// One-shot mode: back-references use absolute offset from dest_start.
|
||||||
|
// Valid only when readAtMost() is called once with the full output buffer.
|
||||||
|
decomp.dest_start = dest;
|
||||||
|
}
|
||||||
|
decomp.dest = dest;
|
||||||
|
decomp.dest_limit = dest + maxLen;
|
||||||
|
|
||||||
|
const int res = uzlib_uncompress(&decomp);
|
||||||
|
*produced = static_cast<size_t>(decomp.dest - dest);
|
||||||
|
|
||||||
|
if (res == TINF_DONE) return InflateStatus::Done;
|
||||||
|
if (res < 0) return InflateStatus::Error;
|
||||||
|
return InflateStatus::Ok;
|
||||||
|
}
|
||||||
85
lib/InflateReader/InflateReader.h
Normal file
85
lib/InflateReader/InflateReader.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <uzlib.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
// Return value for readAtMost().
|
||||||
|
enum class InflateStatus {
|
||||||
|
Ok, // Output buffer full; more compressed data remains.
|
||||||
|
Done, // Stream ended cleanly (TINF_DONE). produced may be < maxLen.
|
||||||
|
Error, // Decompression failed.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Streaming deflate decompressor wrapping uzlib.
|
||||||
|
//
|
||||||
|
// Two modes:
|
||||||
|
// init(false) — one-shot: input is a contiguous buffer, call read() once.
|
||||||
|
// init(true) — streaming: allocates a 32KB ring buffer for back-references
|
||||||
|
// across multiple read() / readAtMost() calls.
|
||||||
|
//
|
||||||
|
// Streaming callback pattern:
|
||||||
|
// The uzlib read callback receives a `struct uzlib_uncomp*` with no separate
|
||||||
|
// context pointer. To attach context, make InflateReader the *first member* of
|
||||||
|
// your context struct, then cast inside the callback:
|
||||||
|
//
|
||||||
|
// struct MyCtx {
|
||||||
|
// InflateReader reader; // must be first
|
||||||
|
// FsFile* file;
|
||||||
|
// // ...
|
||||||
|
// };
|
||||||
|
// static int myCb(struct uzlib_uncomp* u) {
|
||||||
|
// MyCtx* ctx = reinterpret_cast<MyCtx*>(u); // valid: reader.decomp is at offset 0
|
||||||
|
// // ... fill u->source / u->source_limit, return first byte
|
||||||
|
// }
|
||||||
|
// MyCtx ctx;
|
||||||
|
// ctx.reader.init(true);
|
||||||
|
// ctx.reader.setReadCallback(myCb);
|
||||||
|
//
|
||||||
|
class InflateReader {
|
||||||
|
public:
|
||||||
|
InflateReader() = default;
|
||||||
|
~InflateReader();
|
||||||
|
|
||||||
|
InflateReader(const InflateReader&) = delete;
|
||||||
|
InflateReader& operator=(const InflateReader&) = delete;
|
||||||
|
|
||||||
|
// Initialise decompressor. streaming=true allocates a 32KB ring buffer needed
|
||||||
|
// when read() or readAtMost() will be called multiple times.
|
||||||
|
// Returns false only in streaming mode if the ring buffer allocation fails.
|
||||||
|
bool init(bool streaming = false);
|
||||||
|
|
||||||
|
// Release the ring buffer and reset internal state.
|
||||||
|
void deinit();
|
||||||
|
|
||||||
|
// Set the entire compressed input as a contiguous memory buffer.
|
||||||
|
// Used in one-shot mode; not needed when a read callback is set.
|
||||||
|
void setSource(const uint8_t* src, size_t len);
|
||||||
|
|
||||||
|
// Set a uzlib-compatible read callback for streaming input.
|
||||||
|
// See class-level comment for the expected callback/context struct pattern.
|
||||||
|
void setReadCallback(int (*cb)(uzlib_uncomp*));
|
||||||
|
|
||||||
|
// Consume the 2-byte zlib header (CMF + FLG) from the input stream.
|
||||||
|
// Call this once before the first read() when input is zlib-wrapped (e.g. PNG IDAT).
|
||||||
|
void skipZlibHeader();
|
||||||
|
|
||||||
|
// Decompress exactly len bytes into dest.
|
||||||
|
// Returns false if the stream ends before producing len bytes, or on error.
|
||||||
|
bool read(uint8_t* dest, size_t len);
|
||||||
|
|
||||||
|
// Decompress up to maxLen bytes into dest.
|
||||||
|
// Sets *produced to the number of bytes written.
|
||||||
|
// Returns Done when the stream ends cleanly, Ok when there is more to read,
|
||||||
|
// and Error on failure.
|
||||||
|
InflateStatus readAtMost(uint8_t* dest, size_t maxLen, size_t* produced);
|
||||||
|
|
||||||
|
// Returns a pointer to the underlying TINF_DATA.
|
||||||
|
// Useful for advanced streaming setups where the callback needs access to the
|
||||||
|
// uzlib struct directly (e.g. updating source/source_limit).
|
||||||
|
uzlib_uncomp* raw() { return &decomp; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uzlib_uncomp decomp = {};
|
||||||
|
uint8_t* ringBuffer = nullptr;
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "PngToBmpConverter.h"
|
#include "PngToBmpConverter.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <InflateReader.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <miniz.h>
|
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -20,36 +20,6 @@ constexpr int TARGET_MAX_WIDTH = 480;
|
|||||||
constexpr int TARGET_MAX_HEIGHT = 800;
|
constexpr int TARGET_MAX_HEIGHT = 800;
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// PNG constants
|
|
||||||
static constexpr uint8_t PNG_SIGNATURE[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
|
||||||
|
|
||||||
// PNG color types
|
|
||||||
enum PngColorType : uint8_t {
|
|
||||||
PNG_COLOR_GRAYSCALE = 0,
|
|
||||||
PNG_COLOR_RGB = 2,
|
|
||||||
PNG_COLOR_PALETTE = 3,
|
|
||||||
PNG_COLOR_GRAYSCALE_ALPHA = 4,
|
|
||||||
PNG_COLOR_RGBA = 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
// PNG filter types
|
|
||||||
enum PngFilter : uint8_t {
|
|
||||||
PNG_FILTER_NONE = 0,
|
|
||||||
PNG_FILTER_SUB = 1,
|
|
||||||
PNG_FILTER_UP = 2,
|
|
||||||
PNG_FILTER_AVERAGE = 3,
|
|
||||||
PNG_FILTER_PAETH = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read a big-endian 32-bit value from file
|
|
||||||
static bool readBE32(FsFile& file, uint32_t& value) {
|
|
||||||
uint8_t buf[4];
|
|
||||||
if (file.read(buf, 4) != 4) return false;
|
|
||||||
value = (static_cast<uint32_t>(buf[0]) << 24) | (static_cast<uint32_t>(buf[1]) << 16) |
|
|
||||||
(static_cast<uint32_t>(buf[2]) << 8) | buf[3];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BMP writing helpers (same as JpegToBmpConverter)
|
// BMP writing helpers (same as JpegToBmpConverter)
|
||||||
inline void write16(Print& out, const uint16_t value) {
|
inline void write16(Print& out, const uint16_t value) {
|
||||||
out.write(value & 0xFF);
|
out.write(value & 0xFF);
|
||||||
@@ -70,7 +40,49 @@ inline void write32Signed(Print& out, const int32_t value) {
|
|||||||
out.write((value >> 24) & 0xFF);
|
out.write((value >> 24) & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeBmpHeader8bit(Print& bmpOut, const int width, const int height) {
|
// Paeth predictor function per PNG spec
|
||||||
|
inline uint8_t paethPredictor(uint8_t a, uint8_t b, uint8_t c) {
|
||||||
|
int p = static_cast<int>(a) + b - c;
|
||||||
|
int pa = p > a ? p - a : a - p;
|
||||||
|
int pb = p > b ? p - b : b - p;
|
||||||
|
int pc = p > c ? p - c : c - p;
|
||||||
|
if (pa <= pb && pa <= pc) return a;
|
||||||
|
if (pb <= pc) return b;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// PNG constants
|
||||||
|
uint8_t PNG_SIGNATURE[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||||
|
|
||||||
|
// PNG color types
|
||||||
|
enum PngColorType : uint8_t {
|
||||||
|
PNG_COLOR_GRAYSCALE = 0,
|
||||||
|
PNG_COLOR_RGB = 2,
|
||||||
|
PNG_COLOR_PALETTE = 3,
|
||||||
|
PNG_COLOR_GRAYSCALE_ALPHA = 4,
|
||||||
|
PNG_COLOR_RGBA = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
// PNG filter types
|
||||||
|
enum PngFilter : uint8_t {
|
||||||
|
PNG_FILTER_NONE = 0,
|
||||||
|
PNG_FILTER_SUB = 1,
|
||||||
|
PNG_FILTER_UP = 2,
|
||||||
|
PNG_FILTER_AVERAGE = 3,
|
||||||
|
PNG_FILTER_PAETH = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read a big-endian 32-bit value from file
|
||||||
|
bool readBE32(FsFile& file, uint32_t& value) {
|
||||||
|
uint8_t buf[4];
|
||||||
|
if (file.read(buf, 4) != 4) return false;
|
||||||
|
value = (static_cast<uint32_t>(buf[0]) << 24) | (static_cast<uint32_t>(buf[1]) << 16) |
|
||||||
|
(static_cast<uint32_t>(buf[2]) << 8) | buf[3];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeBmpHeader8bit(Print& bmpOut, const int width, const int height) {
|
||||||
const int bytesPerRow = (width + 3) / 4 * 4;
|
const int bytesPerRow = (width + 3) / 4 * 4;
|
||||||
const int imageSize = bytesPerRow * height;
|
const int imageSize = bytesPerRow * height;
|
||||||
const uint32_t paletteSize = 256 * 4;
|
const uint32_t paletteSize = 256 * 4;
|
||||||
@@ -102,7 +114,7 @@ static void writeBmpHeader8bit(Print& bmpOut, const int width, const int height)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeBmpHeader1bit(Print& bmpOut, const int width, const int height) {
|
void writeBmpHeader1bit(Print& bmpOut, const int width, const int height) {
|
||||||
const int bytesPerRow = (width + 31) / 32 * 4;
|
const int bytesPerRow = (width + 31) / 32 * 4;
|
||||||
const int imageSize = bytesPerRow * height;
|
const int imageSize = bytesPerRow * height;
|
||||||
const uint32_t fileSize = 62 + imageSize;
|
const uint32_t fileSize = 62 + imageSize;
|
||||||
@@ -131,7 +143,7 @@ static void writeBmpHeader1bit(Print& bmpOut, const int width, const int height)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeBmpHeader2bit(Print& bmpOut, const int width, const int height) {
|
void writeBmpHeader2bit(Print& bmpOut, const int width, const int height) {
|
||||||
const int bytesPerRow = (width * 2 + 31) / 32 * 4;
|
const int bytesPerRow = (width * 2 + 31) / 32 * 4;
|
||||||
const int imageSize = bytesPerRow * height;
|
const int imageSize = bytesPerRow * height;
|
||||||
const uint32_t fileSize = 70 + imageSize;
|
const uint32_t fileSize = 70 + imageSize;
|
||||||
@@ -160,21 +172,13 @@ static void writeBmpHeader2bit(Print& bmpOut, const int width, const int height)
|
|||||||
bmpOut.write(i);
|
bmpOut.write(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // namespace
|
||||||
// Paeth predictor function per PNG spec
|
|
||||||
static inline uint8_t paethPredictor(uint8_t a, uint8_t b, uint8_t c) {
|
|
||||||
int p = static_cast<int>(a) + b - c;
|
|
||||||
int pa = p > a ? p - a : a - p;
|
|
||||||
int pb = p > b ? p - b : b - p;
|
|
||||||
int pc = p > c ? p - c : c - p;
|
|
||||||
if (pa <= pb && pa <= pc) return a;
|
|
||||||
if (pb <= pc) return b;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context for streaming PNG decompression
|
// Context for streaming PNG decompression
|
||||||
|
// IMPORTANT: reader must be the first field - the uzlib callback casts uzlib_uncomp* to PngDecodeContext*
|
||||||
struct PngDecodeContext {
|
struct PngDecodeContext {
|
||||||
FsFile& file;
|
InflateReader reader; // Must be first — callback casts uzlib_uncomp* to PngDecodeContext*
|
||||||
|
FsFile* file;
|
||||||
|
|
||||||
// PNG image properties
|
// PNG image properties
|
||||||
uint32_t width;
|
uint32_t width;
|
||||||
@@ -188,15 +192,11 @@ struct PngDecodeContext {
|
|||||||
uint8_t* currentRow; // current defiltered scanline
|
uint8_t* currentRow; // current defiltered scanline
|
||||||
uint8_t* previousRow; // previous defiltered scanline
|
uint8_t* previousRow; // previous defiltered scanline
|
||||||
|
|
||||||
// zlib decompression state
|
|
||||||
mz_stream zstream;
|
|
||||||
bool zstreamInitialized;
|
|
||||||
|
|
||||||
// Chunk reading state
|
// Chunk reading state
|
||||||
uint32_t chunkBytesRemaining; // bytes left in current IDAT chunk
|
uint32_t chunkBytesRemaining; // bytes left in current IDAT chunk
|
||||||
bool idatFinished; // no more IDAT chunks
|
bool idatFinished; // no more IDAT chunks
|
||||||
|
|
||||||
// File read buffer for feeding zlib
|
// File read buffer for feeding uzlib
|
||||||
uint8_t readBuf[2048];
|
uint8_t readBuf[2048];
|
||||||
|
|
||||||
// Palette for indexed color (type 3)
|
// Palette for indexed color (type 3)
|
||||||
@@ -209,10 +209,10 @@ struct PngDecodeContext {
|
|||||||
static bool findNextIdatChunk(PngDecodeContext& ctx) {
|
static bool findNextIdatChunk(PngDecodeContext& ctx) {
|
||||||
while (true) {
|
while (true) {
|
||||||
uint32_t chunkLen;
|
uint32_t chunkLen;
|
||||||
if (!readBE32(ctx.file, chunkLen)) return false;
|
if (!readBE32(*ctx.file, chunkLen)) return false;
|
||||||
|
|
||||||
uint8_t chunkType[4];
|
uint8_t chunkType[4];
|
||||||
if (ctx.file.read(chunkType, 4) != 4) return false;
|
if (ctx.file->read(chunkType, 4) != 4) return false;
|
||||||
|
|
||||||
if (memcmp(chunkType, "IDAT", 4) == 0) {
|
if (memcmp(chunkType, "IDAT", 4) == 0) {
|
||||||
ctx.chunkBytesRemaining = chunkLen;
|
ctx.chunkBytesRemaining = chunkLen;
|
||||||
@@ -221,7 +221,7 @@ static bool findNextIdatChunk(PngDecodeContext& ctx) {
|
|||||||
|
|
||||||
// Skip this chunk's data + 4-byte CRC
|
// Skip this chunk's data + 4-byte CRC
|
||||||
// Use seek to skip efficiently
|
// Use seek to skip efficiently
|
||||||
if (!ctx.file.seekCur(chunkLen + 4)) return false;
|
if (!ctx.file->seekCur(chunkLen + 4)) return false;
|
||||||
|
|
||||||
// If we hit IEND, there are no more chunks
|
// If we hit IEND, there are no more chunks
|
||||||
if (memcmp(chunkType, "IEND", 4) == 0) {
|
if (memcmp(chunkType, "IEND", 4) == 0) {
|
||||||
@@ -230,72 +230,50 @@ static bool findNextIdatChunk(PngDecodeContext& ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed compressed data to zlib from IDAT chunks
|
// uzlib callback: reads the next batch of IDAT data from the file
|
||||||
// Returns number of bytes made available in zstream, or -1 on error
|
static int pngIdatReadCallback(uzlib_uncomp* uncomp) {
|
||||||
static int feedZlibInput(PngDecodeContext& ctx) {
|
auto* ctx = reinterpret_cast<PngDecodeContext*>(uncomp);
|
||||||
if (ctx.idatFinished) return 0;
|
|
||||||
|
|
||||||
// If current IDAT chunk is exhausted, skip its CRC and find next
|
if (ctx->idatFinished) return -1;
|
||||||
while (ctx.chunkBytesRemaining == 0) {
|
|
||||||
// Skip 4-byte CRC of previous IDAT
|
|
||||||
if (!ctx.file.seekCur(4)) return -1;
|
|
||||||
|
|
||||||
if (!findNextIdatChunk(ctx)) {
|
// Skip 4-byte CRC and find next IDAT chunk when current chunk is exhausted
|
||||||
ctx.idatFinished = true;
|
while (ctx->chunkBytesRemaining == 0) {
|
||||||
return 0;
|
if (!ctx->file->seekCur(4)) { // skip 4-byte CRC of previous IDAT
|
||||||
|
ctx->idatFinished = true;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!findNextIdatChunk(*ctx)) {
|
||||||
|
ctx->idatFinished = true;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from current IDAT chunk
|
// Read from current IDAT chunk into the read buffer
|
||||||
size_t toRead = sizeof(ctx.readBuf);
|
size_t toRead = sizeof(ctx->readBuf);
|
||||||
if (toRead > ctx.chunkBytesRemaining) toRead = ctx.chunkBytesRemaining;
|
if (toRead > ctx->chunkBytesRemaining) toRead = ctx->chunkBytesRemaining;
|
||||||
|
|
||||||
int bytesRead = ctx.file.read(ctx.readBuf, toRead);
|
int bytesRead = ctx->file->read(ctx->readBuf, toRead);
|
||||||
if (bytesRead <= 0) return -1;
|
if (bytesRead <= 0) {
|
||||||
|
ctx->idatFinished = true;
|
||||||
ctx.chunkBytesRemaining -= bytesRead;
|
return -1;
|
||||||
ctx.zstream.next_in = ctx.readBuf;
|
|
||||||
ctx.zstream.avail_in = bytesRead;
|
|
||||||
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompress exactly 'needed' bytes into 'dest'
|
|
||||||
static bool decompressBytes(PngDecodeContext& ctx, uint8_t* dest, size_t needed) {
|
|
||||||
ctx.zstream.next_out = dest;
|
|
||||||
ctx.zstream.avail_out = needed;
|
|
||||||
|
|
||||||
while (ctx.zstream.avail_out > 0) {
|
|
||||||
if (ctx.zstream.avail_in == 0) {
|
|
||||||
int fed = feedZlibInput(ctx);
|
|
||||||
if (fed < 0) return false;
|
|
||||||
if (fed == 0) {
|
|
||||||
// Try one more inflate to flush
|
|
||||||
int ret = mz_inflate(&ctx.zstream, MZ_SYNC_FLUSH);
|
|
||||||
if (ctx.zstream.avail_out == 0) break;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret = mz_inflate(&ctx.zstream, MZ_SYNC_FLUSH);
|
|
||||||
if (ret != MZ_OK && ret != MZ_STREAM_END && ret != MZ_BUF_ERROR) {
|
|
||||||
LOG_ERR("PNG", "zlib inflate error: %d", ret);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ret == MZ_STREAM_END) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.zstream.avail_out == 0;
|
ctx->chunkBytesRemaining -= bytesRead;
|
||||||
|
|
||||||
|
// Give uzlib the buffer (skip first byte since we return it directly)
|
||||||
|
uncomp->source = ctx->readBuf + 1;
|
||||||
|
uncomp->source_limit = ctx->readBuf + bytesRead;
|
||||||
|
return ctx->readBuf[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode one scanline: decompress filter byte + raw bytes, then unfilter
|
// Decode one scanline: decompress filter byte + raw bytes, then unfilter
|
||||||
static bool decodeScanline(PngDecodeContext& ctx) {
|
static bool decodeScanline(PngDecodeContext& ctx) {
|
||||||
// Decompress filter byte
|
// Decompress filter byte
|
||||||
uint8_t filterType;
|
uint8_t filterType;
|
||||||
if (!decompressBytes(ctx, &filterType, 1)) return false;
|
if (!ctx.reader.read(&filterType, 1)) return false;
|
||||||
|
|
||||||
// Decompress raw row data into currentRow
|
// Decompress raw row data into currentRow
|
||||||
if (!decompressBytes(ctx, ctx.currentRow, ctx.rawRowBytes)) return false;
|
if (!ctx.reader.read(ctx.currentRow, ctx.rawRowBytes)) return false;
|
||||||
|
|
||||||
// Apply reverse filter
|
// Apply reverse filter
|
||||||
const int bpp = ctx.bytesPerPixel;
|
const int bpp = ctx.bytesPerPixel;
|
||||||
@@ -521,22 +499,15 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize decode context
|
// Initialize decode context
|
||||||
PngDecodeContext ctx = {.file = pngFile,
|
PngDecodeContext ctx = {};
|
||||||
.width = width,
|
ctx.file = &pngFile;
|
||||||
.height = height,
|
ctx.width = width;
|
||||||
.bitDepth = bitDepth,
|
ctx.height = height;
|
||||||
.colorType = colorType,
|
ctx.bitDepth = bitDepth;
|
||||||
.bytesPerPixel = bytesPerPixel,
|
ctx.colorType = colorType;
|
||||||
.rawRowBytes = rawRowBytes,
|
ctx.bytesPerPixel = bytesPerPixel;
|
||||||
.currentRow = nullptr,
|
ctx.rawRowBytes = rawRowBytes;
|
||||||
.previousRow = nullptr,
|
ctx.paletteSize = 0;
|
||||||
.zstream = {},
|
|
||||||
.zstreamInitialized = false,
|
|
||||||
.chunkBytesRemaining = 0,
|
|
||||||
.idatFinished = false,
|
|
||||||
.readBuf = {},
|
|
||||||
.palette = {},
|
|
||||||
.paletteSize = 0};
|
|
||||||
|
|
||||||
// Allocate scanline buffers
|
// Allocate scanline buffers
|
||||||
ctx.currentRow = static_cast<uint8_t*>(malloc(rawRowBytes));
|
ctx.currentRow = static_cast<uint8_t*>(malloc(rawRowBytes));
|
||||||
@@ -585,15 +556,16 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize zlib decompression
|
// Initialize streaming decompressor with 32KB ring buffer for back-reference history
|
||||||
memset(&ctx.zstream, 0, sizeof(ctx.zstream));
|
if (!ctx.reader.init(true)) {
|
||||||
if (mz_inflateInit(&ctx.zstream) != MZ_OK) {
|
LOG_ERR("PNG", "Failed to init inflate reader");
|
||||||
LOG_ERR("PNG", "Failed to initialize zlib");
|
|
||||||
free(ctx.currentRow);
|
free(ctx.currentRow);
|
||||||
free(ctx.previousRow);
|
free(ctx.previousRow);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ctx.zstreamInitialized = true;
|
ctx.reader.setReadCallback(pngIdatReadCallback);
|
||||||
|
// PNG IDAT data is zlib-wrapped: consume the 2-byte zlib header (CMF + FLG)
|
||||||
|
ctx.reader.skipZlibHeader();
|
||||||
|
|
||||||
// Calculate output dimensions (same logic as JpegToBmpConverter)
|
// Calculate output dimensions (same logic as JpegToBmpConverter)
|
||||||
int outWidth = width;
|
int outWidth = width;
|
||||||
@@ -618,8 +590,8 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
|||||||
if (outWidth < 1) outWidth = 1;
|
if (outWidth < 1) outWidth = 1;
|
||||||
if (outHeight < 1) outHeight = 1;
|
if (outHeight < 1) outHeight = 1;
|
||||||
|
|
||||||
scaleX_fp = (static_cast<uint32_t>(width) << 16) / outWidth;
|
scaleX_fp = (width << 16) / outWidth;
|
||||||
scaleY_fp = (static_cast<uint32_t>(height) << 16) / outHeight;
|
scaleY_fp = (height << 16) / outHeight;
|
||||||
needsScaling = true;
|
needsScaling = true;
|
||||||
|
|
||||||
LOG_DBG("PNG", "Scaling %ux%u -> %dx%d (target %dx%d)", width, height, outWidth, outHeight, targetWidth,
|
LOG_DBG("PNG", "Scaling %ux%u -> %dx%d (target %dx%d)", width, height, outWidth, outHeight, targetWidth,
|
||||||
@@ -643,7 +615,6 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
|||||||
auto* rowBuffer = static_cast<uint8_t*>(malloc(bytesPerRow));
|
auto* rowBuffer = static_cast<uint8_t*>(malloc(bytesPerRow));
|
||||||
if (!rowBuffer) {
|
if (!rowBuffer) {
|
||||||
LOG_ERR("PNG", "Failed to allocate row buffer");
|
LOG_ERR("PNG", "Failed to allocate row buffer");
|
||||||
mz_inflateEnd(&ctx.zstream);
|
|
||||||
free(ctx.currentRow);
|
free(ctx.currentRow);
|
||||||
free(ctx.previousRow);
|
free(ctx.previousRow);
|
||||||
return false;
|
return false;
|
||||||
@@ -687,7 +658,6 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
|||||||
delete fsDitherer;
|
delete fsDitherer;
|
||||||
delete atkinson1BitDitherer;
|
delete atkinson1BitDitherer;
|
||||||
free(rowBuffer);
|
free(rowBuffer);
|
||||||
mz_inflateEnd(&ctx.zstream);
|
|
||||||
free(ctx.currentRow);
|
free(ctx.currentRow);
|
||||||
free(ctx.previousRow);
|
free(ctx.previousRow);
|
||||||
return false;
|
return false;
|
||||||
@@ -842,7 +812,6 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
|||||||
delete fsDitherer;
|
delete fsDitherer;
|
||||||
delete atkinson1BitDitherer;
|
delete atkinson1BitDitherer;
|
||||||
free(rowBuffer);
|
free(rowBuffer);
|
||||||
mz_inflateEnd(&ctx.zstream);
|
|
||||||
free(ctx.currentRow);
|
free(ctx.currentRow);
|
||||||
free(ctx.previousRow);
|
free(ctx.previousRow);
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,38 @@
|
|||||||
#include "ZipFile.h"
|
#include "ZipFile.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <InflateReader.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <miniz.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
static bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf,
|
struct ZipInflateCtx {
|
||||||
const size_t inflatedSize) {
|
InflateReader reader; // Must be first — callback casts uzlib_uncomp* to ZipInflateCtx*
|
||||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
FsFile* file = nullptr;
|
||||||
if (!inflator) {
|
size_t fileRemaining = 0;
|
||||||
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
uint8_t* readBuf = nullptr;
|
||||||
return false;
|
size_t readBufSize = 0;
|
||||||
}
|
};
|
||||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
|
||||||
tinfl_init(inflator);
|
|
||||||
|
|
||||||
size_t inBytes = deflatedSize;
|
namespace {
|
||||||
size_t outBytes = inflatedSize;
|
constexpr uint16_t ZIP_METHOD_STORED = 0;
|
||||||
const tinfl_status status = tinfl_decompress(inflator, inputBuf, &inBytes, nullptr, outputBuf, &outBytes,
|
constexpr uint16_t ZIP_METHOD_DEFLATED = 8;
|
||||||
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
|
||||||
|
|
||||||
free(inflator);
|
int zipReadCallback(uzlib_uncomp* uncomp) {
|
||||||
|
auto* ctx = reinterpret_cast<ZipInflateCtx*>(uncomp);
|
||||||
|
if (ctx->fileRemaining == 0) return -1;
|
||||||
|
|
||||||
if (status != TINFL_STATUS_DONE) {
|
const size_t toRead = ctx->fileRemaining < ctx->readBufSize ? ctx->fileRemaining : ctx->readBufSize;
|
||||||
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
const size_t bytesRead = ctx->file->read(ctx->readBuf, toRead);
|
||||||
return false;
|
ctx->fileRemaining -= bytesRead;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
if (bytesRead == 0) return -1;
|
||||||
|
|
||||||
|
uncomp->source = ctx->readBuf + 1;
|
||||||
|
uncomp->source_limit = ctx->readBuf + bytesRead;
|
||||||
|
return ctx->readBuf[0];
|
||||||
}
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool ZipFile::loadAllFileStatSlims() {
|
bool ZipFile::loadAllFileStatSlims() {
|
||||||
const bool wasOpen = isOpen();
|
const bool wasOpen = isOpen();
|
||||||
@@ -111,7 +114,6 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
|
|||||||
|
|
||||||
// Phase 1: Try scanning from cursor position first
|
// Phase 1: Try scanning from cursor position first
|
||||||
uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset;
|
uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset;
|
||||||
uint32_t wrapPos = zipDetails.centralDirOffset;
|
|
||||||
bool wrapped = false;
|
bool wrapped = false;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
@@ -201,7 +203,7 @@ long ZipFile::getDataOffset(const FileStatSlim& fileStat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
||||||
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
0x04034b50 /* ZIP local file header signature */) {
|
||||||
LOG_ERR("ZIP", "Not a valid zip file header");
|
LOG_ERR("ZIP", "Not a valid zip file header");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -415,7 +417,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileStat.method == MZ_NO_COMPRESSION) {
|
if (fileStat.method == ZIP_METHOD_STORED) {
|
||||||
// no deflation, just read content
|
// no deflation, just read content
|
||||||
const size_t dataRead = file.read(data, inflatedDataSize);
|
const size_t dataRead = file.read(data, inflatedDataSize);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
@@ -429,7 +431,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Continue out of block with data set
|
// Continue out of block with data set
|
||||||
} else if (fileStat.method == MZ_DEFLATED) {
|
} else if (fileStat.method == ZIP_METHOD_DEFLATED) {
|
||||||
// Read out deflated content from file
|
// Read out deflated content from file
|
||||||
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
||||||
if (deflatedData == nullptr) {
|
if (deflatedData == nullptr) {
|
||||||
@@ -452,7 +454,13 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = inflateOneShot(deflatedData, deflatedDataSize, data, inflatedDataSize);
|
bool success = false;
|
||||||
|
{
|
||||||
|
InflateReader r;
|
||||||
|
r.init(false);
|
||||||
|
r.setSource(deflatedData, deflatedDataSize);
|
||||||
|
success = r.read(data, inflatedDataSize);
|
||||||
|
}
|
||||||
free(deflatedData);
|
free(deflatedData);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -495,7 +503,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
const auto deflatedDataSize = fileStat.compressedSize;
|
const auto deflatedDataSize = fileStat.compressedSize;
|
||||||
const auto inflatedDataSize = fileStat.uncompressedSize;
|
const auto inflatedDataSize = fileStat.uncompressedSize;
|
||||||
|
|
||||||
if (fileStat.method == MZ_NO_COMPRESSION) {
|
if (fileStat.method == ZIP_METHOD_STORED) {
|
||||||
// no deflation, just read content
|
// no deflation, just read content
|
||||||
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
@@ -529,127 +537,88 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileStat.method == MZ_DEFLATED) {
|
if (fileStat.method == ZIP_METHOD_DEFLATED) {
|
||||||
auto* inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
auto* fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
if (!inflator) {
|
|
||||||
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
|
||||||
if (!wasOpen) {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
|
||||||
tinfl_init(inflator);
|
|
||||||
|
|
||||||
// Setup file read buffer
|
|
||||||
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
|
||||||
if (!fileReadBuffer) {
|
if (!fileReadBuffer) {
|
||||||
LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer");
|
LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer");
|
||||||
free(inflator);
|
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
auto* outputBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
if (!outputBuffer) {
|
if (!outputBuffer) {
|
||||||
LOG_ERR("ZIP", "Failed to allocate memory for dictionary");
|
LOG_ERR("ZIP", "Failed to allocate memory for output buffer");
|
||||||
free(inflator);
|
|
||||||
free(fileReadBuffer);
|
free(fileReadBuffer);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
memset(outputBuffer, 0, TINFL_LZ_DICT_SIZE);
|
|
||||||
|
|
||||||
size_t fileRemainingBytes = deflatedDataSize;
|
ZipInflateCtx ctx;
|
||||||
size_t processedOutputBytes = 0;
|
ctx.file = &file;
|
||||||
size_t fileReadBufferFilledBytes = 0;
|
ctx.fileRemaining = deflatedDataSize;
|
||||||
size_t fileReadBufferCursor = 0;
|
ctx.readBuf = fileReadBuffer;
|
||||||
size_t outputCursor = 0; // Current offset in the circular dictionary
|
ctx.readBufSize = chunkSize;
|
||||||
|
|
||||||
|
if (!ctx.reader.init(true)) {
|
||||||
|
LOG_ERR("ZIP", "Failed to init inflate reader");
|
||||||
|
free(outputBuffer);
|
||||||
|
free(fileReadBuffer);
|
||||||
|
if (!wasOpen) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.reader.setReadCallback(zipReadCallback);
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
size_t totalProduced = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Load more compressed bytes when needed
|
size_t produced;
|
||||||
if (fileReadBufferCursor >= fileReadBufferFilledBytes) {
|
const InflateStatus status = ctx.reader.readAtMost(outputBuffer, chunkSize, &produced);
|
||||||
if (fileRemainingBytes == 0) {
|
|
||||||
// Should not be hit, but a safe protection
|
|
||||||
break; // EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
fileReadBufferFilledBytes =
|
totalProduced += produced;
|
||||||
file.read(fileReadBuffer, fileRemainingBytes < chunkSize ? fileRemainingBytes : chunkSize);
|
if (totalProduced > static_cast<size_t>(inflatedDataSize)) {
|
||||||
fileRemainingBytes -= fileReadBufferFilledBytes;
|
LOG_ERR("ZIP", "Decompressed size exceeds expected (%zu > %zu)", totalProduced,
|
||||||
fileReadBufferCursor = 0;
|
static_cast<size_t>(inflatedDataSize));
|
||||||
|
break;
|
||||||
if (fileReadBufferFilledBytes == 0) {
|
|
||||||
// Bad read
|
|
||||||
break; // EOF
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Available bytes in fileReadBuffer to process
|
if (produced > 0) {
|
||||||
size_t inBytes = fileReadBufferFilledBytes - fileReadBufferCursor;
|
if (out.write(outputBuffer, produced) != produced) {
|
||||||
// Space remaining in outputBuffer
|
|
||||||
size_t outBytes = TINFL_LZ_DICT_SIZE - outputCursor;
|
|
||||||
|
|
||||||
const tinfl_status status = tinfl_decompress(inflator, fileReadBuffer + fileReadBufferCursor, &inBytes,
|
|
||||||
outputBuffer, outputBuffer + outputCursor, &outBytes,
|
|
||||||
fileRemainingBytes > 0 ? TINFL_FLAG_HAS_MORE_INPUT : 0);
|
|
||||||
|
|
||||||
// Update input position
|
|
||||||
fileReadBufferCursor += inBytes;
|
|
||||||
|
|
||||||
// Write output chunk
|
|
||||||
if (outBytes > 0) {
|
|
||||||
processedOutputBytes += outBytes;
|
|
||||||
if (out.write(outputBuffer + outputCursor, outBytes) != outBytes) {
|
|
||||||
LOG_ERR("ZIP", "Failed to write all output bytes to stream");
|
LOG_ERR("ZIP", "Failed to write all output bytes to stream");
|
||||||
if (!wasOpen) {
|
break;
|
||||||
close();
|
|
||||||
}
|
|
||||||
free(outputBuffer);
|
|
||||||
free(fileReadBuffer);
|
|
||||||
free(inflator);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// Update output position in buffer (with wraparound)
|
|
||||||
outputCursor = (outputCursor + outBytes) & (TINFL_LZ_DICT_SIZE - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status < 0) {
|
if (status == InflateStatus::Done) {
|
||||||
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
if (totalProduced != static_cast<size_t>(inflatedDataSize)) {
|
||||||
if (!wasOpen) {
|
LOG_ERR("ZIP", "Decompressed size mismatch (expected %zu, got %zu)", static_cast<size_t>(inflatedDataSize),
|
||||||
close();
|
totalProduced);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
free(outputBuffer);
|
LOG_DBG("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize);
|
||||||
free(fileReadBuffer);
|
success = true;
|
||||||
free(inflator);
|
break;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == TINFL_STATUS_DONE) {
|
if (status == InflateStatus::Error) {
|
||||||
LOG_ERR("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize);
|
LOG_ERR("ZIP", "Decompression failed");
|
||||||
if (!wasOpen) {
|
break;
|
||||||
close();
|
|
||||||
}
|
|
||||||
free(inflator);
|
|
||||||
free(fileReadBuffer);
|
|
||||||
free(outputBuffer);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
// InflateStatus::Ok: output buffer full, continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get here, EOF reached without TINFL_STATUS_DONE
|
|
||||||
LOG_ERR("ZIP", "Unexpected EOF");
|
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
free(outputBuffer);
|
free(outputBuffer);
|
||||||
free(fileReadBuffer);
|
free(fileReadBuffer);
|
||||||
free(inflator);
|
return success; // ctx.reader destructor frees the ring buffer
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
|
|||||||
6900
lib/miniz/miniz.c
6900
lib/miniz/miniz.c
File diff suppressed because it is too large
Load Diff
1462
lib/miniz/miniz.h
1462
lib/miniz/miniz.h
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,6 @@ board_upload.offset_address = 0x10000
|
|||||||
build_flags =
|
build_flags =
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
|
||||||
-DMINIZ_NO_STDIO=1
|
|
||||||
-DEINK_DISPLAY_SINGLE_BUFFER_MODE=1
|
-DEINK_DISPLAY_SINGLE_BUFFER_MODE=1
|
||||||
-DDISABLE_FS_H_WARNING=1
|
-DDISABLE_FS_H_WARNING=1
|
||||||
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
||||||
|
|||||||
Reference in New Issue
Block a user