diff --git a/src/meta/fsb.c b/src/meta/fsb.c index 730e896e..33a6bd4f 100644 --- a/src/meta/fsb.c +++ b/src/meta/fsb.c @@ -1,600 +1,600 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../layout/layout.h" -#include "fsb_interleave_streamfile.h" - - -/* ************************************************************************************************ - * FSB defines, copied from the public spec (https://www.fmod.org/questions/question/forum-4928/) - * for reference. The format is mostly compatible for FSB1/2/3/4, but not FSB5. - * ************************************************************************************************ */ -/* These flags are used for FMOD_FSB_HEADER::mode */ -#define FMOD_FSB_SOURCE_FORMAT 0x00000001 /* all samples stored in their original compressed format */ -#define FMOD_FSB_SOURCE_BASICHEADERS 0x00000002 /* samples should use the basic header structure */ -#define FMOD_FSB_SOURCE_ENCRYPTED 0x00000004 /* all sample data is encrypted */ -#define FMOD_FSB_SOURCE_BIGENDIANPCM 0x00000008 /* pcm samples have been written out in big-endian format */ -#define FMOD_FSB_SOURCE_NOTINTERLEAVED 0x00000010 /* Sample data is not interleaved. */ -#define FMOD_FSB_SOURCE_MPEG_PADDED 0x00000020 /* Mpeg frames are now rounded up to the nearest 2 bytes for normal sounds, or 16 bytes for multichannel. */ -#define FMOD_FSB_SOURCE_MPEG_PADDED4 0x00000040 /* Mpeg frames are now rounded up to the nearest 4 bytes for normal sounds, or 16 bytes for multichannel. */ - -/* These flags are used for FMOD_FSB_HEADER::version */ -#define FMOD_FSB_VERSION_3_0 0x00030000 /* FSB version 3.0 */ -#define FMOD_FSB_VERSION_3_1 0x00030001 /* FSB version 3.1 */ -#define FMOD_FSB_VERSION_4_0 0x00040000 /* FSB version 4.0 */ - -/* FMOD 3 defines. These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ -#define FSOUND_LOOP_OFF 0x00000001 /* For non looping samples. */ -#define FSOUND_LOOP_NORMAL 0x00000002 /* For forward looping samples. */ -#define FSOUND_LOOP_BIDI 0x00000004 /* For bidirectional looping samples. (no effect if in hardware). */ -#define FSOUND_8BITS 0x00000008 /* For 8 bit samples. */ -#define FSOUND_16BITS 0x00000010 /* For 16 bit samples. */ -#define FSOUND_MONO 0x00000020 /* For mono samples. */ -#define FSOUND_STEREO 0x00000040 /* For stereo samples. */ -#define FSOUND_UNSIGNED 0x00000080 /* For user created source data containing unsigned samples. */ -#define FSOUND_SIGNED 0x00000100 /* For user created source data containing signed data. */ -#define FSOUND_MPEG 0x00000200 /* For MPEG layer 2/3 data. */ -#define FSOUND_CHANNELMODE_ALLMONO 0x00000400 /* Sample is a collection of mono channels. */ -#define FSOUND_CHANNELMODE_ALLSTEREO 0x00000800 /* Sample is a collection of stereo channel pairs */ -#define FSOUND_HW3D 0x00001000 /* Attempts to make samples use 3d hardware acceleration. (if the card supports it) */ -#define FSOUND_2D 0x00002000 /* Tells software (not hardware) based sample not to be included in 3d processing. */ -#define FSOUND_SYNCPOINTS_NONAMES 0x00004000 /* Specifies that syncpoints are present with no names */ -#define FSOUND_DUPLICATE 0x00008000 /* This subsound is a duplicate of the previous one i.e. it uses the same sample data but w/different mode bits */ -#define FSOUND_CHANNELMODE_PROTOOLS 0x00010000 /* Sample is 6ch and uses L C R LS RS LFE standard. */ -#define FSOUND_MPEGACCURATE 0x00020000 /* For FSOUND_Stream_Open - for accurate FSOUND_Stream_GetLengthMs/FSOUND_Stream_SetTime. WARNING, see FSOUND_Stream_Open for inital opening time performance issues. */ -#define FSOUND_HW2D 0x00080000 /* 2D hardware sounds. allows hardware specific effects */ -#define FSOUND_3D 0x00100000 /* 3D software sounds */ -#define FSOUND_32BITS 0x00200000 /* For 32 bit (float) samples. */ -#define FSOUND_IMAADPCM 0x00400000 /* Contents are stored compressed as IMA ADPCM */ -#define FSOUND_VAG 0x00800000 /* For PS2 only - Contents are compressed as Sony VAG format */ -#define FSOUND_XMA 0x01000000 /* For Xbox360 only - Contents are compressed as XMA format */ -#define FSOUND_GCADPCM 0x02000000 /* For Gamecube only - Contents are compressed as Gamecube DSP-ADPCM format */ -#define FSOUND_MULTICHANNEL 0x04000000 /* For PS2 and Gamecube only - Contents are interleaved into a multi-channel (more than stereo) format */ -#define FSOUND_OGG 0x08000000 /* For vorbis encoded ogg data */ -#define FSOUND_CELT 0x08000000 /* For vorbis encoded ogg data */ -#define FSOUND_MPEG_LAYER3 0x10000000 /* Data is in MP3 format. */ -#define FSOUND_MPEG_LAYER2 0x00040000 /* Data is in MP2 format. */ -#define FSOUND_LOADMEMORYIOP 0x20000000 /* For PS2 only - "name" will be interpreted as a pointer to data for streaming and samples. The address provided will be an IOP address */ -#define FSOUND_IMAADPCMSTEREO 0x20000000 /* Signify IMA ADPCM is actually stereo not two interleaved mono */ -#define FSOUND_IGNORETAGS 0x40000000 /* Skips id3v2 etc tag checks when opening a stream, to reduce seek/read overhead when opening files (helps with CD performance) */ -#define FSOUND_SYNCPOINTS 0x80000000 /* Specifies that syncpoints are present */ - -/* These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ -#define FSOUND_CHANNELMODE_MASK (FSOUND_CHANNELMODE_ALLMONO | FSOUND_CHANNELMODE_ALLSTEREO | FSOUND_CHANNELMODE_PROTOOLS) -#define FSOUND_CHANNELMODE_DEFAULT 0x00000000 /* Determine channel assignment automatically from channel count. */ -#define FSOUND_CHANNELMODE_RESERVED 0x00000C00 -#define FSOUND_NORMAL (FSOUND_16BITS | FSOUND_SIGNED | FSOUND_MONO) -#define FSB_SAMPLE_DATA_ALIGN 32 - - -/* simplified struct based on the original definitions */ -typedef enum { MPEG, IMA, PSX, XMA2, DSP, CELT, PCM8, PCM16 } fsb_codec_t; -typedef struct { - /* main header */ - uint32_t id; - int32_t total_subsongs; - uint32_t sample_headers_size; /* all of them including extended information */ - uint32_t sample_data_size; - uint32_t version; /* extended fsb version (in FSB 3/3.1/4) */ - uint32_t flags; /* flags common to all streams (in FSB 3/3.1/4)*/ - /* sample header */ - uint32_t num_samples; - uint32_t stream_size; - uint32_t loop_start; - uint32_t loop_end; - uint32_t mode; - int32_t sample_rate; - uint16_t channels; - /* extra */ - uint32_t base_header_size; - uint32_t sample_header_min; - off_t extradata_offset; - off_t first_extradata_offset; - - meta_t meta_type; - off_t name_offset; - size_t name_size; - - int loop_flag; - - off_t stream_offset; - - fsb_codec_t codec; -} fsb_header; - -/* ********************************************************************************** */ - -static layered_layout_data* build_layered_fsb_celt(STREAMFILE *streamFile, fsb_header* fsb, int is_new_lib); - -/* FSB1~4 - from games using FMOD audio middleware */ -VGMSTREAM * init_vgmstream_fsb(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - int target_subsong = streamFile->stream_index; - fsb_header fsb = {0}; - - - /* checks - * .fsb: standard - * .bnk: Hard Corps Uprising (PS3) */ - if ( !check_extensions(streamFile, "fsb,bnk") ) - goto fail; - - /* check header */ - fsb.id = read_32bitBE(0x00,streamFile); - if (fsb.id == 0x46534231) { /* "FSB1" (somewhat different from other fsbs) */ - fsb.meta_type = meta_FSB1; - fsb.base_header_size = 0x10; - fsb.sample_header_min = 0x40; - - /* main header */ - fsb.total_subsongs = read_32bitLE(0x04,streamFile); - fsb.sample_data_size = read_32bitLE(0x08,streamFile); - fsb.sample_headers_size = 0x40; - fsb.version = 0; - fsb.flags = 0; - - if (fsb.total_subsongs > 1) goto fail; - - /* sample header (first stream only, not sure if there are multi-FSB1) */ - { - off_t header_offset = fsb.base_header_size; - - fsb.name_offset = header_offset; - fsb.name_size = 0x20; - fsb.num_samples = read_32bitLE(header_offset+0x20,streamFile); - fsb.stream_size = read_32bitLE(header_offset+0x24,streamFile); - fsb.sample_rate = read_32bitLE(header_offset+0x28,streamFile); - /* 0x2c:? 0x2e:? 0x30:? 0x32:? */ - fsb.mode = read_32bitLE(header_offset+0x34,streamFile); - fsb.loop_start = read_32bitLE(header_offset+0x38,streamFile); - fsb.loop_end = read_32bitLE(header_offset+0x3c,streamFile); - - VGM_ASSERT(fsb.loop_end > fsb.num_samples, "FSB: loop end over samples (%i vs %i)\n", fsb.loop_end, fsb.num_samples); - fsb.channels = (fsb.mode & FSOUND_STEREO) ? 2 : 1; - if (fsb.loop_end > fsb.num_samples) /* this seems common... */ - fsb.num_samples = fsb.loop_end; - - /* DSP coefs, seek tables, etc */ - fsb.extradata_offset = header_offset+fsb.sample_header_min; - - fsb.stream_offset = fsb.base_header_size + fsb.sample_headers_size; - } - } - else { /* other FSBs (common/extended format) */ - if (fsb.id == 0x46534232) { /* "FSB2" */ - fsb.meta_type = meta_FSB2; - fsb.base_header_size = 0x10; - fsb.sample_header_min = 0x40; /* guessed */ - } else if (fsb.id == 0x46534233) { /* "FSB3" */ - fsb.meta_type = meta_FSB3; - fsb.base_header_size = 0x18; - fsb.sample_header_min = 0x40; - } else if (fsb.id == 0x46534234) { /* "FSB4" */ - fsb.meta_type = meta_FSB4; - fsb.base_header_size = 0x30; - fsb.sample_header_min = 0x50; - } else { - goto fail; - } - - /* main header */ - fsb.total_subsongs = read_32bitLE(0x04,streamFile); - fsb.sample_headers_size = read_32bitLE(0x08,streamFile); - fsb.sample_data_size = read_32bitLE(0x0c,streamFile); - if (fsb.base_header_size > 0x10) { - fsb.version = read_32bitLE(0x10,streamFile); - fsb.flags = read_32bitLE(0x14,streamFile); - /* FSB4: 0x18(8):hash 0x20(10):guid */ - } else { - fsb.version = 0; - fsb.flags = 0; - } - - if (fsb.version == FMOD_FSB_VERSION_3_1) { - fsb.sample_header_min = 0x50; - } else if (fsb.version != 0 /* FSB2 */ - && fsb.version != FMOD_FSB_VERSION_3_0 - && fsb.version != FMOD_FSB_VERSION_4_0) { - goto fail; - } - - if (fsb.sample_headers_size < fsb.sample_header_min) goto fail; - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > fsb.total_subsongs || fsb.total_subsongs < 1) goto fail; - - /* sample header (N-stream) */ - { - int i; - off_t header_offset = fsb.base_header_size; - off_t data_offset = fsb.base_header_size + fsb.sample_headers_size; - - /* find target_stream header (variable sized) */ - for (i = 0; i < fsb.total_subsongs; i++) { - size_t stream_header_size; - - if ((fsb.flags & FMOD_FSB_SOURCE_BASICHEADERS) && i > 0) { - /* miniheader, all subsongs reuse first header [rare, ex. Biker Mice from Mars (PS2)] */ - stream_header_size = 0x08; - fsb.num_samples = read_32bitLE(header_offset+0x00,streamFile); - fsb.stream_size = read_32bitLE(header_offset+0x04,streamFile); - fsb.loop_start = 0; - fsb.loop_end = 0; - } - else { - /* subsong header for normal files */ - stream_header_size = (uint16_t)read_16bitLE(header_offset+0x00,streamFile); - fsb.name_offset = header_offset+0x02; - fsb.name_size = 0x20-0x02; - fsb.num_samples = read_32bitLE(header_offset+0x20,streamFile); - fsb.stream_size = read_32bitLE(header_offset+0x24,streamFile); - fsb.loop_start = read_32bitLE(header_offset+0x28,streamFile); - fsb.loop_end = read_32bitLE(header_offset+0x2c,streamFile); - fsb.mode = read_32bitLE(header_offset+0x30,streamFile); - fsb.sample_rate = read_32bitLE(header_offset+0x34,streamFile); - /* 0x38: defvol, 0x3a: defpan, 0x3c: defpri */ - fsb.channels = read_16bitLE(header_offset+0x3e,streamFile); - /* FSB3.1/4: - * 0x40: mindistance, 0x44: maxdistance, 0x48: varfreq/size_32bits - * 0x4c: varvol, 0x4e: fsb.varpan */ - - /* DSP coefs, seek tables, etc */ - if (stream_header_size > fsb.sample_header_min) { - fsb.extradata_offset = header_offset+fsb.sample_header_min; - if (fsb.first_extradata_offset == 0) - fsb.first_extradata_offset = fsb.extradata_offset; - } - } - - if (i+1 == target_subsong) /* final data_offset found */ - break; - - header_offset += stream_header_size; - data_offset += fsb.stream_size; /* there is no offset so manually count */ - - /* some subsongs offsets need padding (most FSOUND_IMAADPCM, few MPEG too [Hard Reset (PC) subsong 5]) - * other codecs may set PADDED4 (ex. XMA) but don't seem to need it and work fine */ - if (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4) { - if (data_offset % 0x20) - data_offset += 0x20 - (data_offset % 0x20); - } - } - if (i > fsb.total_subsongs) - goto fail; /* not found */ - - fsb.stream_offset = data_offset; - } - } - - - /* convert to clean some code */ - if (fsb.mode & FSOUND_MPEG) fsb.codec = MPEG; - else if (fsb.mode & FSOUND_IMAADPCM) fsb.codec = IMA; - else if (fsb.mode & FSOUND_VAG) fsb.codec = PSX; - else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA2; - else if (fsb.mode & FSOUND_GCADPCM) fsb.codec = DSP; - else if (fsb.mode & FSOUND_CELT) fsb.codec = CELT; - else if (fsb.mode & FSOUND_8BITS) fsb.codec = PCM8; - else fsb.codec = PCM16; - - /* correct compared to FMOD's tools */ - if (fsb.loop_end) - fsb.loop_end += 1; - - /* ping-pong looping = no looping? (forward > reverse > forward) [ex. Biker Mice from Mars (PS2)] */ - VGM_ASSERT(fsb.mode & FSOUND_LOOP_BIDI, "FSB BIDI looping found\n"); - VGM_ASSERT(fsb.mode & FSOUND_LOOP_OFF, "FSB LOOP OFF found\n"); /* sometimes used */ - VGM_ASSERT(fsb.mode & FSOUND_LOOP_NORMAL, "FSB LOOP NORMAL found\n"); /* very rarely set */ - /* XOR encryption for some FSB4, though the flag is only seen after decrypting */ - //;VGM_ASSERT(fsb.flags & FMOD_FSB_SOURCE_ENCRYPTED, "FSB ENCRYPTED found\n"); - - /* sometimes there is garbage at the end or missing bytes due to improper ripping */ - VGM_ASSERT(fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size != streamFile->get_size(streamFile), - "FSB wrong head/data_size found (expected 0x%x vs 0x%x)\n", - fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size, streamFile->get_size(streamFile)); - - /* autodetect unwanted loops */ - { - /* FMOD tool's default behaviour is creating files with full loops and no flags unless disabled - * manually (can be overriden during program too), for all FSB versions. This makes jingles/sfx/voices - * loop when they shouldn't, but most music does full loops seamlessly, so we only want to disable - * if it looks jingly enough. Incidentally, their tools can only make files with full loops. */ - int enable_loop, full_loop, is_small; - - /* seems to mean forced loop */ - enable_loop = (fsb.mode & FSOUND_LOOP_NORMAL); - - /* for MPEG and CELT sometimes full loops are given with around/exact 1 frame less than num_samples, - * probably to account for encoder/decoder delay (ex. The Witcher 2, Hard Reset, Timeshift) */ - if (fsb.codec == CELT) - full_loop = fsb.loop_start - 512 <= 0 && fsb.loop_end >= fsb.num_samples - 512; /* aproximate */ - else if (fsb.codec == MPEG) - full_loop = fsb.loop_start - 1152 <= 0 && fsb.loop_end >= fsb.num_samples - 1152; /* WWF Legends of Wrestlemania uses 2 frames? */ - else - full_loop = fsb.loop_start == 0 && fsb.loop_end == fsb.num_samples; - - /* in seconds (lame but no better way) */ - is_small = fsb.num_samples < 20 * fsb.sample_rate; - - //;VGM_LOG("FSB: loop start=%i, loop end=%i, samples=%i, mode=%x\n", fsb.loop_start, fsb.loop_end, fsb.num_samples, fsb.mode); - //;VGM_LOG("FSB: enable=%i, full=%i, small=%i\n",enable_loop,full_loop,is_small ); - - fsb.loop_flag = !(fsb.mode & FSOUND_LOOP_OFF); /* disabled manually */ - if (fsb.loop_flag && !enable_loop && full_loop && is_small) { - VGM_LOG("FSB: disable unwanted loop\n"); - fsb.loop_flag = 0; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(fsb.channels,fsb.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = fsb.sample_rate; - vgmstream->num_samples = fsb.num_samples; - vgmstream->loop_start_sample = fsb.loop_start; - vgmstream->loop_end_sample = fsb.loop_end; - vgmstream->num_streams = fsb.total_subsongs; - vgmstream->stream_size = fsb.stream_size; - vgmstream->meta_type = fsb.meta_type; - if (fsb.name_offset) - read_string(vgmstream->stream_name,fsb.name_size+1, fsb.name_offset,streamFile); - - switch(fsb.codec) { -#ifdef VGM_USE_MPEG - case MPEG: { /* FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */ - mpeg_custom_config cfg = {0}; - - cfg.fsb_padding = (vgmstream->channels > 2 ? 16 : - (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 4 : - (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 2 : 0))); - - vgmstream->codec_data = init_mpeg_custom(streamFile, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->layout_type = layout_none; - - //VGM_ASSERT(fsb.mode & FSOUND_MPEG_LAYER2, "FSB FSOUND_MPEG_LAYER2 found\n");/* not always set anyway */ - VGM_ASSERT(fsb.mode & FSOUND_IGNORETAGS, "FSB FSOUND_IGNORETAGS found\n"); /* not seen */ - break; - } -#endif - - case IMA: /* FSB3: Bioshock (PC), FSB4: Blade Kitten (PC) */ - vgmstream->coding_type = coding_XBOX_IMA; - vgmstream->layout_type = layout_none; - /* "interleaved header" IMA, only used with >2ch (ex. Blade Kitten 6ch) - * or (seemingly) when flag is used (ex. Dead to Rights 2 (Xbox) 2ch in FSB3.1) */ - if (vgmstream->channels > 2 || (fsb.mode & FSOUND_MULTICHANNEL)) - vgmstream->coding_type = coding_FSB_IMA; - - /* FSOUND_IMAADPCMSTEREO is "noninterleaved, true stereo IMA", but doesn't seem to be any different - * (found in FSB4: Shatter, Blade Kitten (PC), Hard Corps: Uprising (PS3)) */ - break; - - case PSX: /* FSB1: Jurassic Park Operation Genesis (PS2), FSB4: Spider Man Web of Shadows (PSP) */ - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { - vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; - } - else { - vgmstream->interleave_block_size = 0x10; - } - break; - -#ifdef VGM_USE_FFMPEG - case XMA2: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */ - uint8_t buf[0x100]; - size_t bytes, block_size, block_count; - - block_size = 0x8000; /* FSB default */ - block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */ - - bytes = ffmpeg_make_riff_xma2(buf,0x100, fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size); - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, fsb.stream_offset,fsb.stream_size); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples(vgmstream, streamFile, fsb.stream_offset,fsb.stream_size, 0, 0,0); /* samples look ok */ - break; - } -#endif - - case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */ - if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { /* [de Blob (Wii) sfx)] */ - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; - } - else { - vgmstream->coding_type = coding_NGC_DSP_subint; - vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = 0x2; - } - dsp_read_coefs_be(vgmstream, streamFile, fsb.extradata_offset, 0x2e); - break; - -#ifdef VGM_USE_CELT - case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */ - int is_new_lib; - - /* get libcelt version (set in the first subsong only, but try all extradata just in case) */ - if (fsb.first_extradata_offset || fsb.extradata_offset) { - uint32_t lib = fsb.first_extradata_offset ? - (uint32_t)read_32bitLE(fsb.first_extradata_offset, streamFile) : - (uint32_t)read_32bitLE(fsb.extradata_offset, streamFile);; - switch(lib) { - case 0x80000009: is_new_lib = 0; break; /* War Thunder (PC) */ - case 0x80000010: is_new_lib = 1; break; /* Vessel (PC) */ - default: VGM_LOG("FSB: unknown CELT lib 0x%x\n", lib); goto fail; - } - } - else { - /* split FSBs? try to guess from observed bitstreams */ - uint16_t frame = (uint16_t)read_16bitBE(fsb.stream_offset+0x04+0x04,streamFile); - if ((frame & 0xF000) == 0x6000 || frame == 0xFFFE) { - is_new_lib = 1; - } else { - is_new_lib = 0; - } - } - - if (fsb.channels > 2) { /* multistreams */ - vgmstream->layout_data = build_layered_fsb_celt(streamFile, &fsb, is_new_lib); - if (!vgmstream->layout_data) goto fail; - vgmstream->coding_type = coding_CELT_FSB; - vgmstream->layout_type = layout_layered; - } - else { - vgmstream->codec_data = init_celt_fsb(vgmstream->channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_CELT_FSB; - vgmstream->layout_type = layout_none; - } - - break; - } -#endif - - case PCM8: /* assumed, no games known */ - vgmstream->coding_type = (fsb.mode & FSOUND_UNSIGNED) ? coding_PCM8_U : coding_PCM8; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x1; - break; - - case PCM16: /* (PCM16) FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */ - vgmstream->coding_type = (fsb.flags & FMOD_FSB_SOURCE_BIGENDIANPCM) ? coding_PCM16BE : coding_PCM16LE; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x2; - - /* sometimes FSOUND_MONO/FSOUND_STEREO is not set (ex. Dead Space iOS), - * or only STEREO/MONO but not FSOUND_8BITS/FSOUND_16BITS is set */ - break; - - default: - goto fail; - } - - - if ( !vgmstream_open_stream(vgmstream, streamFile, fsb.stream_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -#ifdef VGM_USE_CELT -static layered_layout_data* build_layered_fsb_celt(STREAMFILE *streamFile, fsb_header* fsb, int is_new_lib) { - layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; - int i, layers = (fsb->channels+1) / 2; - - - /* init layout */ - data = init_layout_layered(layers); - if (!data) goto fail; - - /* open each layer subfile (1/2ch CELT streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch) */ - for (i = 0; i < layers; i++) { - int layer_channels = (i+1 == layers && fsb->channels % 2 == 1) - ? 1 : 2; /* last layer can be 1/2ch */ - - /* build the layer VGMSTREAM */ - data->layers[i] = allocate_vgmstream(layer_channels, fsb->loop_flag); - if (!data->layers[i]) goto fail; - - data->layers[i]->sample_rate = fsb->sample_rate; - data->layers[i]->num_samples = fsb->num_samples; - data->layers[i]->loop_start_sample = fsb->loop_start; - data->layers[i]->loop_end_sample = fsb->loop_end; - -#ifdef VGM_USE_CELT - data->layers[i]->codec_data = init_celt_fsb(layer_channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); - if (!data->layers[i]->codec_data) goto fail; - data->layers[i]->coding_type = coding_CELT_FSB; - data->layers[i]->layout_type = layout_none; -#else - goto fail; -#endif - - temp_streamFile = setup_fsb_interleave_streamfile(streamFile, fsb->stream_offset, fsb->stream_size, layers, i, FSB_INT_CELT); - if (!temp_streamFile) goto fail; - - if ( !vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00) ) { - goto fail; - } - } - - /* setup layered VGMSTREAMs */ - if (!setup_layout_layered(data)) - goto fail; - close_streamfile(temp_streamFile); - return data; - -fail: - close_streamfile(temp_streamFile); - free_layout_layered(data); - return NULL; -} -#endif - -/* ****************************************** */ - -static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE *streamfile, off_t subfile_offset, size_t subfile_size); - -/* FSB4 with "\0WAV" Header, found in Deadly Creatures (Wii). - * Has a 0x10 BE header that holds the filesize (unsure if this is from a proper rip). */ -VGMSTREAM * init_vgmstream_fsb4_wav(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *test_streamFile = NULL; - off_t subfile_start = 0x10; - size_t subfile_size = get_streamfile_size(streamFile) - 0x10 - 0x10; - - /* check extensions */ - if ( !check_extensions(streamFile, "fsb,wii") ) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x00574156) /* "\0WAV" */ - goto fail; - - /* parse FSB subfile */ - test_streamFile = setup_fsb4_wav_streamfile(streamFile, subfile_start,subfile_size); - if (!test_streamFile) goto fail; - - vgmstream = init_vgmstream_fsb(test_streamFile); - if (!vgmstream) goto fail; - - /* init the VGMSTREAM */ - close_streamfile(test_streamFile); - return vgmstream; - -fail: - close_streamfile(test_streamFile); - close_vgmstream(vgmstream); - return NULL; -} - -static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_clamp_streamfile(temp_streamFile, subfile_offset,subfile_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"fsb"); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "fsb_interleave_streamfile.h" + + +/* ************************************************************************************************ + * FSB defines, copied from the public spec (https://www.fmod.org/questions/question/forum-4928/) + * for reference. The format is mostly compatible for FSB1/2/3/4, but not FSB5. + * ************************************************************************************************ */ +/* These flags are used for FMOD_FSB_HEADER::mode */ +#define FMOD_FSB_SOURCE_FORMAT 0x00000001 /* all samples stored in their original compressed format */ +#define FMOD_FSB_SOURCE_BASICHEADERS 0x00000002 /* samples should use the basic header structure */ +#define FMOD_FSB_SOURCE_ENCRYPTED 0x00000004 /* all sample data is encrypted */ +#define FMOD_FSB_SOURCE_BIGENDIANPCM 0x00000008 /* pcm samples have been written out in big-endian format */ +#define FMOD_FSB_SOURCE_NOTINTERLEAVED 0x00000010 /* Sample data is not interleaved. */ +#define FMOD_FSB_SOURCE_MPEG_PADDED 0x00000020 /* Mpeg frames are now rounded up to the nearest 2 bytes for normal sounds, or 16 bytes for multichannel. */ +#define FMOD_FSB_SOURCE_MPEG_PADDED4 0x00000040 /* Mpeg frames are now rounded up to the nearest 4 bytes for normal sounds, or 16 bytes for multichannel. */ + +/* These flags are used for FMOD_FSB_HEADER::version */ +#define FMOD_FSB_VERSION_3_0 0x00030000 /* FSB version 3.0 */ +#define FMOD_FSB_VERSION_3_1 0x00030001 /* FSB version 3.1 */ +#define FMOD_FSB_VERSION_4_0 0x00040000 /* FSB version 4.0 */ + +/* FMOD 3 defines. These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ +#define FSOUND_LOOP_OFF 0x00000001 /* For non looping samples. */ +#define FSOUND_LOOP_NORMAL 0x00000002 /* For forward looping samples. */ +#define FSOUND_LOOP_BIDI 0x00000004 /* For bidirectional looping samples. (no effect if in hardware). */ +#define FSOUND_8BITS 0x00000008 /* For 8 bit samples. */ +#define FSOUND_16BITS 0x00000010 /* For 16 bit samples. */ +#define FSOUND_MONO 0x00000020 /* For mono samples. */ +#define FSOUND_STEREO 0x00000040 /* For stereo samples. */ +#define FSOUND_UNSIGNED 0x00000080 /* For user created source data containing unsigned samples. */ +#define FSOUND_SIGNED 0x00000100 /* For user created source data containing signed data. */ +#define FSOUND_MPEG 0x00000200 /* For MPEG layer 2/3 data. */ +#define FSOUND_CHANNELMODE_ALLMONO 0x00000400 /* Sample is a collection of mono channels. */ +#define FSOUND_CHANNELMODE_ALLSTEREO 0x00000800 /* Sample is a collection of stereo channel pairs */ +#define FSOUND_HW3D 0x00001000 /* Attempts to make samples use 3d hardware acceleration. (if the card supports it) */ +#define FSOUND_2D 0x00002000 /* Tells software (not hardware) based sample not to be included in 3d processing. */ +#define FSOUND_SYNCPOINTS_NONAMES 0x00004000 /* Specifies that syncpoints are present with no names */ +#define FSOUND_DUPLICATE 0x00008000 /* This subsound is a duplicate of the previous one i.e. it uses the same sample data but w/different mode bits */ +#define FSOUND_CHANNELMODE_PROTOOLS 0x00010000 /* Sample is 6ch and uses L C R LS RS LFE standard. */ +#define FSOUND_MPEGACCURATE 0x00020000 /* For FSOUND_Stream_Open - for accurate FSOUND_Stream_GetLengthMs/FSOUND_Stream_SetTime. WARNING, see FSOUND_Stream_Open for inital opening time performance issues. */ +#define FSOUND_HW2D 0x00080000 /* 2D hardware sounds. allows hardware specific effects */ +#define FSOUND_3D 0x00100000 /* 3D software sounds */ +#define FSOUND_32BITS 0x00200000 /* For 32 bit (float) samples. */ +#define FSOUND_IMAADPCM 0x00400000 /* Contents are stored compressed as IMA ADPCM */ +#define FSOUND_VAG 0x00800000 /* For PS2 only - Contents are compressed as Sony VAG format */ +#define FSOUND_XMA 0x01000000 /* For Xbox360 only - Contents are compressed as XMA format */ +#define FSOUND_GCADPCM 0x02000000 /* For Gamecube only - Contents are compressed as Gamecube DSP-ADPCM format */ +#define FSOUND_MULTICHANNEL 0x04000000 /* For PS2 and Gamecube only - Contents are interleaved into a multi-channel (more than stereo) format */ +#define FSOUND_OGG 0x08000000 /* For vorbis encoded ogg data */ +#define FSOUND_CELT 0x08000000 /* For vorbis encoded ogg data */ +#define FSOUND_MPEG_LAYER3 0x10000000 /* Data is in MP3 format. */ +#define FSOUND_MPEG_LAYER2 0x00040000 /* Data is in MP2 format. */ +#define FSOUND_LOADMEMORYIOP 0x20000000 /* For PS2 only - "name" will be interpreted as a pointer to data for streaming and samples. The address provided will be an IOP address */ +#define FSOUND_IMAADPCMSTEREO 0x20000000 /* Signify IMA ADPCM is actually stereo not two interleaved mono */ +#define FSOUND_IGNORETAGS 0x40000000 /* Skips id3v2 etc tag checks when opening a stream, to reduce seek/read overhead when opening files (helps with CD performance) */ +#define FSOUND_SYNCPOINTS 0x80000000 /* Specifies that syncpoints are present */ + +/* These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ +#define FSOUND_CHANNELMODE_MASK (FSOUND_CHANNELMODE_ALLMONO | FSOUND_CHANNELMODE_ALLSTEREO | FSOUND_CHANNELMODE_PROTOOLS) +#define FSOUND_CHANNELMODE_DEFAULT 0x00000000 /* Determine channel assignment automatically from channel count. */ +#define FSOUND_CHANNELMODE_RESERVED 0x00000C00 +#define FSOUND_NORMAL (FSOUND_16BITS | FSOUND_SIGNED | FSOUND_MONO) +#define FSB_SAMPLE_DATA_ALIGN 32 + + +/* simplified struct based on the original definitions */ +typedef enum { MPEG, IMA, PSX, XMA2, DSP, CELT, PCM8, PCM16 } fsb_codec_t; +typedef struct { + /* main header */ + uint32_t id; + int32_t total_subsongs; + uint32_t sample_headers_size; /* all of them including extended information */ + uint32_t sample_data_size; + uint32_t version; /* extended fsb version (in FSB 3/3.1/4) */ + uint32_t flags; /* flags common to all streams (in FSB 3/3.1/4)*/ + /* sample header */ + uint32_t num_samples; + uint32_t stream_size; + uint32_t loop_start; + uint32_t loop_end; + uint32_t mode; + int32_t sample_rate; + uint16_t channels; + /* extra */ + uint32_t base_header_size; + uint32_t sample_header_min; + off_t extradata_offset; + off_t first_extradata_offset; + + meta_t meta_type; + off_t name_offset; + size_t name_size; + + int loop_flag; + + off_t stream_offset; + + fsb_codec_t codec; +} fsb_header; + +/* ********************************************************************************** */ + +static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header* fsb, int is_new_lib); + +/* FSB1~4 - from games using FMOD audio middleware */ +VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + int target_subsong = sf->stream_index; + fsb_header fsb = {0}; + + + /* checks + * .fsb: standard + * .bnk: Hard Corps Uprising (PS3) */ + if ( !check_extensions(sf, "fsb,bnk") ) + goto fail; + + /* check header */ + fsb.id = read_32bitBE(0x00,sf); + if (fsb.id == 0x46534231) { /* "FSB1" (somewhat different from other fsbs) */ + fsb.meta_type = meta_FSB1; + fsb.base_header_size = 0x10; + fsb.sample_header_min = 0x40; + + /* main header */ + fsb.total_subsongs = read_32bitLE(0x04,sf); + fsb.sample_data_size = read_32bitLE(0x08,sf); + fsb.sample_headers_size = 0x40; + fsb.version = 0; + fsb.flags = 0; + + if (fsb.total_subsongs > 1) goto fail; + + /* sample header (first stream only, not sure if there are multi-FSB1) */ + { + off_t header_offset = fsb.base_header_size; + + fsb.name_offset = header_offset; + fsb.name_size = 0x20; + fsb.num_samples = read_32bitLE(header_offset+0x20,sf); + fsb.stream_size = read_32bitLE(header_offset+0x24,sf); + fsb.sample_rate = read_32bitLE(header_offset+0x28,sf); + /* 0x2c:? 0x2e:? 0x30:? 0x32:? */ + fsb.mode = read_32bitLE(header_offset+0x34,sf); + fsb.loop_start = read_32bitLE(header_offset+0x38,sf); + fsb.loop_end = read_32bitLE(header_offset+0x3c,sf); + + VGM_ASSERT(fsb.loop_end > fsb.num_samples, "FSB: loop end over samples (%i vs %i)\n", fsb.loop_end, fsb.num_samples); + fsb.channels = (fsb.mode & FSOUND_STEREO) ? 2 : 1; + if (fsb.loop_end > fsb.num_samples) /* this seems common... */ + fsb.num_samples = fsb.loop_end; + + /* DSP coefs, seek tables, etc */ + fsb.extradata_offset = header_offset+fsb.sample_header_min; + + fsb.stream_offset = fsb.base_header_size + fsb.sample_headers_size; + } + } + else { /* other FSBs (common/extended format) */ + if (fsb.id == 0x46534232) { /* "FSB2" */ + fsb.meta_type = meta_FSB2; + fsb.base_header_size = 0x10; + fsb.sample_header_min = 0x40; /* guessed */ + } else if (fsb.id == 0x46534233) { /* "FSB3" */ + fsb.meta_type = meta_FSB3; + fsb.base_header_size = 0x18; + fsb.sample_header_min = 0x40; + } else if (fsb.id == 0x46534234) { /* "FSB4" */ + fsb.meta_type = meta_FSB4; + fsb.base_header_size = 0x30; + fsb.sample_header_min = 0x50; + } else { + goto fail; + } + + /* main header */ + fsb.total_subsongs = read_32bitLE(0x04,sf); + fsb.sample_headers_size = read_32bitLE(0x08,sf); + fsb.sample_data_size = read_32bitLE(0x0c,sf); + if (fsb.base_header_size > 0x10) { + fsb.version = read_32bitLE(0x10,sf); + fsb.flags = read_32bitLE(0x14,sf); + /* FSB4: 0x18(8):hash 0x20(10):guid */ + } else { + fsb.version = 0; + fsb.flags = 0; + } + + if (fsb.version == FMOD_FSB_VERSION_3_1) { + fsb.sample_header_min = 0x50; + } else if (fsb.version != 0 /* FSB2 */ + && fsb.version != FMOD_FSB_VERSION_3_0 + && fsb.version != FMOD_FSB_VERSION_4_0) { + goto fail; + } + + if (fsb.sample_headers_size < fsb.sample_header_min) goto fail; + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > fsb.total_subsongs || fsb.total_subsongs < 1) goto fail; + + /* sample header (N-stream) */ + { + int i; + off_t header_offset = fsb.base_header_size; + off_t data_offset = fsb.base_header_size + fsb.sample_headers_size; + + /* find target_stream header (variable sized) */ + for (i = 0; i < fsb.total_subsongs; i++) { + size_t stream_header_size; + + if ((fsb.flags & FMOD_FSB_SOURCE_BASICHEADERS) && i > 0) { + /* miniheader, all subsongs reuse first header [rare, ex. Biker Mice from Mars (PS2)] */ + stream_header_size = 0x08; + fsb.num_samples = read_32bitLE(header_offset+0x00,sf); + fsb.stream_size = read_32bitLE(header_offset+0x04,sf); + fsb.loop_start = 0; + fsb.loop_end = 0; + } + else { + /* subsong header for normal files */ + stream_header_size = (uint16_t)read_16bitLE(header_offset+0x00,sf); + fsb.name_offset = header_offset+0x02; + fsb.name_size = 0x20-0x02; + fsb.num_samples = read_32bitLE(header_offset+0x20,sf); + fsb.stream_size = read_32bitLE(header_offset+0x24,sf); + fsb.loop_start = read_32bitLE(header_offset+0x28,sf); + fsb.loop_end = read_32bitLE(header_offset+0x2c,sf); + fsb.mode = read_32bitLE(header_offset+0x30,sf); + fsb.sample_rate = read_32bitLE(header_offset+0x34,sf); + /* 0x38: defvol, 0x3a: defpan, 0x3c: defpri */ + fsb.channels = read_16bitLE(header_offset+0x3e,sf); + /* FSB3.1/4: + * 0x40: mindistance, 0x44: maxdistance, 0x48: varfreq/size_32bits + * 0x4c: varvol, 0x4e: fsb.varpan */ + + /* DSP coefs, seek tables, etc */ + if (stream_header_size > fsb.sample_header_min) { + fsb.extradata_offset = header_offset+fsb.sample_header_min; + if (fsb.first_extradata_offset == 0) + fsb.first_extradata_offset = fsb.extradata_offset; + } + } + + if (i+1 == target_subsong) /* final data_offset found */ + break; + + header_offset += stream_header_size; + data_offset += fsb.stream_size; /* there is no offset so manually count */ + + /* some subsongs offsets need padding (most FSOUND_IMAADPCM, few MPEG too [Hard Reset (PC) subsong 5]) + * other codecs may set PADDED4 (ex. XMA) but don't seem to need it and work fine */ + if (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4) { + if (data_offset % 0x20) + data_offset += 0x20 - (data_offset % 0x20); + } + } + if (i > fsb.total_subsongs) + goto fail; /* not found */ + + fsb.stream_offset = data_offset; + } + } + + + /* convert to clean some code */ + if (fsb.mode & FSOUND_MPEG) fsb.codec = MPEG; + else if (fsb.mode & FSOUND_IMAADPCM) fsb.codec = IMA; + else if (fsb.mode & FSOUND_VAG) fsb.codec = PSX; + else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA2; + else if (fsb.mode & FSOUND_GCADPCM) fsb.codec = DSP; + else if (fsb.mode & FSOUND_CELT) fsb.codec = CELT; + else if (fsb.mode & FSOUND_8BITS) fsb.codec = PCM8; + else fsb.codec = PCM16; + + /* correct compared to FMOD's tools */ + if (fsb.loop_end) + fsb.loop_end += 1; + + /* ping-pong looping = no looping? (forward > reverse > forward) [ex. Biker Mice from Mars (PS2)] */ + VGM_ASSERT(fsb.mode & FSOUND_LOOP_BIDI, "FSB BIDI looping found\n"); + VGM_ASSERT(fsb.mode & FSOUND_LOOP_OFF, "FSB LOOP OFF found\n"); /* sometimes used */ + VGM_ASSERT(fsb.mode & FSOUND_LOOP_NORMAL, "FSB LOOP NORMAL found\n"); /* very rarely set */ + /* XOR encryption for some FSB4, though the flag is only seen after decrypting */ + //;VGM_ASSERT(fsb.flags & FMOD_FSB_SOURCE_ENCRYPTED, "FSB ENCRYPTED found\n"); + + /* sometimes there is garbage at the end or missing bytes due to improper ripping */ + VGM_ASSERT(fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size != sf->get_size(sf), + "FSB wrong head/data_size found (expected 0x%x vs 0x%x)\n", + fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size, sf->get_size(sf)); + + /* autodetect unwanted loops */ + { + /* FMOD tool's default behaviour is creating files with full loops and no flags unless disabled + * manually (can be overriden during program too), for all FSB versions. This makes jingles/sfx/voices + * loop when they shouldn't, but most music does full loops seamlessly, so we only want to disable + * if it looks jingly enough. Incidentally, their tools can only make files with full loops. */ + int enable_loop, full_loop, is_small; + + /* seems to mean forced loop */ + enable_loop = (fsb.mode & FSOUND_LOOP_NORMAL); + + /* for MPEG and CELT sometimes full loops are given with around/exact 1 frame less than num_samples, + * probably to account for encoder/decoder delay (ex. The Witcher 2, Hard Reset, Timeshift) */ + if (fsb.codec == CELT) + full_loop = fsb.loop_start - 512 <= 0 && fsb.loop_end >= fsb.num_samples - 512; /* aproximate */ + else if (fsb.codec == MPEG) + full_loop = fsb.loop_start - 1152 <= 0 && fsb.loop_end >= fsb.num_samples - 1152; /* WWF Legends of Wrestlemania uses 2 frames? */ + else + full_loop = fsb.loop_start == 0 && fsb.loop_end == fsb.num_samples; + + /* in seconds (lame but no better way) */ + is_small = fsb.num_samples < 20 * fsb.sample_rate; + + //;VGM_LOG("FSB: loop start=%i, loop end=%i, samples=%i, mode=%x\n", fsb.loop_start, fsb.loop_end, fsb.num_samples, fsb.mode); + //;VGM_LOG("FSB: enable=%i, full=%i, small=%i\n",enable_loop,full_loop,is_small ); + + fsb.loop_flag = !(fsb.mode & FSOUND_LOOP_OFF); /* disabled manually */ + if (fsb.loop_flag && !enable_loop && full_loop && is_small) { + VGM_LOG("FSB: disable unwanted loop\n"); + fsb.loop_flag = 0; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(fsb.channels,fsb.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = fsb.sample_rate; + vgmstream->num_samples = fsb.num_samples; + vgmstream->loop_start_sample = fsb.loop_start; + vgmstream->loop_end_sample = fsb.loop_end; + vgmstream->num_streams = fsb.total_subsongs; + vgmstream->stream_size = fsb.stream_size; + vgmstream->meta_type = fsb.meta_type; + if (fsb.name_offset) + read_string(vgmstream->stream_name,fsb.name_size+1, fsb.name_offset,sf); + + switch(fsb.codec) { +#ifdef VGM_USE_MPEG + case MPEG: { /* FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */ + mpeg_custom_config cfg = {0}; + + cfg.fsb_padding = (vgmstream->channels > 2 ? 16 : + (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 4 : + (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 2 : 0))); + + vgmstream->codec_data = init_mpeg_custom(sf, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->layout_type = layout_none; + + //VGM_ASSERT(fsb.mode & FSOUND_MPEG_LAYER2, "FSB FSOUND_MPEG_LAYER2 found\n");/* not always set anyway */ + VGM_ASSERT(fsb.mode & FSOUND_IGNORETAGS, "FSB FSOUND_IGNORETAGS found\n"); /* not seen */ + break; + } +#endif + + case IMA: /* FSB3: Bioshock (PC), FSB4: Blade Kitten (PC) */ + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + /* "interleaved header" IMA, only used with >2ch (ex. Blade Kitten 6ch) + * or (seemingly) when flag is used (ex. Dead to Rights 2 (Xbox) 2ch in FSB3.1) */ + if (vgmstream->channels > 2 || (fsb.mode & FSOUND_MULTICHANNEL)) + vgmstream->coding_type = coding_FSB_IMA; + + /* FSOUND_IMAADPCMSTEREO is "noninterleaved, true stereo IMA", but doesn't seem to be any different + * (found in FSB4: Shatter, Blade Kitten (PC), Hard Corps: Uprising (PS3)) */ + break; + + case PSX: /* FSB1: Jurassic Park Operation Genesis (PS2), FSB4: Spider Man Web of Shadows (PSP) */ + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { + vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; + } + else { + vgmstream->interleave_block_size = 0x10; + } + break; + +#ifdef VGM_USE_FFMPEG + case XMA2: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */ + uint8_t buf[0x100]; + size_t bytes, block_size, block_count; + + block_size = 0x8000; /* FSB default */ + block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */ + + bytes = ffmpeg_make_riff_xma2(buf,0x100, fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb.stream_offset,fsb.stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, sf, fsb.stream_offset,fsb.stream_size, 0, 0,0); /* samples look ok */ + break; + } +#endif + + case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */ + if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { /* [de Blob (Wii) sfx)] */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; + } + else { + vgmstream->coding_type = coding_NGC_DSP_subint; + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = 0x2; + } + dsp_read_coefs_be(vgmstream, sf, fsb.extradata_offset, 0x2e); + break; + +#ifdef VGM_USE_CELT + case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */ + int is_new_lib; + + /* get libcelt version (set in the first subsong only, but try all extradata just in case) */ + if (fsb.first_extradata_offset || fsb.extradata_offset) { + uint32_t lib = fsb.first_extradata_offset ? + (uint32_t)read_32bitLE(fsb.first_extradata_offset, sf) : + (uint32_t)read_32bitLE(fsb.extradata_offset, sf);; + switch(lib) { + case 0x80000009: is_new_lib = 0; break; /* War Thunder (PC) */ + case 0x80000010: is_new_lib = 1; break; /* Vessel (PC) */ + default: VGM_LOG("FSB: unknown CELT lib 0x%x\n", lib); goto fail; + } + } + else { + /* split FSBs? try to guess from observed bitstreams */ + uint16_t frame = (uint16_t)read_16bitBE(fsb.stream_offset+0x04+0x04,sf); + if ((frame & 0xF000) == 0x6000 || frame == 0xFFFE) { + is_new_lib = 1; + } else { + is_new_lib = 0; + } + } + + if (fsb.channels > 2) { /* multistreams */ + vgmstream->layout_data = build_layered_fsb_celt(sf, &fsb, is_new_lib); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_CELT_FSB; + vgmstream->layout_type = layout_layered; + } + else { + vgmstream->codec_data = init_celt_fsb(vgmstream->channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_CELT_FSB; + vgmstream->layout_type = layout_none; + } + + break; + } +#endif + + case PCM8: /* assumed, no games known */ + vgmstream->coding_type = (fsb.mode & FSOUND_UNSIGNED) ? coding_PCM8_U : coding_PCM8; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x1; + break; + + case PCM16: /* (PCM16) FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */ + vgmstream->coding_type = (fsb.flags & FMOD_FSB_SOURCE_BIGENDIANPCM) ? coding_PCM16BE : coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x2; + + /* sometimes FSOUND_MONO/FSOUND_STEREO is not set (ex. Dead Space iOS), + * or only STEREO/MONO but not FSOUND_8BITS/FSOUND_16BITS is set */ + break; + + default: + goto fail; + } + + + if ( !vgmstream_open_stream(vgmstream, sf, fsb.stream_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +#ifdef VGM_USE_CELT +static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header* fsb, int is_new_lib) { + layered_layout_data* data = NULL; + STREAMFILE* temp_sf = NULL; + int i, layers = (fsb->channels+1) / 2; + + + /* init layout */ + data = init_layout_layered(layers); + if (!data) goto fail; + + /* open each layer subfile (1/2ch CELT streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch) */ + for (i = 0; i < layers; i++) { + int layer_channels = (i+1 == layers && fsb->channels % 2 == 1) + ? 1 : 2; /* last layer can be 1/2ch */ + + /* build the layer VGMSTREAM */ + data->layers[i] = allocate_vgmstream(layer_channels, fsb->loop_flag); + if (!data->layers[i]) goto fail; + + data->layers[i]->sample_rate = fsb->sample_rate; + data->layers[i]->num_samples = fsb->num_samples; + data->layers[i]->loop_start_sample = fsb->loop_start; + data->layers[i]->loop_end_sample = fsb->loop_end; + +#ifdef VGM_USE_CELT + data->layers[i]->codec_data = init_celt_fsb(layer_channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); + if (!data->layers[i]->codec_data) goto fail; + data->layers[i]->coding_type = coding_CELT_FSB; + data->layers[i]->layout_type = layout_none; +#else + goto fail; +#endif + + temp_sf = setup_fsb_interleave_streamfile(sf, fsb->stream_offset, fsb->stream_size, layers, i, FSB_INT_CELT); + if (!temp_sf) goto fail; + + if ( !vgmstream_open_stream(data->layers[i], temp_sf, 0x00) ) { + goto fail; + } + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + close_streamfile(temp_sf); + return data; + +fail: + close_streamfile(temp_sf); + free_layout_layered(data); + return NULL; +} +#endif + +/* ****************************************** */ + +static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE *streamfile, off_t subfile_offset, size_t subfile_size); + +/* FSB4 with "\0WAV" Header, found in Deadly Creatures (Wii). + * Has a 0x10 BE header that holds the filesize (unsure if this is from a proper rip). */ +VGMSTREAM* init_vgmstream_fsb4_wav(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *test_sf = NULL; + off_t subfile_start = 0x10; + size_t subfile_size = get_streamfile_size(sf) - 0x10 - 0x10; + + /* check extensions */ + if ( !check_extensions(sf, "fsb,wii") ) + goto fail; + + if (read_32bitBE(0x00,sf) != 0x00574156) /* "\0WAV" */ + goto fail; + + /* parse FSB subfile */ + test_sf = setup_fsb4_wav_streamfile(sf, subfile_start,subfile_size); + if (!test_sf) goto fail; + + vgmstream = init_vgmstream_fsb(test_sf); + if (!vgmstream) goto fail; + + /* init the VGMSTREAM */ + close_streamfile(test_sf); + return vgmstream; + +fail: + close_streamfile(test_sf); + close_vgmstream(vgmstream); + return NULL; +} + +static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size) { + STREAMFILE *temp_sf = NULL, *new_sf = NULL; + + /* setup subfile */ + new_sf = open_wrap_streamfile(sf); + if (!new_sf) goto fail; + temp_sf = new_sf; + + new_sf = open_clamp_streamfile(temp_sf, subfile_offset,subfile_size); + if (!new_sf) goto fail; + temp_sf = new_sf; + + new_sf = open_fakename_streamfile(temp_sf, NULL,"fsb"); + if (!new_sf) goto fail; + temp_sf = new_sf; + + return temp_sf; + +fail: + close_streamfile(temp_sf); + return NULL; +} diff --git a/src/meta/fsb5.c b/src/meta/fsb5.c index bd4a6c25..218f8f44 100644 --- a/src/meta/fsb5.c +++ b/src/meta/fsb5.c @@ -33,45 +33,45 @@ typedef struct { /* ********************************************************************************** */ -static layered_layout_data* build_layered_fsb5_celt(STREAMFILE *streamFile, fsb5_header* fsb5); -static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fsb5_header* fsb5, off_t configs_offset, size_t configs_size); +static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5); +static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size); /* FSB5 - FMOD Studio multiplatform format */ -VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; fsb5_header fsb5 = {0}; - int target_subsong = streamFile->stream_index; + int target_subsong = sf->stream_index; int i; /* checks */ /* .fsb: standard * .snd: Alchemy engine (also Unity) */ - if (!check_extensions(streamFile,"fsb,snd")) + if (!check_extensions(sf,"fsb,snd")) goto fail; - if (read_32bitBE(0x00,streamFile) != 0x46534235) /* "FSB5" */ + if (read_32bitBE(0x00,sf) != 0x46534235) /* "FSB5" */ goto fail; /* 0x00 is rare (seen in Tales from Space Vita) */ - fsb5.version = read_32bitLE(0x04,streamFile); + fsb5.version = read_32bitLE(0x04,sf); if (fsb5.version != 0x00 && fsb5.version != 0x01) goto fail; - fsb5.total_subsongs = read_32bitLE(0x08,streamFile); - fsb5.sample_header_size = read_32bitLE(0x0C,streamFile); - fsb5.name_table_size = read_32bitLE(0x10,streamFile); - fsb5.sample_data_size = read_32bitLE(0x14,streamFile); - fsb5.codec = read_32bitLE(0x18,streamFile); + fsb5.total_subsongs = read_32bitLE(0x08,sf); + fsb5.sample_header_size = read_32bitLE(0x0C,sf); + fsb5.name_table_size = read_32bitLE(0x10,sf); + fsb5.sample_data_size = read_32bitLE(0x14,sf); + fsb5.codec = read_32bitLE(0x18,sf); /* version 0x01 - 0x1c(4): zero, 0x24(16): hash, 0x34(8): unk * version 0x00 has an extra field (always 0?) at 0x1c */ if (fsb5.version == 0x01) { /* found by tests and assumed to be flags, no games known */ - fsb5.flags = read_32bitLE(0x20,streamFile); + fsb5.flags = read_32bitLE(0x20,sf); } fsb5.base_header_size = (fsb5.version==0x00) ? 0x40 : 0x3C; - if ((fsb5.sample_header_size + fsb5.name_table_size + fsb5.sample_data_size + fsb5.base_header_size) != get_streamfile_size(streamFile)) { - VGM_LOG("FSB5: bad size (%x + %x + %x + %x != %x)\n", fsb5.sample_header_size, fsb5.name_table_size, fsb5.sample_data_size, fsb5.base_header_size, get_streamfile_size(streamFile)); + if ((fsb5.sample_header_size + fsb5.name_table_size + fsb5.sample_data_size + fsb5.base_header_size) != get_streamfile_size(sf)) { + VGM_LOG("FSB5: bad size (%x + %x + %x + %x != %x)\n", fsb5.sample_header_size, fsb5.name_table_size, fsb5.sample_data_size, fsb5.base_header_size, get_streamfile_size(sf)); goto fail; } @@ -87,8 +87,8 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { off_t data_offset = 0; uint32_t sample_mode1, sample_mode2; /* maybe one uint64? */ - sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x00,streamFile); - sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x04,streamFile); + sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x00,sf); + sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x04,sf); stream_header_size += 0x08; /* get samples */ @@ -133,7 +133,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { uint32_t extraflag, extraflag_type, extraflag_size, extraflag_end; do { - extraflag = read_32bitLE(extraflag_offset,streamFile); + extraflag = read_32bitLE(extraflag_offset,sf); extraflag_type = (extraflag >> 25) & 0x7F; /* bits 32..26 (7) */ extraflag_size = (extraflag >> 1) & 0xFFFFFF; /* bits 25..1 (24)*/ extraflag_end = (extraflag & 0x01); /* bit 0 (1) */ @@ -142,15 +142,15 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { if (i + 1 == target_subsong) { switch(extraflag_type) { case 0x01: /* channels */ - fsb5.channels = read_8bit(extraflag_offset+0x04,streamFile); + fsb5.channels = read_8bit(extraflag_offset+0x04,sf); break; case 0x02: /* sample rate */ - fsb5.sample_rate = read_32bitLE(extraflag_offset+0x04,streamFile); + fsb5.sample_rate = read_32bitLE(extraflag_offset+0x04,sf); break; case 0x03: /* loop info */ - fsb5.loop_start = read_32bitLE(extraflag_offset+0x04,streamFile); + fsb5.loop_start = read_32bitLE(extraflag_offset+0x04,sf); if (extraflag_size > 0x04) { /* probably not needed */ - fsb5.loop_end = read_32bitLE(extraflag_offset+0x08,streamFile); + fsb5.loop_end = read_32bitLE(extraflag_offset+0x08,sf); fsb5.loop_end += 1; /* correct compared to FMOD's tools */ } //;VGM_LOG("FSB5: stream %i loop start=%i, loop end=%i, samples=%i\n", i, fsb5.loop_start, fsb5.loop_end, fsb5.num_samples); @@ -183,7 +183,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { case 0x05: /* unknown 32b */ /* rare, found in Tearaway (Vita) with value 0 in first stream and * Shantae and the Seven Sirens (Mobile) with value 0x0003bd72 BE in #44 (Arena Town) */ - VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,streamFile)); + VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,sf)); break; case 0x06: /* XMA seek table */ /* no need for it */ @@ -209,7 +209,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { break; case 0x0d: /* unknown 32b (config? usually 0x3fnnnn00 BE and sometimes 0x3dnnnn00 BE) */ /* found in some XMA2/Vorbis/FADPCM */ - VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,streamFile)); + VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,sf)); break; default: VGM_LOG("FSB5: stream %i unknown flag 0x%x at %x + 0x04 (size 0x%x)\n", i, extraflag_type, (uint32_t)extraflag_offset, extraflag_size); @@ -233,8 +233,8 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { else { off_t next_data_offset; uint32_t next_sample_mode1, next_sample_mode2; - next_sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x00,streamFile); - next_sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x04,streamFile); + next_sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x00,sf); + next_sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x04,sf); next_data_offset = (((next_sample_mode2 & 0x03) << 25) | ((next_sample_mode1 >> 7) & 0x1FFFFFF)) << 5; fsb5.stream_size = next_data_offset - data_offset; @@ -252,7 +252,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { /* get stream name */ if (fsb5.name_table_size) { off_t name_suboffset = fsb5.base_header_size + fsb5.sample_header_size + 0x04*(target_subsong-1); - fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_32bitLE(name_suboffset,streamFile); + fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_32bitLE(name_suboffset,sf); } @@ -270,7 +270,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { vgmstream->stream_size = fsb5.stream_size; vgmstream->meta_type = meta_FSB5; if (fsb5.name_offset) - read_string(vgmstream->stream_name,STREAM_NAME_SIZE, fsb5.name_offset,streamFile); + read_string(vgmstream->stream_name,STREAM_NAME_SIZE, fsb5.name_offset,sf); switch (fsb5.codec) { case 0x00: /* FMOD_SOUND_FORMAT_NONE */ @@ -313,7 +313,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { vgmstream->layout_type = layout_none; vgmstream->interleave_block_size = 0x02; } - dsp_read_coefs_be(vgmstream,streamFile,fsb5.extradata_offset,0x2E); + dsp_read_coefs_be(vgmstream,sf,fsb5.extradata_offset,0x2E); break; case 0x07: /* FMOD_SOUND_FORMAT_IMAADPCM [Skylanders] */ @@ -347,12 +347,12 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { block_count = fsb5.stream_size / block_size + (fsb5.stream_size % block_size ? 1 : 0); bytes = ffmpeg_make_riff_xma2(buf, 0x100, vgmstream->num_samples, fsb5.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, fsb5.stream_offset,fsb5.stream_size); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb5.stream_offset,fsb5.stream_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - xma_fix_raw_samples(vgmstream, streamFile, fsb5.stream_offset,fsb5.stream_size, 0, 0,0); /* samples look ok */ + xma_fix_raw_samples(vgmstream, sf, fsb5.stream_offset,fsb5.stream_size, 0, 0,0); /* samples look ok */ break; } #endif @@ -363,7 +363,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { cfg.fsb_padding = (vgmstream->channels > 2 ? 16 : 4); /* observed default */ - vgmstream->codec_data = init_mpeg_custom(streamFile, fsb5.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); + vgmstream->codec_data = init_mpeg_custom(sf, fsb5.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; break; @@ -375,7 +375,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { int is_multistream = fsb5.channels > 2; if (is_multistream) { - vgmstream->layout_data = build_layered_fsb5_celt(streamFile, &fsb5); + vgmstream->layout_data = build_layered_fsb5_celt(sf, &fsb5); if (!vgmstream->layout_data) goto fail; vgmstream->coding_type = coding_CELT_FSB; vgmstream->layout_type = layout_layered; @@ -398,7 +398,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { /* skip frame size in newer FSBs [Day of the Tentacle Remastered (Vita), Tearaway Unfolded (PS4)] */ - if (configs_size >= 0x08 && (uint8_t)read_8bit(configs_offset, streamFile) != 0xFE) { /* ATRAC9 sync */ + if (configs_size >= 0x08 && (uint8_t)read_8bit(configs_offset, sf) != 0xFE) { /* ATRAC9 sync */ configs_offset += 0x04; configs_size -= 0x04; } @@ -407,7 +407,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { if (is_multistream) { /* multichannel made of various streams [Little Big Planet (Vita)] */ - vgmstream->layout_data = build_layered_fsb5_atrac9(streamFile, &fsb5, configs_offset, configs_size); + vgmstream->layout_data = build_layered_fsb5_atrac9(sf, &fsb5, configs_offset, configs_size); if (!vgmstream->layout_data) goto fail; vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_layered; @@ -417,7 +417,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { atrac9_config cfg = {0}; cfg.channels = vgmstream->channels; - cfg.config_data = read_32bitBE(configs_offset,streamFile); + cfg.config_data = read_32bitBE(configs_offset,sf); //cfg.encoder_delay = 0x100; //todo not used? num_samples seems to count all data vgmstream->codec_data = init_atrac9(&cfg); @@ -434,14 +434,14 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { uint8_t buf[0x100]; int bytes, format, average_bps, block_align; - format = read_16bitBE(fsb5.extradata_offset+0x00,streamFile); - block_align = (uint16_t)read_16bitBE(fsb5.extradata_offset+0x02,streamFile); - average_bps = (uint32_t)read_32bitBE(fsb5.extradata_offset+0x04,streamFile); + format = read_16bitBE(fsb5.extradata_offset+0x00,sf); + block_align = (uint16_t)read_16bitBE(fsb5.extradata_offset+0x02,sf); + average_bps = (uint32_t)read_32bitBE(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(streamFile, buf,bytes, fsb5.stream_offset,fsb5.stream_size); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb5.stream_offset,fsb5.stream_size); if ( !vgmstream->codec_data ) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; @@ -455,11 +455,11 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { cfg.channels = vgmstream->channels; cfg.sample_rate = vgmstream->sample_rate; - cfg.setup_id = read_32bitLE(fsb5.extradata_offset,streamFile); + cfg.setup_id = read_32bitLE(fsb5.extradata_offset,sf); vgmstream->layout_type = layout_none; vgmstream->coding_type = coding_VORBIS_custom; - vgmstream->codec_data = init_vorbis_custom(streamFile, fsb5.stream_offset, VORBIS_FSB, &cfg); + vgmstream->codec_data = init_vorbis_custom(sf, fsb5.stream_offset, VORBIS_FSB, &cfg); if (!vgmstream->codec_data) goto fail; break; @@ -477,7 +477,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { goto fail; } - if (!vgmstream_open_stream(vgmstream,streamFile,fsb5.stream_offset)) + if (!vgmstream_open_stream(vgmstream,sf,fsb5.stream_offset)) goto fail; return vgmstream; @@ -488,15 +488,15 @@ fail: } -static layered_layout_data* build_layered_fsb5_celt(STREAMFILE *streamFile, fsb5_header* fsb5) { +static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5) { layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; + STREAMFILE* temp_sf = NULL; int i, layers = (fsb5->channels+1) / 2; size_t interleave; - if (read_32bitBE(fsb5->stream_offset+0x00,streamFile) != 0x17C30DF3) /* FSB CELT frame ID */ + if (read_32bitBE(fsb5->stream_offset+0x00,sf) != 0x17C30DF3) /* FSB CELT frame ID */ goto fail; - interleave = 0x04+0x04+read_32bitLE(fsb5->stream_offset+0x04,streamFile); /* frame size */ + interleave = 0x04+0x04+read_32bitLE(fsb5->stream_offset+0x04,sf); /* frame size */ //todo unknown interleave for max quality odd channel streams (found in test files) /* FSB5 odd channels use 2ch+2ch...+1ch streams, and the last only goes up to 0x17a, and other @@ -533,29 +533,29 @@ static layered_layout_data* build_layered_fsb5_celt(STREAMFILE *streamFile, fsb5 goto fail; #endif - temp_streamFile = setup_fsb5_streamfile(streamFile, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); - if (!temp_streamFile) goto fail; + temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); + if (!temp_sf) goto fail; - if (!vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00)) + if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00)) goto fail; } /* setup layered VGMSTREAMs */ if (!setup_layout_layered(data)) goto fail; - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); return data; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); free_layout_layered(data); return NULL; } -static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fsb5_header* fsb5, off_t configs_offset, size_t configs_size) { +static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size) { layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; + STREAMFILE* temp_sf = NULL; int i, layers = (configs_size / 0x04); size_t interleave = 0; @@ -566,7 +566,7 @@ static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fs /* open each layer subfile (2ch+2ch..+1/2ch) */ for (i = 0; i < layers; i++) { - uint32_t config = read_32bitBE(configs_offset + 0x04*i, streamFile); + uint32_t config = read_32bitBE(configs_offset + 0x04*i, sf); int channel_index, layer_channels; size_t frame_size; @@ -609,21 +609,21 @@ static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fs goto fail; #endif - temp_streamFile = setup_fsb5_streamfile(streamFile, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); - if (!temp_streamFile) goto fail; + temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); + if (!temp_sf) goto fail; - if (!vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00)) + if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00)) goto fail; } /* setup layered VGMSTREAMs */ if (!setup_layout_layered(data)) goto fail; - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); return data; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); free_layout_layered(data); return NULL; } diff --git a/src/meta/fsb5_fev.c b/src/meta/fsb5_fev.c index ae69c030..21d23801 100644 --- a/src/meta/fsb5_fev.c +++ b/src/meta/fsb5_fev.c @@ -1,54 +1,54 @@ -#include "meta.h" -#include "../coding/coding.h" - -/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */ -VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t subfile_offset, chunk_offset, first_offset = 0x0c; - size_t subfile_size, chunk_size; - - - /* checks */ - if (!check_extensions(streamFile, "bank")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */ - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x46455620) /* "FEV " */ - goto fail; - - /* .fev is an event format referencing various external .fsb, but FMOD can bake .fev and .fsb to - * form a .bank, which is the format we support here (regular .fev is complex and not very interesting). - * Format is RIFF with FMT (main), LIST (config) and SND (FSB5 data), we want the FSB5 offset inside LIST */ - if (!find_chunk_le(streamFile, 0x4C495354,first_offset,0, &chunk_offset,NULL)) /* "LIST" */ - goto fail; - - if (read_32bitBE(chunk_offset+0x00,streamFile) != 0x50524F4A || /* "PROJ" */ - read_32bitBE(chunk_offset+0x04,streamFile) != 0x424E4B49) /* "BNKI" */ - goto fail; /* event .fev has "OBCT" instead of "BNKI" */ - - /* inside BNKI is a bunch of LIST each with event subchunks and finally the fsb offset */ - first_offset = chunk_offset + 0x04; - if (!find_chunk_le(streamFile, 0x534E4448,first_offset,0, &chunk_offset,&chunk_size)) /* "SNDH" */ - goto fail; - - if (chunk_size != 0x0c) - goto fail; /* assuming only one FSB5 is possible */ - subfile_offset = read_32bitLE(chunk_offset+0x04,streamFile); - subfile_size = read_32bitLE(chunk_offset+0x08,streamFile); - - - temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, "fsb"); - if (!temp_streamFile) goto fail; - - vgmstream = init_vgmstream_fsb5(temp_streamFile); - close_streamfile(temp_streamFile); - - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + +/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */ +VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t subfile_offset, chunk_offset, first_offset = 0x0c; + size_t subfile_size, chunk_size; + + + /* checks */ + if (!check_extensions(sf, "bank")) + goto fail; + + if (read_32bitBE(0x00,sf) != 0x52494646) /* "RIFF" */ + goto fail; + if (read_32bitBE(0x08,sf) != 0x46455620) /* "FEV " */ + goto fail; + + /* .fev is an event format referencing various external .fsb, but FMOD can bake .fev and .fsb to + * form a .bank, which is the format we support here (regular .fev is complex and not very interesting). + * Format is RIFF with FMT (main), LIST (config) and SND (FSB5 data), we want the FSB5 offset inside LIST */ + if (!find_chunk_le(sf, 0x4C495354,first_offset,0, &chunk_offset,NULL)) /* "LIST" */ + goto fail; + + if (read_32bitBE(chunk_offset+0x00,sf) != 0x50524F4A || /* "PROJ" */ + read_32bitBE(chunk_offset+0x04,sf) != 0x424E4B49) /* "BNKI" */ + goto fail; /* event .fev has "OBCT" instead of "BNKI" */ + + /* inside BNKI is a bunch of LIST each with event subchunks and finally the fsb offset */ + first_offset = chunk_offset + 0x04; + if (!find_chunk_le(sf, 0x534E4448,first_offset,0, &chunk_offset,&chunk_size)) /* "SNDH" */ + goto fail; + + if (chunk_size != 0x0c) + goto fail; /* assuming only one FSB5 is possible */ + subfile_offset = read_32bitLE(chunk_offset+0x04,sf); + subfile_size = read_32bitLE(chunk_offset+0x08,sf); + + + temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "fsb"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_fsb5(temp_sf); + close_streamfile(temp_sf); + + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/xvag.c b/src/meta/xvag.c index 7bf94290..482fac12 100644 --- a/src/meta/xvag.c +++ b/src/meta/xvag.c @@ -24,37 +24,37 @@ typedef struct { off_t stream_offset; } xvag_header; -static int init_xvag_atrac9(STREAMFILE *streamFile, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset); -static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_header * xvag, off_t chunk_offset, off_t start_offset); +static int init_xvag_atrac9(STREAMFILE* sf, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset); +static layered_layout_data* build_layered_xvag(STREAMFILE* sf, xvag_header * xvag, off_t chunk_offset, off_t start_offset); /* XVAG - Sony's Scream Tool/Stream Creator format (God of War III, Ratchet & Clank Future, The Last of Us, Uncharted) */ -VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE* temp_streamFile = NULL; +VGMSTREAM* init_vgmstream_xvag(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* temp_sf = NULL; xvag_header xvag = {0}; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; off_t start_offset, chunk_offset, first_offset = 0x20; size_t chunk_size; - int total_subsongs = 0, target_subsong = streamFile->stream_index; + int total_subsongs = 0, target_subsong = sf->stream_index; /* checks */ /* .xvag: standard * (extensionless): The Last Of Us (PS3) speech files */ - if (!check_extensions(streamFile,"xvag,")) + if (!check_extensions(sf,"xvag,")) goto fail; - if (read_32bitBE(0x00,streamFile) != 0x58564147) /* "XVAG" */ + if (read_32bitBE(0x00,sf) != 0x58564147) /* "XVAG" */ goto fail; /* endian flag (XVAGs of the same game can use BE or LE, usually when reusing from other platforms) */ - xvag.big_endian = read_8bit(0x08,streamFile) & 0x01; + xvag.big_endian = read_8bit(0x08,sf) & 0x01; if (xvag.big_endian) { read_32bit = read_32bitBE; } else { read_32bit = read_32bitLE; } - start_offset = read_32bit(0x04,streamFile); + start_offset = read_32bit(0x04,sf); /* 0x08: flags? (&0x01=big endian, 0x02=?, 0x06=full RIFF AT9?) * 0x09: flags2? (0x00/0x01/0x04, speaker mode?) * 0x0a: always 0? @@ -62,24 +62,24 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* "fmat": base format (always first) */ - if (!find_chunk(streamFile, 0x666D6174,first_offset,0, &chunk_offset,&chunk_size, xvag.big_endian, 1)) /*"fmat"*/ + if (!find_chunk(sf, 0x666D6174,first_offset,0, &chunk_offset,&chunk_size, xvag.big_endian, 1)) /*"fmat"*/ goto fail; - xvag.channels = read_32bit(chunk_offset+0x00,streamFile); - xvag.codec = read_32bit(chunk_offset+0x04,streamFile); - xvag.num_samples = read_32bit(chunk_offset+0x08,streamFile); + xvag.channels = read_32bit(chunk_offset+0x00,sf); + xvag.codec = read_32bit(chunk_offset+0x04,sf); + xvag.num_samples = read_32bit(chunk_offset+0x08,sf); /* 0x0c: samples again? */ - VGM_ASSERT(xvag.num_samples != read_32bit(chunk_offset+0x0c,streamFile), "XVAG: num_samples values don't match\n"); + VGM_ASSERT(xvag.num_samples != read_32bit(chunk_offset+0x0c,sf), "XVAG: num_samples values don't match\n"); - xvag.factor = read_32bit(chunk_offset+0x10,streamFile); /* for interleave */ - xvag.sample_rate = read_32bit(chunk_offset+0x14,streamFile); - xvag.data_size = read_32bit(chunk_offset+0x18,streamFile); /* not always accurate */ + xvag.factor = read_32bit(chunk_offset+0x10,sf); /* for interleave */ + xvag.sample_rate = read_32bit(chunk_offset+0x14,sf); + xvag.data_size = read_32bit(chunk_offset+0x18,sf); /* not always accurate */ /* extra data, seen in versions 0x61+ */ if (chunk_size > 0x1c) { /* number of interleaved subsongs */ - xvag.subsongs = read_32bit(chunk_offset+0x1c,streamFile); + xvag.subsongs = read_32bit(chunk_offset+0x1c,sf); /* number of interleaved layers (layers * channels_per_layer = channels) */ - xvag.layers = read_32bit(chunk_offset+0x20,streamFile); + xvag.layers = read_32bit(chunk_offset+0x20,sf); } else { xvag.subsongs = 1; @@ -99,9 +99,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* XVAG has no looping, but some PS3 PS-ADPCM seems to do full loops (without data flags) */ if (xvag.codec == 0x06 && xvag.subsongs == 1) { - size_t file_size = get_streamfile_size(streamFile); + size_t file_size = get_streamfile_size(sf); /* simply test if last frame is not empty = may loop */ - xvag.loop_flag = (read_8bit(file_size - 0x01, streamFile) != 0); + xvag.loop_flag = (read_8bit(file_size - 0x01, sf) != 0); xvag.loop_start = 0; xvag.loop_end = ps_bytes_to_samples(file_size - start_offset, xvag.channels); } @@ -162,7 +162,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { if (xvag.layers > 1 && !(xvag.layers*1 == vgmstream->channels || xvag.layers*2 == vgmstream->channels)) goto fail; /* "mpin": mpeg info */ - if (!find_chunk(streamFile, 0x6D70696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"mpin"*/ + if (!find_chunk(sf, 0x6D70696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"mpin"*/ goto fail; /* all layers/subsongs share the same config; not very useful but for posterity: @@ -182,18 +182,18 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { * - 0x34: data size * (rest is padding) * */ - cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); - cfg.skip_samples = read_32bit(chunk_offset+0x20,streamFile); + cfg.chunk_size = read_32bit(chunk_offset+0x1c,sf); + cfg.skip_samples = read_32bit(chunk_offset+0x20,sf); cfg.interleave = cfg.chunk_size * xvag.factor; - vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); + vgmstream->codec_data = init_mpeg_custom(sf, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; /* interleaved subsongs, rarely [Sly Cooper: Thieves in Time (PS3)] */ if (xvag.subsongs > 1) { - temp_streamFile = setup_xvag_streamfile(streamFile, start_offset, cfg.interleave,cfg.chunk_size, (target_subsong-1), total_subsongs); - if (!temp_streamFile) goto fail; + temp_sf = setup_xvag_streamfile(sf, start_offset, cfg.interleave,cfg.chunk_size, (target_subsong-1), total_subsongs); + if (!temp_sf) goto fail; start_offset = 0; } @@ -207,13 +207,13 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* "a9in": ATRAC9 info */ /* 0x00: frame size, 0x04: samples per frame, 0x0c: fact num_samples (no change), 0x10: encoder delay1 */ - if (!find_chunk(streamFile, 0x6139696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"a9in"*/ + if (!find_chunk(sf, 0x6139696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"a9in"*/ goto fail; if (xvag.layers > 1) { /* some Vita/PS4 multichannel [flower (Vita), Uncharted Collection (PS4)]. PS4 ATRAC9 also * does single-stream >2ch, but this can do configs ATRAC9 can't, like 5ch/14ch/etc */ - vgmstream->layout_data = build_layered_xvag(streamFile, &xvag, chunk_offset, start_offset); + vgmstream->layout_data = build_layered_xvag(sf, &xvag, chunk_offset, start_offset); if (!vgmstream->layout_data) goto fail; vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_layered; @@ -222,12 +222,12 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { } else { /* interleaved subsongs (section layers) */ - size_t frame_size = read_32bit(chunk_offset+0x00,streamFile); + size_t frame_size = read_32bit(chunk_offset+0x00,sf); - if (!init_xvag_atrac9(streamFile, vgmstream, &xvag, chunk_offset)) + if (!init_xvag_atrac9(sf, vgmstream, &xvag, chunk_offset)) goto fail; - temp_streamFile = setup_xvag_streamfile(streamFile, start_offset, frame_size*xvag.factor,frame_size, (target_subsong-1), total_subsongs); - if (!temp_streamFile) goto fail; + temp_sf = setup_xvag_streamfile(sf, start_offset, frame_size*xvag.factor,frame_size, (target_subsong-1), total_subsongs); + if (!temp_sf) goto fail; start_offset = 0; } @@ -240,25 +240,25 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { } - if (!vgmstream_open_stream(vgmstream,temp_streamFile ? temp_streamFile : streamFile,start_offset)) + if (!vgmstream_open_stream(vgmstream,temp_sf ? temp_sf : sf,start_offset)) goto fail; - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); return vgmstream; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); close_vgmstream(vgmstream); return NULL; } #ifdef VGM_USE_ATRAC9 -static int init_xvag_atrac9(STREAMFILE *streamFile, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset) { +static int init_xvag_atrac9(STREAMFILE* sf, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset) { int32_t (*read_32bit)(off_t,STREAMFILE*) = xvag->big_endian ? read_32bitBE : read_32bitLE; atrac9_config cfg = {0}; cfg.channels = vgmstream->channels; - cfg.config_data = read_32bitBE(chunk_offset+0x08,streamFile); - cfg.encoder_delay = read_32bit(chunk_offset+0x14,streamFile); + cfg.config_data = read_32bitBE(chunk_offset+0x08,sf); + cfg.encoder_delay = read_32bit(chunk_offset+0x14,sf); vgmstream->codec_data = init_atrac9(&cfg); if (!vgmstream->codec_data) goto fail; @@ -271,9 +271,9 @@ fail: } #endif -static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_header * xvag, off_t chunk_offset, off_t start_offset) { +static layered_layout_data* build_layered_xvag(STREAMFILE* sf, xvag_header * xvag, off_t chunk_offset, off_t start_offset) { layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; + STREAMFILE* temp_sf = NULL; int32_t (*read_32bit)(off_t,STREAMFILE*) = xvag->big_endian ? read_32bitBE : read_32bitLE; int i, layers = xvag->layers; @@ -296,12 +296,12 @@ static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_head switch(xvag->codec) { #ifdef VGM_USE_ATRAC9 case 0x09: { - size_t frame_size = read_32bit(chunk_offset+0x00,streamFile); + size_t frame_size = read_32bit(chunk_offset+0x00,sf); - if (!init_xvag_atrac9(streamFile, data->layers[i], xvag, chunk_offset)) + if (!init_xvag_atrac9(sf, data->layers[i], xvag, chunk_offset)) goto fail; - temp_streamFile = setup_xvag_streamfile(streamFile, start_offset, frame_size*xvag->factor,frame_size, i, layers); - if (!temp_streamFile) goto fail; + temp_sf = setup_xvag_streamfile(sf, start_offset, frame_size*xvag->factor,frame_size, i, layers); + if (!temp_sf) goto fail; break; } #endif @@ -309,9 +309,9 @@ static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_head goto fail; } - if ( !vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00) ) + if ( !vgmstream_open_stream(data->layers[i], temp_sf, 0x00) ) goto fail; - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); } /* setup layered VGMSTREAMs */ @@ -320,7 +320,7 @@ static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_head return data; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); free_layout_layered(data); return NULL; }