61 lines
2.1 KiB
C++
61 lines
2.1 KiB
C++
#pragma once
|
|
// FastLZ (level 1 and 2) block decompressor, as used by Noita for chunk files and stream info.
|
|
// The level is in the top 3 bits of the first byte (0 = level 1, 1 = level 2).
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
namespace ng {
|
|
|
|
// Returns false on malformed input or if the output would exceed max_out.
|
|
inline bool fastlz_decompress(const uint8_t* in, size_t in_len, std::vector<uint8_t>& out, size_t max_out) {
|
|
out.clear();
|
|
if (in_len == 0) return true;
|
|
const int level = (in[0] >> 5) + 1;
|
|
if (level != 1 && level != 2) return false;
|
|
size_t ip = 0;
|
|
uint32_t ctrl = in[ip++] & 31;
|
|
for (;;) {
|
|
if (ctrl >= 32) {
|
|
uint32_t len = (ctrl >> 5) - 1;
|
|
uint32_t ofs = (ctrl & 31) << 8;
|
|
if (len == 6) {
|
|
uint8_t code;
|
|
do {
|
|
if (ip >= in_len) return false;
|
|
code = in[ip++];
|
|
len += code;
|
|
} while (code == 255 && level == 2);
|
|
if (level == 1 && false) return false;
|
|
}
|
|
if (ip >= in_len) return false;
|
|
const uint8_t code = in[ip++];
|
|
ofs += code;
|
|
size_t back;
|
|
if (level == 2 && code == 255 && (ctrl & 31) == 31) {
|
|
if (ip + 1 >= in_len) return false;
|
|
ofs = ((uint32_t)in[ip] << 8) + in[ip + 1];
|
|
ip += 2;
|
|
back = (size_t)ofs + 8191 + 1; // 16-bit distance: MAX_L2_DISTANCE + 1 (the +1 is easy to miss)
|
|
} else {
|
|
back = (size_t)ofs + 1;
|
|
}
|
|
if (back > out.size()) return false;
|
|
len += 3;
|
|
if (out.size() + len > max_out) return false;
|
|
size_t ref = out.size() - back;
|
|
for (uint32_t k = 0; k < len; ++k) out.push_back(out[ref + k]);
|
|
} else {
|
|
const size_t n = (size_t)ctrl + 1;
|
|
if (ip + n > in_len || out.size() + n > max_out) return false;
|
|
out.insert(out.end(), in + ip, in + ip + n);
|
|
ip += n;
|
|
}
|
|
if (ip >= in_len) break;
|
|
ctrl = in[ip++];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace ng
|