Skip to content

Commit 34b338d

Browse files
authored
Merge pull request #94 from blueskythlikesclouds/xbox-decompress
Implement Xbox decompression support.
2 parents 5145eaf + e66b033 commit 34b338d

4 files changed

Lines changed: 264 additions & 1 deletion

File tree

HedgeLib/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,17 @@ endif()
220220

221221
list(APPEND HEDGELIB_PRIVATE_INCLUDE_DIRS RAPIDJSON_INCLUDE_DIR)
222222

223+
# Download libmspack and add it to HedgeLib private includes
224+
message(STATUS "Downloading and/or configuring libmspack as necessary...")
225+
FetchContent_Declare(libmspack
226+
GIT_REPOSITORY https://github.com/kyz/libmspack.git
227+
GIT_TAG v1.11
228+
GIT_SHALLOW TRUE
229+
)
230+
231+
FetchContent_Populate(libmspack)
232+
list(APPEND HEDGELIB_PRIVATE_INCLUDE_DIRS ${libmspack_SOURCE_DIR}/libmspack/mspack)
233+
223234
# Setup dependencies
224235
target_include_directories(HedgeLib
225236
PUBLIC

HedgeLib/include/hedgelib/hl_compression.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ enum class compress_type
2020
deflate
2121
};
2222

23+
HL_API bool x_check_signature(std::size_t srcSize, const void* src);
24+
25+
HL_API std::size_t x_get_uncompressed_size(std::size_t srcSize, const void* src);
26+
27+
HL_API void x_decompress_no_alloc(std::size_t srcSize,
28+
const void* src, std::size_t dstSize, void* dst);
29+
2330
HL_API void lz4_decompress_no_alloc(std::size_t srcSize,
2431
const void* src, std::size_t dstSize, void* dst);
2532

HedgeLib/src/archives/hl_hh_archive.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ nstring get_root_path(const nstring& filePath)
111111
return rootPath;
112112
}
113113

114-
void read(blob& hhArc, archive_entry_list* hlArc,
114+
static void in_read(blob& hhArc, archive_entry_list* hlArc,
115115
std::vector<blob>* hhArcs)
116116
{
117117
// Add a copy of this blob to the blob list if necessary.
@@ -128,6 +128,24 @@ void read(blob& hhArc, archive_entry_list* hlArc,
128128
}
129129
}
130130

131+
void read(blob& hhArc, archive_entry_list* hlArc,
132+
std::vector<blob>* hhArcs)
133+
{
134+
// Check for Xbox Compression.
135+
if (x_check_signature(hhArc.size(), hhArc.data()))
136+
{
137+
u64 uncompressedSize = x_get_uncompressed_size(hhArc.size(), hhArc.data());
138+
blob uncompressedArc(static_cast<std::size_t>(uncompressedSize));
139+
x_decompress_no_alloc(hhArc.size(), hhArc.data(), uncompressedArc.size(), uncompressedArc.data());
140+
141+
in_read(uncompressedArc, hlArc, hhArcs);
142+
}
143+
else
144+
{
145+
in_read(hhArc, hlArc, hhArcs);
146+
}
147+
}
148+
131149
void load_single(const nchar* filePath,
132150
archive_entry_list* hlArc, std::vector<blob>* hhArcs)
133151
{

HedgeLib/src/hl_compression.cpp

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,235 @@
11
#include "hl_in_blob.h"
22
#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+
312
#include <lz4.h>
13+
414
#define ZLIB_CONST
515
#include <zlib.h>
16+
617
#include <cstring>
718

819
namespace hl
920
{
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+
10233
void lz4_decompress_no_alloc(std::size_t srcSize,
11234
const void* src, std::size_t dstSize, void* dst)
12235
{
@@ -70,6 +293,10 @@ void decompress_no_alloc(compress_type type, std::size_t srcSize,
70293
in_none_decompress_no_alloc(srcSize, src, dstSize, dst);
71294
break;
72295

296+
case compress_type::x:
297+
x_decompress_no_alloc(srcSize, src, dstSize, dst);
298+
break;
299+
73300
case compress_type::lz4:
74301
lz4_decompress_no_alloc(srcSize, src, dstSize, dst);
75302
break;

0 commit comments

Comments
 (0)