mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-17 19:19:16 +01:00
Merge pull request #941 from bnnm/psb-xwma
- Tweak some XWMA total samples - Add PCM24 codec [Legend of Mana (PC)] - Add M2 .psb [Senxin Aleste, Legend of Mana (PC)]
This commit is contained in:
commit
0b01723630
2
Makefile
2
Makefile
@ -224,6 +224,7 @@ ifeq ($(TARGET_OS),Windows_NT)
|
||||
BIN_FILE = vgmstream-$(VGMSTREAM_VERSION)-win.zip
|
||||
ZIP_FILES = COPYING
|
||||
ZIP_FILES += README.md
|
||||
ZIP_FILES += doc/USAGE.md
|
||||
ZIP_FILES += cli/test.exe
|
||||
ZIP_FILES += winamp/in_vgmstream.dll
|
||||
ZIP_FILES += xmplay/xmp-vgmstream.dll
|
||||
@ -235,6 +236,7 @@ else
|
||||
BIN_FILE = vgmstream-$(VGMSTREAM_VERSION)-bin.zip
|
||||
ZIP_FILES = COPYING
|
||||
ZIP_FILES += README.md
|
||||
ZIP_FILES += doc/USAGE.md
|
||||
ZIP_FILES += cli/vgmstream-cli
|
||||
ZIP_FILES_AO = cli/vgmstream123
|
||||
endif
|
||||
|
@ -68,7 +68,7 @@ $sdk = "10.0"
|
||||
```
|
||||
It's also possible to call MSBuild and pass those values from the CMD, see foobar section for an example.
|
||||
|
||||
Once finished resulting binaries are in the *./Release* folder. Remember you need to copy extra `.dll` to run them (see [README.md](../README.md)).
|
||||
Once finished resulting binaries are in the *./Release* folder. Remember you need to copy extra `.dll` to run them (see [USAGE.md](USAGE.md)).
|
||||
|
||||
**For GCC/CLang**: there are basic Makefiles that work like usual with *make* (like `make vgmstream_cli EXTRA_CFLAGS="-DVGM_DEBUG_OUTPUT`). Artifacts are usually in their subdir (*./cli*, *./winamp*, etc).
|
||||
|
||||
@ -357,6 +357,8 @@ May also need to take `vgmstream.h`, `streamfile.h` and `plugins.h`, and trim th
|
||||
|
||||
For MSVC, you could add `__declspec(dllexport)` to exported functions in the "public" API of the above `.h`, and set `<ConfigurationType>DynamicLibrary</ConfigurationType>` in `libvgmstream.vcxproj`, plus add a `<Link>` under `<ClCompile>` to those libs (copy from `vgmstream_cli.vcxproj`).
|
||||
|
||||
For integration and "API" usage, easiest would be checking how `vgmstream_cli.c` works.
|
||||
|
||||
A cleaner API/.h and build methods is planned for the future (low priority though).
|
||||
|
||||
|
||||
|
1016
doc/USAGE.md
Normal file
1016
doc/USAGE.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -183,6 +183,7 @@ $fb2kFiles = @(
|
||||
"ext_libs/libspeex/*.dll",
|
||||
"$configuration/foo_input_vgmstream.dll",
|
||||
"README.md"
|
||||
"doc/USAGE.md"
|
||||
)
|
||||
|
||||
$cliFiles = @(
|
||||
@ -193,6 +194,7 @@ $cliFiles = @(
|
||||
"$configuration/xmp-vgmstream.dll",
|
||||
"COPYING",
|
||||
"README.md"
|
||||
"doc/USAGE.md"
|
||||
)
|
||||
|
||||
$fb2kPdbFiles = @(
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
10
src/decode.c
10
src/decode.c
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -170,6 +170,7 @@
|
||||
<ClInclude Include="layout\layout.h" />
|
||||
<ClInclude Include="util\chunks.h" />
|
||||
<ClInclude Include="util\log.h" />
|
||||
<ClInclude Include="util\m2_psb.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="coding\at3plus_decoder.c" />
|
||||
@ -244,6 +245,7 @@
|
||||
<ClCompile Include="meta\ps2_wmus.c" />
|
||||
<ClCompile Include="meta\ivag.c" />
|
||||
<ClCompile Include="meta\ps3_past.c" />
|
||||
<ClCompile Include="meta\psb.c" />
|
||||
<ClCompile Include="meta\psf.c" />
|
||||
<ClCompile Include="meta\sgxd.c" />
|
||||
<ClCompile Include="meta\silence.c" />
|
||||
@ -712,6 +714,7 @@
|
||||
<ClCompile Include="layout\blocked_xvas.c" />
|
||||
<ClCompile Include="util\chunks.c" />
|
||||
<ClCompile Include="util\log.c" />
|
||||
<ClCompile Include="util\m2_psb.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
@ -308,6 +308,9 @@
|
||||
<ClInclude Include="util\log.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="util\m2_psb.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="formats.c">
|
||||
@ -1597,6 +1600,9 @@
|
||||
<ClCompile Include="meta\ps3_past.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\psb.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\psf.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -1930,5 +1936,8 @@
|
||||
<ClCompile Include="util\log.c">
|
||||
<Filter>util\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util\m2_psb.c">
|
||||
<Filter>util\Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -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;
|
||||
|
@ -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*/
|
||||
|
587
src/meta/psb.c
Normal file
587
src/meta/psb.c
Normal file
@ -0,0 +1,587 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../util/m2_psb.h"
|
||||
|
||||
|
||||
//todo prepare multichannel
|
||||
#define PSB_MAX_LAYERS 1
|
||||
|
||||
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;
|
||||
uint32_t stream_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 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);
|
||||
|
||||
|
||||
/* 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) */
|
||||
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, psb.stream_size, 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;
|
||||
|
||||
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:
|
||||
vgm_logi("PSB: unknown bps %i (report)\n", psb.bps);
|
||||
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, 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, 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, psb.stream_size, 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, sf, 1);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, psb.stream_offset, psb.stream_size);
|
||||
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, psb.stream_size, psb.fmt_offset, 1,1);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
case DSP: /* Legend of Mana (Switch) */
|
||||
case OPUSNX: /* Legend of Mana (Switch) */
|
||||
default:
|
||||
vgm_logi("PSB: not implemented (ignore)\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
strncpy(vgmstream->stream_name, psb.readable_name, STREAM_NAME_SIZE);
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, psb.stream_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
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;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(ext, ".adpcm") == 0) {
|
||||
psb->codec = DSP;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(spec, "ps3") == 0) {
|
||||
psb->codec = RIFF_AT3;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(spec, "vita") == 0) {
|
||||
if (is_id32be(psb->stream_offset, 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 = data.offset;
|
||||
psb->stream_size = 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 = data.offset;
|
||||
psb->stream_size = 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 0
|
||||
if (psb_node_by_key(&narch, "body", &node)) {
|
||||
data = psb_node_get_data(&node, "data");
|
||||
psb->stream_offset = data.offset;
|
||||
psb->stream_size = data.size;
|
||||
psb->num_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->stream_offset = data.offset;
|
||||
psb->stream_size = data.size;
|
||||
psb->num_samples = psb_node_get_integer(&node, "sampleCount");
|
||||
psb->skip_samples = psb_node_get_integer(&node, "skipSampleCount");
|
||||
}
|
||||
#endif
|
||||
|
||||
data = psb_node_get_data(&narch, "dpds");
|
||||
if (data.offset) {
|
||||
psb->dpds_offset = data.offset;
|
||||
psb->dpds_size = data.size;
|
||||
}
|
||||
|
||||
psb->sample_rate = (int)psb_node_get_float(&narch, "samprate");
|
||||
|
||||
psb->tmp->wav = psb_node_get_string(&narch, "wav");
|
||||
|
||||
/* 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
|
@ -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;
|
||||
}
|
||||
|
118
src/meta/xwma.c
118
src/meta/xwma.c
@ -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;
|
||||
|
@ -231,41 +231,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).
|
||||
|
46
src/util.h
46
src/util.h
@ -51,14 +51,52 @@ 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 float 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
|
||||
|
847
src/util/m2_psb.c
Normal file
847
src/util/m2_psb.c
Normal 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);
|
||||
}
|
88
src/util/m2_psb.h
Normal file
88
src/util/m2_psb.h
Normal 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
|
@ -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 */
|
||||
|
@ -67,7 +67,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 */
|
||||
@ -745,6 +746,7 @@ typedef enum {
|
||||
meta_WXD_WXH,
|
||||
meta_BNK_RELIC,
|
||||
meta_XSH_XSD_XSS,
|
||||
meta_PSB,
|
||||
|
||||
} meta_t;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user