#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 #include #include 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& 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