Files
ppsspp/Common/Data/Encoding/Compression.cpp
T
libretroadmin d56b453891 Replace zlib, zstd, libchdr, libzip and libpng with libretro-common
Deflate, zlib and gzip streams go through rinflate and rdeflate, zstd
through rzstd, CHD images through rchd, zip archives through
rzip_archive and PNG through rpng. The bundled libraries, their build
glue and the system-library options are gone; adler32 and uncompress
keep zlib's ABI for the prebuilt FFmpeg archives that still call them.
libretro-common is one CMake library that the core, the headless tool
and the unit tests all link.

The reads take shorter paths on the way. CHD decodes straight from the
loader's reads and a range of blocks at a time, and a file read at any
alignment decodes into place; CSO keeps one inflate stream instead of
one per block; a plain ISO serves a file read with one read of its exact
bytes; a stored zip member is read in place and a deflated one through
restart points rather than a copy of the whole image in memory; zip
members decode into the caller's buffer; sequential reads through the
frontend's VFS skip the seek, which also keeps its buffering.

libretro/test reads one image as ISO, CSO, CHD and zip through the block
devices, boots each, and checks the codecs against Python-made vectors;
a CI job runs it.
2026-10-08 14:14:38 +02:00

114 lines
3.7 KiB
C++

#include <cstring>
#include <encodings/deflate.h>
#include "Common/Data/Encoding/Compression.h"
#include "Common/Log.h"
int64_t InflateBuffer(void *stream, int windowBits, const uint8_t *in, size_t inLen, uint8_t *out, size_t outLen, size_t *consumed) {
rinflate_reset(stream, windowBits);
rinflate_set_in(stream, in, inLen);
rinflate_set_out(stream, out, outLen);
size_t rd = 0, wr = 0;
// One call runs until the stream ends or a buffer runs out.
const int status = rinflate_process(stream, &rd, &wr);
if (consumed)
*consumed = rd;
return status == RDEFLATE_PROCESS_END ? (int64_t)wr : -1;
}
int64_t InflateBuffer(int windowBits, const uint8_t *in, size_t inLen, uint8_t *out, size_t outLen, size_t *consumed) {
void *stream = rinflate_new(windowBits);
if (!stream)
return -1;
const int64_t result = InflateBuffer(stream, windowBits, in, inLen, out, outLen, consumed);
rinflate_free(stream);
return result;
}
uint32_t Adler32(uint32_t adler, const uint8_t *buf, size_t len) {
// 5552 is the most bytes that can be summed before the 32-bit sums overflow.
uint32_t a = adler & 0xFFFF, b = adler >> 16;
while (len > 0) {
size_t n = len < 5552 ? len : 5552;
len -= n;
while (n--) {
a += *buf++;
b += a;
}
a %= 65521;
b %= 65521;
}
return (b << 16) | a;
}
// Runs a stream to its end, growing *dest as output arrives.
template <class Process, class SetOut>
static bool RunToEnd(void *stream, std::string *dest, size_t initial, Process process, SetOut setOut) {
std::string out;
out.resize(initial < 4096 ? 4096 : initial);
size_t used = 0;
for (;;) {
setOut(stream, (uint8_t *)&out[used], out.size() - used);
size_t rd = 0, wr = 0;
const int status = process(stream, &rd, &wr);
used += wr;
if (status == RDEFLATE_PROCESS_END)
break;
if (status != RDEFLATE_PROCESS_NEXT)
return false;
if (used == out.size())
out.resize(out.size() * 2);
else if (rd == 0 && wr == 0)
return false; // input ran out before the end of the stream
}
out.resize(used);
*dest = std::move(out);
return true;
}
bool compress_string(const std::string &str, std::string *dest, int compressionlevel, int windowBits) {
void *stream = rdeflate_new(compressionlevel, windowBits);
if (!stream)
return false;
rdeflate_set_in(stream, (const uint8_t *)str.data(), str.size());
rdeflate_finish(stream);
const bool ok = RunToEnd(stream, dest, str.size() / 2 + 64, rdeflate_process, rdeflate_set_out);
rdeflate_free(stream);
if (!ok)
ERROR_LOG(Log::IO, "deflate failed while compressing.");
return ok;
}
bool decompress_string(const std::string &str, std::string *dest) {
if (str.empty())
return false;
void *stream = rinflate_new(47);
if (!stream)
return false;
rinflate_set_in(stream, (const uint8_t *)str.data(), str.size());
// rinflate_set_out treats each new window as following the last, so growing works.
const bool ok = RunToEnd(stream, dest, str.size() * 4, rinflate_process, rinflate_set_out);
rinflate_free(stream);
if (!ok)
ERROR_LOG(Log::IO, "inflate failed while decompressing.");
return ok;
}
// The prebuilt FFmpeg archives were linked against zlib and still call these two
// (adler32 in the MOV/MXF demuxers, uncompress for compressed ID3v2 frames).
// zlib's own ABI, so those archives resolve against libretro-common's inflate.
extern "C" unsigned long adler32(unsigned long adler, const unsigned char *buf, unsigned int len) {
if (!buf)
return 1;
return Adler32((uint32_t)adler, buf, len);
}
extern "C" int uncompress(unsigned char *dest, unsigned long *destLen, const unsigned char *source, unsigned long sourceLen) {
const int64_t produced = InflateBuffer(15, source, sourceLen, dest, *destLen);
if (produced < 0)
return -3; // Z_DATA_ERROR
*destLen = (unsigned long)produced;
return 0; // Z_OK
}