Files

440 lines
11 KiB
C++

/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// lz77.cpp: LZ77 Compression Algorithm
#include "lz77.h"
#include "../qcommon/q_shared.h"
#include <cstdio>
#include <cstring>
cLZ77 g_lz77;
unsigned int cLZ77::m_pDictionary[0xffff];
static void copy_bytes(unsigned char *dest, unsigned char *from, size_t length)
{
size_t i;
for (i = 0; i < length; i++) {
dest[i] = from[i];
}
}
cLZ77::cLZ77() {}
unsigned int cLZ77::CompressData(unsigned char *in, size_t in_len, unsigned char *out, size_t *out_len)
{
this->in_end = &in[in_len];
this->ip_end = &in[in_len - 13];
this->op = out;
this->ip = in;
this->ii = this->ip;
this->ip += 4;
while (this->ip < this->ip_end) {
bool match_found = false;
while (this->ip < this->ip_end) {
// Primary hash index
this->dindex =
((33 * (this->ip[0] ^ (32 * ((32 * ((this->ip[3] << 6) ^ this->ip[2])) ^ (unsigned int)this->ip[1]))))
>> 5)
& 0x3FFF;
this->m_off = cLZ77::m_pDictionary[this->dindex];
if (this->ip - in <= this->m_off) {
cLZ77::m_pDictionary[this->dindex] = this->ip - in;
this->ip++;
continue;
}
this->m_off = this->ip - in - this->m_off;
if (this->m_off > 0xBFFF) {
cLZ77::m_pDictionary[this->dindex] = this->ip - in;
this->ip++;
continue;
}
this->m_pos = this->ip - this->m_off;
if (this->m_off <= 0x800 || (this->m_pos[3] == this->ip[3])) {
if (*this->m_pos == *this->ip && this->m_pos[1] == this->ip[1] && this->m_pos[2] == this->ip[2]) {
match_found = true;
break;
}
}
// Fallback hash index
this->dindex = (this->dindex & 0x7FF) ^ 0x201F;
this->m_off = cLZ77::m_pDictionary[this->dindex];
if (this->ip - in <= this->m_off) {
cLZ77::m_pDictionary[this->dindex] = this->ip - in;
this->ip++;
continue;
}
this->m_off = this->ip - in - this->m_off;
if (this->m_off > 0xBFFF) {
cLZ77::m_pDictionary[this->dindex] = this->ip - in;
this->ip++;
continue;
}
this->m_pos = this->ip - this->m_off;
if (this->m_off <= 0x800 || (this->m_pos[3] == this->ip[3])) {
if (this->m_pos[0] == this->ip[0] && this->m_pos[1] == this->ip[1] && this->m_pos[2] == this->ip[2]) {
match_found = true;
break;
}
}
cLZ77::m_pDictionary[this->dindex] = this->ip - in;
this->ip++;
}
if (!match_found) {
break;
}
cLZ77::m_pDictionary[this->dindex] = this->ip - in;
unsigned int t = this->ip - this->ii;
if (t > 0) {
if (t <= 3) {
*(this->op - 2) |= t;
} else if (t <= 18) {
*this->op++ = t - 3;
} else {
unsigned int tt = t - 18;
*this->op++ = 0;
while (tt > 255) {
tt -= 255;
*this->op++ = 0;
}
*this->op++ = tt;
}
copy_bytes(op, ii, t);
ii += t;
op += t;
}
this->ip += 3;
for (t = 0; ip < in_end; t++, this->ip++) {
if (this->m_pos[t + 3] != *this->ip) {
break;
}
}
this->m_len = this->ip - this->ii;
if (this->m_off > 0x4000) {
this->m_off -= 0x4000;
if (this->m_len > 9) {
this->m_len -= 9;
*this->op++ = ((this->m_off & 0x4000) >> 11) | 0x10;
while (this->m_len > 0xFF) {
this->m_len -= 255;
*this->op++ = 0;
}
*this->op++ = this->m_len;
} else {
*this->op++ = ((this->m_off & 0x4000) >> 11) | ((this->m_len & 0xFF) - 2) | 0x10;
}
} else {
--this->m_off;
if (this->m_len > 33) {
this->m_len -= 33;
*this->op++ = 32;
while (this->m_len > 255) {
this->m_len -= 255;
*this->op++ = 0;
}
*this->op++ = this->m_len;
} else {
*this->op++ = ((this->m_len & 0xFF) - 2) | 0x20;
}
}
*this->op++ = 4 * (this->m_off & 63);
*this->op++ = this->m_off >> 6;
this->ii = this->ip;
}
*out_len = this->op - out;
return this->in_end - this->ii;
}
int cLZ77::Compress(unsigned char *in, size_t in_len, unsigned char *out, size_t *out_len)
{
byte *op = out;
size_t t = 0;
if (in_len == 0) {
*out_len = 0;
return 0;
}
if (in_len > 13) {
t = CompressData(in, in_len, out, out_len);
op = out + *out_len;
} else {
t = in_len;
}
if (t) {
if (op == out && t <= 238) {
*op++ = t + 17;
} else if (t <= 3) {
*(op - 2) |= t;
} else if (t <= 18) {
*op++ = t - 3;
} else {
unsigned int tt;
*op++ = 0;
tt = t - 18;
while (tt > 255) {
tt -= 255;
*op++ = 0;
}
*op++ = tt;
}
copy_bytes(op, &in[in_len - t], t);
op += t;
}
*op++ = 17;
*op++ = 0;
*op++ = 0;
*out_len = op - out;
return 0;
}
static unsigned int decode_length(unsigned int base, unsigned char *& ip)
{
unsigned int len = base;
while (!*ip) {
len += 255;
++ip;
}
return len + *ip++;
}
int cLZ77::Decompress(unsigned char *in, size_t in_len, unsigned char *out, size_t *out_len)
{
unsigned int t;
unsigned short s;
ip_end = &in[in_len];
ip = in;
op = out;
*out_len = 0;
if (*ip > 17u) {
t = *ip++ - 17;
if (t <= 3) {
copy_bytes(op, ip, t);
op += t;
ip += t;
t = *ip++;
} else {
copy_bytes(op, ip, t);
op += t;
ip += t;
t = *ip++;
}
} else {
t = *ip++;
}
for (;;) {
if (t <= 15) {
if (t == 0) {
t = decode_length(15, ip);
}
memcpy(op, ip, 4);
op += 4;
ip += 4;
t--;
if (t) {
if (t <= 3) {
copy_bytes(op, ip, t);
op += t;
ip += t;
} else {
while (t > 3) {
memcpy(op, ip, 4);
op += 4;
ip += 4;
t -= 4;
}
copy_bytes(op, ip, t);
op += t;
ip += t;
}
}
t = *ip++;
if (t <= 15) {
m_pos = op - 2049 - (t >> 2) - 4 * *ip++;
*op++ = *m_pos++;
*op++ = *m_pos++;
*op++ = *m_pos++;
continue;
}
}
while (true) {
if (t > 63) {
m_pos = op - 1 - ((t >> 2) & 7) - 8 * *ip++;
t = (t >> 5) - 1;
*op++ = *m_pos++;
*op++ = *m_pos++;
copy_bytes(op, m_pos, t);
op += t;
break;
}
if (t > 31) {
t &= 31;
if (t == 0) {
t = decode_length(31, ip);
}
m_pos = op - 1;
CopyLittleShort(&s, ip);
ip += 2;
m_pos -= (s >> 2);
} else {
if (t <= 15) {
m_pos = op - 1 - (t >> 2) - 4 * *ip++;
*op++ = *m_pos++;
*op++ = *m_pos++;
break;
}
m_pos = op - 2048 * (t & 8);
t &= 7u;
if (t == 0) {
t = decode_length(7, ip);
}
CopyLittleShort(&s, ip);
ip += 2;
m_pos -= (s >> 2);
if (m_pos == op) {
*out_len = op - out;
return 0;
}
m_pos -= 0x4000;
}
if (t <= 5 || static_cast<size_t>(op - m_pos) <= 3) {
*op++ = *m_pos++;
*op++ = *m_pos++;
copy_bytes(op, m_pos, t);
op += t;
} else {
memcpy(op, m_pos, 4);
op += 4;
m_pos += 4;
t -= 2;
while (t > 3) {
memcpy(op, m_pos, 4);
op += 4;
m_pos += 4;
t -= 4;
}
copy_bytes(op, m_pos, t);
op += t;
}
break;
}
t = *(ip - 2) & 3;
if (t == 0) {
t = *ip++;
continue;
}
copy_bytes(op, ip, t);
op += t;
ip += t;
t = *ip++;
}
*out_len = op - out;
if (ip == ip_end) {
return 0;
}
return (ip < ip_end) ? -1 : -2;
}
static unsigned char in[0x40000];
static unsigned char out[0x41013];
static void test_compression()
{
size_t in_len;
size_t out_len;
size_t new_len;
cLZ77 lz77;
memset(&in, 0, 0x40000);
if (lz77.Compress(in, sizeof(in), out, &out_len)) {
puts("Compression Failed!");
return;
}
printf("Compressed %i bytes into %zi bytes\n", 0x40000, out_len);
if (lz77.Decompress(out, out_len, in, &in_len)) {
new_len = in_len;
} else {
new_len = in_len;
if (in_len == 0x40000) {
printf("Decompressed %zi bytes into %i bytes\n", out_len, 0x40000);
puts("Compression Test: Passed");
return;
}
}
printf("Decompression got FuBar'd... %i != %zi\n", 0x40000, new_len);
}