Updated VGMStream to r1640-49-g0dd8bdd7

CQTexperiment
Christopher Snowhill 2021-09-17 19:30:02 -07:00
parent da96d2c21e
commit a3aea19525
32 changed files with 3500 additions and 977 deletions

View File

@ -84,6 +84,9 @@
830EBE142004656E0023AA10 /* ktss.c in Sources */ = {isa = PBXBuildFile; fileRef = 830EBE122004656E0023AA10 /* ktss.c */; };
8313E3E61902020400B4B6F1 /* mpg123.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8313E3431901FBDD00B4B6F1 /* mpg123.framework */; };
8313E3E71902021800B4B6F1 /* mpg123.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8313E3431901FBDD00B4B6F1 /* mpg123.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
8315868726F586E200803A3A /* psb.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315868326F586E200803A3A /* psb.c */; };
8315868A26F586F900803A3A /* m2_psb.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315868826F586F900803A3A /* m2_psb.c */; };
8315868B26F586F900803A3A /* m2_psb.h in Headers */ = {isa = PBXBuildFile; fileRef = 8315868926F586F900803A3A /* m2_psb.h */; };
8315958720FEC832007002F0 /* asf_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315958320FEC831007002F0 /* asf_decoder.c */; };
8315958920FEC83F007002F0 /* asf.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315958820FEC83F007002F0 /* asf.c */; };
8317C24C26982CC1007DD0B8 /* sspr.c in Sources */ = {isa = PBXBuildFile; fileRef = 8317C24826982CC1007DD0B8 /* sspr.c */; };
@ -889,6 +892,9 @@
830EBE112004656E0023AA10 /* xnb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xnb.c; sourceTree = "<group>"; };
830EBE122004656E0023AA10 /* ktss.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ktss.c; sourceTree = "<group>"; };
8313E33D1901FBDC00B4B6F1 /* mpg123.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = mpg123.xcodeproj; path = ../mpg123/mpg123.xcodeproj; sourceTree = "<group>"; };
8315868326F586E200803A3A /* psb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = psb.c; sourceTree = "<group>"; };
8315868826F586F900803A3A /* m2_psb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = m2_psb.c; sourceTree = "<group>"; };
8315868926F586F900803A3A /* m2_psb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = m2_psb.h; sourceTree = "<group>"; };
8315958320FEC831007002F0 /* asf_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = asf_decoder.c; sourceTree = "<group>"; };
8315958820FEC83F007002F0 /* asf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = asf.c; sourceTree = "<group>"; };
8317C24826982CC1007DD0B8 /* sspr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sspr.c; sourceTree = "<group>"; };
@ -2071,6 +2077,7 @@
836F6ED318BDC2190095E648 /* ps2_xa30.c */,
836F6ED518BDC2190095E648 /* ps3_cps.c */,
836F6ED918BDC2190095E648 /* ps3_past.c */,
8315868326F586E200803A3A /* psb.c */,
837CEAE823487F2B00E62A4A /* psf.c */,
83997F5722D9569E00633184 /* rad.c */,
8322ECE6240268BA009E9429 /* raw_al.c */,
@ -2244,6 +2251,8 @@
83D26A7E26E66DC2001A9475 /* chunks.h */,
83D26A7D26E66DC2001A9475 /* log.c */,
83D26A7F26E66DC2001A9475 /* log.h */,
8315868826F586F900803A3A /* m2_psb.c */,
8315868926F586F900803A3A /* m2_psb.h */,
);
path = util;
sourceTree = "<group>";
@ -2331,6 +2340,7 @@
8373342723F60CDC00DE14DC /* lrmd_streamfile.h in Headers */,
83C7281122BC893D00678B4A /* 9tav_streamfile.h in Headers */,
83C7280F22BC893D00678B4A /* xwb_xsb.h in Headers */,
8315868B26F586F900803A3A /* m2_psb.h in Headers */,
83AA7F732519BFEA004C5298 /* mpeg_bitreader.h in Headers */,
83C7281622BC893D00678B4A /* xwma_konami_streamfile.h in Headers */,
839E21E81F2EDAF100EE54D7 /* mpeg_decoder.h in Headers */,
@ -2616,6 +2626,7 @@
8349A9121FE6258200E26435 /* vsf_tta.c in Sources */,
836F6FCA18BDC2190095E648 /* ps2_adm.c in Sources */,
836F6FA118BDC2190095E648 /* myspd.c in Sources */,
8315868726F586E200803A3A /* psb.c in Sources */,
837CEB0123487F2C00E62A4A /* xmu.c in Sources */,
836F6FD718BDC2190095E648 /* ps2_filp.c in Sources */,
8346D97925BF838C00D1A8B0 /* idtech.c in Sources */,
@ -2943,6 +2954,7 @@
83AF2CCB26226BA500538240 /* ogv_3rdeye.c in Sources */,
8306B0BB20984552000302D4 /* blocked_gsb.c in Sources */,
83031ECC243C50CC00C3F3E0 /* blocked_ubi_sce.c in Sources */,
8315868A26F586F900803A3A /* m2_psb.c in Sources */,
836F6F7718BDC2190095E648 /* capdsp.c in Sources */,
836F6FB018BDC2190095E648 /* ngc_dsp_ygo.c in Sources */,
836F703318BDC2190095E648 /* str_snds.c in Sources */,

View File

@ -92,6 +92,7 @@ void decode_ulaw(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing,
void decode_ulaw_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_alaw(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_pcmfloat(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian);
void decode_pcm24le(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
int32_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample);
int32_t pcm16_bytes_to_samples(size_t bytes, int channels);
int32_t pcm8_bytes_to_samples(size_t bytes, int channels);
@ -592,7 +593,7 @@ STREAMFILE* ffmpeg_get_streamfile(ffmpeg_codec_data* data);
ffmpeg_codec_data* init_ffmpeg_atrac3_raw(STREAMFILE* sf, off_t offset, size_t data_size, int sample_count, int channels, int sample_rate, int block_align, int encoder_delay);
ffmpeg_codec_data* init_ffmpeg_atrac3_riff(STREAMFILE* sf, off_t offset, int* out_samples);
ffmpeg_codec_data* init_ffmpeg_aac(STREAMFILE* sf, off_t offset, size_t size, int skip_samples);
ffmpeg_codec_data* init_ffmpeg_xwma(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int avg_bitrate, int block_size);
/* ffmpeg_decoder_custom_opus.c (helper-things) */
typedef struct {
@ -663,6 +664,8 @@ typedef struct {
void xma_get_samples(ms_sample_data* msd, STREAMFILE* sf);
void wmapro_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int sample_rate, uint32_t decode_flags);
void wma_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int sample_rate, uint32_t decode_flags);
int32_t xwma_get_samples(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int block_size);
int32_t xwma_dpds_get_samples(STREAMFILE* sf, uint32_t dpds_offset, uint32_t dpds_size, int channels, int be);
void xma1_parse_fmt_chunk(STREAMFILE* sf, off_t chunk_offset, int* channels, int* sample_rate, int* loop_flag, int32_t* loop_start_b, int32_t* loop_end_b, int32_t* loop_subframe, int be);
void xma2_parse_fmt_chunk_extra(STREAMFILE* sf, off_t chunk_offset, int* loop_flag, int32_t* out_num_samples, int32_t* out_loop_start_sample, int32_t* out_loop_end_sample, int be);

View File

@ -760,6 +760,34 @@ void wma_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int s
#endif
}
int32_t xwma_get_samples(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int block_size) {
/* manually find total samples, why don't they put this in the header is beyond me */
ms_sample_data msd = {0};
msd.channels = channels;
msd.data_offset = data_offset;
msd.data_size = data_size;
if (format == 0x0162)
wmapro_get_samples(&msd, sf, block_size, sample_rate, 0x00E0);
else
wma_get_samples(&msd, sf, block_size, sample_rate, 0x001F);
return msd.num_samples;
}
int32_t xwma_dpds_get_samples(STREAMFILE* sf, uint32_t dpds_offset, uint32_t dpds_size, int channels, int be) {
int32_t (*read_s32)(off_t,STREAMFILE*) = be ? read_s32be : read_s32le;
uint32_t offset;
if (!dpds_offset || !dpds_size || !channels)
return 0;
offset = dpds_offset + (dpds_size - 0x04); /* last entry */
/* XWMA's seek table ("dpds") contains max decoded bytes (after encoder delay), checked vs xWMAEncode.
* WMAPRO usually encodes a few more tail samples though (see xwma_get_samples). */
return read_s32(offset, sf) / channels / sizeof(int16_t); /* in PCM16 bytes */
}
/* XMA hell for precise looping and gapless support, fixes raw sample values from headers
* that don't count XMA's final subframe/encoder delay/encoder padding, and FFmpeg stuff.

View File

@ -456,7 +456,7 @@ static size_t make_oggs_page(uint8_t* buf, int buf_size, size_t data_size, int p
}
segment_count = (int)(data_size / 0xFF + 1);
put_u32be(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */
put_u32be(buf+0x00, get_id32be("OggS")); /* capture pattern */
put_u8 (buf+0x04, 0); /* stream structure version, fixed */
put_u8 (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */
put_u32le(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */
@ -517,8 +517,8 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
goto fail;
}
put_u32be(buf+0x00, 0x4F707573); /* "Opus" header magic */
put_u32be(buf+0x04, 0x48656164); /* "Head" header magic */
put_u32be(buf+0x00, get_id32be("Opus"));
put_u32be(buf+0x04, get_id32be("Head"));
put_u8 (buf+0x08, 1); /* version */
put_u8 (buf+0x09, cfg->channels);
put_s16le(buf+0x0A, cfg->skip);
@ -575,19 +575,23 @@ fail:
static size_t make_oggs_first(uint8_t* buf, int buf_size, opus_config* cfg) {
int buf_done = 0;
size_t bytes;
size_t page_size = 0x1c; /* fixed for header page */
if (buf_size < 0x100) /* approx */
goto fail;
/* make header */
bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, cfg);
make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 0, 0);
buf_done += 0x1c + bytes;
/* make header (first data, then page for checksum) */
bytes = make_opus_header(buf + page_size, buf_size - page_size, cfg);
make_oggs_page(buf, buf_size, bytes, 0, 0);
buf_done += (page_size + bytes);
buf += buf_done;
buf_size -= buf_done;
/* make comment */
bytes = make_opus_comment(buf+buf_done + 0x1c,buf_size);
make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 1, 0);
buf_done += 0x1c + bytes;
bytes = make_opus_comment(buf + page_size, buf_size - page_size);
make_oggs_page(buf, buf_size, bytes, 1, 0);
buf_done += (page_size + bytes);
return buf_done;
fail:

View File

@ -203,4 +203,39 @@ fail:
return NULL;
}
//TODO: make init_ffmpeg_xwma_fmt(be) too to pass fmt chunk
ffmpeg_codec_data* init_ffmpeg_xwma(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int avg_bitrate, int block_size) {
ffmpeg_codec_data* data = NULL;
uint8_t buf[0x100];
int bytes;
bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), format, data_size, channels, sample_rate, avg_bitrate, block_size);
data = init_ffmpeg_header_offset(sf, buf,bytes, data_offset, data_size);
if (!data) goto fail;
if (format == 0x161) {
int skip_samples = 0;
/* Skip WMA encoder delay, not specified in the flags or containers (ASF/XWMA),
* but verified compared to Microsoft's output. Seems to match frame_samples * 2 */
if (sample_rate >= 32000)
skip_samples = 4096;
else if (sample_rate >= 22050)
skip_samples = 2048;
else if (sample_rate >= 8000)
skip_samples = 1024;
ffmpeg_set_skip_samples(data, skip_samples);
}
//TODO WMAPro uses variable skips and is more complex
//TODO ffmpeg's WMA doesn't properly output trailing samples (ignored patch...)
return data;
fail:
free_ffmpeg(data);
return NULL;
}
#endif

View File

@ -252,7 +252,7 @@ int msadpcm_check_coefs(STREAMFILE* sf, off_t offset) {
int i;
int count = read_u16le(offset, sf);
if (count != 7) {
VGM_LOG("MSADPCM: bad count %i at %lx\n", count, offset);
vgm_logi("MSADPCM: bad count %i at %lx (report)\n", count, offset);
goto fail;
}
@ -262,7 +262,7 @@ int msadpcm_check_coefs(STREAMFILE* sf, off_t offset) {
int16_t coef2 = read_s16le(offset + 0x02, sf);
if (coef1 != msadpcm_coefs[i][0] || coef2 != msadpcm_coefs[i][1]) {
VGM_LOG("MSADPCM: bad coef %i/%i vs %i/%i\n", coef1, coef2, msadpcm_coefs[i][0], msadpcm_coefs[i][1]);
vgm_logi("MSADPCM: bad coef %i/%i vs %i/%i (report)\n", coef1, coef2, msadpcm_coefs[i][0], msadpcm_coefs[i][1]);
goto fail;
}
offset += 0x02 + 0x02;

View File

@ -2,7 +2,7 @@
#include "../util.h"
#include <math.h>
void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_pcm16le(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
@ -11,7 +11,7 @@ void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa
}
}
void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_pcm16be(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
@ -20,7 +20,7 @@ void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa
}
}
void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) {
void decode_pcm16_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) {
int i, sample_count;
int16_t (*read_16bit)(off_t,STREAMFILE*) = big_endian ? read_16bitBE : read_16bitLE;
@ -29,7 +29,7 @@ void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channels
}
}
void decode_pcm8(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_pcm8(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
@ -38,7 +38,7 @@ void decode_pcm8(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
}
}
void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_pcm8_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
@ -47,7 +47,7 @@ void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelsp
}
}
void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_pcm8_unsigned(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
@ -57,7 +57,7 @@ void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int chan
}
}
void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
@ -67,7 +67,7 @@ void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int
}
}
void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_pcm8_sb(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
@ -78,7 +78,7 @@ void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa
}
}
void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
int i, nibble_shift, is_high_first, is_stereo;
int32_t sample_count;
int16_t v;
@ -101,7 +101,7 @@ void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * ou
}
}
void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
int i, nibble_shift, is_high_first, is_stereo;
int32_t sample_count;
int16_t v;
@ -149,7 +149,7 @@ static int expand_ulaw(uint8_t ulawbyte) {
}
/* decodes u-law (ITU G.711 non-linear PCM), from g711.c */
void decode_ulaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_ulaw(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i, sample_count;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
@ -159,7 +159,7 @@ void decode_ulaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
}
void decode_ulaw_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_ulaw_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i, sample_count;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
@ -195,7 +195,7 @@ static int expand_alaw(uint8_t alawbyte) {
}
/* decodes a-law (ITU G.711 non-linear PCM), from g711.c */
void decode_alaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_alaw(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i, sample_count;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
@ -204,7 +204,7 @@ void decode_alaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
}
}
void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) {
void decode_pcmfloat(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) {
int i, sample_count;
float (*read_f32)(off_t,STREAMFILE*) = big_endian ? read_f32be : read_f32le;
@ -216,6 +216,17 @@ void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelsp
}
}
void decode_pcm24le(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
off_t offset = stream->offset + i * 0x03;
int v = read_u8(offset+0x00, stream->streamfile) | (read_s16le(offset + 0x01, stream->streamfile) << 8);
outbuf[sample_count] = (v >> 8);
}
}
int32_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample) {
if (channels <= 0 || bits_per_sample <= 0) return 0;
return ((int64_t)bytes * 8) / channels / bits_per_sample;

View File

@ -368,6 +368,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) {
case coding_ULAW_int:
case coding_ALAW:
case coding_PCMFLOAT:
case coding_PCM24LE:
return 1;
#ifdef VGM_USE_VORBIS
case coding_OGG_VORBIS:
@ -592,6 +593,8 @@ int get_vgmstream_frame_size(VGMSTREAM* vgmstream) {
return 0x01;
case coding_PCMFLOAT:
return 0x04;
case coding_PCM24LE:
return 0x03;
case coding_SDX2:
case coding_SDX2_int:
@ -886,6 +889,13 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
}
break;
case coding_PCM24LE:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_pcm24le(&vgmstream->ch[ch], buffer+ch,
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_NDS_IMA:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_nds_ima(&vgmstream->ch[ch], buffer+ch,

View File

@ -393,7 +393,7 @@ static const char* extension_list[] = {
"pona",
"pos",
"ps2stm", //fake extension for .stm (renamed? to be removed?)
"psb", //txth/reserved [Legend of Mana (Switch), Senxin Aleste (AC)]
"psb",
"psf",
"psh", //fake extension for .vsv (to be removed)
"psnd",
@ -716,6 +716,7 @@ static const coding_info coding_info_list[] = {
{coding_ULAW_int, "8-bit u-Law with 1 byte interleave (block)"},
{coding_ALAW, "8-bit a-Law"},
{coding_PCMFLOAT, "32-bit float PCM"},
{coding_PCM24LE, "24-bit Little Endian PCM"},
{coding_CRI_ADX, "CRI ADX 4-bit ADPCM"},
{coding_CRI_ADX_fixed, "CRI ADX 4-bit ADPCM (fixed coefficients)"},
@ -1362,6 +1363,7 @@ static const meta_info meta_info_list[] = {
{meta_WXD_WXH, "Relic WXD+WXH header"},
{meta_BNK_RELIC, "Relic BNK header"},
{meta_XSH_XSD_XSS, "Treyarch XSH+XSD/XSS header"},
{meta_PSB, "M2 PSB header"},
};
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {

View File

@ -177,9 +177,6 @@ int setup_layout_segmented(segmented_layout_data* data) {
for (i = 0; i < data->segment_count; i++) {
int segment_input_channels, segment_output_channels;
/* allow config if set for fine-tuned parts (usually TXTP only) */
data->segments[i]->config_enabled = data->segments[i]->config.config_set;
if (data->segments[i] == NULL) {
VGM_LOG("SEGMENTED: no vgmstream in segment %i\n", i);
goto fail;
@ -190,6 +187,9 @@ int setup_layout_segmented(segmented_layout_data* data) {
goto fail;
}
/* allow config if set for fine-tuned parts (usually TXTP only) */
data->segments[i]->config_enabled = data->segments[i]->config.config_set;
/* disable so that looping is controlled by render_vgmstream_segmented */
if (data->segments[i]->loop_flag != 0) {
VGM_LOG("SEGMENTED: segment %i is looped\n", i);

File diff suppressed because it is too large Load Diff

View File

@ -255,6 +255,9 @@ static const adxkey_info adxkey9_list[] = {
/* maimai DX Splash (AC) */
{0x0000,0x0000,0x0000, NULL,9170825592834449000}, // 7F4551499DF55E68
/* Sonic Colors Ultimate (multi) */
{0x0000,0x0000,0x0000, NULL,1991062320101111}, // 000712DC5250B6F7
};
static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]);

View File

@ -1,157 +1,147 @@
#include "meta.h"
#include "../coding/coding.h"
typedef enum { ATRAC3, ATRAC9, KOVS, KTSS, KTAC } atsl_codec;
/* .ATSL - Koei Tecmo audio container [One Piece Pirate Warriors (PS3), Warriors All-Stars (PC)] */
VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) {
VGMSTREAM *vgmstream = NULL;
STREAMFILE *temp_streamFile = NULL;
int total_subsongs, target_subsong = streamFile->stream_index;
int type, big_endian = 0, entries;
atsl_codec codec;
const char* fake_ext;
off_t subfile_offset = 0;
size_t subfile_size = 0, header_size, entry_size;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
/* checks */
/* .atsl: header id (for G1L extractions), .atsl3: PS3 games, .atsl4: PS4 games */
if ( !check_extensions(streamFile,"atsl,atsl3,atsl4"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x4154534C) /* "ATSL" */
goto fail;
/* main header (LE) */
header_size = read_32bitLE(0x04,streamFile);
/* 0x08/0c: flags?, 0x10: fixed? (0x03E8) */
entries = read_32bitLE(0x14,streamFile);
/* 0x18: 0x28, or 0x30 (rarer) */
/* 0x1c: null, 0x20: subheader size, 0x24/28: null */
/* Type byte may be wrong (could need header id tests instead). Example flags at 0x08/0x0c:
* - 00010101 00020001 .atsl3 from One Piece Pirate Warriors (PS3)[ATRAC3]
* - 00000201 00020001 .atsl3 from Fist of North Star: Ken's Rage 2 (PS3)[ATRAC3]
* 00000301 00020101 (same)
* - 01040301 00060301 .atsl4 from Nobunaga's Ambition: Sphere of Influence (PS4)[ATRAC9]
* - 00060301 00040301 atsl in G1L from One Piece Pirate Warriors 3 (Vita)[ATRAC9]
* - 00060301 00010301 atsl in G1L from One Piece Pirate Warriors 3 (PC)[KOVS]
* - 000A0301 00010501 atsl in G1L from Warriors All-Stars (PC)[KOVS]
* - 000B0301 00080601 atsl in G1l from Sengoku Musou Sanada Maru (Switch)[KTSS]
* - 010C0301 01060601 .atsl from Dynasty Warriors 9 (PS4)[KTAC]
*/
entry_size = 0x28;
type = read_16bitLE(0x0c, streamFile);
switch(type) {
case 0x0100:
codec = KOVS;
fake_ext = "kvs";
break;
case 0x0200:
codec = ATRAC3;
fake_ext = "at3";
big_endian = 1;
break;
case 0x0400:
case 0x0600:
codec = ATRAC9;
fake_ext = "at9";
break;
case 0x0601:
codec = KTAC;
fake_ext = "ktac";
entry_size = 0x3c;
break;
case 0x0800:
codec = KTSS;
fake_ext = "ktss";
break;
default:
VGM_LOG("ATSL: unknown type %x\n", type);
goto fail;
}
read_32bit = big_endian ? read_32bitBE : read_32bitLE;
/* entries can point to the same file, count unique only */
{
int i,j;
total_subsongs = 0;
if (target_subsong == 0) target_subsong = 1;
/* parse entry header (in machine endianness) */
for (i = 0; i < entries; i++) {
int is_unique = 1;
/* 0x00: id */
off_t entry_subfile_offset = read_32bit(header_size + i*entry_size + 0x04,streamFile);
size_t entry_subfile_size = read_32bit(header_size + i*entry_size + 0x08,streamFile);
/* 0x08+: channels/sample rate/num_samples/loop_start/etc (match subfile header) */
/* check if current entry was repeated in a prev entry */
for (j = 0; j < i; j++) {
off_t prev_offset = read_32bit(header_size + j*entry_size + 0x04,streamFile);
if (prev_offset == entry_subfile_offset) {
is_unique = 0;
break;
}
}
if (!is_unique)
continue;
total_subsongs++;
/* target GET, but keep going to count subsongs */
if (!subfile_offset && target_subsong == total_subsongs) {
subfile_offset = entry_subfile_offset;
subfile_size = entry_subfile_size;
}
}
}
if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail;
if (!subfile_offset || !subfile_size) goto fail;
/* some kind of seek/switch table may follow (optional, found in .atsl3) */
temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext);
if (!temp_streamFile) goto fail;
/* init the VGMSTREAM */
switch(codec) {
case ATRAC3:
case ATRAC9:
vgmstream = init_vgmstream_riff(temp_streamFile);
if (!vgmstream) goto fail;
break;
#ifdef VGM_USE_VORBIS
case KOVS:
vgmstream = init_vgmstream_ogg_vorbis(temp_streamFile);
if (!vgmstream) goto fail;
break;
#endif
case KTSS:
vgmstream = init_vgmstream_ktss(temp_streamFile);
if (!vgmstream) goto fail;
break;
case KTAC:
//vgmstream = init_vgmstream_ktac(temp_streamFile); //Koei Tecto VBR-like ATRAC9
//if (!vgmstream) goto fail;
//break;
default:
goto fail;
}
vgmstream->num_streams = total_subsongs;
close_streamfile(temp_streamFile);
return vgmstream;
fail:
close_streamfile(temp_streamFile);
close_vgmstream(vgmstream);
return NULL;
}
#include "meta.h"
#include "../coding/coding.h"
/* .ATSL - Koei Tecmo audio container [One Piece Pirate Warriors (PS3), Warriors All-Stars (PC)] */
VGMSTREAM* init_vgmstream_atsl(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
int total_subsongs, target_subsong = sf->stream_index;
int type, big_endian = 0, entries;
uint32_t subfile_offset = 0, subfile_size = 0, header_size, entry_size;
VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL;
const char* fake_ext;
/* checks */
if (!is_id32be(0x00,sf, "ATSL"))
goto fail;
/* .atsl: header id (for G1L extractions), .atsl3: PS3 games, .atsl4: PS4 games */
if (!check_extensions(sf,"atsl,atsl3,atsl4"))
goto fail;
/* main header (LE) */
header_size = read_u32le(0x04,sf);
/* 0x08/0c: flags? */
/* 0x10: volume? (always 1000) */
entries = read_u32le(0x14,sf);
/* 0x18: 0x28, or 0x30 (rarer) */
/* 0x1c: null */
/* 0x20: subheader size */
/* 0x24/28: null */
/* Type byte may be wrong (could need header id tests instead). Example flags at 0x08/0x0c:
* - 00010101 00020001 .atsl3 from One Piece Pirate Warriors (PS3)[ATRAC3]
* - 00000201 00020001 .atsl3 from Fist of North Star: Ken's Rage 2 (PS3)[ATRAC3]
* 00000301 00020101 (same)
* - 01040301 00060301 .atsl4 from Nobunaga's Ambition: Sphere of Influence (PS4)[ATRAC9]
* - 00060301 00040301 atsl in G1L from One Piece Pirate Warriors 3 (Vita)[ATRAC9]
* - 00060301 00010301 atsl in G1L from One Piece Pirate Warriors 3 (PC)[KOVS]
* - 000A0301 00010501 atsl in G1L from Warriors All-Stars (PC)[KOVS]
* - 000B0301 00080601 atsl in G1l from Sengoku Musou Sanada Maru (Switch)[KTSS]
* - 010C0301 01060601 .atsl from Dynasty Warriors 9 (PS4)[KTAC]
* - 01000000 01010501 .atsl from Nioh (PC)[KOVS]
* - 01000000 00010501 .atsl from Nioh (PC)[KOVS]
*/
type = read_u16le(0x0c, sf);
switch(type) {
#ifdef VGM_USE_VORBIS
case 0x0100: /* KOVS */
init_vgmstream = init_vgmstream_ogg_vorbis;
fake_ext = "kvs";
entry_size = 0x28;
break;
case 0x0101:
init_vgmstream = init_vgmstream_ogg_vorbis;
fake_ext = "kvs";
entry_size = 0x3c;
break;
#endif
case 0x0200: /* ATRAC3 */
init_vgmstream = init_vgmstream_riff;
fake_ext = "at3";
entry_size = 0x28;
big_endian = 1;
break;
case 0x0400:
case 0x0600: /* ATRAC9 */
init_vgmstream = init_vgmstream_riff;
fake_ext = "at9";
entry_size = 0x28;
break;
case 0x0601: /* KTAC */
init_vgmstream = init_vgmstream_ktac;
fake_ext = "ktac";
entry_size = 0x3c;
break;
case 0x0800: /* KTSS */
init_vgmstream = init_vgmstream_ktss;
fake_ext = "ktss";
entry_size = 0x28;
break;
default:
vgm_logi("ATSL: unknown type %x (report)\n", type);
goto fail;
}
/* entries can point to the same file, count unique only */
{
int i, j;
uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le;
total_subsongs = 0;
if (target_subsong == 0) target_subsong = 1;
/* parse entry header (in machine endianness) */
for (i = 0; i < entries; i++) {
int is_unique = 1;
/* 0x00: id */
uint32_t entry_subfile_offset = read_u32(header_size + i*entry_size + 0x04,sf);
uint32_t entry_subfile_size = read_u32(header_size + i*entry_size + 0x08,sf);
/* 0x08+: channels/sample rate/num_samples/loop_start/etc (match subfile header) */
/* check if current entry was repeated in a prev entry */
for (j = 0; j < i; j++) {
off_t prev_offset = read_u32(header_size + j*entry_size + 0x04,sf);
if (prev_offset == entry_subfile_offset) {
is_unique = 0;
break;
}
}
if (!is_unique)
continue;
total_subsongs++;
/* target GET, but keep going to count subsongs */
if (!subfile_offset && target_subsong == total_subsongs) {
subfile_offset = entry_subfile_offset;
subfile_size = entry_subfile_size;
}
}
}
if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail;
if (!subfile_offset || !subfile_size) goto fail;
/* some kind of seek/switch table may follow (optional, found in .atsl3) */
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, fake_ext);
if (!temp_sf) goto fail;
/* init the VGMSTREAM */
vgmstream = init_vgmstream(temp_sf);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_subsongs;
close_streamfile(temp_sf);
return vgmstream;
fail:
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -1,7 +1,7 @@
#include "meta.h"
#include "../coding/coding.h"
typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type;
//typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type_t;
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
@ -13,26 +13,21 @@ VGMSTREAM* init_vgmstream_awb(STREAMFILE* sf) {
VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
off_t offset, subfile_offset, subfile_next;
size_t subfile_size;
uint32_t offset, subfile_offset, subfile_next, subfile_size;
int total_subsongs, target_subsong = sf->stream_index;
//uint32_t flags;
uint8_t offset_size;
uint16_t alignment, subkey;
awb_type type;
const char* extension = NULL;
int waveid;
/* checks
* .awb: standard
/* checks */
if (!is_id32be(0x00,sf, "AFS2"))
goto fail;
/* .awb: standard
* .afs2: sometimes [Okami HD (PS4)] */
if (!check_extensions(sf, "awb,afs2"))
goto fail;
if (read_u32be(0x00,sf) != 0x41465332) /* "AFS2" */
goto fail;
//flags = read_32bitLE(0x08,sf);
/* 0x04(1): version? 0x01=common, 0x02=2018+ (no apparent differences) */
offset_size = read_u8(0x05,sf);
/* 0x06(2): always 0x0002? */
@ -45,9 +40,9 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
offset = 0x10;
/* id(?) table: read target */
/* id table: read target */
{
off_t waveid_offset = offset + (target_subsong-1) * 0x02;
uint32_t waveid_offset = offset + (target_subsong-1) * 0x02;
waveid = read_u16le(waveid_offset,sf);
@ -56,7 +51,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
/* offset table: find target */
{
off_t file_size = get_streamfile_size(sf);
uint32_t file_size = get_streamfile_size(sf);
/* last sub-offset is always file end, so table entries = total_subsongs+1 */
offset += (target_subsong-1) * offset_size;
@ -71,7 +66,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
subfile_next = read_u16le(offset+0x02,sf);
break;
default:
VGM_LOG("AWB: unknown offset size\n");
vgm_logi("AWB: unknown offset size (report)\n");
goto fail;
}
@ -83,97 +78,70 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
subfile_size = subfile_next - subfile_offset;
}
//;VGM_LOG("AWB: subfile offset=%lx + %x\n", subfile_offset, subfile_size);
//;VGM_LOG("awb: subfile offset=%x + %x\n", subfile_offset, subfile_size);
/* autodetect as there isn't anything, plus can mix types
* (waveid<>codec info is usually in the companion .acb) */
if (read_u16be(subfile_offset, sf) == 0x8000) { /* ADX id (type 0) */
type = ADX;
extension = "adx";
}
else if ((read_u32be(subfile_offset,sf) & 0x7f7f7f7f) == 0x48434100) { /* "HCA\0" (type 2=HCA, 6=HCA-MX) */
type = HCA;
extension = "hca";
}
else if (read_u32be(subfile_offset,sf) == 0x56414770) { /* "VAGp" (type 7=VAG, 10=HEVAG) */
type = VAG;
extension = "vag";
}
else if (read_u32be(subfile_offset,sf) == 0x52494646) { /* "RIFF" (type 8=ATRAC3, 11=ATRAC9) */
type = RIFF;
extension = "wav";
subfile_size = read_u32le(subfile_offset + 0x04,sf) + 0x08; /* rough size, use RIFF's */
}
else if (read_u32be(subfile_offset,sf) == 0x43574156) { /* "CWAV" (type 9) */
type = CWAV;
extension = "bcwav";
}
else if (read_u32be(subfile_offset + 0x08,sf) >= 8000 &&
read_u32be(subfile_offset + 0x08,sf) <= 48000 &&
read_u16be(subfile_offset + 0x0e,sf) == 0 &&
read_u32be(subfile_offset + 0x18,sf) == 2 &&
read_u32be(subfile_offset + 0x50,sf) == 0) { /* probably should call some check function (type 13) */
type = DSP;
extension = "dsp";
}
else if (is_id32be(subfile_offset,sf, "CWAC")) { /* type 13 again */
type = CWAC;
extension = "dsp";
}
else if (read_u32be(subfile_offset+0x00,sf) == 0x00000018 &&
read_u32be(subfile_offset+0x04,sf) == 0x66747970) { /* chunk size + "ftyp" (type 19) */
type = M4A;
extension = "m4a";
}
else {
VGM_LOG("AWB: unknown codec\n");
goto fail;
}
{
VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL;
VGMSTREAM* (*init_vgmstream_subkey)(STREAMFILE* sf, uint16_t subkey) = NULL;
const char* extension = NULL;
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension);
if (!temp_sf) goto fail;
switch(type) {
case HCA: /* most common */
vgmstream = init_vgmstream_hca_subkey(temp_sf, subkey);
if (!vgmstream) goto fail;
break;
case ADX: /* Okami HD (PS4) */
vgmstream = init_vgmstream_adx_subkey(temp_sf, subkey);
if (!vgmstream) goto fail;
break;
case VAG: /* Ukiyo no Roushi (Vita) */
vgmstream = init_vgmstream_vag(temp_sf);
if (!vgmstream) goto fail;
break;
case RIFF: /* Ukiyo no Roushi (Vita) */
vgmstream = init_vgmstream_riff(temp_sf);
if (!vgmstream) goto fail;
break;
case CWAV: /* Sonic: Lost World (3DS) */
vgmstream = init_vgmstream_rwsd(temp_sf);
if (!vgmstream) goto fail;
break;
case DSP: /* Sonic: Lost World (WiiU) */
vgmstream = init_vgmstream_ngc_dsp_std(temp_sf);
if (!vgmstream) goto fail;
break;
case CWAC: /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */
vgmstream = init_vgmstream_dsp_cwac(temp_sf);
if (!vgmstream) goto fail;
break;
if (read_u16be(subfile_offset, sf) == 0x8000) { /* (type 0=ADX) */
init_vgmstream_subkey = init_vgmstream_adx_subkey; /* Okami HD (PS4) */
extension = "adx";
}
else if ((read_u32be(subfile_offset,sf) & 0x7f7f7f7f) == get_id32be("HCA\0")) { /* (type 2=HCA, 6=HCA-MX) */
init_vgmstream_subkey = init_vgmstream_hca_subkey; /* most common */
extension = "hca";
}
else if (is_id32be(subfile_offset,sf, "VAGp") == 0x56414770) { /* (type 7=VAG, 10=HEVAG) */
init_vgmstream = init_vgmstream_vag; /* Ukiyo no Roushi (Vita) */
extension = "vag";
}
else if (is_id32be(subfile_offset,sf, "RIFF")) { /* (type 8=ATRAC3, 11=ATRAC9) */
init_vgmstream = init_vgmstream_riff; /* Ukiyo no Roushi (Vita) */
extension = "wav";
subfile_size = read_u32le(subfile_offset + 0x04,sf) + 0x08; /* padded size, use RIFF's */
}
else if (is_id32be(subfile_offset,sf, "CWAV")) { /* (type 9=CWAV) */
init_vgmstream = init_vgmstream_rwsd; /* Sonic: Lost World (3DS) */
extension = "bcwav";
}
else if (read_u32be(subfile_offset + 0x08,sf) >= 8000 && read_u32be(subfile_offset + 0x08,sf) <= 48000 &&
read_u16be(subfile_offset + 0x0e,sf) == 0 &&
read_u32be(subfile_offset + 0x18,sf) == 2 &&
read_u32be(subfile_offset + 0x50,sf) == 0) { /* (type 13=DSP), probably should call some check function */
init_vgmstream = init_vgmstream_ngc_dsp_std; /* Sonic: Lost World (WiiU) */
extension = "dsp";
}
else if (is_id32be(subfile_offset,sf, "CWAC")) { /* (type 13=DSP, again) */
init_vgmstream = init_vgmstream_dsp_cwac; /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */
extension = "dsp";
}
#ifdef VGM_USE_FFMPEG
case M4A: /* Imperial SaGa Eclipse (Browser) */
vgmstream = init_vgmstream_mp4_aac_ffmpeg(temp_sf);
if (!vgmstream) goto fail;
break;
else if (read_u32be(subfile_offset+0x00,sf) == 0x00000018 && is_id32be(subfile_offset+0x04,sf, "ftyp")) { /* (type 19=M4A) */
init_vgmstream = init_vgmstream_mp4_aac_ffmpeg; /* Imperial SaGa Eclipse (Browser) */
extension = "m4a";
}
#endif
default:
else {
vgm_logi("AWB: unknown codec (report)\n");
goto fail;
}
}
vgmstream->num_streams = total_subsongs;
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension);
if (!temp_sf) goto fail;
if (init_vgmstream_subkey)
vgmstream = init_vgmstream_subkey(temp_sf, subkey);
else
vgmstream = init_vgmstream(temp_sf);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_subsongs;
}
/* try to load cue names */
load_awb_name(sf, sf_acb, vgmstream, waveid);

View File

@ -1,14 +1,15 @@
#include "cri_utf.h"
#include "../util/log.h"
#define UTF_MAX_SCHEMA_SIZE 0x8000 /* arbitrary max */
#define COLUMN_BITMASK_FLAG 0xf0
#define COLUMN_BITMASK_TYPE 0x0f
enum columna_flag_t {
COLUMN_FLAG_NAME = 0x10,
COLUMN_FLAG_DEFAULT = 0x20,
COLUMN_FLAG_ROW = 0x40,
COLUMN_FLAG_UNDEFINED = 0x80 /* shouldn't exist */
COLUMN_FLAG_NAME = 0x10, /* column has name (may be empty) */
COLUMN_FLAG_DEFAULT = 0x20, /* data is found relative to schema start (typically constant value for all rows) */
COLUMN_FLAG_ROW = 0x40, /* data is found relative to row start */
COLUMN_FLAG_UNDEFINED = 0x80 /* shouldn't exist */
};
enum column_type_t {
@ -28,34 +29,8 @@ enum column_type_t {
COLUMN_TYPE_UNDEFINED = -1
};
typedef struct {
int found;
enum column_type_t type;
union {
int8_t value_s8;
uint8_t value_u8;
int16_t value_s16;
uint16_t value_u16;
int32_t value_s32;
uint32_t value_u32;
int64_t value_s64;
uint64_t value_u64;
float value_float;
double value_double;
struct utf_data_t {
uint32_t offset;
uint32_t size;
} value_data;
//struct utf_u128_t {
// uint64_t hi;
// uint64_t lo;
//} value_u128;
const char *value_string;
} value;
} utf_result_t;
struct utf_context {
STREAMFILE *sf;
STREAMFILE* sf;
uint32_t table_offset;
/* header */
@ -68,25 +43,31 @@ struct utf_context {
uint16_t columns;
uint16_t row_width;
uint32_t rows;
uint8_t* schema_buf;
struct utf_column_t {
uint8_t flag;
uint8_t type;
const char *name;
const char* name;
uint32_t offset;
} *schema;
/* derived */
uint32_t schema_offset;
uint32_t schema_size;
uint32_t rows_size;
uint32_t data_size;
uint32_t strings_size;
char *string_table;
const char *table_name;
char* string_table;
const char* table_name;
};
/* @UTF table context creation */
utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const char** p_row_name) {
utf_context* utf = NULL;
uint8_t buf[0x20];
int bytes;
utf = calloc(1, sizeof(utf_context));
if (!utf) goto fail;
@ -94,27 +75,33 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const
utf->sf = sf;
utf->table_offset = table_offset;
/* check header */
if (read_u32be(table_offset + 0x00, sf) != 0x40555446) /* "@UTF" */
goto fail;
bytes = read_streamfile(buf, table_offset, sizeof(buf), sf);
if (bytes != sizeof(buf)) goto fail;
/* load table header */
utf->table_size = read_u32be(table_offset + 0x04, sf) + 0x08;
utf->version = read_u16be(table_offset + 0x08, sf);
utf->rows_offset = read_u16be(table_offset + 0x0a, sf) + 0x08;
utf->strings_offset = read_u32be(table_offset + 0x0c, sf) + 0x08;
utf->data_offset = read_u32be(table_offset + 0x10, sf) + 0x08;
utf->name_offset = read_u32be(table_offset + 0x14, sf); /* within string table */
utf->columns = read_u16be(table_offset + 0x18, sf);
utf->row_width = read_u16be(table_offset + 0x1a, sf);
utf->rows = read_u32be(table_offset + 0x1c, sf);
if (get_u32be(buf + 0x00) != get_id32be("@UTF"))
goto fail;
utf->table_size = get_u32be(buf + 0x04) + 0x08;
utf->version = get_u16be(buf + 0x08);
utf->rows_offset = get_u16be(buf + 0x0a) + 0x08;
utf->strings_offset = get_u32be(buf + 0x0c) + 0x08;
utf->data_offset = get_u32be(buf + 0x10) + 0x08;
utf->name_offset = get_u32be(buf + 0x14); /* within string table */
utf->columns = get_u16be(buf + 0x18);
utf->row_width = get_u16be(buf + 0x1a);
utf->rows = get_u32be(buf + 0x1c);
utf->schema_offset = 0x20;
utf->schema_size = utf->rows_offset - utf->schema_offset;
utf->rows_size = utf->strings_offset - utf->rows_offset;
utf->strings_size = utf->data_offset - utf->strings_offset;
utf->data_size = utf->table_size - utf->data_offset;
utf->schema_offset = 0x20;
utf->strings_size = utf->data_offset - utf->strings_offset;
/* 00: early (32b rows_offset?), 01: +2017 (no apparent differences) */
if (utf->version != 0x00 && utf->version != 0x01) {
vgm_logi("@UTF: unknown version\n");
goto fail;
}
if (utf->table_offset + utf->table_size > get_streamfile_size(sf))
goto fail;
@ -125,39 +112,48 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const
/* no rows is possible for empty tables (have schema and columns names but no data) [PES 2013 (PC)] */
if (utf->columns <= 0 /*|| utf->rows <= 0 || utf->rows_width <= 0*/)
goto fail;
if (utf->schema_size >= UTF_MAX_SCHEMA_SIZE)
goto fail;
/* load string table */
/* load sections linearly (to optimize stream) */
{
size_t read;
/* schema section: small so keep it around (useful to avoid re-reads on column values) */
utf->schema_buf = malloc(utf->schema_size);
if (!utf->schema_buf) goto fail;
bytes = read_streamfile(utf->schema_buf, utf->table_offset + utf->schema_offset, utf->schema_size, sf);
if (bytes != utf->schema_size) goto fail;
/* row section: skip, mid to big (0x10000~0x50000) so not preloaded for now */
/* string section: low to mid size but used to return c-strings */
utf->string_table = calloc(utf->strings_size + 1, sizeof(char));
if (!utf->string_table) goto fail;
utf->table_name = utf->string_table + utf->name_offset;
bytes = read_streamfile((unsigned char*)utf->string_table, utf->table_offset + utf->strings_offset, utf->strings_size, sf);
if (bytes != utf->strings_size) goto fail;
read = read_streamfile((unsigned char*)utf->string_table, utf->table_offset + utf->strings_offset, utf->strings_size, sf);
if (utf->strings_size != read) goto fail;
/* data section: skip (may be big with memory AWB) */
}
/* load column schema */
{
int i;
uint32_t value_size, column_offset = 0;
uint32_t schema_offset = utf->table_offset + utf->schema_offset;
int schema_pos = 0;
utf->table_name = utf->string_table + utf->name_offset;
utf->schema = malloc(sizeof(struct utf_column_t) * utf->columns);
utf->schema = malloc(utf->columns * sizeof(struct utf_column_t));
if (!utf->schema) goto fail;
for (i = 0; i < utf->columns; i++) {
uint8_t info = read_u8(schema_offset + 0x00, sf);
uint32_t name_offset = read_u32be(schema_offset + 0x01, sf);
uint8_t info = get_u8(utf->schema_buf + schema_pos + 0x00);
uint32_t name_offset = get_u32be(utf->schema_buf + schema_pos + 0x01);
if (name_offset > utf->strings_size)
goto fail;
schema_offset += 0x01 + 0x04;
schema_pos += 0x01 + 0x04;
utf->schema[i].flag = info & COLUMN_BITMASK_FLAG;
utf->schema[i].type = info & COLUMN_BITMASK_TYPE;
@ -165,7 +161,7 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const
utf->schema[i].offset = 0;
/* known flags are name+default or name+row, but name+default+row is mentioned in VGMToolbox
* even though isn't possible in CRI's craft utils, and no name is apparently possible */
* even though isn't possible in CRI's craft utils (meaningless), and no name is apparently possible */
if ( (utf->schema[i].flag == 0) ||
!(utf->schema[i].flag & COLUMN_FLAG_NAME) ||
((utf->schema[i].flag & COLUMN_FLAG_DEFAULT) && (utf->schema[i].flag & COLUMN_FLAG_ROW)) ||
@ -207,20 +203,25 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const
}
if (utf->schema[i].flag & COLUMN_FLAG_DEFAULT) {
/* data is found relative to schema start */
utf->schema[i].offset = schema_offset - (utf->table_offset + utf->schema_offset);
schema_offset += value_size;
utf->schema[i].offset = schema_pos;
schema_pos += value_size;
}
if (utf->schema[i].flag & COLUMN_FLAG_ROW) {
/* data is found relative to row start */
utf->schema[i].offset = column_offset;
column_offset += value_size;
}
}
}
/* next section is row and variable length data (pointed above) then end of table */
#if 0
VGM_LOG("- %s\n", utf->table_name);
VGM_LOG("utf_o=%08x (%x)\n", utf->table_offset, utf->table_size);
VGM_LOG(" sch_o=%08x (%x), c=%i\n", utf->table_offset + utf->schema_offset, utf->schema_size, utf->columns);
VGM_LOG(" row_o=%08x (%x), r=%i\n", utf->table_offset + utf->rows_offset, utf->rows_size, utf->rows);
VGM_LOG(" str_o=%08x (%x)\n", utf->table_offset + utf->strings_offset, utf->strings_size);
VGM_LOG(" dat_o=%08x (%x))\n", utf->table_offset + utf->data_offset, utf->data_size);
#endif
/* write info */
if (p_rows) *p_rows = utf->rows;
@ -237,107 +238,139 @@ void utf_close(utf_context* utf) {
if (!utf) return;
free(utf->string_table);
free(utf->schema_buf);
free(utf->schema);
free(utf);
}
static int utf_query(utf_context* utf, int row, const char* column, utf_result_t* result) {
int utf_get_column(utf_context* utf, const char* column) {
int i;
result->found = 0;
if (row >= utf->rows || row < 0)
goto fail;
/* find target column */
for (i = 0; i < utf->columns; i++) {
struct utf_column_t *col = &utf->schema[i];
uint32_t data_offset;
struct utf_column_t* col = &utf->schema[i];
if (col->name == NULL || strcmp(col->name, column) != 0)
continue;
return i;
}
return -1;
}
typedef struct {
enum column_type_t type;
union {
int8_t s8;
uint8_t u8;
int16_t s16;
uint16_t u16;
int32_t s32;
uint32_t u32;
int64_t s64;
uint64_t u64;
float flt;
double dbl;
struct utf_data_t {
uint32_t offset;
uint32_t size;
} data;
#if 0
struct utf_u128_t {
uint64_t hi;
uint64_t lo;
} value_u128;
#endif
const char* str;
} value;
} utf_result_t;
static int utf_query(utf_context* utf, int row, int column, utf_result_t* result) {
if (row >= utf->rows || row < 0)
goto fail;
if (column >= utf->columns || column < 0)
goto fail;
/* get target column */
{
struct utf_column_t* col = &utf->schema[column];
uint32_t data_offset = 0;
uint8_t* buf = NULL;
result->found = 1;
result->type = col->type;
if (col->flag & COLUMN_FLAG_DEFAULT) {
data_offset = utf->table_offset + utf->schema_offset + col->offset;
if (utf->schema_buf)
buf = utf->schema_buf + col->offset;
else
data_offset = utf->table_offset + utf->schema_offset + col->offset;
}
else if (col->flag & COLUMN_FLAG_ROW) {
data_offset = utf->table_offset + utf->rows_offset + row * utf->row_width + col->offset;
}
else {
data_offset = 0;
/* shouldn't happen */
memset(&result->value, 0, sizeof(result->value));
return 1; /* ??? */
}
/* ignore zero value */
if (data_offset == 0) {
memset(&result->value, 0, sizeof(result->value)); /* just in case... */
break;
}
/* read row/constant value */
/* read row/constant value (use buf if available) */
switch (col->type) {
case COLUMN_TYPE_UINT8:
result->value.value_u8 = read_u8(data_offset, utf->sf);
result->value.u8 = buf ? get_u8(buf) : read_u8(data_offset, utf->sf);
break;
case COLUMN_TYPE_SINT8:
result->value.value_s8 = read_s8(data_offset, utf->sf);
result->value.s8 = buf ? get_s8(buf) : read_s8(data_offset, utf->sf);
break;
case COLUMN_TYPE_UINT16:
result->value.value_u16 = read_u16be(data_offset, utf->sf);
result->value.u16 = buf ? get_u16be(buf) : read_u16be(data_offset, utf->sf);
break;
case COLUMN_TYPE_SINT16:
result->value.value_s16 = read_s16be(data_offset, utf->sf);
result->value.s16 = buf ? get_s16be(buf) : read_s16be(data_offset, utf->sf);
break;
case COLUMN_TYPE_UINT32:
result->value.value_u32 = read_u32be(data_offset, utf->sf);
result->value.u32 = buf ? get_u32be(buf) : read_u32be(data_offset, utf->sf);
break;
case COLUMN_TYPE_SINT32:
result->value.value_s32 = read_s32be(data_offset, utf->sf);
result->value.s32 = buf ? get_s32be(buf) : read_s32be(data_offset, utf->sf);
break;
case COLUMN_TYPE_UINT64:
result->value.value_u64 = read_u64be(data_offset, utf->sf);
result->value.u64 = buf ? get_u64be(buf) : read_u64be(data_offset, utf->sf);
break;
case COLUMN_TYPE_SINT64:
result->value.value_s64 = read_s64be(data_offset, utf->sf);
result->value.s64 = buf ? get_s64be(buf) : read_s64be(data_offset, utf->sf);
break;
case COLUMN_TYPE_FLOAT: {
result->value.value_float = read_f32be(data_offset, utf->sf);
case COLUMN_TYPE_FLOAT:
result->value.flt = buf ? get_f32be(buf) : read_f32be(data_offset, utf->sf);
break;
}
#if 0
case COLUMN_TYPE_DOUBLE: {
result->value.value_double = read_d64be(data_offset, utf->sf);
case COLUMN_TYPE_DOUBLE:
result->value.dbl = buf ? get_d64be(buf) : read_d64be(data_offset, utf->sf);
break;
}
#endif
case COLUMN_TYPE_STRING: {
uint32_t name_offset = read_u32be(data_offset, utf->sf);
uint32_t name_offset = buf ? get_u32be(buf) : read_u32be(data_offset, utf->sf);
if (name_offset > utf->strings_size)
goto fail;
result->value.value_string = utf->string_table + name_offset;
result->value.str = utf->string_table + name_offset;
break;
}
case COLUMN_TYPE_VLDATA:
result->value.value_data.offset = read_u32be(data_offset + 0x00, utf->sf);
result->value.value_data.size = read_u32be(data_offset + 0x04, utf->sf);
result->value.data.offset = buf ? get_u32be(buf + 0x0) : read_u32be(data_offset + 0x00, utf->sf);
result->value.data.size = buf ? get_u32be(buf + 0x4) : read_u32be(data_offset + 0x04, utf->sf);
break;
#if 0
case COLUMN_TYPE_UINT128: {
result->value.value_u128.hi = read_u64be(data_offset + 0x00, utf->sf);
result->value.value_u128.lo = read_u64be(data_offset + 0x08, utf->sf);
case COLUMN_TYPE_UINT128:
result->value.value_u128.hi = buf ? get_u32be(buf + 0x0) : read_u64be(data_offset + 0x00, utf->sf);
result->value.value_u128.lo = buf ? get_u32be(buf + 0x4) : read_u64be(data_offset + 0x08, utf->sf);
break;
}
#endif
default:
goto fail;
}
break; /* column found and read */
}
return 1;
@ -345,24 +378,24 @@ fail:
return 0;
}
static int utf_query_value(utf_context* utf, int row, const char* column, void* value, enum column_type_t type) {
static int utf_query_value(utf_context* utf, int row, int column, void* value, enum column_type_t type) {
utf_result_t result = {0};
int valid;
valid = utf_query(utf, row, column, &result);
if (!valid || !result.found || result.type != type)
if (!valid || result.type != type)
return 0;
switch(result.type) {
case COLUMN_TYPE_UINT8: (*(uint8_t*)value) = result.value.value_u8; break;
case COLUMN_TYPE_SINT8: (*(int8_t*)value) = result.value.value_s8; break;
case COLUMN_TYPE_UINT16: (*(uint16_t*)value) = result.value.value_u16; break;
case COLUMN_TYPE_SINT16: (*(int16_t*)value) = result.value.value_s16; break;
case COLUMN_TYPE_UINT32: (*(uint32_t*)value) = result.value.value_u32; break;
case COLUMN_TYPE_SINT32: (*(int32_t*)value) = result.value.value_s32; break;
case COLUMN_TYPE_UINT64: (*(uint64_t*)value) = result.value.value_u64; break;
case COLUMN_TYPE_SINT64: (*(int64_t*)value) = result.value.value_s64; break;
case COLUMN_TYPE_STRING: (*(const char**)value) = result.value.value_string; break;
case COLUMN_TYPE_UINT8: (*(uint8_t*)value) = result.value.u8; break;
case COLUMN_TYPE_SINT8: (*(int8_t*)value) = result.value.s8; break;
case COLUMN_TYPE_UINT16: (*(uint16_t*)value) = result.value.u16; break;
case COLUMN_TYPE_SINT16: (*(int16_t*)value) = result.value.s16; break;
case COLUMN_TYPE_UINT32: (*(uint32_t*)value) = result.value.u32; break;
case COLUMN_TYPE_SINT32: (*(int32_t*)value) = result.value.s32; break;
case COLUMN_TYPE_UINT64: (*(uint64_t*)value) = result.value.u64; break;
case COLUMN_TYPE_SINT64: (*(int64_t*)value) = result.value.s64; break;
case COLUMN_TYPE_STRING: (*(const char**)value) = result.value.str; break;
default:
return 0;
}
@ -370,43 +403,76 @@ static int utf_query_value(utf_context* utf, int row, const char* column, void*
return 1;
}
int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value) {
int utf_query_col_s8(utf_context* utf, int row, int column, int8_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT8);
}
int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value) {
int utf_query_col_u8(utf_context* utf, int row, int column, uint8_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT8);
}
int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value) {
int utf_query_col_s16(utf_context* utf, int row, int column, int16_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT16);
}
int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value) {
int utf_query_col_u16(utf_context* utf, int row, int column, uint16_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT16);
}
int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value) {
int utf_query_col_s32(utf_context* utf, int row, int column, int32_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT32);
}
int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value) {
int utf_query_col_u32(utf_context* utf, int row, int column, uint32_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT32);
}
int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value) {
int utf_query_col_s64(utf_context* utf, int row, int column, int64_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT64);
}
int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value) {
int utf_query_col_u64(utf_context* utf, int row, int column, uint64_t* value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT64);
}
int utf_query_string(utf_context* utf, int row, const char* column, const char** value) {
int utf_query_col_string(utf_context* utf, int row, int column, const char** value) {
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_STRING);
}
int utf_query_data(utf_context* utf, int row, const char* column, uint32_t* p_offset, uint32_t* p_size) {
int utf_query_col_data(utf_context* utf, int row, int column, uint32_t* p_offset, uint32_t* p_size) {
utf_result_t result = {0};
int valid;
valid = utf_query(utf, row, column, &result);
if (!valid || !result.found || result.type != COLUMN_TYPE_VLDATA)
if (!valid || result.type != COLUMN_TYPE_VLDATA)
return 0;
if (p_offset) *p_offset = utf->table_offset + utf->data_offset + result.value.value_data.offset;
if (p_size) *p_size = result.value.value_data.size;
if (p_offset) *p_offset = utf->table_offset + utf->data_offset + result.value.data.offset;
if (p_size) *p_size = result.value.data.size;
return 1;
}
int utf_query_s8(utf_context* utf, int row, const char* column_name, int8_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT8);
}
int utf_query_u8(utf_context* utf, int row, const char* column_name, uint8_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT8);
}
int utf_query_s16(utf_context* utf, int row, const char* column_name, int16_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT16);
}
int utf_query_u16(utf_context* utf, int row, const char* column_name, uint16_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT16);
}
int utf_query_s32(utf_context* utf, int row, const char* column_name, int32_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT32);
}
int utf_query_u32(utf_context* utf, int row, const char* column_name, uint32_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT32);
}
int utf_query_s64(utf_context* utf, int row, const char* column_name, int64_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT64);
}
int utf_query_u64(utf_context* utf, int row, const char* column_name, uint64_t* value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT64);
}
int utf_query_string(utf_context* utf, int row, const char* column_name, const char** value) {
return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_STRING);
}
int utf_query_data(utf_context* utf, int row, const char* column_name, uint32_t* p_offset, uint32_t* p_size) {
return utf_query_col_data(utf, row, utf_get_column(utf, column_name), p_offset, p_size);
}

View File

@ -23,16 +23,30 @@ typedef struct utf_context utf_context;
/* open a CRI UTF table at offset, returning table name and rows. Passed streamfile is used internally for next calls */
utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const char** p_row_name);
void utf_close(utf_context* utf);
/* query calls */
int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value);
int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value);
int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value);
int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value);
int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value);
int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value);
int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value);
int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value);
int utf_query_string(utf_context* utf, int row, const char* column, const char** value);
int utf_query_data(utf_context* utf, int row, const char* column, uint32_t* offset, uint32_t* size);
int utf_get_column(utf_context* utf, const char* column);
/* query calls (passing column index is faster, when you have to read lots of rows) */
int utf_query_col_s8(utf_context* utf, int row, int column, int8_t* value);
int utf_query_col_u8(utf_context* utf, int row, int column, uint8_t* value);
int utf_query_col_s16(utf_context* utf, int row, int column, int16_t* value);
int utf_query_col_u16(utf_context* utf, int row, int column, uint16_t* value);
int utf_query_col_s32(utf_context* utf, int row, int column, int32_t* value);
int utf_query_col_u32(utf_context* utf, int row, int column, uint32_t* value);
int utf_query_col_s64(utf_context* utf, int row, int column, int64_t* value);
int utf_query_col_u64(utf_context* utf, int row, int column, uint64_t* value);
int utf_query_col_string(utf_context* utf, int row, int column, const char** value);
int utf_query_col_data(utf_context* utf, int row, int column, uint32_t* offset, uint32_t* size);
int utf_query_s8(utf_context* utf, int row, const char* column_name, int8_t* value);
int utf_query_u8(utf_context* utf, int row, const char* column_name, uint8_t* value);
int utf_query_s16(utf_context* utf, int row, const char* column_name, int16_t* value);
int utf_query_u16(utf_context* utf, int row, const char* column_name, uint16_t* value);
int utf_query_s32(utf_context* utf, int row, const char* column_name, int32_t* value);
int utf_query_u32(utf_context* utf, int row, const char* column_name, uint32_t* value);
int utf_query_s64(utf_context* utf, int row, const char* column_name, int64_t* value);
int utf_query_u64(utf_context* utf, int row, const char* column_name, uint64_t* value);
int utf_query_string(utf_context* utf, int row, const char* column_name, const char** value);
int utf_query_data(utf_context* utf, int row, const char* column_name, uint32_t* offset, uint32_t* size);
#endif /* _CRI_UTF_H_ */

View File

@ -454,18 +454,16 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
#ifdef VGM_USE_FFMPEG
case 0x0E: { /* FMOD_SOUND_FORMAT_XWMA [from fsbankex tests, no known games] */
uint8_t buf[0x100];
int bytes, format, average_bps, block_align;
int format,avg_bitrate, block_size;
format = read_u16be(fsb5.extradata_offset+0x00,sf);
block_align = read_u16be(fsb5.extradata_offset+0x02,sf);
average_bps = read_u32be(fsb5.extradata_offset+0x04,sf);
format = read_u16be(fsb5.extradata_offset+0x00,sf);
block_size = read_u16be(fsb5.extradata_offset+0x02,sf);
avg_bitrate = read_u32be(fsb5.extradata_offset+0x04,sf);
/* rest: seek entries + mini seek table? */
/* XWMA encoder only does up to 6ch (doesn't use FSB multistreams for more) */
bytes = ffmpeg_make_riff_xwma(buf,0x100, format, fsb5.stream_size, vgmstream->channels, vgmstream->sample_rate, average_bps, block_align);
vgmstream->codec_data = init_ffmpeg_header_offset(sb, buf,bytes, fsb5.stream_offset, fsb5.stream_size);
if ( !vgmstream->codec_data ) goto fail;
vgmstream->codec_data = init_ffmpeg_xwma(sf, fsb5.stream_offset, fsb5.stream_size, format, fsb5.channels, fsb5.sample_rate, avg_bitrate, block_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
break;

View File

@ -19,15 +19,15 @@ typedef struct {
int32_t num_samples;
int32_t loop_start;
int loop_flag;
off_t extra_offset;
uint32_t extra_offset;
uint32_t channel_layout;
int is_external;
uint32_t stream_offsets[MAX_CHANNELS];
uint32_t stream_sizes[MAX_CHANNELS];
off_t sound_name_offset;
off_t config_name_offset;
uint32_t sound_name_offset;
uint32_t config_name_offset;
char name[255+1];
} ktsr_header;
@ -38,26 +38,25 @@ static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE *
/* KTSR - Koei Tecmo sound resource countainer */
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *sf_b = NULL;
STREAMFILE* sf_b = NULL;
ktsr_header ktsr = {0};
int target_subsong = sf->stream_index;
int separate_offsets = 0;
/* checks */
if (!is_id32be(0x00, sf, "KTSR"))
goto fail;
if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
goto fail;
/* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)] */
if (!check_extensions(sf, "ktsl2asbin"))
goto fail;
/* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin)
* This accepts ktsl2asbin with internal data, or opening external streams as subsongs.
* This accepts .ktsl2asbin with internal data or external streams as subsongs.
* Some info from KTSR.bt */
if (!is_id32be(0x00, sf, "KTSR"))
goto fail;
if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
goto fail;
if (target_subsong == 0) target_subsong = 1;
ktsr.target_subsong = target_subsong;
@ -68,7 +67,7 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
if (ktsr.is_external) {
sf_b = open_streamfile_by_ext(sf, "ktsl2stbin");
if (!sf_b) {
VGM_LOG("KTSR: companion file not found\n");
vgm_logi("KTSR: companion file '*.ktsl2stbin' not found\n");
goto fail;
}
}
@ -129,28 +128,23 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
#ifdef VGM_USE_VORBIS
case KVS: {
VGMSTREAM *ogg_vgmstream = NULL; //TODO: meh
STREAMFILE *sf_kvs = setup_subfile_streamfile(sf_b, ktsr.stream_offsets[0], ktsr.stream_sizes[0], "kvs");
if (!sf_kvs) goto fail;
VGMSTREAM* ogg_vgmstream = NULL; //TODO: meh
STREAMFILE* temp_sf = setup_subfile_streamfile(sf_b, ktsr.stream_offsets[0], ktsr.stream_sizes[0], "kvs");
if (!temp_sf) goto fail;
ogg_vgmstream = init_vgmstream_ogg_vorbis(sf_kvs);
close_streamfile(sf_kvs);
if (ogg_vgmstream) {
ogg_vgmstream->stream_size = vgmstream->stream_size;
ogg_vgmstream->num_streams = vgmstream->num_streams;
ogg_vgmstream->channel_layout = vgmstream->channel_layout;
/* loops look shared */
strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name);
ogg_vgmstream = init_vgmstream_ogg_vorbis(temp_sf);
close_streamfile(temp_sf);
if (!ogg_vgmstream) goto fail;
close_vgmstream(vgmstream);
if (sf_b != sf) close_streamfile(sf_b);
return ogg_vgmstream;
}
else {
goto fail;
}
ogg_vgmstream->stream_size = vgmstream->stream_size;
ogg_vgmstream->num_streams = vgmstream->num_streams;
ogg_vgmstream->channel_layout = vgmstream->channel_layout;
/* loops look shared */
strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name);
break;
close_vgmstream(vgmstream);
if (sf_b != sf) close_streamfile(sf_b);
return ogg_vgmstream;
}
#endif
@ -158,7 +152,6 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
goto fail;
}
if (!vgmstream_open_stream_bf(vgmstream, sf_b, ktsr.stream_offsets[0], 1))
goto fail;
@ -273,22 +266,23 @@ static int parse_codec(ktsr_header* ktsr) {
return 1;
fail:
VGM_LOG("KTSR: unknown codec combo: ext=%x, fmt=%x, ptf=%x\n", ktsr->is_external, ktsr->format, ktsr->platform);
VGM_LOG("ktsr: unknown codec combo: ext=%x, fmt=%x, ptf=%x\n", ktsr->is_external, ktsr->format, ktsr->platform);
return 0;
}
static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) {
off_t suboffset, starts_offset, sizes_offset;
static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset) {
uint32_t suboffset, starts_offset, sizes_offset;
int i;
uint32_t type;
type = read_u32be(offset + 0x00, sf);
type = read_u32be(offset + 0x00, sf); /* hash-id? */
//size = read_u32le(offset + 0x04, sf);
/* probably could check the flag in sound header, but the format is kinda messy */
switch(type) { /* hash-id? */
switch(type) {
case 0x38D0437D: /* external [Nioh (PC), Atelier Ryza (PC)] */
case 0x3DEA478D: /* external [Nioh (PC)] */
case 0xDF92529F: /* external [Atelier Ryza (PC)] */
case 0x6422007C: /* external [Atelier Ryza (PC)] */
/* 08 subtype? (ex. 0x522B86B9)
@ -311,14 +305,20 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) {
ktsr->format = read_u32le(offset + 0x14, sf);
/* other fields will be read in the external stream */
ktsr->channel_layout= read_u32le(offset + 0x28, sf);
ktsr->channel_layout = read_u32le(offset + 0x28, sf);
ktsr->stream_offsets[0] = read_u32le(offset + 0x34, sf);
ktsr->stream_sizes[0] = read_u32le(offset + 0x38, sf);
if (type == 0x3DEA478D) { /* Nioh (PC) has one less field, some files only [ABS.ktsl2asbin] */
ktsr->stream_offsets[0] = read_u32le(offset + 0x30, sf);
ktsr->stream_sizes[0] = read_u32le(offset + 0x34, sf);
}
else {
ktsr->stream_offsets[0] = read_u32le(offset + 0x34, sf);
ktsr->stream_sizes[0] = read_u32le(offset + 0x38, sf);
}
ktsr->is_external = 1;
if (ktsr->format != 0x05) {
VGM_LOG("KTSR: unknown subcodec at %lx\n", offset);
VGM_LOG("ktsr: unknown subcodec at %x\n", offset);
goto fail;
}
@ -362,7 +362,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) {
suboffset = offset + 0x30;
if (ktsr->channels > MAX_CHANNELS) {
VGM_LOG("KTSR: max channels found\n");
VGM_LOG("ktsr: max channels found\n");
goto fail;
}
@ -379,7 +379,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) {
default:
/* streams also have their own chunks like 0x09D4F415, not needed here */
VGM_LOG("KTSR: unknown subheader at %lx\n", offset);
VGM_LOG("ktsr: unknown subheader at %x\n", offset);
goto fail;
}
@ -388,7 +388,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) {
return 1;
fail:
VGM_LOG("KTSR: error parsing subheader\n");
VGM_LOG("ktsr: error parsing subheader\n");
return 0;
}
@ -419,7 +419,7 @@ static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id) {
/* more configs than sounds is possible so we need target_id first */
off_t offset, end, name_offset;
uint32_t offset, end, name_offset;
uint32_t stream_id;
offset = 0x40;
@ -447,7 +447,7 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id
}
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
off_t offset, end, header_offset, name_offset;
uint32_t offset, end, header_offset, name_offset;
uint32_t stream_id = 0, stream_count;
/* 00: KTSR
@ -486,7 +486,6 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
break;
case 0xC5CCCB70: /* sound (internal data or external stream) */
//VGM_LOG("info at %lx\n", offset);
ktsr->total_subsongs++;
/* sound table:
@ -503,13 +502,12 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
if (ktsr->total_subsongs == ktsr->target_subsong) {
//;VGM_LOG("KTSR: target at %lx\n", offset);
stream_id = read_u32be(offset + 0x08,sf);
//ktsr->is_external = read_u16le(offset + 0x0e,sf);
stream_count = read_u32le(offset + 0x10,sf);
if (stream_count != 1) {
VGM_LOG("KTSR: unknown stream count\n");
VGM_LOG("ktsr: unknown stream count\n");
goto fail;
}
@ -527,7 +525,7 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
default:
/* streams also have their own chunks like 0x09D4F415, not needed here */
VGM_LOG("KTSR: unknown chunk at %lx\n", offset);
VGM_LOG("ktsr: unknown chunk at %x\n", offset);
goto fail;
}
@ -542,5 +540,6 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
return 1;
fail:
vgm_logi("KTSR: unknown variation (report)\n");
return 0;
}

View File

@ -962,4 +962,6 @@ VGMSTREAM* init_vgmstream_bnk_relic(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_xsh_xsd_xss(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf);
#endif /*_META_H*/

View File

@ -1,139 +1,152 @@
#include "meta.h"
#include "../coding/coding.h"
/* P3D - from Radical's Prototype 1/2 (PC/PS3/X360) */
VGMSTREAM * init_vgmstream_p3d(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset, parse_offset, name_offset = 0;
/* P3D - from Radical's Prototype 1/2 (PC/PS3/X360), Spider-Man 4 Beta (X360) */
VGMSTREAM* init_vgmstream_p3d(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset, offset, name_offset = 0;
size_t header_size, file_size, data_size;
int loop_flag = 0, channel_count, sample_rate, codec;
int i, name_count, text_len, block_size = 0, block_count = 0, num_samples;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
uint32_t xma2_offset = 0, xma2_size = 0;
int loop_flag, channels, sample_rate, codec;
int i, name_count, text_len, block_size = 0, num_samples;
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
/* checks */
if (!check_extensions(streamFile,"p3d"))
if (!is_id32be(0x00,sf, "P3D\xFF") && /* LE: PC */
!is_id32le(0x00,sf, "P3D\xFF")) /* BE: PS3, X360 */
goto fail;
if (read_32bitBE(0x0,streamFile) != 0x503344FF && /* "P3D"\FF (LE: PC) */
read_32bitBE(0x0,streamFile) != 0xFF443350) /* \FF"D3P" (BE: PS3, X360) */
if (!check_extensions(sf,"p3d"))
goto fail;
read_32bit = read_32bitBE(0x0,streamFile) == 0xFF443350 ? read_32bitBE : read_32bitLE;
file_size = get_streamfile_size(streamFile);
read_u32 = guess_endianness32bit(0x04,sf) ? read_u32be : read_u32le;
file_size = get_streamfile_size(sf);
/* base header */
header_size = read_32bit(0x4,streamFile);
if (0x0C != header_size) goto fail;
if (read_32bit(0x08,streamFile) != file_size) goto fail;
if (read_32bit(0x0C,streamFile) != 0xFE000000) goto fail; /* fixed */
if (read_32bit(0x10,streamFile) + header_size != file_size) goto fail;
if (read_32bit(0x14,streamFile) + header_size != file_size) goto fail; /* body size again */
if (read_32bit(0x18,streamFile) != 0x0000000A) goto fail; /* fixed */
header_size = read_u32(0x04,sf);
if (header_size != 0x0C) goto fail;
if (read_u32(0x08,sf) != file_size) goto fail;
if (read_u32(0x0C,sf) != 0xFE000000) goto fail; /* fixed */
if (read_u32(0x10,sf) + header_size != file_size) goto fail;
if (read_u32(0x14,sf) + header_size != file_size) goto fail; /* body size again */
if (read_u32(0x18,sf) != 0x0000000A) goto fail; /* fixed */
/* header text */
parse_offset = 0x1C;
text_len = read_32bit(parse_offset,streamFile);
if (9 != text_len) goto fail;
parse_offset += 4;
offset = 0x1C;
text_len = read_u32(offset,sf);
if (text_len != 9) goto fail;
offset += 0x04;
/* check the type as P3D is just a generic container used in Radical's games */
if (read_32bitBE(parse_offset+0x00,streamFile) != 0x41756469 ||
read_32bitBE(parse_offset+0x04,streamFile) != 0x6F46696C ||
read_16bitBE(parse_offset+0x08,streamFile) != 0x6500) goto fail; /* "AudioFile\0" */
parse_offset += text_len + 1;
if (!is_id64be(offset+0x00,sf, "AudioFil") || read_u16be(offset+0x08,sf) != 0x6500) /* "AudioFile\0" */
goto fail;
offset += text_len + 0x01;
/* file names: always 2 (repeated); but if it's 3 there is an extra string later */
name_count = read_32bit(parse_offset,streamFile);
name_count = read_u32(offset,sf);
if (name_count != 2 && name_count != 3) goto fail; /* 2: Prototype1, 3: Prototype2 */
parse_offset += 4;
offset += 0x04;
/* skip names */
for (i = 0; i < 2; i++) {
if (!name_offset)
name_offset = parse_offset + 4;
text_len = read_32bit(parse_offset,streamFile) + 1; /* null-terminated */
parse_offset += 4 + text_len;
name_offset = offset + 0x04;
text_len = read_u32(offset,sf) + 1; /* null-terminated */
offset += 0x04 + text_len;
}
/* info count? */
if (0x01 != read_32bit(parse_offset,streamFile)) goto fail;
parse_offset += 4;
if (0x01 != read_u32(offset,sf)) goto fail;
offset += 0x04;
/* next string can be used as a codec id */
text_len = read_32bit(parse_offset,streamFile);
codec = read_32bitBE(parse_offset+4,streamFile);
parse_offset += 4 + text_len + 1;
text_len = read_u32(offset,sf);
codec = read_u32be(offset+0x04,sf);
offset += 0x04 + text_len + 0x01;
/* extra "Music" string in Prototype 2 */
if (name_count == 3) {
text_len = read_32bit(parse_offset,streamFile) + 1; /* null-terminated */
parse_offset += 4 + text_len;
text_len = read_u32(offset,sf) + 1; /* null-terminated */
offset += 0x04 + text_len;
}
loop_flag = 0;
/* sub-header per codec */
switch(codec) {
case 0x72616470: /* "radp" (PC) */
if (read_32bitBE(parse_offset,streamFile) != 0x52414450) goto fail; /* "RADP" */
parse_offset += 0x04;
channel_count = read_32bit(parse_offset+0x00,streamFile);
sample_rate = read_32bit(parse_offset+0x04,streamFile);
if (!is_id32be(offset,sf, "RADP"))
goto fail;
offset += 0x04;
channels = read_u32(offset+0x00,sf);
sample_rate = read_u32(offset+0x04,sf);
/* 0x08: ? (0x0F) */
data_size = read_32bit(parse_offset+0x0c,streamFile);
block_size = 0x14;
num_samples = data_size / block_size / channel_count * 32;
start_offset = parse_offset+0x10;
data_size = read_u32(offset+0x0c,sf);
block_size = 0x14;
num_samples = data_size / block_size / channels * 32;
start_offset = offset + 0x10;
break;
case 0x6D703300: /* "mp3\0" (PS3) */
if ((read_32bitBE(parse_offset,streamFile) & 0xFFFFFF00) != 0x6D703300) goto fail; /* "mp3" */
parse_offset += 0x03;
if ((read_u32be(offset,sf) & 0xFFFFFF00) != get_id32be("mp3\0"))
goto fail;
offset += 0x03;
/* all fields LE even though the prev parts were BE */
sample_rate = read_32bitLE(parse_offset+0x00,streamFile);
sample_rate = read_s32le(offset+0x00,sf);
/* 0x04: mp3 sample rate (ex. @0x00 is 47999 and @0x04 is 48000) */
num_samples = read_32bitLE(parse_offset+0x08,streamFile);
data_size = read_32bitLE(parse_offset+0x0c,streamFile);
channel_count = read_32bitLE(parse_offset+0x10,streamFile);
block_size = read_32bitLE(parse_offset+0x14,streamFile);
num_samples = num_samples / channel_count; /* total samples */
start_offset = parse_offset+0x18;
num_samples = read_s32le(offset+0x08,sf);
data_size = read_u32le(offset+0x0c,sf);
channels = read_s32le(offset+0x10,sf);
block_size = read_u32le(offset+0x14,sf);
num_samples = num_samples / channels; /* total samples */
start_offset = offset + 0x18;
break;
case 0x786D6100: /* "xma\0" (X360) */
if (read_32bitBE(parse_offset,streamFile) != 0x584D4132) goto fail; /* "XMA2" */
parse_offset += 0x04;
/* 0x00: subheader size? (0x2c), 0x04: seek table size */
data_size = read_32bitBE(parse_offset+0x08,streamFile);
/* 0x0c: ?, 0x10: ?, 0x14/18: 0x0 */
sample_rate = read_32bitBE(parse_offset+0x1c,streamFile);
/* 0x20: XMA decoder params, 0x24: abr */
block_size = read_32bitBE(parse_offset+0x28,streamFile);
num_samples = read_32bitBE(parse_offset+0x2c,streamFile);
/* 0x30: original file's samples */
block_count = read_32bitBE(parse_offset+0x34,streamFile);
channel_count = read_8bit(parse_offset+0x38,streamFile);
/* 0x39: channel related? (stream config? channel layout?) */
start_offset = parse_offset + 0x3c + read_32bitBE(parse_offset+0x04,streamFile);
case 0x786D6100: { /* "xma\0" (X360) */
uint32_t seek_size;
if (!is_id32be(offset,sf, "XMA2"))
goto fail;
offset += 0x04;
xma2_size = read_u32be(offset+0x00,sf);
seek_size = read_u32be(offset+0x04,sf);
data_size = read_u32be(offset+0x08,sf);
/* 0x0c: ? */
xma2_offset = offset+0x10;
if (!read_u8(xma2_offset+0x00, sf)) /* needs "xma2" chunk (Spider-Man 4 beta has multi-streams) */
goto fail;
/* loops never set */
xma2_parse_xma2_chunk(sf, xma2_offset, &channels, &sample_rate, &loop_flag, &num_samples, NULL, NULL);
start_offset = offset + 0x10 + xma2_size + seek_size;
break;
}
default:
VGM_LOG("P3D: unknown codec 0x%04x\n", codec);
vgm_logi("P3D: unknown codec 0x%04x\n", codec);
goto fail;
}
if (start_offset + data_size != file_size) goto fail;
if (start_offset + data_size != file_size)
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_P3D;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->meta_type = meta_P3D;
if (name_offset)
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile);
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,sf);
/* codec init */
switch(codec) {
case 0x72616470: /* "radp" (PC) */
vgmstream->coding_type = coding_RAD_IMA_mono;
@ -149,7 +162,7 @@ VGMSTREAM * init_vgmstream_p3d(STREAMFILE *streamFile) {
cfg.data_size = data_size;
/* block_size * 3 = frame size (0x60*3=0x120 or 0x40*3=0xC0) but doesn't seem to have any significance) */
vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_P3D, &cfg);
vgmstream->codec_data = init_mpeg_custom(sf, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_P3D, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
break;
@ -161,23 +174,24 @@ VGMSTREAM * init_vgmstream_p3d(STREAMFILE *streamFile) {
uint8_t buf[0x100];
size_t bytes;
bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->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);
if ( !vgmstream->codec_data ) goto fail;
//TODO: some in Spider-Man 4 beta use 18ch but ffmpeg supports max 16ch XMA2
bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf, sizeof(buf), xma2_offset, xma2_size, data_size, sf);
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, start_offset,data_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, 0, 1,1); /* samples needs adjustment */
xma_fix_raw_samples(vgmstream, sf, start_offset, data_size, 0, 1,1); /* samples needs adjustment */
break;
}
#endif
default:
VGM_LOG("P3D: unknown codec 0x%04x\n", codec);
vgm_logi("P3D: unknown codec 0x%04x\n", codec);
goto fail;
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;

View File

@ -0,0 +1,735 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util/m2_psb.h"
#include "../layout/layout.h"
#define PSB_MAX_LAYERS 2
typedef enum { PCM, RIFF_AT3, XMA2, MSADPCM, XWMA, DSP, OPUSNX, RIFF_AT9, VAG } psb_codec_t;
typedef struct {
const char* id; /* format */
const char* spec; /* platform */
const char* ext; /* codec extension (not always) */
const char* voice; /* base name (mandatory) */
const char* file; /* original name, often but not always same as voice (optional?) */
const char* uniq; /* unique name, typically same as file without extension (optional) */
const char* wav; /* same as file (optional) */
} psb_temp_t;
typedef struct {
psb_temp_t* tmp;
psb_codec_t codec;
char readable_name[STREAM_NAME_SIZE];
int total_subsongs;
int target_subsong;
/* chunks references */
uint32_t stream_offset[PSB_MAX_LAYERS];
uint32_t stream_size[PSB_MAX_LAYERS];
uint32_t body_offset;
uint32_t body_size;
uint32_t intro_offset;
uint32_t intro_size;
uint32_t fmt_offset;
uint32_t fmt_size;
uint32_t dpds_offset;
uint32_t dpds_size;
int layers;
int channels;
int format;
int sample_rate;
int block_size;
int avg_bitrate;
int bps;
int32_t num_samples;
int32_t body_samples;
int32_t intro_samples;
int32_t skip_samples;
int loop_flag;
int32_t loop_start;
int32_t loop_end;
} psb_header_t;
static int parse_psb(STREAMFILE* sf, psb_header_t* psb);
static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb);
static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb);
/* PSB - M2 container [Sega Vintage Collection (multi), Legend of Mana (multi)] */
VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
psb_header_t psb = {0};
/* checks */
if (!is_id32be(0x00,sf, "PSB\0"))
goto fail;
if (!check_extensions(sf, "psb"))
goto fail;
if (!parse_psb(sf, &psb))
goto fail;
/* handle subfiles */
{
const char* ext = NULL;
VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL;
switch(psb.codec) {
case RIFF_AT3: /* Sega Vintage Collection (PS3) */
ext = "at3";
init_vgmstream = init_vgmstream_riff;
break;
case VAG: /* Plastic Memories (Vita), Judgment (PS4) */
ext = "vag";
init_vgmstream = init_vgmstream_vag;
break;
case RIFF_AT9: /* Plastic Memories (Vita) */
ext = "at9";
init_vgmstream = init_vgmstream_riff;
break;
default:
break;
}
if (init_vgmstream != NULL) {
STREAMFILE* temp_sf = setup_subfile_streamfile(sf, psb.stream_offset[0], psb.stream_size[0], ext);
if (!temp_sf) goto fail;
vgmstream = init_vgmstream(temp_sf);
close_streamfile(temp_sf);
if (!vgmstream) goto fail;
vgmstream->num_streams = psb.total_subsongs;
strncpy(vgmstream->stream_name, psb.readable_name, STREAM_NAME_SIZE);
return vgmstream;
}
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(psb.channels, psb.loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_PSB;
vgmstream->sample_rate = psb.sample_rate;
vgmstream->num_samples = psb.num_samples;
vgmstream->loop_start_sample = psb.loop_start;
vgmstream->loop_end_sample = psb.loop_end;
vgmstream->num_streams = psb.total_subsongs;
vgmstream->stream_size = psb.stream_size[0];
switch(psb.codec) {
case PCM:
switch(psb.bps) {
case 16: vgmstream->coding_type = coding_PCM16LE; break; /* Legend of Mana (PC), Namco Museum Archives Vol.1 (PC) */
case 24: vgmstream->coding_type = coding_PCM24LE; break; /* Legend of Mana (PC) */
default: goto fail;
}
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = psb.block_size / psb.channels;
if (!vgmstream->num_samples)
vgmstream->num_samples = pcm_bytes_to_samples(psb.stream_size[0], psb.channels, psb.bps);
break;
case MSADPCM: /* [Senxin Aleste (AC)] */
vgmstream->coding_type = coding_MSADPCM;
vgmstream->layout_type = layout_none;
vgmstream->frame_size = psb.block_size;
if (!vgmstream->num_samples)
vgmstream->num_samples = msadpcm_bytes_to_samples(psb.stream_size[0], psb.block_size, psb.channels);
break;
#ifdef VGM_USE_FFMPEG
case XWMA: { /* [Senxin Aleste (AC)] */
vgmstream->codec_data = init_ffmpeg_xwma(sf, psb.stream_offset[0], psb.stream_size[0], psb.format, psb.channels, psb.sample_rate, psb.avg_bitrate, psb.block_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
if (!vgmstream->num_samples) {
vgmstream->num_samples = xwma_dpds_get_samples(sf, psb.dpds_offset, psb.dpds_size, psb.channels, 0);
}
break;
}
case XMA2: { /* Sega Vintage Collection (X360) */
uint8_t buf[0x100];
size_t bytes;
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, sizeof(buf), psb.fmt_offset, psb.fmt_size, psb.stream_size[0], sf, 1);
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, psb.stream_offset[0], psb.stream_size[0]);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
xma_fix_raw_samples(vgmstream, sf, psb.stream_offset[0], psb.stream_size[0], psb.fmt_offset, 1,1);
break;
}
case OPUSNX: { /* Legend of Mana (Switch) */
vgmstream->layout_data = build_segmented_psb_opus(sf, &psb);
if (!vgmstream->layout_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_segmented;
break;
}
#endif
case DSP: /* Legend of Mana (Switch) */
/* standard DSP resources */
if (psb.layers > 1) {
/* somehow R offset can go before L, use layered */
vgmstream->layout_data = build_layered_psb(sf, &psb);
if (!vgmstream->layout_data) goto fail;
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_layered;
}
else {
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_none;
dsp_read_coefs_le(vgmstream,sf, psb.stream_offset[0] + 0x1c, 0);
dsp_read_hist_le(vgmstream,sf, psb.stream_offset[0] + 0x1c + 0x20, 0);
}
vgmstream->num_samples = read_u32le(psb.stream_offset[0] + 0x00, sf);
break;
default:
goto fail;
}
strncpy(vgmstream->stream_name, psb.readable_name, STREAM_NAME_SIZE);
if (!vgmstream_open_stream(vgmstream, sf, psb.stream_offset[0]))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb) {
segmented_layout_data* data = NULL;
int i, pos = 0, segment_count = 0, max_count = 2;
//TODO improve
//TODO these use standard switch opus (VBR), could sub-file? but skip_samples becomes more complex
uint32_t offsets[] = {psb->intro_offset, psb->body_offset};
uint32_t sizes[] = {psb->intro_size, psb->body_size};
uint32_t samples[] = {psb->intro_samples, psb->body_samples};
uint32_t skips[] = {0, psb->skip_samples};
/* intro + body (looped songs) or just body (standard songs)
in full loops intro is 0 samples with a micro 1-frame opus [Nekopara (Switch)] */
if (offsets[0] && samples[0])
segment_count++;
if (offsets[1] && samples[1])
segment_count++;
/* init layout */
data = init_layout_segmented(segment_count);
if (!data) goto fail;
for (i = 0; i < max_count; i++) {
if (!offsets[i] || !samples[i])
continue;
#ifdef VGM_USE_FFMPEG
{
int start = read_u32le(offsets[i] + 0x10, sf) + 0x08;
int skip = read_s16le(offsets[i] + 0x1c, sf);
VGMSTREAM* v = allocate_vgmstream(psb->channels, 0);
if (!v) goto fail;
data->segments[pos++] = v;
v->sample_rate = psb->sample_rate;
v->num_samples = samples[i];
v->codec_data = init_ffmpeg_switch_opus(sf, offsets[i] + start, sizes[i] - start, psb->channels, skips[i] + skip, psb->sample_rate);
if (!v->codec_data) goto fail;
v->coding_type = coding_FFmpeg;
v->layout_type = layout_none;
}
#else
goto fail;
#endif
}
if (!setup_layout_segmented(data))
goto fail;
return data;
fail:
free_layout_segmented(data);
return NULL;
}
static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb) {
layered_layout_data* data = NULL;
int i;
/* init layout */
data = init_layout_layered(psb->layers);
if (!data) goto fail;
for (i = 0; i < psb->layers; i++) {
STREAMFILE* temp_sf = NULL;
VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL;
const char* extension = NULL;
switch (psb->codec) {
case DSP:
extension = "adpcm";
init_vgmstream = init_vgmstream_ngc_dsp_std_le;
break;
default:
goto fail;
}
temp_sf = setup_subfile_streamfile(sf, psb->stream_offset[i], psb->stream_size[i], extension);
if (!temp_sf) goto fail;
data->layers[i] = init_vgmstream(temp_sf);
close_streamfile(temp_sf);
if (!data->layers[i]) goto fail;
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data))
goto fail;
return data;
fail:
free_layout_layered(data);
return NULL;
}
/*****************************************************************************/
static int prepare_fmt(STREAMFILE* sf, psb_header_t* psb) {
uint32_t offset = psb->fmt_offset;
if (!offset)
return 1; /* other codec, probably */
psb->format = read_u16le(offset + 0x00,sf);
if (psb->format == 0x6601) { /* X360 */
psb->format = read_u16be(offset + 0x00,sf);
psb->channels = read_u16be(offset + 0x02,sf);
psb->sample_rate = read_u32be(offset + 0x04,sf);
xma2_parse_fmt_chunk_extra(sf,
offset,
&psb->loop_flag,
&psb->num_samples,
&psb->loop_start,
&psb->loop_end,
1);
}
else {
psb->channels = read_u16le(offset + 0x02,sf);
psb->sample_rate = read_u32le(offset + 0x04,sf);
psb->avg_bitrate = read_u32le(offset + 0x08,sf);
psb->block_size = read_u16le(offset + 0x0c,sf);
psb->bps = read_u16le(offset + 0x0e,sf);
/* 0x10+ varies */
switch(psb->format) {
case 0x0002:
if (!msadpcm_check_coefs(sf, offset + 0x14))
goto fail;
break;
default:
break;
}
}
return 1;
fail:
return 0;
}
static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
const char* spec = psb->tmp->spec;
const char* ext = psb->tmp->ext;
/* try fmt (most common) */
if (psb->format != 0) {
switch(psb->format) {
case 0x01:
psb->codec = PCM;
break;
case 0x02:
psb->codec = MSADPCM;
break;
case 0x161:
psb->codec = XWMA;
break;
case 0x166:
psb->codec = XMA2;
break;
default:
goto fail;
}
return 1;
}
/* try console strings */
if (!spec)
goto fail;
if (strcmp(spec, "nx") == 0) {
if (!ext)
goto fail;
if (strcmp(ext, ".opus") == 0) {
psb->codec = OPUSNX;
psb->body_samples -= psb->skip_samples;
if (!psb->loop_flag)
psb->loop_flag = psb->intro_samples > 0;
psb->loop_start = psb->intro_samples;
psb->loop_end = psb->body_samples + psb->intro_samples;
psb->num_samples = psb->intro_samples + psb->body_samples;
return 1;
}
if (strcmp(ext, ".adpcm") == 0) {
psb->codec = DSP;
psb->channels = psb->layers;
return 1;
}
}
if (strcmp(spec, "ps3") == 0) {
psb->codec = RIFF_AT3;
return 1;
}
if (strcmp(spec, "vita") == 0 || strcmp(spec, "ps4") == 0) {
if (is_id32be(psb->stream_offset[0], sf, "RIFF"))
psb->codec = RIFF_AT9;
else
psb->codec = VAG;
return 1;
}
fail:
vgm_logi("PSB: unknown codec (report)\n");
return 0;
}
static int prepare_name(psb_header_t* psb) {
char* buf = psb->readable_name;
int buf_size = sizeof(psb->readable_name);
const char* main_name = psb->tmp->voice;
const char* sub_name = psb->tmp->uniq;
int main_len;
if (!sub_name)
sub_name = psb->tmp->wav;
if (!sub_name)
sub_name = psb->tmp->file;
if (!main_name) /* shouldn't happen */
return 1;
/* sometimes we have main="bgm01", sub="bgm01.wav" = detect and ignore */
main_len = strlen(main_name);
if (sub_name && strncmp(main_name, sub_name, main_len) == 0) {
if (sub_name[main_len] == '\0' || strcmp(sub_name + main_len, ".wav") == 0)
sub_name = NULL;
}
if (sub_name) {
snprintf(buf, buf_size, "%s/%s", main_name, sub_name);
}
else {
snprintf(buf, buf_size, "%s", main_name);
}
return 1;
}
static int prepare_psb_extra(STREAMFILE* sf, psb_header_t* psb) {
if (!prepare_fmt(sf, psb))
goto fail;
if (!prepare_codec(sf, psb))
goto fail;
if (!prepare_name(psb))
goto fail;
return 1;
fail:
return 0;
}
/* channelList is an array (N layers, though typically only mono codecs like DSP) of objects:
* - archData: resource offset (RIFF) or sub-object
* - data/fmt/loop/wav
* - data/ext/samprate
* - body/channelCount/ext/intro/loop/samprate [Legend of Mana (Switch)]
* - body: data/sampleCount/skipSampleCount, intro: data/sampleCount
* - data/dpds/fmt/wav/loop
* - pan: array [N.0 .. 0.N] (when N layers, in practice just a wonky L/R definition)
*/
static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) {
int i;
psb_node_t nchan, narch, nsub, node;
psb->layers = psb_node_get_count(nchans);
if (psb->layers == 0) goto fail;
if (psb->layers > PSB_MAX_LAYERS) goto fail;
for (i = 0; i < psb->layers; i++) {
psb_data_t data;
psb_type_t type;
psb_node_by_index(nchans, i, &nchan);
/* try to get possible keys (without overwritting), results will be handled and validated later as combos get complex */
psb_node_by_key(&nchan, "archData", &narch);
type = psb_node_get_type(&narch);
switch (type) {
case PSB_TYPE_DATA: /* Sega Vintage Collection (PS3) */
data = psb_node_get_result(&narch).data;
psb->stream_offset[i] = data.offset;
psb->stream_size[i] = data.size;
break;
case PSB_TYPE_OBJECT: /* rest */
/* typically:
* - data + fmt + others
* - body {data + fmt} + intro {data + fmt} + others [Legend of Mana (Switch)]
*/
data = psb_node_get_data(&narch, "data");
if (data.offset) {
psb->stream_offset[i] = data.offset;
psb->stream_size[i] = data.size;
}
data = psb_node_get_data(&narch, "fmt");
if (data.offset) {
psb->fmt_offset = data.offset;
psb->fmt_size = data.size;
}
if (psb_node_by_key(&narch, "loop", &node)) {
/* can be found as "false" with body+intro */
if (psb_node_get_type(&node) == PSB_TYPE_ARRAY) {
//todo improve
psb_node_by_index(&node, 0, &nsub);
psb->loop_start = psb_node_get_result(&nsub).num;
psb_node_by_index(&node, 1, &nsub);
psb->loop_end = psb_node_get_result(&nsub).num + psb->loop_start; /* duration */
}
}
if (psb_node_by_key(&narch, "body", &node)) {
data = psb_node_get_data(&node, "data");
psb->body_offset = data.offset;
psb->body_size = data.size;
psb->body_samples = psb_node_get_integer(&node, "sampleCount");
psb->skip_samples = psb_node_get_integer(&node, "skipSampleCount");
}
if (psb_node_by_key(&narch, "intro", &node)) {
data = psb_node_get_data(&node, "data");
psb->intro_offset = data.offset;
psb->intro_size = data.size;
psb->intro_samples = psb_node_get_integer(&node, "sampleCount");
}
data = psb_node_get_data(&narch, "dpds");
if (data.offset) {
psb->dpds_offset = data.offset;
psb->dpds_size = data.size;
}
psb->channels = psb_node_get_integer(&narch, "channelCount");
psb->sample_rate = (int)psb_node_get_float(&narch, "samprate"); /* seen in DSP */
if (!psb->sample_rate)
psb->sample_rate = psb_node_get_integer(&narch, "samprate"); /* seen in OpusNX */
psb->tmp->ext = psb_node_get_string(&narch, "ext"); /* appears for all channels, assumed to be the same */
psb->tmp->wav = psb_node_get_string(&narch, "wav");
/* DSP has a "pan" array like: [1.0, 0.0]=L, [0.0, 1.0 ]=R */
if (psb_node_by_key(&narch, "pan", &node)) {
psb_node_by_index(&node, i, &nsub);
if (psb_node_get_result(&nsub).flt != 1.0f) {
vgm_logi("PSB: unexpected pan (report)\n");
};
}
/* background: false?
*/
break;
default:
goto fail;
}
}
return 1;
fail:
VGM_LOG("psb: can't parse channel\n");
return 0;
}
/* parse a single archive, that can contain extra info here or inside channels */
static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) {
psb_node_t nsong, nchans;
psb->total_subsongs = psb_node_get_count(nvoice);
if (psb->target_subsong == 0) psb->target_subsong = 1;
if (psb->total_subsongs <= 0 || psb->target_subsong > psb->total_subsongs) goto fail;
/* target voice and stream info */
if (!psb_node_by_index(nvoice, psb->target_subsong - 1, &nsong))
goto fail;
psb->tmp->voice = psb_node_get_key(nvoice, psb->target_subsong - 1);
psb_node_by_key(&nsong, "channelList", &nchans);
if (!parse_psb_channels(psb, &nchans))
goto fail;
/* unsure of meaning but must exist (usually 0/1) */
if (psb_node_exists(&nsong, "device") <= 0)
goto fail;
/* names (optional) */
psb->tmp->file = psb_node_get_string(&nsong, "file");
psb->tmp->uniq = psb_node_get_string(&nsong, "uniq");
/* optional loop flag (loop points go in channels, or implicit in fmt/RIFF) */
if (!psb->loop_flag) {
psb->loop_flag = psb_node_get_integer(&nsong, "loop") > 1;
/* There is also loopstr/loopStr = "all" when "loop"=2 and "none" when "loop"=0
* SFX set loop=0, and sometimes songs that look like they could do full loops do too */
}
/* other optional keys:
* - quality: ? (1=MSADPCM, 2=OPUSNX/PCM)
* - priority: f32, -1.0, 1.0 or 10.0 = max?
* - type: 0/1? (internal classification?)
* - volume: 0.0 .. 1.0
* - group?
*/
return 1;
fail:
VGM_LOG("psb: can't parse voice\n");
return 0;
}
/* .psb is binary JSON-like structure that can be used to hold various formats, we want audio data:
* - (root): (object)
* - "id": (format string)
* - "spec": (platform string)
* - "version": (float)
* - "voice": (objects, one per subsong)
* - (voice name 1): (object)
* - "channelList": (array of N objects)
* - "archData": (main audio part, varies per game/platform/codec)
* - "device": ?
* ...
* - (voice name N): ...
* From decompilations, audio code reads common keys up to "archData", then depends on game (not unified).
* Keys are (seemingly) stored in text order.
*/
static int parse_psb(STREAMFILE* sf, psb_header_t* psb) {
psb_temp_t tmp;
psb_context_t* ctx = NULL;
psb_node_t nroot, nvoice;
float version;
psb->tmp = &tmp;
psb->target_subsong = sf->stream_index;
ctx = psb_init(sf);
if (!ctx) goto fail;
//psb_print(ctx);
/* main process */
psb_get_root(ctx, &nroot);
/* format definition, non-audio IDs include "motion", "font", or no "id" at all */
psb->tmp->id = psb_node_get_string(&nroot, "id");
if (!psb->tmp->id || strcmp(psb->tmp->id, "sound_archive") != 0) {
/* "sound" is just a list of available "sound_archive" */
if (psb->tmp->id && strcmp(psb->tmp->id, "sound") == 0)
vgm_logi("PSB: empty archive type '%s' (ignore)\n", psb->tmp->id);
else
vgm_logi("PSB: unsupported archive type '%s' (ignore?)\n", psb->tmp->id);
goto fail;
}
/* platform: x360/ps3/win/nx/etc */
psb->tmp->spec = psb_node_get_string(&nroot, "spec");
/* enforced by M2 code */
version = psb_node_get_float(&nroot, "version");
if (version < 1.02f || version > 1.02f) {
vgm_logi("PSB: unsupported version %f (report)\n", version);
goto fail;
}
/* main subsong */
psb_node_by_key(&nroot, "voice", &nvoice);
if (!parse_psb_voice(psb, &nvoice))
goto fail;
/* post stuff before closing PSB */
if (!prepare_psb_extra(sf, psb))
goto fail;
psb->tmp = NULL;
psb_close(ctx);
return 1;
fail:
psb_close(ctx);
VGM_LOG("psb: can't parse PSB\n");
return 0;
}
#if 0
typedef struct {
void* init;
const char* id32;
const char* exts;
} metadef_t;
metadef_t md_psb = {
.init = init_vgmstream_psb,
.exts = "psb",
.id32 = "PSB\0", //24b/masked IDs?
.id32 = get_id32be("PSB\0"), //???
.idfn = psb_check_id,
}
#endif

View File

@ -6,7 +6,7 @@
static STREAMFILE* setup_ubi_lyn_streamfile(STREAMFILE* sf, off_t stream_offset, size_t interleave_size, int stream_number, int stream_count, size_t logical_size) {
STREAMFILE *new_sf = NULL;
deblock_config_t cfg = {0};
VGM_LOG("so=%lx, chu=%x, n=%i, c=%i, lo=%x\n", stream_offset, interleave_size, stream_number, stream_count, logical_size);
cfg.stream_start = stream_offset;
cfg.chunk_size = interleave_size;
cfg.step_start = stream_number;

View File

@ -38,8 +38,8 @@ typedef struct {
int format;
int channels;
int sample_rate;
int block_align;
int average_bps;
int block_size;
int avg_bitrate;
int bits_per_sample;
uint8_t channel_type;
uint32_t channel_layout;
@ -121,11 +121,11 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
if (ww.fmt_size != 0x14 && ww.fmt_size != 0x28 && ww.fmt_size != 0x18) goto fail; /* oldest, old, new */
if (ww.bits_per_sample != 4) goto fail;
if (ww.block_align != 0x24 * ww.channels) goto fail;
if (ww.block_size != 0x24 * ww.channels) goto fail;
vgmstream->coding_type = coding_WWISE_IMA;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = ww.block_align / ww.channels;
vgmstream->interleave_block_size = ww.block_size / ww.channels;
vgmstream->codec_endian = ww.big_endian;
/* oldest version uses regular XBOX IMA with stereo mode [Shadowrun (PC)] */
@ -155,7 +155,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
cfg.sample_rate = ww.sample_rate;
cfg.big_endian = ww.big_endian;
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */
if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */
/* autodetect format (fields are mostly common, see the end of the file) */
if (ww.vorb_offset) {
@ -305,7 +305,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x08; /* ww.block_align = 0x8 in older Wwise, samples per block in newer Wwise */
vgmstream->interleave_block_size = 0x08; /* ww.block_size = 0x8 in older Wwise, samples per block in newer Wwise */
/* find coef position */
if (ww.wiih_offset) { /* older */
@ -382,40 +382,18 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
}
case XWMA: { /* X360 */
ffmpeg_codec_data *ffmpeg_data = NULL;
uint8_t buf[0x100];
int bytes;
if (ww.fmt_size != 0x18) goto fail;
if (!ww.big_endian) goto fail; /* must be from Wwise X360 (PC LE XWMA is parsed elsewhere) */
bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), ww.format, ww.data_size, ww.channels, ww.sample_rate, ww.average_bps, ww.block_align);
ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, ww.data_offset, ww.data_size);
if ( !ffmpeg_data ) goto fail;
vgmstream->codec_data = ffmpeg_data;
vgmstream->codec_data = init_ffmpeg_xwma(sf, ww.data_offset, ww.data_size, ww.format, ww.channels, ww.sample_rate, ww.avg_bitrate, ww.block_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
/* manually find total samples, why don't they put this in the header is beyond me */
{
ms_sample_data msd = {0};
msd.channels = ww.channels;
msd.data_offset = ww.data_offset;
msd.data_size = ww.data_size;
if (ww.format == 0x0162)
wmapro_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x00E0);
else
wma_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x001F);
vgmstream->num_samples = msd.num_samples;
if (!vgmstream->num_samples)
vgmstream->num_samples = ffmpeg_get_samples(ffmpeg_data); /* very wrong, from avg-br */
//num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2
}
/* seek table seems BE dpds */
vgmstream->num_samples = xwma_dpds_get_samples(sf, ww.seek_offset, ww.seek_size, ww.channels, ww.big_endian);
if (!vgmstream->num_samples)
vgmstream->num_samples = xwma_get_samples(sf, ww.data_offset, ww.data_size, ww.format, ww.channels, ww.sample_rate, ww.block_size);
break;
}
@ -423,7 +401,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
ffmpeg_codec_data * ffmpeg_data = NULL;
if (ww.fmt_size != 0x24) goto fail;
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail;
/* extra: size 0x12, unknown values */
@ -442,7 +420,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
size_t seek_size;
if (ww.fmt_size != 0x28) goto fail;
/* values up to 0x14 seem fixed and similar to HEVAG's (block_align 0x02/04, bits_per_sample 0x10) */
/* values up to 0x14 seem fixed and similar to HEVAG's (block_size 0x02/04, bits_per_sample 0x10) */
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
/* 0x1c: null?
@ -476,7 +454,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
}
case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile), Gears 5 (PC)] */
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail;
/* extra: size 0x12 */
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
@ -526,7 +504,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
int mapping;
opus_config cfg = {0};
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail;
if (!ww.seek_offset) goto fail;
if (ww.channels > 8) goto fail; /* mapping not defined */
@ -596,7 +574,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
case HEVAG: /* PSV */
/* changed values, another bizarre Wwise quirk */
//ww.block_align /* unknown (1ch=2, 2ch=4) */
//ww.block_size /* unknown (1ch=2, 2ch=4) */
//ww.bits_per_sample; /* unknown (0x10) */
//if (ww.bits_per_sample != 4) goto fail;
@ -647,11 +625,11 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
case PTADPCM: /* newer ADPCM [Bayonetta 2 (Switch), Genshin Impact (PC)] */
if (ww.bits_per_sample != 4) goto fail;
if (ww.block_align != 0x24 * ww.channels && ww.block_align != 0x104 * ww.channels) goto fail;
if (ww.block_size != 0x24 * ww.channels && ww.block_size != 0x104 * ww.channels) goto fail;
vgmstream->coding_type = coding_PTADPCM;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = ww.block_align / ww.channels;
vgmstream->interleave_block_size = ww.block_size / ww.channels;
//vgmstream->codec_endian = ww.big_endian; //?
if (ww.truncated) {
@ -827,8 +805,8 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
ww->format = read_u16(ww->fmt_offset + 0x00,sf);
ww->channels = read_u16(ww->fmt_offset + 0x02,sf);
ww->sample_rate = read_u32(ww->fmt_offset + 0x04,sf);
ww->average_bps = read_u32(ww->fmt_offset + 0x08,sf);
ww->block_align = read_u16(ww->fmt_offset + 0x0c,sf);
ww->avg_bitrate = read_u32(ww->fmt_offset + 0x08,sf);
ww->block_size = read_u16(ww->fmt_offset + 0x0c,sf);
ww->bits_per_sample = read_u16(ww->fmt_offset + 0x0e,sf);
if (ww->fmt_size > 0x10 && ww->format != 0x0165 && ww->format != 0x0166) /* ignore XMAWAVEFORMAT */
ww->extra_size = read_u16(ww->fmt_offset + 0x10,sf);
@ -900,7 +878,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
/* few older Wwise DSP with num_samples in extra_size [Tony Hawk: Shred (Wii)] */
ww->codec = DSP;
}
else if (ww->block_align == 0x104 * ww->channels) {
else if (ww->block_size == 0x104 * ww->channels) {
/* Bayonetta 2 (Switch) */
ww->codec = PTADPCM;
}

View File

@ -1,79 +1,101 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util/chunks.h"
typedef struct {
uint32_t data_offset;
uint32_t data_size;
uint32_t dpds_offset;
uint32_t dpds_size;
int loop_flag;
int format;
int channels;
int sample_rate;
int bytes;
int avg_bitrate;
int block_size;
} xwma_header_t;
/* XWMA - Microsoft WMA container [The Elder Scrolls: Skyrim (PC/X360), Hydrophobia (PC)] */
VGMSTREAM * init_vgmstream_xwma(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t fmt_offset, data_offset, first_offset = 0xc;
size_t fmt_size, data_size;
int loop_flag, channel_count;
VGMSTREAM* init_vgmstream_xwma(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
xwma_header_t xwma = {0};
/* checks */
if (!is_id32be(0x00,sf, "RIFF"))
goto fail;
if (!is_id32be(0x08,sf, "XWMA"))
goto fail;
/* .xwma: standard
* .xwm: The Elder Scrolls: Skyrim (PC), Blade Arcus from Shining (PC) */
if (!check_extensions(streamFile, "xwma,xwm"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */
goto fail;
if (read_32bitBE(0x08,streamFile) != 0x58574D41) /* "XWMA" */
if (!check_extensions(sf, "xwma,xwm"))
goto fail;
if ( !find_chunk_le(streamFile, 0x666d7420,first_offset,0, &fmt_offset,&fmt_size) ) /* "fmt "*/
goto fail;
if ( !find_chunk_le(streamFile, 0x64617461,first_offset,0, &data_offset,&data_size) ) /* "data"*/
goto fail;
{
enum {
CHUNK_fmt = 0x666d7420, /* "fmt " */
CHUNK_data = 0x64617461, /* "data" */
CHUNK_dpds = 0x64706473, /* "dpds" */
};
chunk_t rc = {0};
channel_count = read_16bitLE(fmt_offset+0x02,streamFile);
loop_flag = 0;
rc.current = 0x0c;
while (next_chunk(&rc, sf)) {
switch(rc.type) {
case CHUNK_fmt:
xwma.format = read_u16le(rc.offset+0x00, sf);
xwma.channels = read_u16le(rc.offset+0x02, sf);
xwma.sample_rate = read_u32le(rc.offset+0x04, sf);
xwma.avg_bitrate = read_u32le(rc.offset+0x08, sf);
xwma.block_size = read_u16le(rc.offset+0x0c, sf);
break;
case CHUNK_data:
xwma.data_offset = rc.offset;
xwma.data_size = rc.size;
break;
case CHUNK_dpds:
xwma.dpds_offset = rc.offset;
xwma.dpds_size = rc.size;
break;
default:
break;
}
}
if (!xwma.format || !xwma.data_offset)
goto fail;
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag);
vgmstream = allocate_vgmstream(xwma.channels, xwma.loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = read_32bitLE(fmt_offset+0x04, streamFile);
vgmstream->meta_type = meta_XWMA;
vgmstream->sample_rate = xwma.sample_rate;
/* the main purpose of this meta is redoing the XWMA header to:
* - redo header to fix XWMA with buggy bit rates so FFmpeg can play them ok
* - skip seek table to fix FFmpeg buggy XWMA seeking (see init_seek)
* - fix XWMA with buggy bit rates so FFmpeg can play them ok
* - remove seek table to fix FFmpeg buggy XWMA seeking (see init_seek)
* - read num_samples correctly
*/
#ifdef VGM_USE_FFMPEG
{
uint8_t buf[0x100];
int bytes, avg_bps, block_align, wma_codec;
avg_bps = read_32bitLE(fmt_offset+0x08, streamFile);
block_align = (uint16_t)read_16bitLE(fmt_offset+0x0c, streamFile);
wma_codec = (uint16_t)read_16bitLE(fmt_offset+0x00, streamFile);
bytes = ffmpeg_make_riff_xwma(buf,0x100, wma_codec, data_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align);
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, data_offset,data_size);
vgmstream->codec_data = init_ffmpeg_xwma(sf, xwma.data_offset, xwma.data_size, xwma.format, xwma.channels, xwma.sample_rate, xwma.avg_bitrate, xwma.block_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
/* manually find total samples, why don't they put this in the header is beyond me */
{
ms_sample_data msd = {0};
msd.channels = vgmstream->channels;
msd.data_offset = data_offset;
msd.data_size = data_size;
if (wma_codec == 0x0162)
wmapro_get_samples(&msd, streamFile, block_align, vgmstream->sample_rate,0x00E0);
else
wma_get_samples(&msd, streamFile, block_align, vgmstream->sample_rate,0x001F);
vgmstream->num_samples = msd.num_samples;
if (vgmstream->num_samples == 0)
vgmstream->num_samples = ffmpeg_get_samples(vgmstream->codec_data); /* from avg-br */
//num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2
}
/* try from (optional) seek table, or (less accurate) manual count */
vgmstream->num_samples = xwma_dpds_get_samples(sf, xwma.dpds_offset, xwma.dpds_size, xwma.channels, 0);
if (!vgmstream->num_samples)
vgmstream->num_samples = xwma_get_samples(sf, xwma.data_offset, xwma.data_size, xwma.format, xwma.channels, xwma.sample_rate, xwma.block_size);
}
#else
goto fail;

View File

@ -2,21 +2,35 @@
#include "util.h"
#include "vgmstream.h"
/* for dup/fdopen in some systems */
#ifndef _MSC_VER
#include <unistd.h>
#endif
/* For (rarely needed) +2GB file support we use fseek64/ftell64. Those are usually available
* but may depend on compiler.
* - MSVC: +VS2008 should work
* - GCC/MingW: should be available
* - GCC/Linux: should be available but some systems may need __USE_FILE_OFFSET64,
* that we (probably) don't want since that turns off_t to off64_t
* - Clang: seems only defined on Linux/GNU environments, somehow emscripten is out
* (unsure about Clang Win since apparently they define _MSC_VER)
* - Android: API +24 if not using __USE_FILE_OFFSET64
* Not sure if fopen64 is needed in some cases. May be worth adding some compiler flag to enable 64 versions manually.
*/
/* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */
#if defined(__MSVCRT__) || defined(_MSC_VER)
#include <io.h>
/*
#ifndef fseeko
#define fseeko fseek
#define fopen_v fopen
#if (_MSC_VER >= 1400)
#define fseek_v _fseeki64
#define ftell_v _ftelli64
#else
#define fseek_v fseek
#define ftell_v ftell
#endif
#ifndef ftello
#define ftello ftell
#endif
*/
#define fseek_v _fseeki64 //fseek/fseeko
#define ftell_v _ftelli64 //ftell/ftello
#ifdef fileno
#undef fileno
@ -25,16 +39,18 @@
#define fdopen _fdopen
#define dup _dup
#ifndef off64_t
#define off_t __int64
#endif
//#ifndef off64_t
// #define off_t/off64_t __int64
//#endif
#elif defined(XBMC) || defined(__APPLE__)
#elif defined(XBMC) || defined(__EMSCRIPTEN__) || defined (__ANDROID__) || defined(__APPLE__)
#define fopen_v fopen
#define fseek_v fseek
#define ftell_v ftell
#else
#define fopen_v fopen
#define fseek_v fseeko64 //fseeko
#define ftell_v ftello64 //ftelloo
#define ftell_v ftello64 //ftello
#endif
@ -44,11 +60,12 @@ typedef struct {
FILE* infile; /* actual FILE */
char name[PATH_LIMIT]; /* FILE filename */
int name_len; /* cache */
offv_t offset; /* last read offset (info) */
offv_t buf_offset; /* current buffer data start */
uint8_t* buf; /* data buffer */
size_t buf_size; /* max buffer size */
size_t valid_size; /* current buffer size */
size_t valid_size; /* current buffer size */
size_t file_size; /* buffered file size */
} STDIO_STREAMFILE;
@ -61,7 +78,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size
if (!sf->infile || !dst || length <= 0 || offset < 0)
return 0;
//;VGM_LOG("STDIO: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size);
//;VGM_LOG("stdio: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size);
/* is the part of the requested length in the buffer? */
if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) {
@ -72,7 +89,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size
if (buf_limit > length)
buf_limit = length;
//;VGM_LOG("STDIO: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size);
//;VGM_LOG("stdio: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size);
memcpy(dst, sf->buf + buf_into, buf_limit);
read_total += buf_limit;
@ -83,7 +100,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size
#ifdef VGM_DEBUG_OUTPUT
if (offset < sf->buf_offset && length > 0) {
VGM_LOG("STDIO: rebuffer, requested %lx vs %lx (sf %x)\n", offset, sf->buf_offset, (uint32_t)sf);
VGM_LOG("stdio: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf);
//sf->rebuffer++;
//if (rebuffer > N) ...
}
@ -117,7 +134,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size
/* fill the buffer (offset now is beyond buf_offset) */
sf->buf_offset = offset;
sf->valid_size = fread(sf->buf, sizeof(uint8_t), sf->buf_size, sf->infile);
//;VGM_LOG("STDIO: read buf %lx + %x\n", sf->buf_offset, sf->valid_size);
//;VGM_LOG("stdio: read buf %lx + %x\n", sf->buf_offset, sf->valid_size);
/* decide how much must be read this time */
if (length > sf->buf_size)
@ -151,14 +168,12 @@ static offv_t stdio_get_offset(STDIO_STREAMFILE* sf) {
return sf->offset;
}
static void stdio_get_name(STDIO_STREAMFILE* sf, char* name, size_t name_size) {
strncpy(name, sf->name, name_size);
name[name_size - 1] = '\0';
}
static void stdio_close(STDIO_STREAMFILE* sf) {
if (sf->infile)
fclose(sf->infile);
free(sf->buf);
free(sf);
int copy_size = sf->name_len + 1;
if (copy_size > name_size)
copy_size = name_size;
memcpy(name, sf->name, copy_size);
name[copy_size - 1] = '\0';
}
static STREAMFILE* stdio_open(STDIO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
@ -187,11 +202,19 @@ static STREAMFILE* stdio_open(STDIO_STREAMFILE* sf, const char* const filename,
/* on failure just close and try the default path (which will probably fail a second time) */
}
#endif
#endif
// a normal open, open a new file
return open_stdio_streamfile_buffer(filename, buf_size);
}
static void stdio_close(STDIO_STREAMFILE* sf) {
if (sf->infile)
fclose(sf->infile);
free(sf->buf);
free(sf);
}
static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE* infile, const char* const filename, size_t buf_size) {
uint8_t* buf = NULL;
STDIO_STREAMFILE* this_sf = NULL;
@ -213,8 +236,11 @@ static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE* infile, const char
this_sf->buf_size = buf_size;
this_sf->buf = buf;
strncpy(this_sf->name, filename, sizeof(this_sf->name));
this_sf->name[sizeof(this_sf->name)-1] = '\0';
this_sf->name_len = strlen(filename);
if (this_sf->name_len >= sizeof(this_sf->name))
goto fail;
memcpy(this_sf->name, filename, this_sf->name_len);
this_sf->name[this_sf->name_len] = '\0';
/* cache file_size */
if (infile) {
@ -245,7 +271,7 @@ static STREAMFILE* open_stdio_streamfile_buffer(const char* const filename, size
FILE* infile = NULL;
STREAMFILE* sf = NULL;
infile = fopen(filename,"rb");
infile = fopen_v(filename,"rb");
if (!infile) {
/* allow non-existing files in some cases */
if (!vgmstream_is_virtual_filename(filename))
@ -307,7 +333,7 @@ static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, si
#ifdef VGM_DEBUG_OUTPUT
if (offset < sf->buf_offset) {
VGM_LOG("BUFFER: rebuffer, requested %lx vs %lx (sf %x)\n", offset, sf->buf_offset, (uint32_t)sf);
VGM_LOG("buffer: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf);
}
#endif
@ -318,7 +344,7 @@ static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, si
/* ignore requests at EOF */
if (offset >= sf->file_size) {
//offset = sf->file_size; /* seems fseek doesn't clamp offset */
VGM_ASSERT_ONCE(offset > sf->file_size, "BUFFER: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length);
VGM_ASSERT_ONCE(offset > sf->file_size, "buffer: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length);
break;
}
@ -360,10 +386,12 @@ static offv_t buffer_get_offset(BUFFER_STREAMFILE* sf) {
static void buffer_get_name(BUFFER_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static STREAMFILE* buffer_open(BUFFER_STREAMFILE* sf, const char* const filename, size_t buf_size) {
STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
return open_buffer_streamfile(new_inner_sf, buf_size); /* original buffer size is preferable? */
}
static void buffer_close(BUFFER_STREAMFILE* sf) {
sf->inner_sf->close(sf->inner_sf);
free(sf->buf);
@ -435,12 +463,14 @@ static size_t wrap_get_size(WRAP_STREAMFILE* sf) {
static offv_t wrap_get_offset(WRAP_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
}
static void wrap_get_name(WRAP_STREAMFILE* sf, char* name, size_t name_len) {
sf->inner_sf->get_name(sf->inner_sf, name, name_len); /* default */
static void wrap_get_name(WRAP_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static void wrap_open(WRAP_STREAMFILE* sf, const char* const filename, size_t buf_size) {
sf->inner_sf->open(sf->inner_sf, filename, buf_size); /* default (don't wrap) */
static STREAMFILE* wrap_open(WRAP_STREAMFILE* sf, const char* const filename, size_t buf_size) {
return sf->inner_sf->open(sf->inner_sf, filename, buf_size); /* default (don't call open_wrap_streamfile) */
}
static void wrap_close(WRAP_STREAMFILE* sf) {
//sf->inner_sf->close(sf->inner_sf); /* don't close */
free(sf);
@ -503,9 +533,10 @@ static size_t clamp_get_size(CLAMP_STREAMFILE* sf) {
static offv_t clamp_get_offset(CLAMP_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf) - sf->start;
}
static void clamp_get_name(CLAMP_STREAMFILE* sf, char* name, size_t name_len) {
sf->inner_sf->get_name(sf->inner_sf, name, name_len); /* default */
static void clamp_get_name(CLAMP_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static STREAMFILE* clamp_open(CLAMP_STREAMFILE* sf, const char* const filename, size_t buf_size) {
char original_filename[PATH_LIMIT];
STREAMFILE* new_inner_sf = NULL;
@ -520,6 +551,7 @@ static STREAMFILE* clamp_open(CLAMP_STREAMFILE* sf, const char* const filename,
return new_inner_sf;
}
}
static void clamp_close(CLAMP_STREAMFILE* sf) {
sf->inner_sf->close(sf->inner_sf);
free(sf);
@ -564,14 +596,15 @@ typedef struct {
STREAMFILE* inner_sf;
void* data; /* state for custom reads, malloc'ed + copied on open (to re-open streamfiles cleanly) */
size_t data_size;
size_t (*read_callback)(STREAMFILE*, uint8_t*, offv_t, size_t, void*); /* custom read to modify data before copying into buffer */
size_t (*read_callback)(STREAMFILE*, uint8_t*, off_t, size_t, void*); /* custom read to modify data before copying into buffer */
size_t (*size_callback)(STREAMFILE*, void*); /* size when custom reads make data smaller/bigger than underlying streamfile */
int (*init_callback)(STREAMFILE*, void*); /* init the data struct members somehow, return >= 0 if ok */
void (*close_callback)(STREAMFILE*, void*); /* close the data struct members somehow */
/* read doesn't use offv_t since callbacks would need to be modified */
} IO_STREAMFILE;
static size_t io_read(IO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
return sf->read_callback(sf->inner_sf, dst, offset, length, sf->data);
return sf->read_callback(sf->inner_sf, dst, (off_t)offset, length, sf->data);
}
static size_t io_get_size(IO_STREAMFILE* sf) {
if (sf->size_callback)
@ -582,13 +615,15 @@ static size_t io_get_size(IO_STREAMFILE* sf) {
static offv_t io_get_offset(IO_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
}
static void io_get_name(IO_STREAMFILE* sf, char* name, size_t name_len) {
sf->inner_sf->get_name(sf->inner_sf, name, name_len); /* default */
static void io_get_name(IO_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static STREAMFILE* io_open(IO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
return open_io_streamfile_ex(new_inner_sf, sf->data, sf->data_size, sf->read_callback, sf->size_callback, sf->init_callback, sf->close_callback);
}
static void io_close(IO_STREAMFILE* sf) {
if (sf->close_callback)
sf->close_callback(sf->inner_sf, sf->data);
@ -633,7 +668,7 @@ STREAMFILE* open_io_streamfile_ex(STREAMFILE* sf, void* data, size_t data_size,
}
return &this_sf->vt;
fail:
if (this_sf) free(this_sf->data);
free(this_sf);
@ -661,6 +696,7 @@ typedef struct {
STREAMFILE* inner_sf;
char fakename[PATH_LIMIT];
int fakename_len;
} FAKENAME_STREAMFILE;
static size_t fakename_read(FAKENAME_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
@ -673,9 +709,13 @@ static offv_t fakename_get_offset(FAKENAME_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
}
static void fakename_get_name(FAKENAME_STREAMFILE* sf, char* name, size_t name_size) {
strncpy(name,sf->fakename, name_size);
name[name_size - 1] = '\0';
int copy_size = sf->fakename_len + 1;
if (copy_size > name_size)
copy_size = name_size;
memcpy(name, sf->fakename, copy_size);
name[copy_size - 1] = '\0';
}
static STREAMFILE* fakename_open(FAKENAME_STREAMFILE* sf, const char* const filename, size_t buf_size) {
/* detect re-opening the file */
if (strcmp(filename, sf->fakename) == 0) {
@ -716,7 +756,7 @@ STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const
/* copy passed name or retain current, and swap extension if expected */
if (fakename) {
strcpy(this_sf->fakename,fakename);
strcpy(this_sf->fakename, fakename);
} else {
sf->get_name(sf, this_sf->fakename, PATH_LIMIT);
}
@ -731,6 +771,8 @@ STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const
strcat(this_sf->fakename, fakeext);
}
this_sf->fakename_len = strlen(this_sf->fakename);
return &this_sf->vt;
}
STREAMFILE* open_fakename_streamfile_f(STREAMFILE* sf, const char* fakename, const char* fakeext) {
@ -797,6 +839,7 @@ static offv_t multifile_get_offset(MULTIFILE_STREAMFILE* sf) {
static void multifile_get_name(MULTIFILE_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sfs[0]->get_name(sf->inner_sfs[0], name, name_size);
}
static STREAMFILE* multifile_open(MULTIFILE_STREAMFILE* sf, const char* const filename, size_t buf_size) {
char original_filename[PATH_LIMIT];
STREAMFILE* new_sf = NULL;
@ -1219,7 +1262,7 @@ STREAMFILE* read_filemap_file_pos(STREAMFILE* sf, int file_num, int* p_pos) {
/* get key/val (ignores lead/trailing spaces, stops at comment/separator) */
ok = sscanf(line, " %[^\t#:] : %[^\t#\r\n] ", key, val);
if (ok != 2) { /* ignore line if no key=val (comment or garbage) */
continue;
continue;
}
if (strcmp(key, filename) == 0) {
@ -1327,7 +1370,6 @@ static int find_chunk_internal(STREAMFILE* sf, uint32_t chunk_id, off_t start_of
while (offset < max_offset) {
uint32_t chunk_type = read_32bit_type(offset + 0x00,sf);
uint32_t chunk_size = read_32bit_size(offset + 0x04,sf);
//;VGM_LOG("CHUNK: type=%x, size=%x at %lx\n", chunk_type, chunk_size, offset);
if (chunk_type == 0xFFFFFFFF || chunk_size == 0xFFFFFFFF)
return 0;
@ -1454,7 +1496,7 @@ void dump_streamfile(STREAMFILE* sf, int num) {
get_streamfile_filename(sf, filename, sizeof(filename));
snprintf(dumpname, sizeof(dumpname), "%s_%02i.dump", filename, num);
f = fopen(dumpname,"wb");
f = fopen_v(dumpname,"wb");
if (!f) return;
}
@ -1465,7 +1507,7 @@ void dump_streamfile(STREAMFILE* sf, int num) {
bytes = read_streamfile(buf, offset, sizeof(buf), sf);
if(!bytes) {
VGM_LOG("dump streamfile: can't read at %lx\n", offset);
VGM_LOG("dump streamfile: can't read at %x\n", (uint32_t)offset);
break;
}

View File

@ -20,10 +20,6 @@
/* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */
#if defined(__MSVCRT__) || defined(_MSC_VER)
#include <io.h>
#ifndef off64_t
#define off_t __int64
#endif
#endif
#ifndef DIR_SEPARATOR
@ -66,16 +62,16 @@ typedef struct _STREAMFILE {
size_t (*get_size)(struct _STREAMFILE* sf);
//todo: DO NOT USE, NOT RESET PROPERLY (remove?)
offv_t (*get_offset)(struct _STREAMFILE*);
offv_t (*get_offset)(struct _STREAMFILE* sf);
/* copy current filename to name buf */
void (*get_name)(struct _STREAMFILE* sf, char* name, size_t name_size);
/* open another streamfile from filename */
struct _STREAMFILE* (*open)(struct _STREAMFILE* sf, const char* const filename, size_t buffer_size);
struct _STREAMFILE* (*open)(struct _STREAMFILE* sf, const char* const filename, size_t buf_size);
/* free current STREAMFILE */
void (*close)(struct _STREAMFILE*);
void (*close)(struct _STREAMFILE* sf);
/* Substream selection for formats with subsongs.
* Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */
@ -231,41 +227,22 @@ static inline uint64_t read_u64be(off_t offset, STREAMFILE* sf) { return (uint64
static inline int64_t read_s64le(off_t offset, STREAMFILE* sf) { return read_64bitLE(offset, sf); }
static inline uint64_t read_u64le(off_t offset, STREAMFILE* sf) { return (uint64_t)read_64bitLE(offset, sf); }
/* The recommended int-to-float type punning in C is through union, but pointer casting
* works too (though less portable due to aliasing rules?). For C++ memcpy seems
* recommended. Both work in GCC and VS2015+ (not sure about older, ifdef as needed). */
static inline float read_f32be(off_t offset, STREAMFILE* sf) {
union {
uint32_t u32;
float f32;
} temp;
temp.u32 = read_u32be(offset, sf);
return temp.f32;
static inline float read_f32be(off_t offset, STREAMFILE* sf) {
uint8_t buf[4];
if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf))
return -1;
return get_f32be(buf);
}
static inline float read_f32le(off_t offset, STREAMFILE* sf) {
union {
uint32_t u32;
float f32;
} temp;
temp.u32 = read_u32le(offset, sf);
return temp.f32;
uint8_t buf[4];
if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf))
return -1;
return get_f32le(buf);
}
#if 0
static inline float read_f32be_p(off_t offset, STREAMFILE* sf) {
uint32_t sample_int = read_u32be(offset, sf);
float* sample_float = (float*)&sample_int;
return *sample_float;
}
static inline float read_f32be_m(off_t offset, STREAMFILE* sf) {
uint32_t sample_int = read_u32be(offset, sf);
float sample_float;
memcpy(&sample_float, &sample_int, sizeof(uint32_t));
return sample_float;
}
#endif
#if 0
// on GCC, this reader will be correctly optimized out (as long as it's static/inline), would be same as declaring:
// uintXX_t (*read_uXX)(off_t,uint8_t*) = be ? get_uXXbe : get_uXXle;
// only for the functions actually used in code, and inlined if possible (like big_endian param being a constant).

View File

@ -51,14 +51,60 @@ static inline uint64_t get_u64le(const uint8_t* p) { return (uint64_t)get_64bitL
static inline int64_t get_s64be(const uint8_t* p) { return ( int64_t)get_64bitBE(p); }
static inline uint64_t get_u64be(const uint8_t* p) { return (uint64_t)get_64bitBE(p); }
/* The recommended int-to-float type punning in C is through union, but pointer casting
* works too (though less portable due to aliasing rules?). For C++ memcpy seems
* recommended. Both work in GCC and VS2015+ (not sure about older, ifdef as needed). */
static inline float get_f32be(const uint8_t* p) {
union {
uint32_t u32;
float f32;
} temp;
temp.u32 = get_u32be(p);
return temp.f32;
}
static inline float get_f32le(const uint8_t* p) {
union {
uint32_t u32;
float f32;
} temp;
temp.u32 = get_u32le(p);
return temp.f32;
}
static inline double get_d64be(const uint8_t* p) {
union {
uint64_t u64;
double d64;
} temp;
temp.u64 = get_u64be(p);
return temp.d64;
}
static inline double get_d64le(const uint8_t* p) {
union {
uint64_t u64;
double d64;
} temp;
temp.u64 = get_u64le(p);
return temp.d64;
}
#if 0
static inline float get_f32be_cast(const uint8_t* p) {
uint32_t sample_int = get_u32be(p);
float* sample_float = (float*)&sample_int;
return *sample_float;
}
static inline float get_f32be_mcpy(const uint8_t* p) {
uint32_t sample_int = get_u32be(p);
float sample_float;
memcpy(&sample_float, &sample_int, sizeof(uint32_t));
return sample_float;
}
#endif
void put_8bit(uint8_t* buf, int8_t i);
void put_16bitLE(uint8_t* buf, int16_t i);
void put_32bitLE(uint8_t* buf, int32_t i);
void put_16bitBE(uint8_t* buf, int16_t i);
void put_32bitBE(uint8_t* buf, int32_t i);
/* alias of the above */ //TODO: improve
@ -102,7 +148,7 @@ static inline int clamp16(int32_t val) {
/* transforms a string to uint32 (for comparison), but if this is static + all goes well
* compiler should pre-calculate and use uint32 directly */
static inline /*const*/ uint32_t get_id32be(const char* s) {
return (uint32_t)(s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0);
return (uint32_t)((uint8_t)s[0] << 24) | ((uint8_t)s[1] << 16) | ((uint8_t)s[2] << 8) | ((uint8_t)s[3] << 0);
}
//static inline /*const*/ uint32_t get_id32le(const char* s) {

View File

@ -15,12 +15,22 @@
* - still WIP, some stuff not working ATM or may change
*/
/* compiler hints to force printf-style checks, butt-ugly but so useful... */
/* supposedly MSCV has _Printf_format_string_ with /analyze but I can't get it to work */
#if defined(__GNUC__) /* clang too */
#define GNUC_LOG_ATRIB __attribute__ ((format(printf, 1, 2))) /* only with -Wformat (1=format param, 2=other params) */
#define GNUC_ASR_ATRIB __attribute__ ((format(printf, 2, 3)))
#else
#define GNUC_LOG_ATRIB /* none */
#define GNUC_ASR_ATRIB /* none */
#endif
// void (*callback)(int level, const char* str);
void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback);
#if defined(VGM_LOG_OUTPUT) || defined(VGM_DEBUG_OUTPUT)
void vgm_logi(/*void* ctx,*/ const char* fmt, ...);
void vgm_asserti(/*void* ctx,*/ int condition, const char* fmt, ...);
void vgm_logi(/*void* ctx,*/ const char* fmt, ...) GNUC_LOG_ATRIB;
void vgm_asserti(/*void* ctx,*/ int condition, const char* fmt, ...) GNUC_ASR_ATRIB;
//void vgm_logi_once(/*void* ctx, int* once_flag, */ const char* fmt, ...);
#else
#define vgm_logi(...) /* nothing */
@ -28,7 +38,7 @@ void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback);
#endif
#ifdef VGM_DEBUG_OUTPUT
void vgm_logd(/*void* ctx,*/ const char* fmt, ...);
void vgm_logd(/*void* ctx,*/ const char* fmt, ...) GNUC_LOG_ATRIB;
#define VGM_LOG(...) do { vgm_logd(__VA_ARGS__); } while (0)
#define VGM_ASSERT(condition, ...) do { if (condition) {vgm_logd(__VA_ARGS__);} } while (0)
#else

View File

@ -0,0 +1,847 @@
#include <string.h>
#include "m2_psb.h"
#include "../util.h"
#include "log.h"
/* Code below roughly follows original m2lib internal API b/c why not. Rather than pre-parsing the tree
* to struct/memory, seems it re-reads bytes from buf as needed (there might be some compiler optims going on too).
* Always LE even on X360.
*
* Info from: decompiled exes and parts (mainly key decoding) from exm2lib by asmodean (http://asmodean.reverse.net/),
* also https://github.com/number201724/psbfile and https://github.com/UlyssesWu/FreeMote
*
* PSB defines a header with offsets to sections within the header, binary format being type-value (where type could be
* int8/int16/float/array/object/etc). Example:
* 21 // object: root (x2 info lists + items)
* 0D 04 0D 06,0B,0D,0E // list8[4]: key indexes ("id/spec/version/voice")
* 0D 04 0D 00,02,04,09 // list8[4]: byte offsets of next 4 items
* 15 02 // 0 string8: string#2 ("spec")
* 1E 5C8F823F // 1 float32: 1.02
* 05 02 // 2 int8: 2
* 21 // 3 object
* 0D 02 0D 02,05 // list8[2]: key indexes
* 0D 02 0D 00,02 // list8[2]: byte offsets
* 19 00 // 0 resource8: resource#0 (subfile)
* 20 // 1 array: loops
* 0D 02 0D 00,04 // list8[2]
* 07 D69107 // 0 int24
* 07 31A45C // 1 int24
*/
//TODO: som
//TODO: add validations on buf over max size
//TODO: validate strings table ends with null (buf[max - 1] = '\0')
/******************************************************************************/
/* DEFS */
#define PSB_VERSION2 2 /* older (x360/ps3) games */
#define PSB_VERSION3 3 /* current games */
#define PSB_MAX_HEADER 0x40000 /* max seen ~0x1000 */
/* Internal type used in binary data, that defines bytes used to store value.
* A common optimization is (type - base-1) to convert to used bytes (like NUMBER_16 - 0x04 = 2).
* Often M2 code seems to ignore max sizes and casts to int32, no concept of signed/unsigned either.
* Sometimes M2 code converts to external type to do general checks too. */
typedef enum {
PSB_ITYPE_NONE = 0x0,
PSB_ITYPE_NULL = 0x1,
PSB_ITYPE_TRUE = 0x2,
PSB_ITYPE_FALSE = 0x3,
PSB_ITYPE_INTEGER_0 = 0x4,
PSB_ITYPE_INTEGER_8 = 0x5,
PSB_ITYPE_INTEGER_16 = 0x6,
PSB_ITYPE_INTEGER_24 = 0x7,
PSB_ITYPE_INTEGER_32 = 0x8,
PSB_ITYPE_INTEGER_40 = 0x9, /* assumed, decomp does same as 32b due to int cast (compiler over-optimization?) */
PSB_ITYPE_INTEGER_48 = 0xA, /* same */
PSB_ITYPE_INTEGER_56 = 0xB,
PSB_ITYPE_INTEGER_64 = 0xC,
PSB_ITYPE_LIST_8 = 0xD,
PSB_ITYPE_LIST_16 = 0xE,
PSB_ITYPE_LIST_24 = 0xF,
PSB_ITYPE_LIST_32 = 0x10,
PSB_ITYPE_LIST_40 = 0x11, /* assumed, no refs in code (same up to 64) */
PSB_ITYPE_LIST_48 = 0x12,
PSB_ITYPE_LIST_56 = 0x13,
PSB_ITYPE_LIST_64 = 0x14,
PSB_ITYPE_STRING_8 = 0x15,
PSB_ITYPE_STRING_16 = 0x16,
PSB_ITYPE_STRING_24 = 0x17,
PSB_ITYPE_STRING_32 = 0x18,
PSB_ITYPE_DATA_8 = 0x19,
PSB_ITYPE_DATA_16 = 0x1A,
PSB_ITYPE_DATA_24 = 0x1B,
PSB_ITYPE_DATA_32 = 0x1C,
PSB_ITYPE_DATA_40 = 0x22, /* assumed, some refs in code (same up to 64) */
PSB_ITYPE_DATA_48 = 0x23,
PSB_ITYPE_DATA_56 = 0x24,
PSB_ITYPE_DATA_64 = 0x25,
PSB_ITYPE_FLOAT_0 = 0x1D,
PSB_ITYPE_FLOAT_32 = 0x1E,
PSB_ITYPE_DOUBLE_64 = 0x1F,
PSB_ITYPE_ARRAY = 0x20,
PSB_ITYPE_OBJECT = 0x21,
} psb_itype_t;
typedef struct {
int bytes; /* total bytes (including headers) to skip this list */
int count; /* number of entries */
int esize; /* size per entry */
uint8_t* edata; /* start of entries */
} list_t;
struct psb_context_t {
uint32_t header_id;
uint16_t version;
uint16_t encrypt_value;
uint32_t encrypt_offset;
uint32_t keys_offset;
uint32_t strings_list_offset;
uint32_t strings_data_offset;
uint32_t data_offsets_offset; //todo resources
uint32_t data_sizes_offset;
uint32_t data_offset; //todo resources
uint32_t root_offset;
uint32_t unknown; /* hash/crc? (v3) */
/* main buf and derived stuff*/
uint8_t* buf;
list_t strings_list;
uint8_t* strings_data;
list_t data_offsets_list;
list_t data_sizes_list;
/* keys buf */
char* keys;
int* keys_pos;
int keys_count;
};
/******************************************************************************/
/* COMMON */
/* output seems to be signed but some of M2 code casts to unsigned, not sure if important for indexes (known cases never get too high) */
static uint32_t item_get_int(int size, uint8_t* buf) {
switch (size) {
case 1:
return get_u8(buf);
case 2:
return get_u16le(buf);
case 3:
return (get_u16le(buf+0x01) << 8) | get_u8(buf);
//return get_u24le(buf+0x01);
case 4:
return get_u32le(buf);
default:
return 0;
}
}
static int list_get_count(uint8_t* buf) {
uint8_t itype = buf[0];
switch (itype) {
case PSB_ITYPE_LIST_8:
case PSB_ITYPE_LIST_16:
case PSB_ITYPE_LIST_24:
case PSB_ITYPE_LIST_32: {
int size = itype - PSB_ITYPE_LIST_8 + 1;
return item_get_int(size, &buf[1]);
}
default:
return 0;
}
}
static uint32_t list_get_entry(list_t* lst, uint32_t index) {
uint8_t* buf = &lst->edata[index * lst->esize];
return item_get_int(lst->esize, buf);
}
static int list_init(list_t* lst, uint8_t* buf) {
int count_size, count, entry_size;
uint8_t count_itype, entry_itype;
/* ex. 0D 04 0D 00,01,02,03 */
/* get count info (0D + 04) */
count_itype = buf[0];
switch (count_itype) {
case PSB_ITYPE_LIST_8:
case PSB_ITYPE_LIST_16:
case PSB_ITYPE_LIST_24:
case PSB_ITYPE_LIST_32:
count_size = count_itype - PSB_ITYPE_LIST_8 + 1;
count = item_get_int(count_size, &buf[1]);
break;
default:
goto fail;
}
/* get entry info (0D + 00,01,02,03) */
entry_itype = buf[1 + count_size];
switch (entry_itype) {
case PSB_ITYPE_LIST_8:
case PSB_ITYPE_LIST_16:
case PSB_ITYPE_LIST_24:
case PSB_ITYPE_LIST_32:
entry_size = entry_itype - PSB_ITYPE_LIST_8 + 1;
break;
default:
goto fail;
}
lst->bytes = 1 + count_size + 1 + entry_size * count;
lst->count = count;
lst->esize = entry_size;
lst->edata = &buf[1 + count_size + 1];
return 1;
fail:
memset(lst, 0, sizeof(list_t));
return 0;
}
/* when a function that should modify p_out fails, memset just in case wasn't init and p_out is chained */
static void node_error(psb_node_t* p_out) {
if (!p_out)
return;
p_out->ctx = NULL;
p_out->data = NULL;
}
/******************************************************************************/
/* INIT */
/* Keys seems to use a kind of linked list where each element points to next and char is encoded
* with a distance-based metric. Notice it's encoded in reverse order, so it's tuned to save
* common prefixes (like bgmXXX in big archives). Those aren't that common, and to encode N chars
* often needs x2/x3 bytes (and it's slower) so it's probably more of a form of obfuscation. */
int decode_key(list_t* kidx1, list_t* kidx2, list_t* kidx3, char* str, int str_len, int index) {
int i;
uint32_t entry_point = list_get_entry(kidx3, index);
uint32_t point = list_get_entry(kidx2, entry_point);
for (i = 0; i < str_len; i++) {
uint32_t next = list_get_entry(kidx2, point);
uint32_t diff = list_get_entry(kidx1, next);
uint32_t curr = point - diff;
str[i] = (char)curr;
point = next;
if (!point)
break;
}
if (i == str_len) {
vgm_logi("PSBLIB: truncated key (report)\n");
}
else {
i++;
}
str[i] = '\0';
return i;
}
/* Keys are packed in a particular format (see get_key_string), and M2 code seems to do some unknown
* pre-parse, so for now do a simple copy to string buf to simplify handling and returning. */
int init_keys(psb_context_t* ctx) {
list_t kidx1, kidx2, kidx3;
uint8_t* buf = &ctx->buf[ctx->keys_offset];
int i, j, pos;
char key[256]; /* ~50 aren't too uncommon (used in names) */
int keys_size;
/* character/diff table */
if (!list_init(&kidx1, &buf[0]))
goto fail;
/* next point table */
if (!list_init(&kidx2, &buf[kidx1.bytes]))
goto fail;
/* entry point table */
if (!list_init(&kidx3, &buf[kidx1.bytes + kidx2.bytes]))
goto fail;
ctx->keys_count = kidx3.count;
ctx->keys_pos = malloc(sizeof(int) * ctx->keys_count);
if (!ctx->keys_pos) goto fail;
/* packed lists are usually *bigger* than final raw strings, but put some extra size just in case */
keys_size = (kidx1.bytes + kidx2.bytes + kidx3.bytes) * 2;
ctx->keys = malloc(keys_size);
if (!ctx->keys) goto fail;
pos = 0;
for (i = 0; i < kidx3.count; i++) {
int key_len = decode_key(&kidx1, &kidx2, &kidx3, key, sizeof(key), i);
/* could realloc but meh */
if (pos + key_len > keys_size)
goto fail;
/* copy key in reverse (strrev + memcpy C99 only) */
for (j = 0; j < key_len; j++) {
ctx->keys[pos + key_len - 1 - j] = key[j];
}
ctx->keys[pos + key_len] = '\0';
ctx->keys_pos[i] = pos;
pos += key_len + 1;
}
return 1;
fail:
vgm_logi("PSBLIB: failed getting keys\n");
return 0;
}
psb_context_t* psb_init(STREAMFILE* sf) {
psb_context_t* ctx;
uint8_t header[0x2c];
int bytes;
uint32_t buf_len;
ctx = calloc(1, sizeof(psb_context_t));
if (!ctx) goto fail;
bytes = read_streamfile(header, 0x00, sizeof(header), sf);
if (bytes != sizeof(header)) goto fail;
ctx->header_id = get_u32be(header + 0x00);
ctx->version = get_u16le(header + 0x04);
ctx->encrypt_value = get_u32le(header + 0x06);
ctx->encrypt_offset = get_u32le(header + 0x08);
ctx->keys_offset = get_u32le(header + 0x0c);
ctx->strings_list_offset = get_u32le(header + 0x10);
ctx->strings_data_offset = get_u32le(header + 0x14);
ctx->data_offsets_offset = get_u32le(header + 0x18);
ctx->data_sizes_offset = get_u32le(header + 0x1c);
ctx->data_offset = get_u32le(header + 0x20);
ctx->root_offset = get_u32le(header + 0x24);
if (ctx->version >= PSB_VERSION3)
ctx->unknown = get_u32le(header + 0x28);
/* some validations, not sure if checked by M2 */
if (ctx->header_id != get_id32be("PSB\0"))
goto fail;
if (ctx->version != PSB_VERSION2 && ctx->version != PSB_VERSION3)
goto fail;
/* not seen */
if (ctx->encrypt_value != 0)
goto fail;
/* 0 in some v2 */
if (ctx->encrypt_offset != 0 && ctx->encrypt_offset != ctx->keys_offset)
goto fail;
/* data should be last as it's used to read buf */
if (ctx->keys_offset >= ctx->data_offset ||
ctx->strings_list_offset >= ctx->data_offset ||
ctx->strings_data_offset >= ctx->data_offset ||
ctx->data_offsets_offset >= ctx->data_offset ||
ctx->data_sizes_offset >= ctx->data_offset ||
ctx->root_offset >= ctx->data_offset)
goto fail;
/* copy data for easier access */
buf_len = ctx->data_offset;
if (buf_len > PSB_MAX_HEADER)
goto fail;
ctx->buf = malloc(buf_len);
if (!ctx->buf) goto fail;
bytes = read_streamfile(ctx->buf, 0x00, buf_len, sf);
if (bytes != buf_len) goto fail;
if (!list_init(&ctx->strings_list, &ctx->buf[ctx->strings_list_offset]))
goto fail;
ctx->strings_data = &ctx->buf[ctx->strings_data_offset];
if (!list_init(&ctx->data_offsets_list, &ctx->buf[ctx->data_offsets_offset]))
goto fail;
if (!list_init(&ctx->data_sizes_list, &ctx->buf[ctx->data_sizes_offset]))
goto fail;
if (!init_keys(ctx))
goto fail;
return ctx;
fail:
psb_close(ctx);
vgm_logi("PSBLIB: init error (report)\n");
return NULL;
}
void psb_close(psb_context_t* ctx) {
if (!ctx)
return;
free(ctx->keys_pos);
free(ctx->keys);
free(ctx->buf);
free(ctx);
}
int psb_get_root(psb_context_t* ctx, psb_node_t* p_root) {
if (!ctx || !p_root)
return 0;
p_root->ctx = ctx;
p_root->data = &ctx->buf[ctx->root_offset];
return 1;
}
/******************************************************************************/
/* NODES */
psb_type_t psb_node_get_type(const psb_node_t* node) {
uint8_t* buf;
uint8_t itype;
if (!node || !node->data)
goto fail;
buf = node->data;
itype = buf[0];
switch (itype) {
case PSB_ITYPE_NULL:
return PSB_TYPE_NULL;
case PSB_ITYPE_TRUE:
case PSB_ITYPE_FALSE:
return PSB_TYPE_BOOL;
case PSB_ITYPE_INTEGER_0:
case PSB_ITYPE_INTEGER_8:
case PSB_ITYPE_INTEGER_16:
case PSB_ITYPE_INTEGER_24:
case PSB_ITYPE_INTEGER_32:
case PSB_ITYPE_INTEGER_40:
case PSB_ITYPE_INTEGER_48:
case PSB_ITYPE_INTEGER_56:
case PSB_ITYPE_INTEGER_64:
return PSB_TYPE_INTEGER;
case PSB_ITYPE_STRING_8:
case PSB_ITYPE_STRING_16:
case PSB_ITYPE_STRING_24:
case PSB_ITYPE_STRING_32:
return PSB_TYPE_STRING;
case PSB_ITYPE_DATA_8:
case PSB_ITYPE_DATA_16:
case PSB_ITYPE_DATA_24:
case PSB_ITYPE_DATA_32:
case PSB_ITYPE_DATA_40:
case PSB_ITYPE_DATA_48:
case PSB_ITYPE_DATA_56:
case PSB_ITYPE_DATA_64:
return PSB_TYPE_DATA;
case PSB_ITYPE_FLOAT_0:
case PSB_ITYPE_FLOAT_32:
case PSB_ITYPE_DOUBLE_64:
return PSB_TYPE_FLOAT;
case PSB_ITYPE_ARRAY:
return PSB_TYPE_ARRAY;
case PSB_ITYPE_OBJECT:
return PSB_TYPE_OBJECT;
/* M2 just aborts for other internal types (like lists) */
default:
goto fail;
}
fail:
return PSB_TYPE_UNKNOWN;
}
int psb_node_get_count(const psb_node_t* node) {
uint8_t* buf;
if (!node || !node->data)
goto fail;
buf = node->data;
switch (buf[0]) {
case PSB_ITYPE_ARRAY:
case PSB_ITYPE_OBJECT:
/* both start with a list, that can be used as count */
return list_get_count(&buf[1]);
default:
return 0;
}
fail:
return -1;
}
int psb_node_by_index(const psb_node_t* node, int index, psb_node_t* p_out) {
uint8_t* buf;
if (!node || !node->data)
goto fail;
buf = node->data;
switch (buf[0]) {
case PSB_ITYPE_ARRAY: {
list_t offsets;
int skip;
list_init(&offsets, &buf[1]);
skip = list_get_entry(&offsets, index);
p_out->ctx = node->ctx;
p_out->data = &buf[1 + offsets.bytes + skip];
return 1;
}
case PSB_ITYPE_OBJECT: {
list_t keys, offsets;
int skip;
list_init(&keys, &buf[1]);
list_init(&offsets, &buf[1 + keys.bytes]);
skip = list_get_entry(&offsets, index);
p_out->ctx = node->ctx;
p_out->data = &buf[1 + keys.bytes + offsets.bytes + skip];
return 1;
}
default:
goto fail;
}
fail:
vgm_logi("PSBLIB: cannot get node at index %i\n", index);
node_error(p_out);
return 0;
}
int psb_node_by_key(const psb_node_t* node, const char* key, psb_node_t* p_out) {
int i;
int max;
if (!node || !node->ctx)
goto fail;
max = psb_node_get_count(node);
if (max < 0 || max > node->ctx->keys_count)
goto fail;
for (i = 0; i < max; i++) {
const char* key_test = psb_node_get_key(node, i);
if (!key_test)
goto fail;
//todo could improve by getting strlen(key) + ctx->key_len + check + strncmp
if (strcmp(key_test, key) == 0)
return psb_node_by_index(node, i, p_out);
}
fail:
//VGM_LOG("psblib: cannot get node at key '%s'\n", key); /* not uncommon to query */
node_error(p_out);
return 0;
}
const char* psb_node_get_key(const psb_node_t* node, int index) {
uint8_t* buf;
int pos;
if (!node || !node->ctx || !node->data)
goto fail;
buf = node->data;
switch (buf[0]) {
case PSB_ITYPE_OBJECT: {
list_t keys;
int keys_index;
list_init(&keys, &buf[1]);
keys_index = list_get_entry(&keys, index);
if (keys_index < 0 || keys_index > node->ctx->keys_count)
goto fail;
pos = node->ctx->keys_pos[keys_index];
return &node->ctx->keys[pos];
}
default:
goto fail;
}
fail:
vgm_logi("PSBLIB: cannot get key at index '%i'\n", index);
return NULL;
}
psb_result_t psb_node_get_result(psb_node_t* node) {
uint8_t* buf;
uint8_t itype;
psb_result_t res = {0};
int size, index, skip;
if (!node || !node->ctx || !node->data)
goto fail;
buf = node->data;
itype = buf[0];
switch (itype) {
case PSB_ITYPE_NULL:
break;
case PSB_ITYPE_TRUE:
case PSB_ITYPE_FALSE:
res.bln = (itype == PSB_ITYPE_TRUE);
break;
case PSB_ITYPE_INTEGER_0:
res.num = 0;
break;
case PSB_ITYPE_INTEGER_8:
case PSB_ITYPE_INTEGER_16:
case PSB_ITYPE_INTEGER_24:
case PSB_ITYPE_INTEGER_32:
size = itype - PSB_ITYPE_INTEGER_8 + 1;
res.num = item_get_int(size, &buf[1]);
break;
case PSB_ITYPE_INTEGER_40:
case PSB_ITYPE_INTEGER_48:
case PSB_ITYPE_INTEGER_56:
case PSB_ITYPE_INTEGER_64:
vgm_logi("PSBLIB: not implemented (report)\n");
break;
case PSB_ITYPE_STRING_8:
case PSB_ITYPE_STRING_16:
case PSB_ITYPE_STRING_24:
case PSB_ITYPE_STRING_32: {
size = itype - PSB_ITYPE_STRING_8 + 1;
index = item_get_int(size, &buf[1]);
skip = list_get_entry(&node->ctx->strings_list, index);
res.str = (const char*)&node->ctx->strings_data[skip]; /* null-terminated */
//todo test max strlen to see if it's null-terminated
break;
}
case PSB_ITYPE_DATA_8:
case PSB_ITYPE_DATA_16:
case PSB_ITYPE_DATA_24:
case PSB_ITYPE_DATA_32:
size = itype - PSB_ITYPE_DATA_8 + 1;
index = item_get_int(size, &buf[1]);
res.data.offset = list_get_entry(&node->ctx->data_offsets_list, index);
res.data.size = list_get_entry(&node->ctx->data_sizes_list, index);
res.data.offset += node->ctx->data_offset;
break;
case PSB_ITYPE_DATA_40:
case PSB_ITYPE_DATA_48:
case PSB_ITYPE_DATA_56:
case PSB_ITYPE_DATA_64:
vgm_logi("PSBLIB: not implemented (report)\n");
break;
case PSB_ITYPE_FLOAT_0:
res.flt = 0.0f;
break;
case PSB_ITYPE_FLOAT_32:
res.flt = get_f32le(&buf[1]);
break;
case PSB_ITYPE_DOUBLE_64:
res.dbl = get_d64le(&buf[1]);
res.flt = (float)res.dbl; /* doubles seem ignored */
break;
case PSB_ITYPE_ARRAY:
case PSB_ITYPE_OBJECT:
res.count = list_get_count(&buf[1]);
break;
default:
goto fail;
}
return res;
fail:
return res; /* should be all null */
}
/******************************************************************************/
/* HELPERS */
static int get_expected_node(const psb_node_t* node, const char* key, psb_node_t* p_out, psb_type_t expected) {
if (!psb_node_by_key(node, key, p_out))
goto fail;
if (psb_node_get_type(p_out) != expected)
goto fail;
return 1;
fail:
return 0;
}
/* M2 coerces values (like float to bool) but it's kinda messy so whatevs */
const char* psb_node_get_string(const psb_node_t* node, const char* key) {
psb_node_t out;
if (!get_expected_node(node, key, &out, PSB_TYPE_STRING))
return NULL;
return psb_node_get_result(&out).str;
}
float psb_node_get_float(const psb_node_t* node, const char* key) {
psb_node_t out;
if (!get_expected_node(node, key, &out, PSB_TYPE_FLOAT))
return 0.0f;
return psb_node_get_result(&out).flt;
}
int32_t psb_node_get_integer(const psb_node_t* node, const char* key) {
psb_node_t out;
if (!get_expected_node(node, key, &out, PSB_TYPE_INTEGER))
return 0;
return psb_node_get_result(&out).num;
}
int psb_node_get_bool(const psb_node_t* node, const char* key) {
psb_node_t out;
if (!get_expected_node(node, key, &out, PSB_TYPE_BOOL))
return 0;
return psb_node_get_result(&out).bln;
}
psb_data_t psb_node_get_data(const psb_node_t* node, const char* key) {
psb_node_t out;
if (!get_expected_node(node, key, &out, PSB_TYPE_DATA)) {
psb_data_t data = {0};
return data;
}
return psb_node_get_result(&out).data;
}
int psb_node_exists(const psb_node_t* node, const char* key) {
psb_node_t out;
if (!psb_node_by_key(node, key, &out))
return 0;
return 1;
}
/******************************************************************************/
/* ETC */
#define PSB_DEPTH_STEP 2
static void print_internal(psb_node_t* curr, int depth) {
int i;
psb_node_t node;
const char* key;
psb_type_t type;
psb_result_t res;
if (!curr)
return;
type = psb_node_get_type(curr);
res = psb_node_get_result(curr);
switch (type) {
case PSB_TYPE_NULL:
printf("%s,\n", "null");
break;
case PSB_TYPE_BOOL:
printf("%s,\n", (res.bln == 1 ? "true" : "false"));
break;
case PSB_TYPE_INTEGER:
printf("%i,\n", res.num);
break;
case PSB_TYPE_FLOAT:
printf("%f,\n", res.flt);
break;
case PSB_TYPE_STRING:
printf("\"%s\",\n", res.str);
break;
case PSB_TYPE_DATA:
printf("<0x%08x,0x%08x>\n", res.data.offset, res.data.size);
break;
case PSB_TYPE_ARRAY:
printf("[\n");
for (i = 0; i < res.count; i++) {
psb_node_by_index(curr, i, &node);
printf("%*s", depth + PSB_DEPTH_STEP, "");
print_internal(&node, depth + PSB_DEPTH_STEP);
}
printf("%*s],\n", depth, "");
break;
case PSB_TYPE_OBJECT:
printf("{\n");
for (i = 0; i < res.count; i++) {
key = psb_node_get_key(curr, i);
psb_node_by_index(curr, i, &node);
printf("%*s\"%s\": ", depth + PSB_DEPTH_STEP, "", key);
print_internal(&node, depth + PSB_DEPTH_STEP);
}
printf("%*s},\n", depth, "");
break;
default:
printf("???,\n");
break;
}
}
void psb_print(psb_context_t* ctx) {
psb_node_t node;
psb_get_root(ctx, &node);
print_internal(&node, 0);
}

View File

@ -0,0 +1,88 @@
#ifndef _M2_PSB_H_
#define _M2_PSB_H_
#include "../streamfile.h"
/* M2's PSB (Packaged Struct Binary) is binary format similar to JSON with a tree-like structure of
* string keys = multitype values (objects, arrays, bools, strings, ints, raw data and so on)
* but better packed (like support of ints of all sizes).
*
* It's used to access values in different M2 formats, including audio containers (MSound::SoundArchive)
* so rather than data accessing by offsets they just use "key" = values.
*/
/* opaque struct */
typedef struct psb_context_t psb_context_t;
/* represents an object in the tree */
typedef struct {
psb_context_t* ctx;
void* data;
} psb_node_t;
/* open a PSB */
psb_context_t* psb_init(STREAMFILE* sf);
void psb_close(psb_context_t* ctx);
/* get base root object */
int psb_get_root(psb_context_t* ctx, psb_node_t* p_root);
typedef enum {
PSB_TYPE_NULL = 0x0,
PSB_TYPE_BOOL = 0x1,
PSB_TYPE_INTEGER = 0x2,
PSB_TYPE_FLOAT = 0x3,
PSB_TYPE_STRING = 0x4,
PSB_TYPE_DATA = 0x5, /* possibly "userdata" */
PSB_TYPE_ARRAY = 0x6,
PSB_TYPE_OBJECT = 0x7, /* also "table" */
PSB_TYPE_UNKNOWN = 0x8, /* error */
} psb_type_t;
/* get current type */
psb_type_t psb_node_get_type(const psb_node_t* node);
/* get item count (valid for 'array/object' nodes) */
int psb_node_get_count(const psb_node_t* node);
/* get key string of sub-node N (valid for 'object' node) */
const char* psb_node_get_key(const psb_node_t* node, int index);
/* get sub-node from node at index (valid for 'array/object') */
int psb_node_by_index(const psb_node_t* node, int index, psb_node_t* p_out);
/* get sub-node from node at key (valid for 'object') */
int psb_node_by_key(const psb_node_t* node, const char* key, psb_node_t* p_out);
typedef struct {
uint32_t offset;
uint32_t size;
} psb_data_t;
typedef union {
int bln;
int32_t num;
double dbl;
float flt;
const char* str;
int count;
psb_data_t data;
} psb_result_t;
/* generic result (returns all to 0 on failure) */
psb_result_t psb_node_get_result(psb_node_t* node);
/* helpers */
const char* psb_node_get_string(const psb_node_t* node, const char* key);
float psb_node_get_float(const psb_node_t* node, const char* key);
int32_t psb_node_get_integer(const psb_node_t* node, const char* key);
int psb_node_get_bool(const psb_node_t* node, const char* key);
psb_data_t psb_node_get_data(const psb_node_t* node, const char* key);
int psb_node_exists(const psb_node_t* node, const char* key);
/* print in JSON-style (for debugging) */
void psb_print(psb_context_t* ctx);
#endif

View File

@ -528,6 +528,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_wxd_wxh,
init_vgmstream_bnk_relic,
init_vgmstream_xsh_xsd_xss,
init_vgmstream_psb,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */

View File

@ -76,7 +76,8 @@ typedef enum {
coding_ULAW_int, /* 8-bit u-Law (non-linear PCM) with sample-level interleave (for blocks) */
coding_ALAW, /* 8-bit a-Law (non-linear PCM) */
coding_PCMFLOAT, /* 32 bit float PCM */
coding_PCMFLOAT, /* 32-bit float PCM */
coding_PCM24LE, /* 24-bit PCM */
/* ADPCM */
coding_CRI_ADX, /* CRI ADX */
@ -754,6 +755,7 @@ typedef enum {
meta_WXD_WXH,
meta_BNK_RELIC,
meta_XSH_XSD_XSS,
meta_PSB,
} meta_t;