mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-17 23:36:41 +01:00
Add LZ4-compressed .xnb [Square Heroes (PS4), Little Savior (PC)]
This commit is contained in:
parent
7100e9099c
commit
0da7ea6d76
@ -328,6 +328,14 @@
|
||||
RelativePath=".\meta\xavs_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\xnb_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\xnb_lz4mg.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\riff_ogg_streamfile.h"
|
||||
>
|
||||
|
@ -119,6 +119,8 @@
|
||||
<ClInclude Include="meta\ppst_streamfile.h" />
|
||||
<ClInclude Include="meta\vsv_streamfile.h" />
|
||||
<ClInclude Include="meta\xavs_streamfile.h" />
|
||||
<ClInclude Include="meta\xnb_streamfile.h" />
|
||||
<ClInclude Include="meta\xnb_lz4mg.h" />
|
||||
<ClInclude Include="meta\mta2_streamfile.h" />
|
||||
<ClInclude Include="meta\mzrt_streamfile.h" />
|
||||
<ClInclude Include="meta\nus3bank_streamfile.h" />
|
||||
|
@ -143,6 +143,12 @@
|
||||
<ClInclude Include="meta\xavs_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\xnb_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\xnb_lz4mg.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\riff_ogg_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
151
src/meta/xnb.c
151
src/meta/xnb.c
@ -1,106 +1,160 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "xnb_streamfile.h"
|
||||
|
||||
|
||||
/* XNB - Microsoft XNA Game Studio 4.0 format */
|
||||
VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, xma_chunk_offset = 0;
|
||||
VGMSTREAM* init_vgmstream_xnb(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_h = NULL;
|
||||
off_t start_offset, offset, xma_chunk_offset = 0;
|
||||
int loop_flag = 0, channel_count, num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
int big_endian, flags, codec, sample_rate, block_align, bps;
|
||||
size_t data_size;
|
||||
char platform;
|
||||
int is_ogg = 0, is_at9 = 0;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile,"xnb"))
|
||||
if (!check_extensions(sf,"xnb"))
|
||||
goto fail;
|
||||
if ((read_32bitBE(0,streamFile) & 0xFFFFFF00) != 0x584E4200) /* "XNB" */
|
||||
if ((read_32bitBE(0x00, sf) & 0xFFFFFF00) != 0x584E4200) /* "XNB" */
|
||||
goto fail;
|
||||
|
||||
/* XNA Studio platforms: 'w' = Windows, 'm' = Windows Phone 7, 'x' = X360
|
||||
* MonoGame extensions: 'i' = iOS, 'a' = Android, 'X' = MacOSX, 'P' = PS4, 'S' = Switch, etc */
|
||||
platform = read_8bit(0x03,streamFile);
|
||||
platform = read_u8(0x03, sf);
|
||||
big_endian = (platform == 'x');
|
||||
|
||||
if (read_8bit(0x04,streamFile) != 0x04 && /* XNA 3.0? found on Scare Me (XBLIG), no notable diffs */
|
||||
read_8bit(0x04,streamFile) != 0x05) /* XNA 4.0 version */
|
||||
if (read_u8(0x04,sf) != 0x04 && /* XNA 3.0? found on Scare Me (XBLIG), no notable diffs */
|
||||
read_u8(0x04,sf) != 0x05) /* XNA 4.0 version */
|
||||
goto fail;
|
||||
|
||||
flags = read_8bit(0x05,streamFile);
|
||||
flags = read_u8(0x05, sf);
|
||||
//if (flags & 0x01) goto fail; /* "HiDef profile" content (no actual difference) */
|
||||
if (flags & 0x80) goto fail; /* compressed with LZX/XMemCompress (at 0x0a is decompressed size) */
|
||||
if (flags & 0x40) goto fail; /* compressed with LZ4, MonoGame extension [ex. Square Heroes (PS4)] */
|
||||
|
||||
|
||||
/* full size */
|
||||
if (read_32bitLE(0x06,streamFile) != get_streamfile_size(streamFile))
|
||||
if (read_32bitLE(0x06, sf) != get_streamfile_size(sf)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* handle XNB compression as a normal non-compressed stream, normally for graphics
|
||||
* and other formats, but confused devs use it with already-compressed audio like Ogg/ATRAC9 */
|
||||
if ((flags & 0x80) || /* LZX/XMemCompress (not used with audio?) */
|
||||
(flags & 0x40)) { /* LZ4 (MonoGame extension) [Square Heroes (PS4), Little Savior (PC)] */
|
||||
size_t compression_start = 0x0e;
|
||||
size_t compression_size = read_u32le(0x0a, sf);
|
||||
sf_h = setup_xnb_streamfile(sf, flags, compression_start, compression_size);
|
||||
if (!sf_h) goto fail;
|
||||
|
||||
//dump_streamfile(sf_h, 0);
|
||||
offset = 0x0e; /* refers to decompressed stream */
|
||||
}
|
||||
else {
|
||||
sf_h = sf;
|
||||
offset = 0x0a;
|
||||
}
|
||||
|
||||
|
||||
/* XNB contains "type reader" class references to parse "shared resource" data (can be any implemented filetype) */
|
||||
{
|
||||
char reader_name[255+1];
|
||||
off_t current_offset = 0x0a;
|
||||
size_t reader_string_len;
|
||||
uint32_t fmt_chunk_size;
|
||||
const char * type_sound = "Microsoft.Xna.Framework.Content.SoundEffectReader"; /* partial "fmt" chunk or XMA */
|
||||
//const char * type_song = "Microsoft.Xna.Framework.Content.SongReader"; /* just references a companion .wma */
|
||||
const char* type_sound = "Microsoft.Xna.Framework.Content.SoundEffectReader"; /* partial "fmt" chunk or XMA */
|
||||
const char* type_ogg = "SoundEffectFromOggReader"; /* has extra text info after base part */
|
||||
//const char* type_song = "Microsoft.Xna.Framework.Content.SongReader"; /* references a companion .wma */
|
||||
|
||||
/* type reader count, accept only one for now */
|
||||
if (read_8bit(current_offset++, streamFile) != 1)
|
||||
if (read_u8(offset++, sf_h) != 1)
|
||||
goto fail;
|
||||
|
||||
reader_string_len = read_8bit(current_offset++, streamFile); /* doesn't count null */
|
||||
reader_string_len = read_u8(offset++, sf_h); /* doesn't count null */
|
||||
if (reader_string_len > 255) goto fail;
|
||||
|
||||
/* check SoundEffect type string */
|
||||
if (read_string(reader_name,reader_string_len+1,current_offset,streamFile) != reader_string_len)
|
||||
if (read_string(reader_name, reader_string_len+1, offset, sf_h) != reader_string_len)
|
||||
goto fail;
|
||||
if ( strcmp(reader_name, type_sound) != 0 )
|
||||
goto fail;
|
||||
current_offset += reader_string_len + 1;
|
||||
current_offset += 0x04; /* reader version */
|
||||
if (strcmp(reader_name, type_sound) != 0) {
|
||||
is_ogg = strncmp(reader_name, type_ogg, strlen(type_ogg)) == 0;
|
||||
if (!is_ogg)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
offset += reader_string_len + 1;
|
||||
offset += 0x04; /* reader version, 0 */
|
||||
|
||||
/* shared resource count */
|
||||
if (read_8bit(current_offset++, streamFile) != 1)
|
||||
if (read_u8(offset++, sf_h) != 1)
|
||||
goto fail;
|
||||
|
||||
/* shared resource: partial "fmt" chunk */
|
||||
fmt_chunk_size = read_32bitLE(current_offset, streamFile);
|
||||
current_offset += 0x04;
|
||||
fmt_chunk_size = read_32bitLE(offset, sf_h);
|
||||
offset += 0x04;
|
||||
|
||||
{
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = big_endian ? read_32bitBE : read_32bitLE;
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = big_endian ? read_16bitBE : read_16bitLE;
|
||||
|
||||
codec = (uint16_t)read_16bit(current_offset+0x00, streamFile);
|
||||
channel_count = read_16bit(current_offset+0x02, streamFile);
|
||||
sample_rate = read_32bit(current_offset+0x04, streamFile);
|
||||
codec = (uint16_t)read_16bit(offset+0x00, sf_h);
|
||||
channel_count = read_16bit(offset+0x02, sf_h);
|
||||
sample_rate = read_32bit(offset+0x04, sf_h);
|
||||
/* 0x08: byte rate */
|
||||
block_align = read_16bit(current_offset+0x0c, streamFile);
|
||||
bps = read_16bit(current_offset+0x0e, streamFile);
|
||||
block_align = read_16bit(offset+0x0c, sf_h);
|
||||
bps = read_16bit(offset+0x0e, sf_h);
|
||||
|
||||
if (codec == 0x0002) {
|
||||
if (!msadpcm_check_coefs(streamFile, current_offset + 0x14))
|
||||
if (!msadpcm_check_coefs(sf_h, offset + 0x14))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (codec == 0x0166) {
|
||||
xma2_parse_fmt_chunk_extra(streamFile, current_offset, &loop_flag, &num_samples, &loop_start, &loop_end, big_endian);
|
||||
xma_chunk_offset = current_offset;
|
||||
xma2_parse_fmt_chunk_extra(sf_h, offset, &loop_flag, &num_samples, &loop_start, &loop_end, big_endian);
|
||||
xma_chunk_offset = offset;
|
||||
}
|
||||
|
||||
if (codec == 0xFFFF) {
|
||||
if (platform != 'S') goto fail;
|
||||
sample_rate = read_32bit(current_offset+fmt_chunk_size+0x04+0x08, streamFile);
|
||||
sample_rate = read_32bit(offset+fmt_chunk_size+0x04+0x08, sf_h);
|
||||
}
|
||||
|
||||
/* mini-fmt has AT9 stuff then a regular RIFF [Square Heroes (PS4)] */
|
||||
if (codec == 0xFFFE) {
|
||||
is_at9 = 1;
|
||||
}
|
||||
|
||||
/* regular (with loop tags) Ogg poses as PCM [Little Savior (PC)] */
|
||||
}
|
||||
|
||||
current_offset += fmt_chunk_size;
|
||||
offset += fmt_chunk_size;
|
||||
|
||||
data_size = read_32bitLE(current_offset, streamFile);
|
||||
current_offset += 0x04;
|
||||
data_size = read_32bitLE(offset, sf_h);
|
||||
offset += 0x04;
|
||||
|
||||
start_offset = current_offset;
|
||||
start_offset = offset;
|
||||
}
|
||||
|
||||
/* container handling */
|
||||
if (is_ogg || is_at9) {
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
const char* fake_ext = is_ogg ? "ogg" : "at9";
|
||||
|
||||
/* after data_size is loop start + loop length and offset? (same as loop tags), 0 if not enabled */
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf_h, start_offset, data_size, fake_ext);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
if (is_ogg) {
|
||||
vgmstream = init_vgmstream_ogg_vorbis(temp_sf);
|
||||
} else {
|
||||
vgmstream = init_vgmstream_riff(temp_sf);
|
||||
}
|
||||
close_streamfile(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_XNB;
|
||||
if (sf_h != sf) close_streamfile(sf_h);
|
||||
return vgmstream;
|
||||
}
|
||||
|
||||
|
||||
@ -113,8 +167,7 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
|
||||
switch (codec) {
|
||||
case 0x01: /* Dragon's Blade (Android) */
|
||||
/* null in Metagalactic Blitz (PC) */
|
||||
if (!block_align)
|
||||
if (!block_align) /* null in Metagalactic Blitz (PC) */
|
||||
block_align = (bps == 8 ? 0x01 : 0x02) * channel_count;
|
||||
|
||||
vgmstream->coding_type = bps == 8 ? coding_PCM8_U : coding_PCM16LE;
|
||||
@ -148,7 +201,7 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
block_count = data_size / block_size + (data_size % block_size ? 1 : 0);
|
||||
|
||||
bytes = ffmpeg_make_riff_xma2(buf,0x100, num_samples, data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf_h, buf,bytes, start_offset,data_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
@ -157,19 +210,20 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
xma_fix_raw_samples(vgmstream, streamFile, start_offset,data_size, xma_chunk_offset, 1,1);
|
||||
xma_fix_raw_samples(vgmstream, sf_h, start_offset,data_size, xma_chunk_offset, 1,1);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
case 0xFFFF: /* Eagle Island (Switch) */
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = data_size / channel_count;
|
||||
vgmstream->num_samples = read_32bitLE(start_offset + 0x00, streamFile);
|
||||
vgmstream->num_samples = read_32bitLE(start_offset + 0x00, sf_h);
|
||||
//vgmstream->num_samples = dsp_bytes_to_samples(data_size - 0x60*channel_count, channel_count);
|
||||
|
||||
dsp_read_coefs(vgmstream,streamFile,start_offset + 0x1c,vgmstream->interleave_block_size,big_endian);
|
||||
dsp_read_hist (vgmstream,streamFile,start_offset + 0x3c,vgmstream->interleave_block_size,big_endian);
|
||||
dsp_read_coefs(vgmstream, sf_h, start_offset + 0x1c, vgmstream->interleave_block_size, big_endian);
|
||||
dsp_read_hist (vgmstream, sf_h, start_offset + 0x3c, vgmstream->interleave_block_size, big_endian);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -177,11 +231,14 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset) )
|
||||
if (!vgmstream_open_stream(vgmstream, sf_h, start_offset))
|
||||
goto fail;
|
||||
|
||||
if (sf_h != sf) close_streamfile(sf_h);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (sf_h != sf) close_streamfile(sf_h);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
266
src/meta/xnb_lz4mg.h
Normal file
266
src/meta/xnb_lz4mg.h
Normal file
@ -0,0 +1,266 @@
|
||||
#ifndef _XNB_LZ4MG_H_
|
||||
#define _XNB_LZ4MG_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Decompresses LZ4 from MonoGame. The original C lib has a lot of modes and configs, but
|
||||
* MonoGame only uses the core 'block' part, which is a fairly simple LZ77 (has one command
|
||||
* to copy literal and window values, with variable copy lengths).
|
||||
*
|
||||
* This is a basic re-implementation (not tuned for performance) for practice/test purposes,
|
||||
* that handles streaming decompression as a state machine since we can run out of src or dst
|
||||
* bytes anytime and LZ4 allows any copy length, with copy window as a circular buffer. Not
|
||||
* sure what's the best/standard way to do it though. Info:
|
||||
* - https://github.com/lz4/lz4
|
||||
* - https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Utilities/Lz4Stream/Lz4DecoderStream.cs
|
||||
*/
|
||||
|
||||
#define LZ4MG_OK 0
|
||||
#define LZ4MG_ERROR -1
|
||||
#define LZ4MG_WINDOW_SIZE (1 << 16)
|
||||
#define LZ4MG_WINDOW_BYTES 2
|
||||
#define LZ4MG_MIN_MATCH_LEN 4
|
||||
#define LZ4MG_VARLEN_MARK 15
|
||||
#define LZ4MG_VARLEN_CONTINUE 255
|
||||
|
||||
|
||||
typedef enum {
|
||||
READ_TOKEN,
|
||||
READ_LITERAL,
|
||||
COPY_LITERAL,
|
||||
READ_OFFSET,
|
||||
READ_MATCH,
|
||||
SET_MATCH,
|
||||
COPY_MATCH
|
||||
} lz4mg_state_t;
|
||||
|
||||
typedef struct {
|
||||
lz4mg_state_t state;
|
||||
|
||||
uint8_t token;
|
||||
int literal_len;
|
||||
int offset_cur;
|
||||
int offset_pos;
|
||||
int match_len;
|
||||
int match_pos;
|
||||
|
||||
int window_pos;
|
||||
uint8_t window[LZ4MG_WINDOW_SIZE];
|
||||
} lz4mg_context_t;
|
||||
|
||||
typedef struct {
|
||||
lz4mg_context_t ctx;
|
||||
|
||||
uint8_t *next_out; /* next bytes to write (reassign when avail is 0) */
|
||||
int avail_out; /* bytes available at next_out */
|
||||
int total_out; /* written bytes, for reference (set to 0 per call if needed) */
|
||||
|
||||
const uint8_t *next_in; /* next bytes to read (reassign when avail is 0) */
|
||||
int avail_in; /* bytes available at next_in */
|
||||
int total_in; /* read bytes, for reference (set to 0 per call if needed) */
|
||||
} lz4mg_stream_t;
|
||||
|
||||
static void lz4mg_reset(lz4mg_stream_t* strm) {
|
||||
memset(strm, 0, sizeof(lz4mg_stream_t));
|
||||
}
|
||||
|
||||
/* Decompress src into dst, returning a code and number of bytes used. Caller must handle
|
||||
* stop (when no more input data or all data has been decompressed) as LZ4 has no end markers. */
|
||||
static int lz4mg_decompress(lz4mg_stream_t* strm) {
|
||||
lz4mg_context_t* ctx = &strm->ctx;
|
||||
uint8_t* dst = strm->next_out;
|
||||
const uint8_t* src = strm->next_in;
|
||||
int dst_size = strm->avail_out;
|
||||
int src_size = strm->avail_in;
|
||||
int dst_pos = 0;
|
||||
int src_pos = 0;
|
||||
uint8_t next_len, next_val;
|
||||
|
||||
|
||||
while (1) {
|
||||
/* mostly linear state machine, but it may break anytime when reaching dst or src
|
||||
* end, and resume from same state in next call */
|
||||
switch(ctx->state) {
|
||||
|
||||
case READ_TOKEN:
|
||||
if (src_pos >= src_size)
|
||||
goto buffer_end;
|
||||
ctx->token = src[src_pos++];
|
||||
|
||||
ctx->literal_len = (ctx->token >> 4) & 0xF;
|
||||
if (ctx->literal_len == LZ4MG_VARLEN_MARK)
|
||||
ctx->state = READ_LITERAL;
|
||||
else
|
||||
ctx->state = COPY_LITERAL;
|
||||
break;
|
||||
|
||||
case READ_LITERAL:
|
||||
do {
|
||||
if (src_pos >= src_size)
|
||||
goto buffer_end;
|
||||
next_len = src[src_pos++];
|
||||
ctx->literal_len += next_len;
|
||||
} while (next_len == LZ4MG_VARLEN_CONTINUE);
|
||||
|
||||
ctx->state = COPY_LITERAL;
|
||||
break;
|
||||
|
||||
case COPY_LITERAL:
|
||||
while (ctx->literal_len > 0) { /* may be 0 */
|
||||
if (src_pos >= src_size || dst_pos >= dst_size)
|
||||
goto buffer_end;
|
||||
next_val = src[src_pos++];
|
||||
|
||||
dst[dst_pos++] = next_val;
|
||||
|
||||
ctx->window[ctx->window_pos++] = next_val;
|
||||
if (ctx->window_pos == LZ4MG_WINDOW_SIZE)
|
||||
ctx->window_pos = 0;
|
||||
|
||||
ctx->literal_len--;
|
||||
};
|
||||
|
||||
/* LZ4 is designed to reach EOF with a literal in this state with some empty values */
|
||||
|
||||
ctx->offset_cur = 0;
|
||||
ctx->offset_pos = 0;
|
||||
ctx->state = READ_OFFSET;
|
||||
break;
|
||||
|
||||
case READ_OFFSET:
|
||||
do {
|
||||
if (src_pos >= src_size)
|
||||
goto buffer_end;
|
||||
ctx->offset_pos |= (src[src_pos++] << ctx->offset_cur*8);
|
||||
ctx->offset_cur++;
|
||||
} while (ctx->offset_cur < LZ4MG_WINDOW_BYTES);
|
||||
|
||||
ctx->match_len = (ctx->token & 0xF);
|
||||
if (ctx->match_len == LZ4MG_VARLEN_MARK)
|
||||
ctx->state = READ_MATCH;
|
||||
else
|
||||
ctx->state = SET_MATCH;
|
||||
break;
|
||||
|
||||
case READ_MATCH:
|
||||
do {
|
||||
if (src_pos >= src_size)
|
||||
goto buffer_end;
|
||||
next_len = src[src_pos++];
|
||||
ctx->match_len += next_len;
|
||||
} while (next_len == LZ4MG_VARLEN_CONTINUE);
|
||||
|
||||
ctx->state = SET_MATCH;
|
||||
break;
|
||||
|
||||
case SET_MATCH:
|
||||
ctx->match_len += LZ4MG_MIN_MATCH_LEN;
|
||||
|
||||
ctx->match_pos = ctx->window_pos - ctx->offset_pos;
|
||||
if (ctx->match_pos < 0) /* circular buffer so negative is from window end */
|
||||
ctx->match_pos = LZ4MG_WINDOW_SIZE + ctx->match_pos;
|
||||
|
||||
ctx->state = COPY_MATCH;
|
||||
break;
|
||||
|
||||
case COPY_MATCH:
|
||||
while (ctx->match_len > 0) {
|
||||
if (dst_pos >= dst_size)
|
||||
goto buffer_end;
|
||||
|
||||
next_val = ctx->window[ctx->match_pos++];
|
||||
if (ctx->match_pos == LZ4MG_WINDOW_SIZE)
|
||||
ctx->match_pos = 0;
|
||||
|
||||
dst[dst_pos++] = next_val;
|
||||
|
||||
ctx->window[ctx->window_pos++] = next_val;
|
||||
if (ctx->window_pos == LZ4MG_WINDOW_SIZE)
|
||||
ctx->window_pos = 0;
|
||||
|
||||
ctx->match_len--;
|
||||
};
|
||||
|
||||
ctx->state = READ_TOKEN;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
buffer_end:
|
||||
strm->next_out += dst_pos;
|
||||
strm->next_in += src_pos;
|
||||
strm->avail_out -= dst_pos;
|
||||
strm->avail_in -= src_pos;
|
||||
strm->total_out += dst_pos;
|
||||
strm->total_in += src_pos;
|
||||
|
||||
return LZ4MG_OK;
|
||||
fail:
|
||||
return LZ4MG_ERROR;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* non-streamed form for reference, assumes buffers are big enough */
|
||||
static void decompress_lz4mg(uint8_t* dst, size_t dst_size, const uint8_t* src, size_t src_size) {
|
||||
size_t src_pos = 0;
|
||||
size_t dst_pos = 0;
|
||||
uint8_t token;
|
||||
int literal_len, match_len, next_len;
|
||||
int match_pos, match_offset;
|
||||
int i;
|
||||
|
||||
while (src_pos < src_size && dst_pos < dst_size) {
|
||||
|
||||
token = src[src_pos++];
|
||||
if (src_pos > src_size)
|
||||
break;
|
||||
|
||||
/* handle literals */
|
||||
literal_len = token >> 4;
|
||||
if (literal_len == 15) {
|
||||
do {
|
||||
next_len = src[src_pos++];
|
||||
literal_len += next_len;
|
||||
if (src_pos > src_size)
|
||||
break;
|
||||
} while (next_len == 255);
|
||||
}
|
||||
|
||||
for (i = 0; i < literal_len; i++) {
|
||||
dst[dst_pos++] = src[src_pos++];
|
||||
}
|
||||
|
||||
/* can happen at EOF */
|
||||
if (dst_pos >= dst_size)
|
||||
break;
|
||||
|
||||
/* handle window matches */
|
||||
match_offset = src[src_pos++];
|
||||
match_offset |= (src[src_pos++] << 8);
|
||||
|
||||
match_len = (token & 0xF);
|
||||
if (match_len == 15) {
|
||||
do {
|
||||
next_len = src[src_pos++];
|
||||
match_len += next_len;
|
||||
if (src_pos > src_size)
|
||||
break;
|
||||
} while (next_len == 255);
|
||||
}
|
||||
match_len += 4; /* min len */
|
||||
|
||||
match_pos = dst_pos - match_offset;
|
||||
for(i = 0; i < match_len; i++) {
|
||||
dst[dst_pos++] = dst[match_pos++]; /* note RLE with short offsets */
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _XNB_LZ4MG_H_ */
|
300
src/meta/xnb_streamfile.h
Normal file
300
src/meta/xnb_streamfile.h
Normal file
@ -0,0 +1,300 @@
|
||||
#ifndef _XNB_STREAMFILE_H_
|
||||
#define _XNB_STREAMFILE_H_
|
||||
|
||||
//#define XNB_ENABLE_LZX 1
|
||||
|
||||
#ifdef XNB_ENABLE_LZX
|
||||
/* lib from https://github.com/sumatrapdfreader/chmlib
|
||||
* which is a cleaned-up version of https://github.com/jedwing/CHMLib */
|
||||
#include "lzx.h"
|
||||
|
||||
#define LZX_XNB_WINDOW_BITS 16
|
||||
#endif
|
||||
|
||||
#include "xnb_lz4mg.h"
|
||||
|
||||
|
||||
#define XNB_TYPE_LZX 1
|
||||
#define XNB_TYPE_LZ4 2
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int type;
|
||||
off_t compression_start;
|
||||
size_t compression_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
|
||||
size_t block_size; /* current block size */
|
||||
size_t skip_size; /* size to skip from a block start to reach data start */
|
||||
size_t data_size; /* logical size of the block */
|
||||
|
||||
size_t logical_size;
|
||||
|
||||
/* decompression state (dst size min for LZX) */
|
||||
uint8_t dst[0x10000];
|
||||
uint8_t src[0x10000];
|
||||
|
||||
lz4mg_stream_t lz4s;
|
||||
#ifdef XNB_ENABLE_LZX
|
||||
struct lzx_state* lzxs;
|
||||
#endif
|
||||
} xnb_io_data;
|
||||
|
||||
static int xnb_io_init(STREAMFILE* sf, xnb_io_data* data) {
|
||||
/* When a new IO SF is opened, the data struct is malloc'd and cloned. This works
|
||||
* well enough in other cases, but b/c the decompression context works with buf ptrs
|
||||
* the clone will point to the original struct bufs, so we need to force
|
||||
* reset on new open so that new bufs of the clone are used. */
|
||||
data->logical_offset = -1;
|
||||
|
||||
#ifdef XNB_ENABLE_LZX
|
||||
if (data->type == XNB_TYPE_LZX) {
|
||||
data->lzxs = lzx_init(LZX_XNB_WINDOW_BITS);
|
||||
if (!data->lzxs)
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xnb_io_close(STREAMFILE* sf, xnb_io_data* data) {
|
||||
#ifdef XNB_ENABLE_LZX
|
||||
if (data->type == XNB_TYPE_LZX) {
|
||||
lzx_teardown(data->lzxs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef XNB_ENABLE_LZX
|
||||
/* Decompresses LZX used in XNB. Has 16b window and headered blocks (with input size and
|
||||
* optionally output size) and standard LZX inside, probably what's known as XMemCompress.
|
||||
* Info: https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Utilities/LzxStream/LzxDecoderStream.cs */
|
||||
static int decompress_lzx_block(STREAMFILE* sf, xnb_io_data* data) {
|
||||
int src_size, dst_size, ret;
|
||||
uint8_t hi, lo;
|
||||
off_t head_size;
|
||||
off_t offset = data->physical_offset;
|
||||
|
||||
|
||||
hi = read_u8(offset + 0x00, sf);
|
||||
if (hi == 0xFF) {
|
||||
hi = read_u8(offset + 0x01, sf);
|
||||
lo = read_u8(offset + 0x02, sf);
|
||||
dst_size = (hi << 8) | lo;
|
||||
|
||||
hi = read_u8(offset + 0x03, sf);
|
||||
lo = read_u8(offset + 0x04, sf);
|
||||
src_size = (hi << 8) | lo;
|
||||
|
||||
head_size = 0x05;
|
||||
}
|
||||
else {
|
||||
dst_size = 0x8000; /* default */
|
||||
|
||||
lo = read_u8(offset + 0x01, sf);
|
||||
src_size = (hi << 8) | lo;
|
||||
|
||||
head_size = 0x02;
|
||||
}
|
||||
|
||||
if (src_size == 0 || dst_size == 0) {
|
||||
VGM_LOG("LZX: EOF\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
read_streamfile(data->src, offset + head_size, src_size, sf);
|
||||
|
||||
ret = lzx_decompress(data->lzxs, data->src, data->dst, src_size, dst_size);
|
||||
if (ret != DECR_OK) {
|
||||
VGM_LOG("LZX: decompression error %i\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->data_size = dst_size;
|
||||
data->block_size = head_size + src_size;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int decompress_lz4_block(STREAMFILE* sf, xnb_io_data* data) {
|
||||
int ret;
|
||||
|
||||
if (data->lz4s.avail_in == 0) {
|
||||
data->lz4s.next_in = data->src;
|
||||
data->lz4s.avail_in = read_streamfile(data->src, data->physical_offset, sizeof(data->src), sf);
|
||||
|
||||
/* shouldn't happen since we have decomp size */
|
||||
if (data->lz4s.avail_in <= 0) {
|
||||
VGM_LOG("XNB: EOF reached\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->block_size = data->lz4s.avail_in; /* physical data (smaller) */
|
||||
}
|
||||
else {
|
||||
data->block_size = 0; /* physical data doesn't move until new read */
|
||||
}
|
||||
|
||||
data->lz4s.total_out = 0;
|
||||
data->lz4s.avail_out = sizeof(data->dst);
|
||||
data->lz4s.next_out = data->dst;
|
||||
|
||||
ret = lz4mg_decompress(&data->lz4s);
|
||||
|
||||
data->data_size = data->lz4s.total_out; /* logical data (bigger) */
|
||||
|
||||
if (ret != LZ4MG_OK) {
|
||||
VGM_LOG("XNB: LZ4 error %i\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t xnb_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t length, xnb_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* reset */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = 0x00;
|
||||
data->logical_offset = 0x00;
|
||||
data->block_size = 0;
|
||||
data->data_size = 0;
|
||||
data->skip_size = 0;
|
||||
|
||||
switch(data->type) {
|
||||
#ifdef XNB_ENABLE_LZX
|
||||
case XNB_TYPE_LZX: lzx_reset(data->lzxs); break;
|
||||
#endif
|
||||
case XNB_TYPE_LZ4: lz4mg_reset(&data->lz4s); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/* read blocks, one at a time */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->logical_offset >= data->logical_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size <= 0) {
|
||||
|
||||
if (data->physical_offset < data->compression_start) {
|
||||
/* copy data */
|
||||
int to_read = data->compression_start - data->physical_offset;
|
||||
|
||||
data->data_size = read_streamfile(data->dst, data->physical_offset, to_read, sf);
|
||||
data->block_size = data->data_size;
|
||||
}
|
||||
else {
|
||||
/* decompress data */
|
||||
int ok = 0;
|
||||
|
||||
switch(data->type) {
|
||||
#ifdef XNB_ENABLE_LZX
|
||||
case XNB_TYPE_LZX: ok = decompress_lzx_block(sf, data); break;
|
||||
#endif
|
||||
case XNB_TYPE_LZ4: ok = decompress_lz4_block(sf, data); break;
|
||||
default: break;
|
||||
}
|
||||
if (!ok) {
|
||||
VGM_LOG("XNB: decompression error\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read/copy block data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
memcpy(dest, data->dst + data->skip_size + bytes_consumed, to_read);
|
||||
bytes_done = to_read;
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
|
||||
static size_t xnb_io_size(STREAMFILE *streamfile, xnb_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
xnb_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
|
||||
/* Decompresses XNB streams */
|
||||
static STREAMFILE* setup_xnb_streamfile(STREAMFILE* sf, int flags, off_t compression_start, size_t compression_size) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
xnb_io_data io_data = {0};
|
||||
|
||||
if (flags & 0x80)
|
||||
io_data.type = XNB_TYPE_LZX;
|
||||
else if (flags & 0x40)
|
||||
io_data.type = XNB_TYPE_LZ4;
|
||||
else
|
||||
goto fail;
|
||||
io_data.compression_start = compression_start;
|
||||
io_data.compression_size = compression_size;
|
||||
io_data.physical_offset = 0x00;
|
||||
io_data.logical_size = compression_start + compression_size;
|
||||
io_data.logical_offset = -1; /* force reset */
|
||||
|
||||
#ifndef XNB_ENABLE_LZX
|
||||
/* no known audio use it (otherwise works if enabled and included lzx.c/lzx.h) */
|
||||
if (io_data.type == XNB_TYPE_LZX)
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_ex_f(new_sf, &io_data, sizeof(xnb_io_data), xnb_io_read, xnb_io_size, xnb_io_init, xnb_io_close);
|
||||
//new_sf = open_buffer_streamfile_f(new_sf, 0); /* useful? we already have a decompression buffer */
|
||||
return new_sf;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _XNB_STREAMFILE_H_ */
|
Loading…
x
Reference in New Issue
Block a user