|
1 | 1 | #include "hl_in_blob.h" |
2 | 2 | #include "hedgelib/hl_compression.h" |
| 3 | + |
| 4 | +#include <mspack.h> |
| 5 | +#include <lzx.h> |
| 6 | + |
| 7 | +// Hack to make the warning about "register" being deprecated shut up. |
| 8 | +#define register |
| 9 | +#include <lzxd.c> |
| 10 | +#undef register |
| 11 | + |
3 | 12 | #include <lz4.h> |
| 13 | + |
4 | 14 | #define ZLIB_CONST |
5 | 15 | #include <zlib.h> |
| 16 | + |
6 | 17 | #include <cstring> |
7 | 18 |
|
8 | 19 | namespace hl |
9 | 20 | { |
| 21 | +struct in_mspack_read_stream |
| 22 | +{ |
| 23 | + const u8* data = nullptr; |
| 24 | + int size = 0; // Size from every compressed block. |
| 25 | +}; |
| 26 | + |
| 27 | +// libmspack interface implementation for Xbox decompression. |
| 28 | +static int in_mspack_read(mspack_file* file, void* buffer, int bytes) |
| 29 | +{ |
| 30 | + in_mspack_read_stream* stream = reinterpret_cast<in_mspack_read_stream*>(file); |
| 31 | + |
| 32 | + if (stream->size == 0) |
| 33 | + { |
| 34 | + u16 size = *reinterpret_cast<const u16*>(stream->data); |
| 35 | + stream->data += sizeof(u16); |
| 36 | + |
| 37 | +#ifndef HL_IS_BIG_ENDIAN |
| 38 | + hl::endian_swap(size); |
| 39 | +#endif |
| 40 | + |
| 41 | + // This indicates there is an uncompressed block size available. We don't need it so we skip it. |
| 42 | + if ((size & 0xFF00) == 0xFF00) |
| 43 | + { |
| 44 | + stream->data += 1; |
| 45 | + size = *reinterpret_cast<const u16*>(stream->data); |
| 46 | + stream->data += sizeof(u16); |
| 47 | + |
| 48 | +#ifndef HL_IS_BIG_ENDIAN |
| 49 | + hl::endian_swap(size); |
| 50 | +#endif |
| 51 | + } |
| 52 | + |
| 53 | + stream->size = size; |
| 54 | + } |
| 55 | + |
| 56 | + int sizeToRead = std::min(stream->size, bytes); |
| 57 | + |
| 58 | + memcpy(buffer, stream->data, sizeToRead); |
| 59 | + stream->data += sizeToRead; |
| 60 | + stream->size -= sizeToRead; |
| 61 | + |
| 62 | + return sizeToRead; |
| 63 | +} |
| 64 | + |
| 65 | +struct in_mspack_write_stream |
| 66 | +{ |
| 67 | + u8* data = nullptr; |
| 68 | + std::size_t size = 0; // Remaining available space in the stream. |
| 69 | +}; |
| 70 | + |
| 71 | +static int in_mspack_write(mspack_file* file, void* buffer, int bytes) |
| 72 | +{ |
| 73 | + in_mspack_write_stream* stream = reinterpret_cast<in_mspack_write_stream*>(file); |
| 74 | + |
| 75 | + std::size_t sizeToWrite = std::min(stream->size, static_cast<std::size_t>(bytes)); |
| 76 | + |
| 77 | + memcpy(stream->data, buffer, sizeToWrite); |
| 78 | + stream->data += sizeToWrite; |
| 79 | + stream->size -= sizeToWrite; |
| 80 | + |
| 81 | + return static_cast<int>(sizeToWrite); |
| 82 | +} |
| 83 | + |
| 84 | +static void* in_mspack_alloc(mspack_system* self, size_t bytes) |
| 85 | +{ |
| 86 | + return operator new(bytes); |
| 87 | +} |
| 88 | + |
| 89 | +static void in_mspack_free(void* ptr) |
| 90 | +{ |
| 91 | + operator delete(ptr); |
| 92 | +} |
| 93 | + |
| 94 | +static void in_mspack_copy(void* src, void* dst, size_t bytes) |
| 95 | +{ |
| 96 | + memcpy(dst, src, bytes); |
| 97 | +} |
| 98 | + |
| 99 | +static mspack_system in_lzx_system = |
| 100 | +{ |
| 101 | + nullptr, |
| 102 | + nullptr, |
| 103 | + in_mspack_read, |
| 104 | + in_mspack_write, |
| 105 | + nullptr, |
| 106 | + nullptr, |
| 107 | + nullptr, |
| 108 | + in_mspack_alloc, |
| 109 | + in_mspack_free, |
| 110 | + in_mspack_copy |
| 111 | +}; |
| 112 | + |
| 113 | +// Xbox Compression header definitions. |
| 114 | +static constexpr u32 x_compress_signature = 0xFF512EE; |
| 115 | + |
| 116 | +struct x_compress_header |
| 117 | +{ |
| 118 | + u32 signature; |
| 119 | + u32 field04; |
| 120 | + u32 field08; |
| 121 | + u32 field0C; |
| 122 | + u32 windowSize; |
| 123 | + u32 compressedBlockSize; |
| 124 | + u64 uncompressedSize; |
| 125 | + u64 compressedSize; |
| 126 | + u32 uncompressedBlockSize; |
| 127 | + u32 field2C; |
| 128 | + |
| 129 | + template<bool swapOffsets = true> |
| 130 | + void endian_swap() noexcept |
| 131 | + { |
| 132 | + hl::endian_swap(signature); |
| 133 | + hl::endian_swap(field04); |
| 134 | + hl::endian_swap(field08); |
| 135 | + hl::endian_swap(field0C); |
| 136 | + hl::endian_swap(windowSize); |
| 137 | + hl::endian_swap(compressedBlockSize); |
| 138 | + hl::endian_swap(uncompressedSize); |
| 139 | + hl::endian_swap(compressedSize); |
| 140 | + hl::endian_swap(uncompressedBlockSize); |
| 141 | + hl::endian_swap(field2C); |
| 142 | + } |
| 143 | +}; |
| 144 | + |
| 145 | +static x_compress_header in_parse_x_compress_header(const void* src) |
| 146 | +{ |
| 147 | + x_compress_header header = *reinterpret_cast<const x_compress_header*>(src); |
| 148 | +#ifndef HL_IS_BIG_ENDIAN |
| 149 | + header.endian_swap(); |
| 150 | +#endif |
| 151 | + return header; |
| 152 | +} |
| 153 | + |
| 154 | +bool x_check_signature(std::size_t srcSize, const void* src) |
| 155 | +{ |
| 156 | + // Should at least be the header size. |
| 157 | + if (srcSize >= sizeof(x_compress_header)) |
| 158 | + { |
| 159 | + x_compress_header header = in_parse_x_compress_header(src); |
| 160 | + return header.signature == x_compress_signature; |
| 161 | + } |
| 162 | + |
| 163 | + return false; |
| 164 | +} |
| 165 | + |
| 166 | +std::size_t x_get_uncompressed_size(std::size_t srcSize, const void* src) |
| 167 | +{ |
| 168 | + x_compress_header header = in_parse_x_compress_header(src); |
| 169 | + return header.uncompressedSize; |
| 170 | +} |
| 171 | + |
| 172 | +void x_decompress_no_alloc(std::size_t srcSize, |
| 173 | + const void* src, std::size_t dstSize, void* dst) |
| 174 | +{ |
| 175 | + x_compress_header header = *reinterpret_cast<const x_compress_header*>(src); |
| 176 | +#ifndef HL_IS_BIG_ENDIAN |
| 177 | + header.endian_swap(); |
| 178 | +#endif |
| 179 | + |
| 180 | + if (header.uncompressedSize > dstSize) |
| 181 | + { |
| 182 | + throw std::out_of_range("Destination buffer is not large enough " |
| 183 | + "to contain uncompressed data"); |
| 184 | + } |
| 185 | + |
| 186 | + const u8* srcBytes = hl::ptradd<u8>(src, sizeof(x_compress_header)); |
| 187 | + |
| 188 | + in_mspack_write_stream dstStream; |
| 189 | + dstStream.data = reinterpret_cast<uint8_t*>(dst); |
| 190 | + dstStream.size = header.uncompressedSize; |
| 191 | + |
| 192 | + // libmspack wants the bit index. This value is always guaranteed to be a power of two, |
| 193 | + // so we can extract the bit index by counting the amount of leading zeroes. |
| 194 | + int windowBits = 0; |
| 195 | + u32 windowSize = header.windowSize; |
| 196 | + while ((windowSize & 0x1) == 0) |
| 197 | + { |
| 198 | + ++windowBits; |
| 199 | + windowSize >>= 1; |
| 200 | + } |
| 201 | + |
| 202 | + // Loop over compressed blocks. |
| 203 | + while (srcBytes < hl::ptradd<u8>(src, srcSize) && dstStream.data < hl::ptradd<u8>(dst, header.uncompressedSize)) |
| 204 | + { |
| 205 | + u32 compressedSize = *reinterpret_cast<const u32*>(srcBytes); |
| 206 | +#ifndef HL_IS_BIG_ENDIAN |
| 207 | + hl::endian_swap(compressedSize); |
| 208 | +#endif |
| 209 | + srcBytes += sizeof(u32); |
| 210 | + |
| 211 | + in_mspack_read_stream srcStream; |
| 212 | + srcStream.data = srcBytes; |
| 213 | + |
| 214 | + std::size_t uncompressedBlockSize = std::min(static_cast<std::size_t>(header.uncompressedBlockSize), dstStream.size); |
| 215 | + |
| 216 | + lzxd_stream* lzx = lzxd_init( |
| 217 | + &in_lzx_system, |
| 218 | + reinterpret_cast<mspack_file*>(&srcStream), |
| 219 | + reinterpret_cast<mspack_file*>(&dstStream), |
| 220 | + windowBits, |
| 221 | + 0, |
| 222 | + static_cast<int>(header.compressedBlockSize), |
| 223 | + static_cast<off_t>(uncompressedBlockSize), |
| 224 | + 0); |
| 225 | + |
| 226 | + lzxd_decompress(lzx, uncompressedBlockSize); |
| 227 | + lzxd_free(lzx); |
| 228 | + |
| 229 | + srcBytes += compressedSize; |
| 230 | + } |
| 231 | +} |
| 232 | + |
10 | 233 | void lz4_decompress_no_alloc(std::size_t srcSize, |
11 | 234 | const void* src, std::size_t dstSize, void* dst) |
12 | 235 | { |
@@ -70,6 +293,10 @@ void decompress_no_alloc(compress_type type, std::size_t srcSize, |
70 | 293 | in_none_decompress_no_alloc(srcSize, src, dstSize, dst); |
71 | 294 | break; |
72 | 295 |
|
| 296 | + case compress_type::x: |
| 297 | + x_decompress_no_alloc(srcSize, src, dstSize, dst); |
| 298 | + break; |
| 299 | + |
73 | 300 | case compress_type::lz4: |
74 | 301 | lz4_decompress_no_alloc(srcSize, src, dstSize, dst); |
75 | 302 | break; |
|
0 commit comments