diff --git a/src/formats.c b/src/formats.c index ec7155f1..861d844e 100644 --- a/src/formats.c +++ b/src/formats.c @@ -523,6 +523,7 @@ static const char* extension_list[] = { "xa30", "xag", "xau", + "xav", "xen", "xma", "xma2", @@ -1203,6 +1204,7 @@ static const meta_info meta_info_list[] = { {meta_RAD, "Traveller's Tales .RAD header"}, {meta_SMACKER, "RAD Game Tools SMACKER header"}, {meta_MZRT, "id Software MZRT header"}, + {meta_XAVS, "Reflections XAVS header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index e3c2959f..aa7aa84e 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -300,6 +300,10 @@ RelativePath=".\meta\vsv_streamfile.h" > + + @@ -1709,6 +1713,10 @@ + + + @@ -503,6 +504,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 529ce7f6..454f77e6 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -122,6 +122,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -1054,6 +1057,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index fd625aea..e2a26c27 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -868,4 +868,6 @@ VGMSTREAM * init_vgmstream_smk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_mzrt(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_xavs(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/src/meta/xavs.c b/src/meta/xavs.c new file mode 100644 index 00000000..6e464326 --- /dev/null +++ b/src/meta/xavs.c @@ -0,0 +1,87 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "xavs_streamfile.h" + +/* XAVS - Reflections audio and video+audio container [Stuntman (PS2)] */ +VGMSTREAM * init_vgmstream_xavs(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count; + int total_subsongs, target_subsong = streamFile->stream_index; + STREAMFILE *temp_streamFile = NULL; + + + /* checks */ + if (!check_extensions(streamFile, "xav")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x58415653) /* "XAVS" */ + goto fail; + + loop_flag = 0; + channel_count = 2; + start_offset = 0x00; + + /* 0x04: 16b width + height (0 if file has no video) */ + /* 0x08: related to video (0 if file has no video) */ + total_subsongs = read_16bitLE(0x0c, streamFile); + /* 0x0c: volume? (0x50, 0x4e) */ + /* 0x10: biggest video chunk? (0 if file has no video) */ + /* 0x14: biggest audio chunk? */ + + if (target_subsong == 0) target_subsong = 1; + if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail; + + /* could use a blocked layout, but this needs interleaved PCM within blocks which can't be done ATM */ + temp_streamFile = setup_xavs_streamfile(streamFile, 0x18, target_subsong - 1); + if (!temp_streamFile) goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_XAVS; + vgmstream->num_streams = total_subsongs; + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + + /* no apparent flags, most videos use 0x41 but not all */ + { + off_t offset = 0x18; + while (offset < get_streamfile_size(streamFile)) { + uint32_t chunk_id = read_32bitLE(offset+0x00, streamFile) & 0xFF; + uint32_t chunk_size = read_32bitLE(offset+0x00, streamFile) >> 8; + + if ((chunk_id & 0xF0) == 0x40) { + vgmstream->sample_rate = 48000; + vgmstream->interleave_block_size = 0x200; + break; + } else if ((chunk_id & 0xF0) == 0x60) { + vgmstream->sample_rate = 24000; + vgmstream->interleave_block_size = 0x100; + break; + } else if (chunk_id == 0x56) { + offset += 0x04 + chunk_size; + } else if (chunk_id == 0x21) { + offset += 0x04; + } else { + goto fail; + } + } + } + + vgmstream->num_samples = pcm_bytes_to_samples(get_streamfile_size(temp_streamFile), channel_count, 16); + + 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/xavs_streamfile.h b/src/meta/xavs_streamfile.h new file mode 100644 index 00000000..ac5e7d3b --- /dev/null +++ b/src/meta/xavs_streamfile.h @@ -0,0 +1,160 @@ +#ifndef _XAVS_STREAMFILE_H_ +#define _XAVS_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + int stream_number; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} xavs_io_data; + + +static size_t xavs_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xavs_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + uint32_t chunk_id = read_32bitLE(data->physical_offset+0x00, streamfile) & 0xFF; + uint32_t chunk_size = read_32bitLE(data->physical_offset+0x00, streamfile) >> 8; + + data->skip_size = 0x04; + + switch(chunk_id) { + /* audio */ + case 0x41: + case 0x61: + case 0x62: + case 0x63: + data->block_size = 0x04 + chunk_size; + if (data->stream_number + 1 == (chunk_id & 0x0F)) { + data->data_size = chunk_size; + } else { + data->data_size = 0; /* ignore other subsongs */ + } + break; + + /* video */ + case 0x56: + data->block_size = 0x04 + chunk_size; + data->data_size = 0; + break; + + /* empty */ + case 0x21: /* related to video */ + case 0x5F: /* "_EOS" */ + data->block_size = 0x04; + data->data_size = 0; + break; + + default: + VGM_LOG("XAVS: unknown type at %lx\n", data->physical_offset); + data->block_size = 0x04; + data->data_size = 0; + break; + } + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t xavs_io_size(STREAMFILE *streamfile, xavs_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + xavs_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of XAVS blocked streams */ +static STREAMFILE* setup_xavs_streamfile(STREAMFILE *streamFile, off_t stream_offset, int stream_number) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + xavs_io_data io_data = {0}; + size_t io_data_size = sizeof(xavs_io_data); + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.stream_number = stream_number; + io_data.logical_offset = -1; /* force phys offset reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, xavs_io_read,xavs_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _XAVS_STREAMFILE_H_ */ diff --git a/src/vgmstream.c b/src/vgmstream.c index 85109070..87454406 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -484,6 +484,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_rad, init_vgmstream_smk, init_vgmstream_mzrt, + init_vgmstream_xavs, /* 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 4f6f98ee..e2ced4bc 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -731,6 +731,7 @@ typedef enum { meta_RAD, meta_SMACKER, meta_MZRT, + meta_XAVS, } meta_t;