From 60945577c00f8dd2f0eec11387eaff077e480526 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 8 Dec 2018 00:10:44 +0100 Subject: [PATCH] Add Vicarious Visions .zss/zsm/ens/enm [X-Men Legends II (multi)] --- src/formats.c | 5 + src/libvgmstream.vcproj | 8 ++ src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 6 + src/meta/meta.h | 2 + src/meta/zsnd.c | 222 +++++++++++++++++++++++++++++++ src/meta/zsnd_streamfile.h | 58 ++++++++ src/vgmstream.c | 1 + src/vgmstream.h | 1 + 9 files changed, 304 insertions(+) create mode 100644 src/meta/zsnd.c create mode 100644 src/meta/zsnd_streamfile.h diff --git a/src/formats.c b/src/formats.c index 015cc16c..cb6f8efe 100644 --- a/src/formats.c +++ b/src/formats.c @@ -128,7 +128,9 @@ static const char* extension_list[] = { "e4x", "eam", "emff", + "enm", "eno", + "ens", "enth", "exa", "ezw", @@ -483,6 +485,8 @@ static const char* extension_list[] = { "ymf", "zsd", + "zsm", + "zss", "zwdsp", "vgmstream" /* fake extension, catch-all for FFmpeg/txth/etc */ @@ -1115,6 +1119,7 @@ static const meta_info meta_info_list[] = { {meta_XPCM, "Circus XPCM header"}, {meta_MSF_TAMASOFT, "Tama-Soft MSF header"}, {meta_XPS_DAT, "From Software .XPS+DAT header"}, + {meta_ZSND, "Vicarious Visions ZSND header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 2ad722bc..c2f8af84 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -279,6 +279,10 @@ + + + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index b72f0d50..a8b064ef 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -490,6 +490,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 3ebfb4bc..4002124a 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -110,6 +110,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -1015,6 +1018,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index 3e6f0f50..7317f75b 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -813,4 +813,6 @@ VGMSTREAM * init_vgmstream_msf_tamasoft(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_xps_dat(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_xps(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_zsnd(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/src/meta/zsnd.c b/src/meta/zsnd.c new file mode 100644 index 00000000..4d055a9a --- /dev/null +++ b/src/meta/zsnd.c @@ -0,0 +1,222 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "zsnd_streamfile.h" + + +/* ZSND - Vicarious Visions games [X-Men Legends II (multi), Marvel Ultimate Alliance (multi)] */ +VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t start_offset, name_offset; + size_t stream_size, name_size; + int loop_flag, channel_count, sample_rate, layers; + uint32_t codec; + int total_subsongs, target_subsong = streamFile->stream_index; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + /* .zss/zsm: standard, .ens/enm: same for PS2 */ + if (!check_extensions(streamFile, "zss,zsm,ens,enm")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x5A534E44) /* "ZSND" */ + goto fail; + /* probably zss=stream, zsm=memory; no diffs other than size */ + + codec = read_32bitBE(0x04,streamFile); + /* 0x08: file size, but slightly bigger (+0x01~04) in some platforms */ + /* 0x0c: header end/first stream start (unneeded as all offsets are absolute) */ + + if (codec == 0x47435542) { /* "GCUB" */ + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } + else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + + /* parse header tables (7*0x0c) */ + { + off_t header2_offset, header3_offset; + + /* table2: stream head */ + int table2_entries = read_32bit(0x1c,streamFile); + //off_t table2_head = read_32bit(0x20,streamFile); + off_t table2_body = read_32bit(0x24,streamFile); + + /* table3: stream body */ + int table3_entries = read_32bit(0x28,streamFile); + //off_t table3_head = read_32bit(0x2c,streamFile); + off_t table3_body = read_32bit(0x30,streamFile); + + /* table1: stream cues? (entry=0x18) + * tables 4-7 seem reserved with 0 entries and offsets to header end, + * though table5 can be seen in boss4_m.zsm (1 entry) */ + + /* table heads are always 0x08 * entries */ + /* 0x00 = ? (varies between tables but consistent between platforms) */ + /* 0x04 = id? (also in table2_body at 0x00?) */ + + /* table1 may have more entries than table2/3 */ + if (table2_entries != table3_entries) { + VGM_LOG("ZSND: table2/3 entries don't match\n"); + goto fail; + } + + + total_subsongs = table2_entries; + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + switch (codec) { + case 0x50432020: /* "PC " */ + header2_offset = table2_body + 0x18*(target_subsong-1); + header3_offset = table3_body + 0x4c*(target_subsong-1); + + layers = read_16bit(header2_offset + 0x02,streamFile); + sample_rate = read_32bit(header2_offset + 0x04,streamFile); + start_offset = read_32bit(header3_offset + 0x00,streamFile); + stream_size = read_32bit(header3_offset + 0x04,streamFile); + name_offset = header3_offset + 0x0c; + name_size = 0x40; + break; + + case 0x58424F58: /* "XBOX" */ + header2_offset = table2_body + 0x1c*(target_subsong-1); + header3_offset = table3_body + 0x54*(target_subsong-1); + + layers = read_16bit(header2_offset + 0x02,streamFile); + sample_rate = read_32bit(header2_offset + 0x04,streamFile); + start_offset = read_32bit(header3_offset + 0x00,streamFile); + stream_size = read_32bit(header3_offset + 0x04,streamFile); + name_offset = header3_offset + 0x14; + name_size = 0x40; + break; + + case 0x50533220: /* "PS2 " (also for PSP) */ + header2_offset = table2_body + 0x10*(target_subsong-1); + header3_offset = table3_body + 0x08*(target_subsong-1); + + sample_rate = read_16bit(header2_offset + 0x02,streamFile); + layers = read_16bit(header2_offset + 0x04,streamFile); + start_offset = read_32bit(header3_offset + 0x00,streamFile); + stream_size = read_32bit(header3_offset + 0x04,streamFile); + name_offset = 0; + name_size = 0; + switch(sample_rate) { + case 0x0800: sample_rate = 22050; break; + case 0x0400: sample_rate = 11025; break; + default: + VGM_LOG("ZSND: unknown sample_rate\n"); + goto fail; + } + break; + + case 0x47435542: /* "GCUB" (also for Wii) */ + header2_offset = table2_body + 0x18*(target_subsong-1); + header3_offset = table3_body + 0x0c*(target_subsong-1); + + layers = read_16bit(header2_offset + 0x02,streamFile); + sample_rate = read_32bit(header2_offset + 0x04,streamFile); + start_offset = read_32bit(header3_offset + 0x00,streamFile); + stream_size = read_32bit(header3_offset + 0x04,streamFile); + /* 0x08: "DSP " for some reason */ + name_offset = 0; + name_size = 0; + break; + + default: + goto fail; + } + + /* maybe flags? */ + switch (layers) { + case 0x00: channel_count = 1; break; + case 0x01: channel_count = 1; break; /* related to looping? */ + case 0x02: channel_count = 2; break; + case 0x22: channel_count = 4; break; + default: + VGM_LOG("ZSND: unknown layers\n"); + goto fail; + } + + loop_flag = 0; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_ZSND; + vgmstream->sample_rate = sample_rate; + + switch (codec) { + case 0x50432020: /* "PC " */ + vgmstream->coding_type = coding_IMA; + vgmstream->layout_type = layout_none; + //todo interleaved stereo (needs to adapt decoder) + //vgmstream->layout_type = layout_interleave; /* interleaved stereo for >2ch*/ + //vgmstream->interleave_block_size = 0x2000 * 2 / channel_count; + + vgmstream->num_samples = ima_bytes_to_samples(stream_size, channel_count); + break; + + case 0x58424F58: /* "XBOX" */ + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_interleave; /* interleaved stereo for >2ch*/ + vgmstream->interleave_block_size = 0x9000 * 2 / channel_count; + + vgmstream->num_samples = xbox_ima_bytes_to_samples(stream_size, channel_count); + break; + + case 0x50533220: /* "PS2 " (also for PSP) */ + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x800; + + vgmstream->num_samples = ps_bytes_to_samples(stream_size, channel_count); + break; + + case 0x47435542: /* "GCUB" (also for Wii) */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x8000; + + /* has a full DSP header, but num_samples may vary slighly between channels, so calc manually */ + dsp_read_coefs_be(vgmstream, streamFile, start_offset+0x1c,0x60); + dsp_read_hist_be(vgmstream, streamFile, start_offset+0x40, 0x60); + start_offset += 0x60*channel_count; + stream_size -= 0x60*channel_count; + + vgmstream->num_samples = dsp_bytes_to_samples(stream_size, channel_count); + break; + + default: + goto fail; + } + + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + + if (name_offset) { + read_string(vgmstream->stream_name,name_size, name_offset,streamFile); + } + + + temp_streamFile = setup_zsnd_streamfile(streamFile, start_offset, stream_size); /* fixes last interleave reads */ + if (!temp_streamFile) goto fail; + + if (!vgmstream_open_stream(vgmstream,temp_streamFile,start_offset)) + goto fail; + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/zsnd_streamfile.h b/src/meta/zsnd_streamfile.h new file mode 100644 index 00000000..950c49be --- /dev/null +++ b/src/meta/zsnd_streamfile.h @@ -0,0 +1,58 @@ +#ifndef _ZSND_STREAMFILE_H_ +#define _ZSND_STREAMFILE_H_ +#include "../streamfile.h" + +typedef struct { + off_t max_offset; +} zsnd_io_data; + +static size_t zsnd_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, zsnd_io_data* data) { + size_t bytes_read, bytes_to_do; + int i; + + /* clamp reads */ + bytes_to_do = length; + if (offset > data->max_offset) + offset = data->max_offset; + if (offset + length > data->max_offset) + bytes_to_do = data->max_offset - offset; + + bytes_read = streamfile->read(streamfile, dest, offset, bytes_to_do); + + /* pretend we got data after max_offset */ + if (bytes_read < length) { + for (i = bytes_read; i < length; i++) { + dest[i] = 0; + } + bytes_read = length; + } + + return bytes_read; +} + +/* ZSND removes last interleave data from the file if blank, but codecs still need to read after it. + * This data could be from next stream or from EOF, so return blank data instead. */ +static STREAMFILE* setup_zsnd_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t stream_size) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + zsnd_io_data io_data = {0}; + size_t io_data_size = sizeof(zsnd_io_data); + + io_data.max_offset = start_offset + stream_size; + + /* setup custom streamfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, zsnd_io_read,NULL); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _ZSND_STREAMFILE_H_ */ diff --git a/src/vgmstream.c b/src/vgmstream.c index 1f455f6a..f896a656 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -452,6 +452,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_msf_tamasoft, init_vgmstream_xps_dat, init_vgmstream_xps, + init_vgmstream_zsnd, /* 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 cb107e02..b897bb56 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -707,6 +707,7 @@ typedef enum { meta_XPCM, meta_MSF_TAMASOFT, meta_XPS_DAT, + meta_ZSND, } meta_t;