From 42bc5de623fe3a8f9ea641f77f13be65e5a127d6 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 1 Dec 2018 14:01:52 +0100 Subject: [PATCH] Add From Software .xps/.xps+dat [Metal Wolf Chaos (Xbox), Otogi (Xbox)] --- src/formats.c | 4 +- src/libvgmstream.vcproj | 4 + src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 + src/meta/meta.h | 3 + src/meta/xps.c | 223 +++++++++++++++++++++++++++++++ src/vgmstream.c | 2 + src/vgmstream.h | 1 + 8 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/meta/xps.c diff --git a/src/formats.c b/src/formats.c index 8530d34b..015cc16c 100644 --- a/src/formats.c +++ b/src/formats.c @@ -472,6 +472,7 @@ static const char* extension_list[] = { "xwb", "xmd", "xopus", + "xps", "xwc", "xwm", "xwma", @@ -1112,7 +1113,8 @@ static const meta_info meta_info_list[] = { {meta_VS_FFX, "Square VS header"}, {meta_NWAV, "Chunsoft NWAV header"}, {meta_XPCM, "Circus XPCM header"}, - {meta_MSF_TAMASOFT, "TamaSoft MSF header"}, + {meta_MSF_TAMASOFT, "Tama-Soft MSF header"}, + {meta_XPS_DAT, "From Software .XPS+DAT header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index cfc9bf4a..2ad722bc 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1625,6 +1625,10 @@ + + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index a82efc8b..3ebfb4bc 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1465,6 +1465,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index 01158936..3e6f0f50 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -810,4 +810,7 @@ VGMSTREAM * init_vgmstream_xpcm(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_msf_tamasoft(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_xps_dat(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_xps(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/src/meta/xps.c b/src/meta/xps.c new file mode 100644 index 00000000..da84fbb5 --- /dev/null +++ b/src/meta/xps.c @@ -0,0 +1,223 @@ +#include "meta.h" +#include "../coding/coding.h" + +static void read_xps_name(VGMSTREAM *vgmstream, STREAMFILE *streamFile, int file_id); + +/* .XPS+DAT - From Software games streams [Metal Wolf Chaos (Xbox), Otogi (Xbox)] */ +VGMSTREAM * init_vgmstream_xps_dat(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * streamData = NULL; + off_t start_offset, header_offset; + size_t stream_size; + int loop_flag, channel_count, sample_rate, codec, loop_start_sample, loop_end_sample, file_id; + int total_subsongs, target_subsong = streamFile->stream_index; + + + /* checks */ + if (!check_extensions(streamFile, "xps")) + goto fail; + + if (read_32bitLE(0x00,streamFile) != get_streamfile_size(streamFile)) + goto fail; + if (read_32bitBE(0x0c,streamFile) != 0x64696666) /* "diff" */ + goto fail; + + /* handle .xps+dat (bank .xps are done below) */ + streamData = open_streamfile_by_ext(streamFile, "dat"); + if (!streamData) goto fail; + + /* 0x00: approximate file size */ + + total_subsongs = read_32bitLE(0x04,streamData); + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + header_offset = 0x20 + 0x94*(target_subsong-1); /* could start at 0x0c too */ + + file_id = read_32bitLE(header_offset+0x00,streamData); + start_offset = read_32bitLE(header_offset+0x04,streamData); + stream_size = read_32bitLE(header_offset+0x08,streamData); + /* 0x0c: loop start offset? */ + /* 0x10: loop end offset? */ + /* 0x14: always null? */ + codec = read_16bitLE(header_offset+0x18,streamData); + channel_count = read_16bitLE(header_offset+0x1a,streamData); + sample_rate = read_32bitLE(header_offset+0x1c,streamData); + /* 0x20: average bitrate */ + /* 0x24: block size, bps */ + loop_flag = read_32bitLE(header_offset+0x5c,streamData); + loop_start_sample = read_32bitLE(header_offset+0x6c,streamData); + loop_end_sample = read_32bitLE(header_offset+0x70,streamData) + 1; /* a "smpl" chunk basically */ + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->meta_type = meta_XPS_DAT; + vgmstream->loop_start_sample = loop_start_sample; + vgmstream->loop_end_sample = loop_end_sample; + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + + switch(codec) { + case 0x01: + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16); + break; + + case 0x69: + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + vgmstream->num_samples = xbox_ima_bytes_to_samples(stream_size, channel_count); + break; + + default: + goto fail; + } + + read_xps_name(vgmstream, streamFile, file_id); + + if (!vgmstream_open_stream(vgmstream,streamData,start_offset)) + goto fail; + + close_streamfile(streamData); + return vgmstream; + +fail: + close_streamfile(streamData); + close_vgmstream(vgmstream); + return NULL; +} + +static void read_xps_name(VGMSTREAM *vgmstream, STREAMFILE *streamFile, int file_id) { + int i, entries; + int name_id = -1, udss_name_id; + off_t entry_offset = 0x10; + + + /* main section + stream sections (usually same number but not always) */ + entries = read_32bitLE(0x04,streamFile); + + /* "sid\0" entries: find name_id of file_id */ + for (i = 0; i < entries; i++) { + off_t entry_base = entry_offset; + size_t entry_size = read_32bitLE(entry_base+0x00,streamFile); + uint32_t entry_id = read_32bitBE(entry_base+0x04,streamFile); + size_t entry_pad = read_32bitLE(entry_base+0x08,streamFile); + /* 0x0c: always null, rest: entry (format varies) */ + + entry_offset += entry_size + entry_pad + 0x10; + + /* sound info entry */ + if (entry_id == 0x73696400) { /* "sid\0" */ + int entry_file_id = read_32bitLE(entry_base+0x10,streamFile); + int entry_name_id = read_32bitLE(entry_base+0x14,streamFile); + if (entry_file_id == file_id && name_id == -1) { + name_id = entry_name_id; + } + continue; + } + + /* sound stream entry, otherwise no good */ + if (entry_id != 0x75647373) { /* "udss" */ + goto fail; + } + + udss_name_id = read_32bitLE(entry_base+0x10,streamFile); + if (udss_name_id == name_id) { + off_t name_offset = entry_base + 0x10 + 0x08; + size_t name_size = entry_size - 0x08; /* includes null */ + read_string(vgmstream->stream_name,name_size, name_offset,streamFile); + return; + } + } + +fail: + return; +} + +/* .XPS - From Software games banks [Metal Wolf Chaos (Xbox), Otogi (Xbox)] */ +VGMSTREAM * init_vgmstream_xps(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * streamData = NULL; + int i, entries; + off_t entry_offset = 0x10; + int total_subsongs, target_subsong = streamFile->stream_index; + + + /* checks */ + if (!check_extensions(streamFile, "xps")) + goto fail; + + if (read_32bitLE(0x00,streamFile) != get_streamfile_size(streamFile)) + goto fail; + if (read_32bitBE(0x0c,streamFile) != 0x64696666) /* "diff" */ + goto fail; + + /* handle .xps alone (stream .xps+data are done above) */ + streamData = open_streamfile_by_ext(streamFile, "dat"); + if (streamData) goto fail; + + /* main section + bank sections (usually same number but not always) */ + entries = read_32bitLE(0x04,streamFile); + + total_subsongs = 0; + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 /*|| target_subsong > total_subsongs || total_subsongs < 1*/) goto fail; + + + /* parse entries: skip (there is probably a stream/bank flag here) */ + for (i = 0; i < entries; i++) { + off_t entry_base = entry_offset; + size_t entry_size = read_32bitLE(entry_base+0x00,streamFile); + uint32_t entry_id = read_32bitBE(entry_base+0x04,streamFile); + size_t entry_pad = read_32bitLE(entry_base+0x08,streamFile); + /* 0x0c: always null, rest: entry (format varies) */ + + entry_offset += entry_size + entry_pad + 0x10; + + /* sound info entry */ + if (entry_id == 0x73696400) { /* "sid\0" */ + /* keep looking for sound banks */ + continue; + } + + /* sound bank entry, otherwise no good */ + if (entry_id != 0x75647362) { /* "udsb" */ + goto fail; + } + + total_subsongs++; + + /* open internal RIFF */ + if (target_subsong == total_subsongs && vgmstream == NULL) { + STREAMFILE* temp_streamFile; + off_t subsong_offset = entry_base+0x18; + size_t subsong_size = read_32bitLE(entry_base+0x14,streamFile); + + temp_streamFile = setup_subfile_streamfile(streamFile, subsong_offset,subsong_size, "wav"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_riff(temp_streamFile); + close_streamfile(temp_streamFile); + if (!vgmstream) goto fail; + + } + } + + /* subsong not found */ + if (!vgmstream) + goto fail; + + vgmstream->num_streams = total_subsongs; + return vgmstream; + +fail: + close_streamfile(streamData); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 2e870bb4..1f455f6a 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -450,6 +450,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_nwav, init_vgmstream_xpcm, init_vgmstream_msf_tamasoft, + init_vgmstream_xps_dat, + init_vgmstream_xps, /* 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 */ diff --git a/src/vgmstream.h b/src/vgmstream.h index da7709b5..cb107e02 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -706,6 +706,7 @@ typedef enum { meta_NWAV, meta_XPCM, meta_MSF_TAMASOFT, + meta_XPS_DAT, } meta_t;