From 1d491526ca490bc3cdcdfc7f3c199ae43a0408f4 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 26 Jan 2019 16:14:50 +0100 Subject: [PATCH 1/8] Add extensionless RIFF [Myst III (Xbox)] --- src/meta/riff.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/meta/riff.c b/src/meta/riff.c index f4f0d588..36aa034b 100644 --- a/src/meta/riff.c +++ b/src/meta/riff.c @@ -279,8 +279,9 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { * .xsew: Mega Man X Legacy Collections (PC) * .adpcm: Angry Birds Transformers (Android) * .adw: Dead Rising 2 (PC) - * .wd: Genma Onimusha (Xbox) voices */ - if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd") ) { + * .wd: Genma Onimusha (Xbox) voices + * (extensionless): Myst III (Xbox) voices */ + if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,") ) { ; } else if ( check_extensions(streamFile, "mwv") ) { From a37afdee667d862b7d9c921578962fb945cb064c Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 26 Jan 2019 16:16:40 +0100 Subject: [PATCH 2/8] Fix Ubi IMA v2 [Donald Duck: Goin' Quackers (GC)] --- src/coding/coding.h | 1 - src/coding/ima_decoder.c | 27 +++++++-------------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index fdf3fa0f..d9f0a4b3 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -41,7 +41,6 @@ void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci size_t ima_bytes_to_samples(size_t bytes, int channels); size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels); size_t xbox_ima_bytes_to_samples(size_t bytes, int channels); -size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset); size_t apple_ima4_bytes_to_samples(size_t bytes, int channels); /* ngc_dsp_decoder */ diff --git a/src/coding/ima_decoder.c b/src/coding/ima_decoder.c index 13213ed0..b43094a2 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -937,16 +937,19 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; off_t offset = stream->offset; - /* header fields mostly unknown (vary a lot or look like flags), - * 0x07 0x06 = major/minor tool version?, 0x0c: stereo flag? */ + /* header fields mostly unknown (vary a lot or look like flags, tool version?, 0x08: stereo flag?) */ version = read_8bit(offset + 0x00, stream->streamfile); - big_endian = version < 5; //todo and sb.big_endian? + big_endian = version < 5; read_16bit = big_endian ? read_16bitBE : read_16bitLE; header_samples = read_16bit(offset + 0x0E, stream->streamfile); /* always 10 (per channel) */ hist1 = read_16bit(offset + 0x10 + channel*0x04,stream->streamfile); step_index = read_8bit(offset + 0x12 + channel*0x04,stream->streamfile); - offset += 0x10 + 0x08 + 0x04; //todo v6 has extra 0x08? + offset += 0x10 + 0x08; + if (version >= 3) + offset += 0x04; + //if (version >= 6) /* supposedly this exists, maybe in later BAOs */ + // offset += 0x08; /* write PCM samples, must be written to match header's num_samples (hist mustn't) */ max_samples_to_do = ((samples_to_do > header_samples) ? header_samples : samples_to_do); @@ -1081,19 +1084,3 @@ size_t apple_ima4_bytes_to_samples(size_t bytes, int channels) { return (bytes / block_align) * (block_align - 0x02*channels) * 2 / channels + ((bytes % block_align) ? ((bytes % block_align) - 0x02*channels) * 2 / channels : 0); } - -size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset) { - int version, big_endian, header_samples; - int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; - size_t header_size = 0; - - version = read_8bit(offset + 0x00, streamFile); - big_endian = version < 5; //todo and sb.big_endian? - read_16bit = big_endian ? read_16bitBE : read_16bitLE; - - header_samples = read_16bit(offset + 0x0E, streamFile); /* always 10 (per channel) */ - header_size += 0x10 + 0x04 * channels + 0x04; //todo v6 has extra 0x08? - header_size += header_samples * channels * sizeof(sample); - - return header_samples + ima_bytes_to_samples(bytes - header_size, channels); -} From f806f287f22ca95dc3b677063d16a896ef357408 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 26 Jan 2019 16:30:36 +0100 Subject: [PATCH 3/8] Fix most Ubi SB stuff and yet more games --- src/meta/ubi_sb.c | 1039 ++++++++++++++++++++++------------ src/meta/ubi_sb_streamfile.h | 2 +- 2 files changed, 674 insertions(+), 367 deletions(-) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 242e642a..ad8f7738 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -4,9 +4,67 @@ #include "ubi_sb_streamfile.h" -typedef enum { UBI_IMA, UBI_UNK, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV } ubi_sb_codec; +typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV } ubi_sb_codec; typedef enum { UBI_PC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_PSP, UBI_PS3, UBI_WII, UBI_3DS } ubi_sb_platform; -typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE } ubi_sb_type; +typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_sb_type; + +typedef struct { + int map_version; + size_t map_entry_size; + size_t section1_entry_size; + size_t section2_entry_size; + size_t section3_entry_size; + size_t resource_name_size; + + off_t audio_extra_offset; + off_t audio_stream_size; + off_t audio_stream_offset; + off_t audio_stream_type; + off_t audio_group_id; + off_t audio_external_flag; + off_t audio_loop_flag; + off_t audio_num_samples; + off_t audio_num_samples2; + off_t audio_sample_rate; + off_t audio_channels; + off_t audio_stream_name; + off_t audio_extra_name; + off_t audio_xma_offset; + int audio_external_and; + int audio_loop_and; + int audio_group_and; + int audio_has_internal_names; + size_t audio_internal_interleave; + + off_t sequence_extra_offset; + off_t sequence_sequence_loop; + off_t sequence_sequence_single; + off_t sequence_sequence_count; + off_t sequence_entry_number; + size_t sequence_entry_size; + + off_t layer_extra_offset; + off_t layer_layer_count; + off_t layer_stream_size; + off_t layer_stream_offset; + off_t layer_stream_name; + off_t layer_extra_name; + off_t layer_sample_rate; + off_t layer_channels; + off_t layer_stream_type; + off_t layer_num_samples; + size_t layer_entry_size; + + off_t silence_duration_int; + off_t silence_duration_float; + + int is_padded_section1_offset; + int is_padded_section2_offset; + int is_padded_section3_offset; + int is_padded_sectionX_offset; + int is_padded_sounds_offset; + int ignore_layer_error; +} ubi_sb_config; typedef struct { ubi_sb_platform platform; @@ -15,10 +73,14 @@ typedef struct { int total_subsongs; int bank_subsongs; + /* SB config */ + /* header varies slightly per game/version but not enough parse case by case, + * instead we configure sizes and offsets to where each variable is */ + ubi_sb_config cfg; + /* map base header info */ off_t map_start; off_t map_num; - size_t map_entry_size; uint32_t map_type; uint32_t map_zero; @@ -51,53 +113,6 @@ typedef struct { int flag1; int flag2; - /* header/stream info config */ - /* audio header varies slightly per game/version but not enough parse case by case, - * instead we configure sizes and offsets to where each variable is */ - int map_version; - size_t section1_entry_size; - size_t section2_entry_size; - size_t section3_entry_size; - size_t resource_name_size; - /* type 0x01 (audio) config */ - off_t cfga_extra_offset; - off_t cfga_stream_size; - off_t cfga_stream_offset; - off_t cfga_stream_type; - off_t cfga_group_id; - off_t cfga_external_flag; - off_t cfga_loop_flag; - off_t cfga_num_samples; - off_t cfga_num_samples2; - off_t cfga_sample_rate; - off_t cfga_channels; - off_t cfga_stream_name; - off_t cfga_extra_name; - off_t cfga_xma_offset; - int cfga_and_external_flag; - int cfga_and_loop_flag; - int cfga_and_group_id; - int cfga_has_internal_names; - /* type 0x05/0c (sequence) config */ - off_t cfgs_extra_offset; - off_t cfgs_sequence_loop; - off_t cfgs_sequence_single; - off_t cfgs_sequence_count; - off_t cfgs_entry_number; - size_t sequence_entry_size; - /* type 0x06/0d (layer) config */ - off_t cfgl_extra_offset; - off_t cfgl_layer_count; - off_t cfgl_stream_size; - off_t cfgl_stream_offset; - off_t cfgl_stream_name; - off_t cfgl_extra_name; - off_t cfgl_sample_rate; - off_t cfgl_channels; - off_t cfgl_stream_type; - off_t cfgl_num_samples; - size_t layer_entry_size; - /* header/stream info */ ubi_sb_type type; /* unified type */ ubi_sb_codec codec; /* unified codec */ @@ -124,6 +139,8 @@ typedef struct { int sequence_loop; /* chain index to loop */ int sequence_single; /* if que sequence plays once (loops by default) */ + float duration; /* silence duration */ + int is_external; /* stream is in a external file */ char resource_name[0x24]; /* filename to the external stream, or internal stream info for some games */ @@ -160,11 +177,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { /* PLATFORM DETECTION */ if (!config_sb_platform(&sb, streamFile)) goto fail; - if (sb.big_endian) { - read_32bit = read_32bitBE; - } else { - read_32bit = read_32bitLE; - } + read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; if (target_subsong <= 0) target_subsong = 1; @@ -188,9 +201,20 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { goto fail; sb.section1_offset = 0x1c; - sb.section2_offset = sb.section1_offset + sb.section1_entry_size * sb.section1_num; - sb.sectionX_offset = sb.section2_offset + sb.section2_entry_size * sb.section2_num; + if (sb.cfg.is_padded_section1_offset) + sb.section1_offset = align_size_to_block(sb.section1_offset, 0x10); + + sb.section2_offset = sb.section1_offset + sb.cfg.section1_entry_size * sb.section1_num; + if (sb.cfg.is_padded_section2_offset) + sb.section2_offset = align_size_to_block(sb.section2_offset, 0x10); + + sb.sectionX_offset = sb.section2_offset + sb.cfg.section2_entry_size * sb.section2_num; + if (sb.cfg.is_padded_sectionX_offset) + sb.sectionX_offset = align_size_to_block(sb.sectionX_offset, 0x10); + sb.section3_offset = sb.sectionX_offset + sb.sectionX_size; + if (sb.cfg.is_padded_section3_offset) + sb.section3_offset = align_size_to_block(sb.section3_offset, 0x10); if (!parse_sb_header(&sb, streamTest, target_subsong)) goto fail; @@ -227,11 +251,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { /* PLATFORM DETECTION */ if (!config_sb_platform(&sb, streamFile)) goto fail; - if (sb.big_endian) { - read_32bit = read_32bitBE; - } else { - read_32bit = read_32bitLE; - } + read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; if (target_subsong <= 0) target_subsong = 1; @@ -252,7 +272,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { for (i = 0; i < sb.map_num; i++) { - off_t offset = sb.map_start + i * sb.map_entry_size; + off_t offset = sb.map_start + i * sb.cfg.map_entry_size; /* SUBMAP HEADER */ sb.map_type = read_32bit(offset + 0x00, streamFile); /* usually 0/1=first, 0=rest */ @@ -260,7 +280,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.map_offset = read_32bit(offset + 0x08, streamFile); sb.map_size = read_32bit(offset + 0x0c, streamFile); /* includes sbX header, but not internal streams */ read_string(sb.map_name, sizeof(sb.map_name), offset + 0x10, streamFile); /* null-terminated and may contain garbage after null */ - if (sb.map_version >= 3) + if (sb.cfg.map_version >= 3) sb.map_unknown = read_32bit(offset + 0x30, streamFile); /* uncommon, id/config? longer name? mem garbage? */ /* SB HEADER */ @@ -271,7 +291,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.section2_offset = read_32bit(sb.map_offset + 0x0c, streamFile) + sb.map_offset; sb.section2_num = read_32bit(sb.map_offset + 0x10, streamFile); - if (sb.map_version < 3) { + if (sb.cfg.map_version < 3) { sb.section3_offset = read_32bit(sb.map_offset + 0x14, streamFile) + sb.map_offset; sb.section3_num = read_32bit(sb.map_offset + 0x18, streamFile); sb.sectionX_offset = read_32bit(sb.map_offset + 0x1c, streamFile) + sb.map_offset; @@ -285,7 +305,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.sectionX_size = read_32bit(sb.map_offset + 0x28, streamFile); /* latest map format has another section with sounds after section 2 */ - sb.section2_num += sb.section4_num; /* Let's just merge it with section 2 */ + sb.section2_num += sb.section4_num; /* let's just merge it with section 2 */ sb.sectionX_offset += sb.section4_offset; /* for some reason, this is relative to section 4 here */ } @@ -317,6 +337,36 @@ fail: return NULL; } +#if 0 +/* .BNM - proto-sbX with map style format [Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */ +VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) { + + /* v0x00000000, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields + * are fixed/reserved (unused?). Header entry sizes and config looks the same as early games. + * Main codecs are 01=RAW_PCM and 04=FMT_APM (.apm if external, possibly Ubi IMA v0 but has a full header). + * The sound engine doesn't seem to be named DARE yet. + */ + return NULL; +} +#endif + +#if 0 +/* .BLK - maps in separate .blk chunks [Donald Duck: Goin' Quackers (PS2), The Jungle Book Rhythm N'Groove (PS2)] */ +VGMSTREAM * init_vgmstream_ubi_blk(STREAMFILE *streamFile) { + + /* Somewhat equivalent to a v0x00000003 map: + * - HEADER.BLK: base map header (slightly different?) + submaps headers + * - EVT.BLK: section1 from all submaps + * - RES.BLK: section2 + sectionX from all submaps + * - MAPS.BLK, MAPLANG.BLK: section3 variation? + * - STREAMED.BLK, STRLANG.BLK: audio data + * + * Parsing may be be simplified with multifile_streamfiles? + */ + return NULL; +} +#endif + /* ************************************************************************* */ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *streamHead, STREAMFILE *streamData, off_t start_offset) { @@ -342,14 +392,14 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str vgmstream->layout_type = layout_none; break; - case UBI_UNK: - // todo some kind of blocked layout + Ubi 4/6-bit ADPCM? - // used in Splinter Cell and some Myst IV banks (ex. puzzle_si_splintercell.sb2) + case UBI_ADPCM: + // todo custom Ubi 4/6-bit ADPCM (4b for music/stereo and 6b for voices?) + // used in Batman: Vengeance (PC), Splinter Cell (PC) and some Myst IV (PC/Xbox) banks (ex. puzzle_si_splintercell.sb2) VGM_LOG("UBI SB: unsupported Ubi ADPCM found\n"); goto fail; case RAW_PCM: - vgmstream->coding_type = coding_PCM16LE; /* always LE even on Wii */ + vgmstream->coding_type = coding_PCM16LE; /* always LE */ vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; break; @@ -357,9 +407,11 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case RAW_PSX: vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = (sb->stream_type == 0x00) ? sb->stream_size / sb->channels : 0x10; /* TODO: needs testing */ - if (vgmstream->num_samples == 0) { /* early PS2 games may not set it for a few internal streams */ - vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels) ; + vgmstream->interleave_block_size = (sb->cfg.audio_internal_interleave) ? + sb->cfg.audio_internal_interleave : + sb->stream_size / sb->channels; + if (vgmstream->num_samples == 0) { /* early PS2 games may not set it for internal streams */ + vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels); vgmstream->loop_end_sample = vgmstream->num_samples; } break; @@ -379,7 +431,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str break; case FMT_VAG: - /* skip VAG header (some sb4 use VAG and others raw PSX) */ //todo remove + /* skip VAG header (some sb4 use VAG and others raw PSX) */ if (read_32bitBE(start_offset, streamData) == 0x56414770) { /* "VAGp" */ start_offset += 0x30; sb->stream_size -= 0x30; @@ -394,7 +446,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case FMT_AT3: { ffmpeg_codec_data *ffmpeg_data; - /* skip weird value (3, 4) in Brothers in Arms: D-Day (PSP) */ //todo remove + /* skip weird value (3, 4) in Brothers in Arms: D-Day (PSP) */ if (read_32bitBE(start_offset+0x04,streamData) == 0x52494646) { VGM_LOG("UBI SB: skipping unknown value 0x%x before RIFF\n", read_32bitBE(start_offset+0x00,streamData)); start_offset += 0x04; @@ -433,7 +485,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case FMT_XMA1: { ffmpeg_codec_data *ffmpeg_data; uint8_t buf[0x100]; - uint32_t sec1_num, sec2_num, sec3_num, num_frames, bits_per_frame; + uint32_t sec1_num, sec2_num, sec3_num, bits_per_frame; uint8_t flag; size_t bytes, chunk_size, header_size, data_size; off_t header_offset; @@ -446,7 +498,6 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str sec2_num = read_32bitBE(header_offset + 0x24, streamData); /* number of XMA frames */ sec1_num = read_32bitBE(header_offset + 0x28, streamData); sec3_num = read_32bitBE(header_offset + 0x2c, streamData); - num_frames = sec2_num; bits_per_frame = 4; if (flag == 0x02 || flag == 0x04) @@ -459,7 +510,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str header_size += align_size_to_block(sec2_num * bits_per_frame, 32) / 8; /* bitstream seek table? */ header_size += sec3_num * 0x08; start_offset += header_size; - data_size = num_frames * 0x800; + data_size = sec2_num * 0x800; bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, streamData, 1); @@ -597,11 +648,13 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st if (!data->layers[i]) goto fail; close_streamfile(temp_streamFile); + temp_streamFile = NULL; } if (!setup_layout_layered(data)) goto fail; + /* build the base VGMSTREAM */ vgmstream = allocate_vgmstream(sb->channels * sb->layer_count, sb->loop_flag); if (!vgmstream) goto fail; @@ -619,7 +672,6 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st vgmstream->layout_type = layout_layered; vgmstream->layout_data = data; - if (sb->is_external && streamData) close_streamfile(streamData); return vgmstream; @@ -638,25 +690,26 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE segmented_layout_data* data = NULL; int i; + //todo optimization: open streamData once / only if new name (doesn't change 99% of the time) + /* init layout */ data = init_layout_segmented(sb->sequence_count); if (!data) goto fail; + sb->channels = 0; sb->num_samples = 0; /* open all segments and mix */ for (i = 0; i < sb->sequence_count; i++) { ubi_sb_header temp_sb = *sb; /* memcpy'ed */ int entry_index = sb->sequence_chain[i]; - off_t entry_offset = sb->section2_offset + sb->section2_entry_size * entry_index; - - ;VGM_LOG("UBI SB: index=%x, offset=%lx\n", entry_index, entry_offset); + off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_index; /* parse expected entry */ if (!parse_header(&temp_sb, streamTest, entry_offset, entry_index)) goto fail; - if (temp_sb.type != UBI_AUDIO && temp_sb.type != UBI_LAYER) { + if (temp_sb.type == UBI_NONE || temp_sb.type == UBI_SEQUENCE) { VGM_LOG("UBI SB: unexpected sequence entry type at %x\n", (uint32_t)entry_offset); goto fail; /* technically ok but too much recursiveness? */ } @@ -668,8 +721,11 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE if (i == sb->sequence_loop) sb->loop_start = sb->num_samples; sb->num_samples += data->segments[i]->num_samples; - } + /* save current (silences don't have values, so this unsures they know when memcpy'ed) */ + sb->channels = temp_sb.channels; + sb->sample_rate = temp_sb.sample_rate; + } if (!setup_layout_segmented(data)) goto fail; @@ -700,6 +756,78 @@ fail: return NULL; } +static size_t silence_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, void* data) { + int i; + for (i = 0; i < length; i++) { + dest[i] = 0; + } + return length; /* pretend we read zeroes */ +} +static size_t silence_io_size(STREAMFILE *streamfile, void* data) { + return 0x7FFFFFF; /* whatevs */ +} +static STREAMFILE* setup_silence_streamfile(STREAMFILE *streamFile) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + + /* 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, NULL,0, silence_io_read,silence_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_sb_silence(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + int channel_count, sample_rate; + + channel_count = sb->channels; + sample_rate = sb->sample_rate; + + /* by default silences don't have settings so let's pretend */ + if (channel_count == 0) + channel_count = 2; + if (sample_rate == 0) + sample_rate = 48000; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, 0); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_SB; + vgmstream->sample_rate = sample_rate; + + vgmstream->num_samples = sb->duration * sample_rate; + vgmstream->num_streams = sb->total_subsongs; + vgmstream->stream_size = vgmstream->num_samples * channel_count * 0x02; /* PCM size */ + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + temp_streamFile = setup_silence_streamfile(streamFile); + if ( !vgmstream_open_stream(vgmstream, temp_streamFile, 0x00) ) + goto fail; + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + close_streamfile(temp_streamFile); + return vgmstream; +} + + static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; @@ -708,10 +836,10 @@ static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* s goto fail; } - ;VGM_LOG("UBI SB: target at %x + %x, extra=%x, name=%s, id=%i, t=%i\n", - (uint32_t)sb->header_offset, sb->section2_entry_size, (uint32_t)sb->extra_offset, sb->resource_name, sb->group_id, sb->stream_type); + //;VGM_LOG("UBI SB: target at %x + %x, extra=%x, name=%s, id=%i, t=%i\n", + // (uint32_t)sb->header_offset, sb->cfg.section2_entry_size, (uint32_t)sb->extra_offset, sb->resource_name, sb->group_id, sb->stream_type); - ;VGM_LOG("UBI SB: stream offset=%x, size=%x, name=%s\n", (uint32_t)sb->stream_offset, sb->stream_size, sb->is_external ? sb->resource_name : "internal" ); + //;VGM_LOG("UBI SB: stream offset=%x, size=%x, name=%s\n", (uint32_t)sb->stream_offset, sb->stream_size, sb->is_external ? sb->resource_name : "internal" ); switch(sb->type) { case UBI_AUDIO: @@ -726,6 +854,10 @@ static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* s vgmstream = init_vgmstream_ubi_sb_sequence(sb, streamTest, streamFile); break; + case UBI_SILENCE: + vgmstream = init_vgmstream_ubi_sb_silence(sb, streamTest, streamFile); + break; + case UBI_NONE: default: VGM_LOG("UBI SB: subsong not found/parsed\n"); @@ -777,14 +909,14 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) } } else { - if (sb->is_external || sb->cfga_has_internal_names) + if (sb->is_external || sb->cfg.audio_has_internal_names) res_name = sb->resource_name; else res_name = NULL; } - - /* create name */ + /* maps can contain +10000 subsongs, we need something helpful + * (best done right after subsong detection, since some sequence re-parse types) */ if (grp_name) { if (res_name && res_name[0]) { if (index >= 0) @@ -821,66 +953,70 @@ static int parse_type_audio(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* audio header */ sb->type = UBI_AUDIO; - sb->extra_offset = read_32bit(offset + sb->cfga_extra_offset, streamFile) + sb->sectionX_offset; - sb->stream_size = read_32bit(offset + sb->cfga_stream_size, streamFile); - sb->stream_offset = read_32bit(offset + sb->cfga_stream_offset, streamFile); - sb->channels = (sb->cfga_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(offset + sb->cfga_channels, streamFile) : - (uint32_t)read_32bit(offset + sb->cfga_channels, streamFile); - sb->sample_rate = read_32bit(offset + sb->cfga_sample_rate, streamFile); - sb->stream_type = read_32bit(offset + sb->cfga_stream_type, streamFile); + sb->extra_offset = read_32bit(offset + sb->cfg.audio_extra_offset, streamFile) + sb->sectionX_offset; + sb->stream_size = read_32bit(offset + sb->cfg.audio_stream_size, streamFile); + sb->stream_offset = read_32bit(offset + sb->cfg.audio_stream_offset, streamFile); + sb->channels = (sb->cfg.audio_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(offset + sb->cfg.audio_channels, streamFile) : + (uint32_t)read_32bit(offset + sb->cfg.audio_channels, streamFile); + sb->sample_rate = read_32bit(offset + sb->cfg.audio_sample_rate, streamFile); + sb->stream_type = read_32bit(offset + sb->cfg.audio_stream_type, streamFile); if (sb->stream_size == 0) { VGM_LOG("UBI SB: bad stream size\n"); goto fail; } - if (sb->cfga_loop_flag) { - sb->loop_flag = (read_32bit(offset + sb->cfga_loop_flag, streamFile) & sb->cfga_and_loop_flag); + if (sb->cfg.audio_external_flag) { + sb->is_external = (read_32bit(offset + sb->cfg.audio_external_flag, streamFile) & sb->cfg.audio_external_and); } - if (sb->loop_flag) { - sb->loop_start = read_32bit(offset + sb->cfga_num_samples, streamFile); - sb->num_samples = read_32bit(offset + sb->cfga_num_samples2, streamFile) + sb->loop_start; - if (sb->cfga_num_samples == sb->cfga_num_samples2) { /* early games just repeat and don't set loop start */ - sb->num_samples = sb->loop_start; - sb->loop_start = 0; - } - /* loop starts that aren't 0 do exist but are very rare (ex. Beowulf PSP sb 82, index 575) */ - } else { - sb->num_samples = read_32bit(offset + sb->cfga_num_samples, streamFile); - } - - if (sb->cfga_group_id) { - sb->group_id = read_32bit(offset + sb->cfga_group_id, streamFile); - if (sb->cfga_and_group_id) sb->group_id &= sb->cfga_and_group_id; + if (sb->cfg.audio_group_id) { + sb->group_id = read_32bit(offset + sb->cfg.audio_group_id, streamFile); + if (sb->cfg.audio_group_and) sb->group_id &= sb->cfg.audio_group_and; /* normalize bitflag, known groups are only id 0/1 (if needed could calculate - * (shift-right value here, based on cfga_and_group_id first 1-bit) */ + * shift-right value here, based on cfg.audio_group_and first 1-bit) */ if (sb->group_id > 1) sb->group_id = 1; } - if (sb->cfga_external_flag) { - sb->is_external = (read_32bit(offset + sb->cfga_external_flag, streamFile) & sb->cfga_and_external_flag); + if (sb->cfg.audio_loop_flag) { + sb->loop_flag = (read_32bit(offset + sb->cfg.audio_loop_flag, streamFile) & sb->cfg.audio_loop_and); } - if (sb->resource_name_size > sizeof(sb->resource_name)) { + if (sb->loop_flag) { + sb->loop_start = read_32bit(offset + sb->cfg.audio_num_samples, streamFile); + sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples2, streamFile) + sb->loop_start; + + if (sb->cfg.audio_num_samples == sb->cfg.audio_num_samples2) { /* early games just repeat and don't set loop start */ + sb->num_samples = sb->loop_start; + sb->loop_start = 0; + } + /* Loop starts that aren't 0 do exist but are very rare (ex. Splinter Cell PC, Beowulf PSP sb 82, index 575). + * Also rare are looping external streams, since it's normally done through sequences (ex. Surf's Up). + * Loop end may be +1? (ex. Splinter Cell: Double Agent PS3 #14331). */ + } else { + sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples, streamFile); + } + + if (sb->cfg.resource_name_size > sizeof(sb->resource_name)) { goto fail; } /* external stream name can be found in the header (first versions) or the sectionX table (later versions) */ - if (sb->cfga_stream_name) { - read_string(sb->resource_name, sb->resource_name_size, offset + sb->cfga_stream_name, streamFile); - } else { - sb->cfga_stream_name = read_32bit(offset + sb->cfga_extra_name, streamFile); - if (sb->cfgl_stream_name != 0xFFFFFFFF) - read_string(sb->resource_name, sb->resource_name_size, sb->sectionX_offset + sb->cfga_stream_name, streamFile); + if (sb->cfg.audio_stream_name) { + read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.audio_stream_name, streamFile); + } + else { + sb->cfg.audio_stream_name = read_32bit(offset + sb->cfg.audio_extra_name, streamFile); + if (sb->cfg.layer_stream_name != 0xFFFFFFFF) + read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.audio_stream_name, streamFile); } /* points at XMA1 header in the extra section (only for RAW_XMA1, garbage ignored otherwise) */ - if (sb->cfga_xma_offset) { - sb->xma_header_offset = read_32bit(offset + sb->cfga_xma_offset, streamFile) + sb->sectionX_offset; + if (sb->cfg.audio_xma_offset) { + sb->xma_header_offset = read_32bit(offset + sb->cfg.audio_xma_offset, streamFile) + sb->sectionX_offset; } return 1; @@ -895,16 +1031,16 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str /* sequence chain */ sb->type = UBI_SEQUENCE; - - sb->extra_offset = read_32bit(offset + sb->cfgs_extra_offset, streamFile) + sb->sectionX_offset; - sb->sequence_loop = read_32bit(offset + sb->cfgs_sequence_loop, streamFile); - sb->sequence_single = read_32bit(offset + sb->cfgs_sequence_single, streamFile); - sb->sequence_count = read_32bit(offset + sb->cfgs_sequence_count, streamFile); - - if (sb->sequence_entry_size == 0) { + if (sb->cfg.sequence_entry_size == 0) { VGM_LOG("Ubi SB: sequence entry size not configured at %x\n", (uint32_t)offset); goto fail; } + + sb->extra_offset = read_32bit(offset + sb->cfg.sequence_extra_offset, streamFile) + sb->sectionX_offset; + sb->sequence_loop = read_32bit(offset + sb->cfg.sequence_sequence_loop, streamFile); + sb->sequence_single = read_32bit(offset + sb->cfg.sequence_sequence_single, streamFile); + sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, streamFile); + if (sb->sequence_count > sizeof(sb->sequence_chain)) { /* arbitrary max */ VGM_LOG("Ubi SB: incorrect layer count\n"); goto fail; @@ -913,12 +1049,12 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str /* get chain in extra table */ table_offset = sb->extra_offset; for (i = 0; i < sb->sequence_count; i++) { - uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfgs_entry_number, streamFile); + uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfg.sequence_entry_number, streamFile); - /* some sequences have an upper bit for some reason */ - ;VGM_ASSERT_ONCE(entry_number & 0x80000000, "UBI SB: sequence bit entry found at %x\n", (uint32_t)sb->extra_offset); + /* some sequences have an upper bit (2 bits in Donald Duck voices) for some reason */ + //;VGM_ASSERT_ONCE(entry_number & 0xC0000000, "UBI SB: sequence bit entry found at %x\n", (uint32_t)sb->extra_offset); - entry_number = entry_number & 0x7FFFFFFF; + entry_number = entry_number & 0x3FFFFFFF; if (entry_number > sb->section2_num) { VGM_LOG("UBI SB: chain with wrong entry %i vs %i at %x\n", entry_number, sb->section2_num, (uint32_t)sb->extra_offset); goto fail; @@ -926,7 +1062,7 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str sb->sequence_chain[i] = entry_number; - table_offset += sb->sequence_entry_size; + table_offset += sb->cfg.sequence_entry_size; } return 1; @@ -942,21 +1078,21 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* layer header */ sb->type = UBI_LAYER; + if (sb->cfg.layer_entry_size == 0) { + VGM_LOG("Ubi SB: layer entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } - sb->extra_offset = read_32bit(offset + sb->cfgl_extra_offset, streamFile) + sb->sectionX_offset; - sb->layer_count = read_32bit(offset + sb->cfgl_layer_count, streamFile); - sb->stream_size = read_32bit(offset + sb->cfgl_stream_size, streamFile); - sb->stream_offset = read_32bit(offset + sb->cfgl_stream_offset, streamFile); + sb->extra_offset = read_32bit(offset + sb->cfg.layer_extra_offset, streamFile) + sb->sectionX_offset; + sb->layer_count = read_32bit(offset + sb->cfg.layer_layer_count, streamFile); + sb->stream_size = read_32bit(offset + sb->cfg.layer_stream_size, streamFile); + sb->stream_offset = read_32bit(offset + sb->cfg.layer_stream_offset, streamFile); if (sb->stream_size == 0) { VGM_LOG("UBI SB: bad stream size\n"); goto fail; } - if (sb->layer_entry_size == 0) { - VGM_LOG("Ubi SB: layer entry size not configured at %x\n", (uint32_t)offset); - goto fail; - } if (sb->layer_count > 16) { /* arbitrary max */ VGM_LOG("Ubi SB: incorrect layer count\n"); goto fail; @@ -964,22 +1100,28 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* get 1st layer header in extra table and validate all headers match */ table_offset = sb->extra_offset; - sb->channels = (sb->cfgl_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(table_offset + sb->cfgl_channels, streamFile) : - (uint32_t)read_32bit(table_offset + sb->cfgl_channels, streamFile); - sb->sample_rate = read_32bit(table_offset + sb->cfgl_sample_rate, streamFile); - sb->stream_type = read_32bit(table_offset + sb->cfgl_stream_type, streamFile); - sb->num_samples = read_32bit(table_offset + sb->cfgl_num_samples, streamFile); + sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : + (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); + sb->sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); + sb->stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); + sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); for (i = 0; i < sb->layer_count; i++) { - int channels = (sb->cfgl_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(table_offset + sb->cfgl_channels, streamFile) : - (uint32_t)read_32bit(table_offset + sb->cfgl_channels, streamFile); - int sample_rate = read_32bit(table_offset + sb->cfgl_sample_rate, streamFile); - int stream_type = read_32bit(table_offset + sb->cfgl_stream_type, streamFile); - int num_samples = read_32bit(table_offset + sb->cfgl_num_samples, streamFile); + int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : + (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); + int sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); + int stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); + int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); if (sb->channels != channels || sb->sample_rate != sample_rate || sb->stream_type != stream_type) { - VGM_LOG("Ubi SB: layer headers don't match at %x\n", (uint32_t)table_offset); + VGM_LOG("Ubi SB: %i layer headers don't match at %x\n", sb->layer_count, (uint32_t)table_offset); + + if (sb->cfg.ignore_layer_error) { + sb->layer_count = 1; + break; + } + goto fail; } @@ -988,19 +1130,19 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream sb->num_samples -= 1; } - table_offset += sb->layer_entry_size; + table_offset += sb->cfg.layer_entry_size; } /* all layers seem external */ sb->is_external = 1; /* external stream name can be found in the header (first versions) or the sectionX table (later versions) */ - if (sb->cfgl_stream_name) { - read_string(sb->resource_name, sb->resource_name_size, offset + sb->cfgl_stream_name, streamFile); + if (sb->cfg.layer_stream_name) { + read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.layer_stream_name, streamFile); } else { - sb->cfgl_stream_name = read_32bit(offset + sb->cfgl_extra_name, streamFile); - if (sb->cfgl_stream_name != 0xFFFFFFFF) - read_string(sb->resource_name, sb->resource_name_size, sb->sectionX_offset + sb->cfgl_stream_name, streamFile); + sb->cfg.layer_stream_name = read_32bit(offset + sb->cfg.layer_extra_name, streamFile); + if (sb->cfg.layer_stream_name != 0xFFFFFFFF) + read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.layer_stream_name, streamFile); } /* layers seem to include XMA header */ @@ -1010,15 +1152,44 @@ fail: return 0; } -static int parse_stream_type(ubi_sb_header * sb) { +static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + uint32_t duration_int; + float* duration_float; + + /* silence header */ + sb->type = UBI_SILENCE; + if (sb->cfg.silence_duration_int == 0 && sb->cfg.silence_duration_float == 0) { + VGM_LOG("Ubi SB: silence duration not configured at %x\n", (uint32_t)offset); + goto fail; + } + + if (sb->cfg.silence_duration_int) { + duration_int = (uint32_t)read_32bit(offset + sb->cfg.silence_duration_int, streamFile); + sb->duration = (float)duration_int / 65536.0f; /* 65536.0 is common so probably means 1.0 */ + } + else if (sb->cfg.silence_duration_float) { + duration_int = (uint32_t)read_32bit(offset + sb->cfg.silence_duration_float, streamFile); + duration_float = (float*)&duration_int; + sb->duration = *duration_float; + } + + return 1; +fail: + return 0; +} + + +/* find actual codec from type (as different games' stream_type can overlap) */ +static int parse_stream_codec(ubi_sb_header * sb) { if (sb->type == UBI_SEQUENCE) return 1; - /* happens in a few internal sounds from early Xbox games */ + /* happens randomly in internal sounds in early Xbox games */ if (sb->stream_type > 0xFF) { - VGM_LOG("UBI SB: garbage in stream_type\n"); - sb->stream_type = 0; + //;VGM_LOG("UBI SB: garbage in stream_type\n"); + sb->stream_type = 0; /* probably ignored for non-stream types */ } /* guess codec */ @@ -1048,7 +1219,7 @@ static int parse_stream_type(ubi_sb_header * sb) { sb->codec = RAW_XMA1; break; #if 0 - case UBI_PS3: /* assumed, but not games seem to use it */ + case UBI_PS3: /* assumed, but no games seem to use it */ sb->codec = RAW_AT3; break; #endif @@ -1064,7 +1235,15 @@ static int parse_stream_type(ubi_sb_header * sb) { case 0x01: switch (sb->version) { case 0x00000003: /* Donald Duck: Goin' Quackers */ - sb->codec = RAW_DSP; + case 0x00000004: /* Myst III: Exile */ + case 0x00000007: /* Splinter Cell */ + switch (sb->platform) { + case UBI_PS2: sb->codec = RAW_PSX; break; + case UBI_GC: sb->codec = RAW_DSP; break; + case UBI_PC: sb->codec = RAW_PCM; break; + case UBI_XBOX: sb->codec = RAW_XBOX; break; + default: VGM_LOG("UBI SB: unknown old internal format\n"); goto fail; + } break; default: sb->codec = RAW_PCM; /* uncommon, ex. Wii/PSP/3DS */ @@ -1076,7 +1255,7 @@ static int parse_stream_type(ubi_sb_header * sb) { switch (sb->version) { case 0x00000007: /* Splinter Cell, Splinter Cell: Pandora Tomorrow */ case 0x00120012: /* Myst IV: Exile */ - sb->codec = UBI_UNK; + sb->codec = UBI_ADPCM; break; default: sb->codec = RAW_PSX; /* PS3 */ @@ -1115,11 +1294,18 @@ static int parse_stream_type(ubi_sb_header * sb) { break; case 0x06: - sb->codec = RAW_PSX; /* later PSP and PS3(?) games */ + switch (sb->version) { + case 0x00000003: /* Batman: Vengeance (PC) */ + sb->codec = UBI_ADPCM; + break; + default: + sb->codec = RAW_PSX; /* later PSP and PS3(?) games */ + break; + } break; case 0x07: - sb->codec = RAW_AT3; + sb->codec = RAW_AT3; /* PS3 games */ break; case 0x08: @@ -1144,6 +1330,7 @@ fail: return 0; } +/* find actual stream offset in section3 */ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; int i, j, k; @@ -1153,7 +1340,7 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { VGM_ASSERT(!sb->is_map && sb->section3_num > 2, "UBI SB: section3 > 2 found\n"); - if (!(sb->cfga_group_id || sb->is_map) && sb->section3_num > 1) { + if (!(sb->cfg.audio_group_id || sb->is_map) && sb->section3_num > 1) { VGM_LOG("UBI SB: unexpected number of internal stream groups %i\n", sb->section3_num); goto fail; } @@ -1179,7 +1366,7 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { int index = read_32bit(table_offset + 0x08 * j + 0x00, streamFile) & 0x0000FFFF; if (index == sb->header_index) { - if (!sb->cfga_group_id && table2_num > 1) { + if (!sb->cfg.audio_group_id && table2_num > 1) { VGM_LOG("UBI SB: unexpected number of internal stream map groups %i at %x\n", table2_num, (uint32_t)table2_offset); goto fail; } @@ -1190,8 +1377,7 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { * 0x00 - group ID * 0x04 - size with padding included * 0x08 - size without padding - * 0x0c - absolute offset */ - uint32_t id = read_32bit(table2_offset + 0x10 * k + 0x00, streamFile); + * 0x0c - absolute offset */ uint32_t id = read_32bit(table2_offset + 0x10 * k + 0x00, streamFile); if (id == sb->group_id) { sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x0c, streamFile); break; @@ -1208,12 +1394,14 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { else { /* banks store internal sounds after all headers and adjusted by the group table, find the matching entry */ - off_t sounds_offset = sb->section3_offset + sb->section3_entry_size*sb->section3_num; + off_t sounds_offset = sb->section3_offset + sb->cfg.section3_entry_size*sb->section3_num; + if (sb->cfg.is_padded_sounds_offset) + sounds_offset = align_size_to_block(sounds_offset, 0x10); sb->stream_offset = sounds_offset + sb->stream_offset; - if (sb->cfga_group_id && sb->section3_num > 1) { /* maybe should always test this? */ + if (sb->cfg.audio_group_id && sb->section3_num > 1) { /* maybe should always test this? */ for (i = 0; i < sb->section3_num; i++) { - off_t offset = sb->section3_offset + sb->section3_entry_size * i; + off_t offset = sb->section3_offset + sb->cfg.section3_entry_size * i; /* table has unordered ids+size, so if our id doesn't match current data offset must be beyond */ if (read_32bit(offset + 0x00, streamFile) == sb->group_id) @@ -1228,11 +1416,10 @@ fail: return 0; } -/* parse a header resource at offset */ +/* parse a single known header resource at offset (see config_sb for info) */ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset, int index) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - /* parse known headers (see config_sb for info) */ sb->header_index = index; sb->header_offset = offset; @@ -1254,16 +1441,19 @@ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset if (!parse_type_layer(sb, offset, streamFile)) goto fail; break; + case 0x08: + case 0x0f: + if (!parse_type_silence(sb, offset, streamFile)) + goto fail; + break; default: VGM_LOG("UBI SB: unknown header type at %x\n", (uint32_t)offset); goto fail; } - /* find actual codec (as different games' stream_type can overlap) */ - if (!parse_stream_type(sb)) + if (!parse_stream_codec(sb)) goto fail; - /* find actual stream offset in section3 */ if (!parse_internal_offset(sb, streamFile)) goto fail; @@ -1272,22 +1462,21 @@ fail: return 0; } -/* parse a bank and its possible audio headers (resources). */ +/* parse a bank and its possible audio headers */ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; int i; - ;VGM_LOG("UBI SB: s1=%x (%x*%x), s2=%x (%x*%x), sX=%x (%x), s3=%x (%x*%x)\n", - sb->section1_offset,sb->section1_entry_size,sb->section1_num,sb->section2_offset,sb->section2_entry_size,sb->section2_num, - sb->sectionX_offset,sb->sectionX_size,sb->section3_offset,sb->section3_entry_size,sb->section3_num); + //;VGM_LOG("UBI SB: s1=%x (%x*%x), s2=%x (%x*%x), sX=%x (%x), s3=%x (%x*%x)\n", + // sb->section1_offset,sb->cfg.section1_entry_size,sb->section1_num,sb->section2_offset,sb->cfg.section2_entry_size,sb->section2_num, + // sb->sectionX_offset,sb->sectionX_size,sb->section3_offset,sb->cfg.section3_entry_size,sb->section3_num); - /* find target subsong info in section2 */ + /* find target subsong info in section2 and keeps counting */ sb->bank_subsongs = 0; for (i = 0; i < sb->section2_num; i++) { - off_t offset = sb->section2_offset + sb->section2_entry_size*i; + off_t offset = sb->section2_offset + sb->cfg.section2_entry_size*i; uint32_t header_type; - /* check candidate types */ /*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */ header_type = read_32bit(offset + 0x04, streamFile); @@ -1297,28 +1486,23 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe } sb->types[header_type]++; - if (!sb->allowed_types[header_type]) continue; - /* update subsongs (keep counting even after found) */ sb->bank_subsongs++; sb->total_subsongs++; if (sb->total_subsongs != target_subsong) continue; - /* parse target subsong */ if (!parse_header(sb, streamFile, offset, i)) goto fail; - /* maps can contain +10000 subsongs, we need something helpful - * (best here right after subsong detection, since some sequence re-parse types) */ build_readable_name(sb->readable_name, sizeof(sb->readable_name), sb); } - /* either target subsong found or it's in another bank (in case of maps), both handled externally */ + /* either found target subsong or it's in another bank (in case of maps), both handled externally */ - ;VGM_LOG("UBI SB: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(sb->types[i],"%02x=%i ",i,sb->types[i]); }} VGM_LOG("\n"); + //;VGM_LOG("UBI SB: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(sb->types[i],"%02x=%i ",i,sb->types[i]); }} VGM_LOG("\n"); return 1; fail: @@ -1398,79 +1582,88 @@ fail: static void config_sb_entry(ubi_sb_header * sb, size_t section1_size_entry, size_t section2_size_entry) { - sb->section1_entry_size = section1_size_entry; - sb->section2_entry_size = section2_size_entry; - sb->section3_entry_size = 0x08; + sb->cfg.section1_entry_size = section1_size_entry; + sb->cfg.section2_entry_size = section2_size_entry; + sb->cfg.section3_entry_size = 0x08; } static void config_sb_audio_fs(ubi_sb_header * sb, off_t external_flag, off_t group_id, off_t loop_flag) { /* audio header with standard flags */ - sb->cfga_external_flag = external_flag; - sb->cfga_group_id = group_id; - sb->cfga_loop_flag = loop_flag; - sb->cfga_and_external_flag = 1; - sb->cfga_and_group_id = 1; - sb->cfga_and_loop_flag = 1; + sb->cfg.audio_external_flag = external_flag; + sb->cfg.audio_group_id = group_id; + sb->cfg.audio_loop_flag = loop_flag; + sb->cfg.audio_external_and = 1; + sb->cfg.audio_group_and = 1; + sb->cfg.audio_loop_and = 1; } static void config_sb_audio_fb(ubi_sb_header * sb, off_t flag_bits, int external_and, int group_and, int loop_and) { /* audio header with bit flags */ - sb->cfga_external_flag = flag_bits; - sb->cfga_group_id = flag_bits; - sb->cfga_loop_flag = flag_bits; - sb->cfga_and_external_flag = external_and; - sb->cfga_and_group_id = group_and; - sb->cfga_and_loop_flag = loop_and; + sb->cfg.audio_external_flag = flag_bits; + sb->cfg.audio_group_id = flag_bits; + sb->cfg.audio_loop_flag = flag_bits; + sb->cfg.audio_external_and = external_and; + sb->cfg.audio_group_and = group_and; + sb->cfg.audio_loop_and = loop_and; } static void config_sb_audio_hs(ubi_sb_header * sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t stream_name, off_t stream_type) { /* audio header with stream name */ - sb->cfga_channels = channels; - sb->cfga_sample_rate = sample_rate; - sb->cfga_num_samples = num_samples; - sb->cfga_num_samples2 = num_samples2; - sb->cfga_stream_name = stream_name; - sb->cfga_stream_type = stream_type; + sb->cfg.audio_channels = channels; + sb->cfg.audio_sample_rate = sample_rate; + sb->cfg.audio_num_samples = num_samples; + sb->cfg.audio_num_samples2 = num_samples2; + sb->cfg.audio_stream_name = stream_name; + sb->cfg.audio_stream_type = stream_type; } static void config_sb_audio_he(ubi_sb_header * sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t extra_name, off_t stream_type) { /* audio header with extra name */ - sb->cfga_channels = channels; - sb->cfga_sample_rate = sample_rate; - sb->cfga_num_samples = num_samples; - sb->cfga_num_samples2 = num_samples2; - sb->cfga_extra_name = extra_name; - sb->cfga_stream_type = stream_type; + sb->cfg.audio_channels = channels; + sb->cfg.audio_sample_rate = sample_rate; + sb->cfg.audio_num_samples = num_samples; + sb->cfg.audio_num_samples2 = num_samples2; + sb->cfg.audio_extra_name = extra_name; + sb->cfg.audio_stream_type = stream_type; } static void config_sb_sequence(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) { /* sequence header and chain table */ - sb->cfgs_sequence_loop = sequence_count - 0x10; - sb->cfgs_sequence_single = sequence_count - 0x0c; - sb->cfgs_sequence_count = sequence_count; - sb->sequence_entry_size = entry_size; - sb->cfgs_entry_number = 0x00; + sb->cfg.sequence_sequence_loop = sequence_count - 0x10; + sb->cfg.sequence_sequence_single= sequence_count - 0x0c; + sb->cfg.sequence_sequence_count = sequence_count; + sb->cfg.sequence_entry_size = entry_size; + sb->cfg.sequence_entry_number = 0x00; } static void config_sb_layer_hs(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t stream_name) { /* layer headers with stream name */ - sb->cfgl_layer_count = layer_count; - sb->cfgl_stream_size = stream_size; - sb->cfgl_stream_offset = stream_offset; - sb->cfgl_stream_name = stream_name; + sb->cfg.layer_layer_count = layer_count; + sb->cfg.layer_stream_size = stream_size; + sb->cfg.layer_stream_offset = stream_offset; + sb->cfg.layer_stream_name = stream_name; } static void config_sb_layer_he(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t extra_name) { /* layer headers with extra name */ - sb->cfgl_layer_count = layer_count; - sb->cfgl_stream_size = stream_size; - sb->cfgl_stream_offset = stream_offset; - sb->cfgl_extra_name = extra_name; + sb->cfg.layer_layer_count = layer_count; + sb->cfg.layer_stream_size = stream_size; + sb->cfg.layer_stream_offset = stream_offset; + sb->cfg.layer_extra_name = extra_name; } static void config_sb_layer_sh(ubi_sb_header * sb, off_t entry_size, off_t sample_rate, off_t channels, off_t stream_type, off_t num_samples) { /* layer sub-headers in extra table */ - sb->layer_entry_size = entry_size; - sb->cfgl_sample_rate = sample_rate; - sb->cfgl_channels = channels; - sb->cfgl_stream_type = stream_type; - sb->cfgl_num_samples = num_samples; + sb->cfg.layer_entry_size = entry_size; + sb->cfg.layer_sample_rate = sample_rate; + sb->cfg.layer_channels = channels; + sb->cfg.layer_stream_type = stream_type; + sb->cfg.layer_num_samples = num_samples; +} +static void config_sb_silence_i(ubi_sb_header * sb, off_t duration) { + /* silence headers in int value */ + sb->cfg.silence_duration_int = duration; +} +static void config_sb_silence_f(ubi_sb_header * sb, off_t duration) { + /* silence headers in float value */ + sb->cfg.silence_duration_float = duration; } static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { - int is_biadd_psp = 0; + int is_bia_ps2 = 0, is_biadd_psp = 0; + int is_sc2_ps2_gc = 0; /* Most of the format varies with almost every game + platform (struct serialization?). * Support is configured case-by-case as offsets/order/fields only change slightly, @@ -1492,6 +1685,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * - external filename or internal filename on some platforms (earlier versions) * - external filename offset in the extra table (later versions) * - end flags? + * A few games (Splinter Cell, Rainbow Six) have wrong voice sample rates, + * maybe there is some pitch value too. * * Type 02 (old?/new): * Chain, possibly to play with config (ex: type 08 (float 0.3) + 01) @@ -1499,9 +1694,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * Type 03 (new), 09? (old): * Chain, other way to play things? (ex: type 03 + 04) * - * Type 04 (old/new), 0a (old): - * Chain of N types, possibly to play one as random (usually N voice/sfx like - * like death screams, but may include sequences). + * Type 04 (old?/new), 0a (old): + * Table of N types + chance % (sums to 65536), to play one as random. Usually N + * voice/sfx variations like death screams, or sequence alts. * * Type 05 (new), 0c (old): sequences * N audio segment, normally with lead-in but not lead-outs. Sequences can reuse @@ -1509,8 +1704,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * Sequences seem to include only music or cutscenes, so even single entries can be * useful to parse, since the readable name would make them stand out. Format: * - extra offset to chain - * - intro flag - * - outro flag + * - loop segment + * - non-looping flag * - sequence count * - ID-like fields in the header and sequence table may point to other chains? * @@ -1527,19 +1722,20 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * Some values may be flags/config as multiple 0x06 can point to the same layer, with different 'flags'? * * Type 07 (new), 0e (old): - * - another chain of something (single entry?), rare. + * Another chain of something (single entry?), rare. * * Type 08 (new), 0f (old): - * - audio config? (almost all fields 0 except sometimes 1.0 float in the middle). - * in older games may also point to the extra table, maybe equivalent to 02. + * Silence, with a value representing duration (no sample rate/channels/extra table/etc given). + * Typically used in chains to extend play time of another audio. + * For older games 08 is used for something else (maybe equivalent to 02?) */ /* debug strings reference: * - TYPE_SAMPLE: should be 0x01 (also "sound resource") * - TYPE_MULTITRACK: should be 0x06/0x0d (also "multilayer resource") - * - TYPE_SILENCE: ? + * - TYPE_SILENCE: should be 0x08 * sequences may be "theme resource" - * "class descryptor" is referenced, + * "class descryptor" is referenced too. * * Possible type names from .bnm (.sb's predecessor): * 0: TYPE_INVALID @@ -1562,37 +1758,39 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * So if some non-audio type looks like audio it's probably repeating old data. * This even happens for common fields (ex. type 6 at 0x08 has prev garbage, not stream size). */ + /* games <= 0x00100000 seem to use old types, rest new types */ + /* common */ - sb->resource_name_size = 0x24; /* maybe 0x20/0x28 for some but ok enough (null terminated) */ + sb->cfg.resource_name_size = 0x24; /* maybe 0x20/0x28 for some but ok enough (null terminated) */ /* represents map style (1=first, 2=mid, 3=latest) */ if (sb->version <= 0x00000007) - sb->map_version = 1; + sb->cfg.map_version = 1; else if (sb->version < 0x00150000) - sb->map_version = 2; + sb->cfg.map_version = 2; else - sb->map_version = 3; + sb->cfg.map_version = 3; - sb->map_entry_size = (sb->map_version < 2) ? 0x30 : 0x34; + sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34; if (sb->version <= 0x00000007) { - sb->cfga_stream_size = 0x0c; - sb->cfga_extra_offset = 0x10; - sb->cfga_stream_offset = 0x14; + sb->cfg.audio_stream_size = 0x0c; + sb->cfg.audio_extra_offset = 0x10; + sb->cfg.audio_stream_offset = 0x14; - sb->cfgs_extra_offset = 0x10; + sb->cfg.sequence_extra_offset = 0x10; - sb->cfgl_extra_offset = 0x10; + sb->cfg.layer_extra_offset = 0x10; } else { - sb->cfga_stream_size = 0x08; - sb->cfga_extra_offset = 0x0c; - sb->cfga_stream_offset = 0x10; + sb->cfg.audio_stream_size = 0x08; + sb->cfg.audio_extra_offset = 0x0c; + sb->cfg.audio_stream_offset = 0x10; - sb->cfgs_extra_offset = 0x0c; + sb->cfg.sequence_extra_offset = 0x0c; - sb->cfgl_extra_offset = 0x0c; + sb->cfg.layer_extra_offset = 0x0c; } sb->allowed_types[0x01] = 1; @@ -1600,6 +1798,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->allowed_types[0x0c] = 1; sb->allowed_types[0x06] = 1; sb->allowed_types[0x0d] = 1; + //sb->allowed_types[0x08] = 1; /* only needed inside sequences */ + //sb->allowed_types[0x0f] = 1; #if 0 { @@ -1615,18 +1815,29 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } #endif - //todo some dsp offsets have problems, wrong id? - //todo uses Ubi IMA v2 has has some deviation in the right channel + clicks? - //todo has some sample rate / loop configs problems? (ex Batman #5451) - //todo buggy reads in layers? + /* Batman: Vengeance (2001)(PC)-map */ + if (sb->version == 0x00000003 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x40, 0x68); + + config_sb_audio_fs(sb, 0x30, 0x30, 0x34); /* no group id? use external flag */ + config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x2c, 0x1c); + + config_sb_layer_hs(sb, 0x20, 0x4c, 0x44, 0x34); + config_sb_layer_sh(sb, 0x1c, 0x04, 0x0a, 0x0c, 0x18); + return 1; + } + /* Disney's Tarzan: Untamed (2001)(GC)-map */ /* Batman: Vengeance (2001)(GC)-map */ /* Donald Duck: Goin' Quackers (2002)(GC)-map */ if (sb->version == 0x00000003 && sb->platform == UBI_GC) { config_sb_entry(sb, 0x40, 0x6c); - config_sb_audio_fs(sb, 0x30, 0x2c, 0x34); - config_sb_audio_hs(sb, 0x56, 0x50, 0x48, 0x48, 0x5c, 0x58); + config_sb_audio_fs(sb, 0x30, 0x30, 0x34); /* no group id? use external flag */ + config_sb_audio_hs(sb, 0x56, 0x50, 0x48, 0x48, 0x5c, 0x58); /* 0x38 may be num samples too? */ config_sb_sequence(sb, 0x2c, 0x1c); @@ -1636,80 +1847,120 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } #if 0 + //todo too weird /* Batman: Vengeance (2001)(PS2)-map */ /* Disney's Tarzan: Untamed (2001)(PS2)-map */ if (sb->version == 0x00000003 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x30, 0x3c); - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4));//? - sb->cfga_group_id = 0x1c?; - sb->cfga_external_flag = 0x1c?; - sb->cfga_loop_flag = 0x1c?; - sb->cfga_num_samples = 0x28; - sb->cfga_num_samples2 = 0x28; - sb->cfga_sample_rate = 0x24; - sb->cfga_channels = 0x2a? - sb->cfga_stream_type = 0x34? 0x38; - sb->cfga_stream_name = -1; /* implicit STRM.SM1 */ + config_sb_audio_fb(sb, 0x1c, (1 << 2), (1 << 3), (1 << 4)); /* not ok */ + config_sb_audio_hs(sb, 0x00, 0x24, 0x28, 0x28, 0x00, 0x00); + /* channels: 0? maybe 2=external, 1=internal? */ + /* stream type: always PS-ADPCM (interleave unknown) */ + /* sb->cfg.audio_stream_string = "STRM.SM1"; */ /* fixed */ - config_sb_sequence(sb, 0x2c, 0x10); + config_sb_sequence(sb, 0x2c, 0x10); /* this is normal enough */ - //layer format ??? + /* layers have a weird format too */ return 1; } #endif -#if 0 - //todo offsets seems to work differently (stream offset is always 0) + //todo group flags and maybe num_samples for sfx are off /* Myst III: Exile (2001)(PS2)-map */ if (sb->version == 0x00000004 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x34, 0x70); - config_sb_audio_fb(sb, 0x1c, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_fb(sb, 0x1c, (1 << 3), (1 << 6), (1 << 4)); //??? config_sb_audio_hs(sb, 0x24, 0x28, 0x2c, 0x34, 0x44, 0x6c); + sb->cfg.audio_external_flag = 0x6c; /* no external flag? use codec as flag */ - //todo sequences - - //todo layers + config_sb_sequence(sb, 0x2c, 0x24); return 1; } -#endif -#if 0 - //todo uses codec 02 /* Splinter Cell (2002)(PC)-map */ /* Splinter Cell: Pandora Tomorrow (2004)(PC)-map */ if (sb->version == 0x00000007 && sb->platform == UBI_PC) { config_sb_entry(sb, 0x58, 0x80); - config_sb_audio_fs(sb, 0x28, 0x2c, 0x24?); + config_sb_audio_fs(sb, 0x28, 0x28, 0x2c); /* no group id? use external flag */ config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; - //todo sequences + config_sb_sequence(sb, 0x2c, 0x34); - config_sb_layer_hs(sb, 0x20, 0x64, 0x5c, 0x34); + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); return 1; } -#endif -#if 0 /* Splinter Cell (2002)(Xbox)-map */ /* Splinter Cell: Pandora Tomorrow (2004)(Xbox)-map */ if (sb->version == 0x00000007 && sb->platform == UBI_XBOX) { config_sb_entry(sb, 0x58, 0x78); - config_sb_audio_fs(sb, 0x28, 0x24? 0x2c?, 0x2c? 0x24?); + config_sb_audio_fs(sb, 0x28, 0x28, 0x2c); /* no group id? use external flag */ config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; - //todo sequences + config_sb_sequence(sb, 0x2c, 0x34); - //todo layers + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); + config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); + return 1; + } + + /* SC:PT PS2/GC has some quirks, noooo (lame autodetection but this stuff is too hard) */ + if ((sb->version == 0x00000007 && sb->platform == UBI_PS2) || + (sb->version == 0x00000007 && sb->platform == UBI_GC) ) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* both SC:PT's LMx and SMx have 33 maps, SC1 doesn't */ + is_sc2_ps2_gc = read_32bit(0x08, streamFile) == 0x21; + + /* could also load ECHELON.SP1/Echelon.SP3 and test BE 0x04 == 0x00ACBF77, + * but it's worse for localization subdirs without it */ + } + + /* Splinter Cell (2002)(PS2)-map */ + if (sb->version == 0x00000007 && sb->platform == UBI_PS2) { + config_sb_entry(sb, 0x40, 0x70); + + config_sb_audio_fb(sb, 0x1c, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_hs(sb, 0x24, 0x28, 0x34, 0x3c, 0x44, 0x6c); /* num_samples may be null */ + + config_sb_sequence(sb, 0x2c, 0x30); + + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); + config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); + + if (is_sc2_ps2_gc) { + sb->cfg.map_entry_size = 0x38; + /* some amb .ss2 have bad sizes with mixed random data, bad extraction/unused crap? */ + } + return 1; + } + + /* Splinter Cell (2002)(GC)-map */ + /* Splinter Cell: Pandora Tomorrow (2004)(GC)-map */ + if (sb->version == 0x00000007 && sb->platform == UBI_GC) { + config_sb_entry(sb, 0x58, 0x78); + + config_sb_audio_fs(sb, 0x24, 0x24, 0x28); /* no group id? use external flag */ + config_sb_audio_hs(sb, 0x4a, 0x44, 0x2c, 0x34, 0x50, 0x4c); + + config_sb_sequence(sb, 0x2c, 0x34); + + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); + config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); + + if (is_sc2_ps2_gc) { + sb->cfg.map_entry_size = 0x38; + sb->cfg.audio_external_and = 0x01000000; /* did somebody forget about BE? */ + } return 1; } -#endif /* Prince of Persia: Sands of Time (2003)(PC)-bank 0x000A0004 / 0x000A0002 (just in case) */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PC) || @@ -1718,7 +1969,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -1727,13 +1978,23 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* two configs with same id; use project file as identifier */ + if (sb->version == 0x000A0007 && sb->platform == UBI_PS2) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "BIAAUDIO.SP1"); + if (streamTest) { + is_bia_ps2 = 1; + close_streamfile(streamTest); + } + } + /* Prince of Persia: Sands of Time (2003)(PS2)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ /* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank 0x000A0007 */ - /* Splinter Cell: Pandora Tomorrow(?) (2006)(PS2)-bank 0x000A0008 */ + /* Tom Clancy's Ghost Recon 2 (2004)(PS2)-bank 0x000A0007 */ + /* Splinter Cell: Pandora Tomorrow (2006)(PS2)-bank 0x000A0008 */ /* Prince of Persia: Warrior Within (2004)(PS2)-bank 0x00120009 */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PS2) || (sb->version == 0x000A0004 && sb->platform == UBI_PS2) || - (sb->version == 0x000A0007 && sb->platform == UBI_PS2) || + (sb->version == 0x000A0007 && sb->platform == UBI_PS2 && !is_bia_ps2) || (sb->version == 0x000A0008 && sb->platform == UBI_PS2) || (sb->version == 0x00120009 && sb->platform == UBI_PS2)) { config_sb_entry(sb, 0x48, 0x6c); @@ -1748,6 +2009,27 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Brothers in Arms: Road to Hill 30 (2005)[PS2] */ + /* Brothers in Arms: Earned in Blood (2005)[PS2] */ + if (sb->version == 0x000A0007 && sb->platform == UBI_PS2 && is_bia_ps2) { + config_sb_entry(sb, 0x5c, 0x14c); + + config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_hs(sb, 0x20, 0x24, 0x30, 0x38, 0x40, 0x148); /* num_samples may be null */ + + config_sb_sequence(sb, 0x28, 0x10); + + config_sb_layer_hs(sb, 0x20, 0x140, 0x138, 0x30); + config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + + sb->cfg.is_padded_section1_offset = 1; + sb->cfg.is_padded_section2_offset = 1; + sb->cfg.is_padded_section3_offset = 1; + sb->cfg.is_padded_sectionX_offset = 1; + sb->cfg.is_padded_sounds_offset = 1; + return 1; + } + /* Prince of Persia: Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ if ((sb->version == 0x000A0002 && sb->platform == UBI_XBOX) || (sb->version == 0x000A0004 && sb->platform == UBI_XBOX)) { @@ -1755,7 +2037,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x28, 0x2c); config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); /* stream_type may contain garbage */ - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -1765,9 +2047,11 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } /* Batman: Rise of Sin Tzu (2003)(GC)-map 0x000A0002 */ - /* Prince of Persia: Sands of Time (2003)(GC)-bank 0x000A0004 / 0x000A0002 (POP1 port)*/ + /* Prince of Persia: Sands of Time (2003)(GC)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ + /* Tom Clancy's Rainbow Six 3 (2003)(Xbox)-bank 0x000A0007 */ if ((sb->version == 0x000A0002 && sb->platform == UBI_GC) || - (sb->version == 0x000A0004 && sb->platform == UBI_GC)) { + (sb->version == 0x000A0004 && sb->platform == UBI_GC) || + (sb->version == 0x000A0007 && sb->platform == UBI_GC)) { config_sb_entry(sb, 0x64, 0x74); config_sb_audio_fs(sb, 0x20, 0x24, 0x28); @@ -1777,6 +2061,25 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + + config_sb_silence_i(sb, 0x18); + return 1; + } + + /* Tom Clancy's Rainbow Six 3 (2003)(Xbox)-bank 0x000A0007 */ + if (sb->version == 0x000A0007 && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x64, 0x8c); + + config_sb_audio_fs(sb, 0x24, 0x28, 0x40); + config_sb_audio_hs(sb, 0x5e, 0x58, 0x44, 0x4c, 0x64, 0x60); /* stream_type may contain garbage */ + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x28, 0x14); + + config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); + config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + + config_sb_silence_i(sb, 0x18); return 1; } @@ -1786,11 +2089,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; - - /* no sequences */ - - /* no layers */ + sb->cfg.audio_has_internal_names = 1; return 1; } @@ -1800,11 +2099,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1814,11 +2111,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x28, 0x40); config_sb_audio_hs(sb, 0x60, 0x58, 0x44, 0x4c, 0x68, 0x64); /* stream_type may contain garbage */ - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1830,8 +2125,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_hs(sb, 0x48, 0x40, 0x2c, 0x34, 0x50, 0x4c); config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1852,7 +2145,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -1868,11 +2161,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; - - /* no sequences */ - - /* no layers */ + sb->cfg.audio_has_internal_names = 1; return 1; } @@ -1884,8 +2173,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1898,8 +2185,20 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x38, 0x30, 0x1c, 0x24, 0x40, 0x3c); config_sb_sequence(sb, 0x28, 0x10); + return 1; + } - /* no layers */ + /* Splinter Cell: Chaos Theory (2005)(GC)-map */ + if (sb->version == 0x00130001 && sb->platform == UBI_GC) { + config_sb_entry(sb, 0x68, 0x54); + + config_sb_audio_fs(sb, 0x20, 0x24, 0x28); + config_sb_audio_he(sb, 0x48, 0x40, 0x2c, 0x34, 0x50, 0x4c); + + config_sb_sequence(sb, 0x28, 0x14); + + config_sb_layer_he(sb, 0x1c, 0x34, 0x3c, 0x40); + config_sb_layer_sh(sb, 0x18, 0x00, 0x08, 0x0c, 0x14); return 1; } @@ -1917,6 +2216,19 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Tom Clancy's Ghost Recon Advanced Warfighter (2006)(PS2)-bank */ + if (sb->version == 0x00130004 && sb->platform == UBI_PS2) { + config_sb_entry(sb, 0x48, 0x50); + + config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_he(sb, 0x20, 0x24, 0x30, 0x38, 0x40, 0x4c); + sb->cfg.audio_internal_interleave = 0x8000; + + sb->cfg.is_padded_section1_offset = 1; + sb->cfg.is_padded_sounds_offset = 1; + return 1; + } + /* Prince of Persia: The Two Thrones (2005)(PC)-bank */ if (sb->version == 0x00150000 && sb->platform == UBI_PC) { config_sb_entry(sb, 0x68, 0x78); @@ -1925,8 +2237,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); config_sb_sequence(sb, 0x2c, 0x14); - - /* no layers */ return 1; } @@ -1938,8 +2248,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x2c, 0x30, 0x3c, 0x44, 0x4c, 0x50); config_sb_sequence(sb, 0x2c, 0x10); - - /* no layers */ return 1; } @@ -1978,11 +2286,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - //todo Open Season (PSP) uses sequence with type 0x08 (silence?) - //todo Rainbow Six Vegas (PSP) has layers with different sample rates (but 2nd layer is silent, can be ignored) /* Splinter Cell: Double Agent (2006)(PS2)-map 0x00160002 */ - /* Open Season (2005)(PS2)-map 0x00180003 */ - /* Open Season (2005)(PSP)-map 0x00180003 */ + /* Open Season (2006)(PS2)-map 0x00180003 */ + /* Open Season (2006)(PSP)-map 0x00180003 */ /* Shaun White Snowboarding (2008)(PS2)-map 0x00180003 */ /* Prince of Persia: Rival Swords (2007)(PSP)-bank 0x00180005 */ /* Rainbow Six Vegas (2007)(PSP)-bank 0x00180006 */ @@ -2002,6 +2308,12 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + + config_sb_silence_f(sb, 0x1c); + + /* Rainbow Six Vegas (PSP) has 2 layers with different sample rates, but 2nd layer is silent and can be ignored */ + if (sb->version == 0x00180006 && sb->platform == UBI_PSP) + sb->cfg.ignore_layer_error = 1; return 1; } @@ -2012,8 +2324,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x2c, 0x34, 0x30); config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); - /* no sequences */ - config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); return 1; @@ -2025,16 +2335,14 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); - sb->cfga_xma_offset = 0x70; - - /* no sequences */ + sb->cfg.audio_xma_offset = 0x70; config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); return 1; } - /* Red Steel (2006)(Wii)-bank 0x00180006 */ + /* Red Steel (2006)(Wii)-bank */ if (sb->version == 0x00180006 && sb->platform == UBI_WII) { config_sb_entry(sb, 0x68, 0x6c); @@ -2064,7 +2372,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - //todo one sequence is using type 0x08 (only) with 5.0: maybe type_silence? /* TMNT (2007)(GC)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_GC) { config_sb_entry(sb, 0x68, 0x6c); @@ -2076,10 +2383,11 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x34, 0x38, 0x40); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + config_sb_silence_f(sb, 0x1c); return 1; } - //todo one sequence is using type 0x08 (only) with 5.0: maybe type_silence? /* TMNT (2007)(PS2)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x48, 0x5c); @@ -2091,6 +2399,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + config_sb_silence_f(sb, 0x1c); return 1; } @@ -2108,12 +2418,15 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); - sb->cfga_xma_offset = 0x6c; + sb->cfg.audio_xma_offset = 0x6c; + sb->cfg.audio_internal_interleave = 0x10; config_sb_sequence(sb, 0x2c, 0x14); config_sb_layer_he(sb, 0x20, 0x34, 0x38, 0x40); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + /* Splinter Cell: Double Agent (PS3) #13214 only has double num_samples, unused/Ubi's bug/may need a flag? */ return 1; } @@ -2130,38 +2443,34 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x34, 0x38, 0x40); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + config_sb_silence_f(sb, 0x1c); return 1; } /* Rainbow Six Vegas 2 (2008)(PS3)-map */ - if (sb->version == 0x001c0000 && sb->platform == UBI_PS3) { + if (sb->version == 0x001C0000 && sb->platform == UBI_PS3) { config_sb_entry(sb, 0x64, 0x7c); config_sb_audio_fs(sb, 0x28, 0x30, 0x34); config_sb_audio_he(sb, 0x44, 0x48, 0x50, 0x58, 0x60, 0x64); - /* no sequences */ - config_sb_layer_he(sb, 0x20, 0x44, 0x48, 0x54); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); return 1; } /* Michael Jackson: The Experience (2010)(PSP)-map */ - if (sb->version == 0x001d0000 && sb->platform == UBI_PSP) { + if (sb->version == 0x001D0000 && sb->platform == UBI_PSP) { config_sb_entry(sb, 0x40, 0x60); config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 5)); /* assumed group_flag */ config_sb_audio_he(sb, 0x28, 0x30, 0x38, 0x40, 0x48, 0x4c); - - /* no sequences */ - - /* no layers */ return 1; } /* Splinter Cell Classic Trilogy HD (2011)(PS3)-map */ - if (sb->version == 0x001d0000 && sb->platform == UBI_PS3) { + if (sb->version == 0x001D0000 && sb->platform == UBI_PS3) { config_sb_entry(sb, 0x5c, 0x80); config_sb_audio_fs(sb, 0x28, 0x30, 0x34); @@ -2171,8 +2480,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x44, 0x48, 0x54); config_sb_layer_sh(sb, 0x38, 0x00, 0x04, 0x08, 0x10); - - return 1; } diff --git a/src/meta/ubi_sb_streamfile.h b/src/meta/ubi_sb_streamfile.h index 88c3b17f..45f03fea 100644 --- a/src/meta/ubi_sb_streamfile.h +++ b/src/meta/ubi_sb_streamfile.h @@ -345,7 +345,7 @@ static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_ if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ubi_sb_io_read,ubi_sb_io_size); + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, ubi_sb_io_read,ubi_sb_io_size); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; From aecd7da55eb2e09a22e4c717c5e3c2e6625523f5 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 26 Jan 2019 16:34:25 +0100 Subject: [PATCH 4/8] Add .sbv RIFF [Spongebob Squarepants - The Movie (PC)] --- src/formats.c | 1 + src/meta/riff.c | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/formats.c b/src/formats.c index 7f35538b..b30d63bc 100644 --- a/src/formats.c +++ b/src/formats.c @@ -351,6 +351,7 @@ static const char* extension_list[] = { "sb6", "sb7", "sbr", + "sbv", "sm0", "sm1", "sm2", diff --git a/src/meta/riff.c b/src/meta/riff.c index 36aa034b..bc3ce390 100644 --- a/src/meta/riff.c +++ b/src/meta/riff.c @@ -280,8 +280,9 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { * .adpcm: Angry Birds Transformers (Android) * .adw: Dead Rising 2 (PC) * .wd: Genma Onimusha (Xbox) voices - * (extensionless): Myst III (Xbox) voices */ - if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,") ) { + * (extensionless): Myst III (Xbox) + * .sbv: Spongebob Squarepants - The Movie (PC) */ + if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv") ) { ; } else if ( check_extensions(streamFile, "mwv") ) { From 097f7b028584113f4cc612fe34b206220f55c739 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 26 Jan 2019 17:04:43 +0100 Subject: [PATCH 5/8] Fix some SDF [Mr. Bean's Wacky World (Wii)] --- src/formats.c | 3 +- src/meta/meta.h | 3 +- src/meta/sdf.c | 131 +++++++++++++++++++++++++----------------------- src/vgmstream.c | 4 +- src/vgmstream.h | 3 +- 5 files changed, 71 insertions(+), 73 deletions(-) diff --git a/src/formats.c b/src/formats.c index b30d63bc..8be4032e 100644 --- a/src/formats.c +++ b/src/formats.c @@ -1135,10 +1135,9 @@ static const meta_info meta_info_list[] = { {meta_A2M, "Artificial Mind & Movement A2M header"}, {meta_AHV, "Amuze AHV header"}, {meta_MSV, "Sony MultiStream MSV header"}, - {meta_SDF_PS2, "Beyond Reality PS2 SDF header"}, + {meta_SDF, "Beyond Reality SDF header"}, {meta_SVG, "High Voltage SVG header"}, {meta_VIS, "Konami VIS header"}, - {meta_SDF_3DS, "Beyond Reality 3DS SDF header"}, {meta_VAI, "Asobo Studio .VAI header"}, {meta_AIF_ASOBO, "Asobo Studio .AIF header"}, {meta_AO, "AlphaOgg .AO header"}, diff --git a/src/meta/meta.h b/src/meta/meta.h index 2807d519..c25ff390 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -768,8 +768,7 @@ VGMSTREAM * init_vgmstream_ahv(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_msv(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_sdf_ps2(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_sdf_3ds(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_sdf(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_svg(STREAMFILE *streamFile); diff --git a/src/meta/sdf.c b/src/meta/sdf.c index 1b298015..a1c18ec0 100644 --- a/src/meta/sdf.c +++ b/src/meta/sdf.c @@ -1,90 +1,93 @@ #include "meta.h" #include "../coding/coding.h" -/* SDF - from Beyond Reality games [Agent Hugo - Lemoon Twist (PS2)] */ -VGMSTREAM * init_vgmstream_sdf_ps2(STREAMFILE *streamFile) { +/* SDF - from Beyond Reality games */ +VGMSTREAM * init_vgmstream_sdf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; size_t data_size; - int loop_flag, channel_count; + int loop_flag, channel_count, sample_rate, interleave, coefs_offset; /* checks */ - if ( !check_extensions(streamFile,"sdf") ) + if (!check_extensions(streamFile,"sdf")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x53444600) /* "SDF\0" */ goto fail; if (read_32bitBE(0x04,streamFile) != 0x03000000) /* version? */ goto fail; - start_offset = 0x18; - data_size = get_streamfile_size(streamFile) - start_offset; - if (read_32bitLE(0x08,streamFile) != data_size) - goto fail; + data_size = read_32bitLE(0x08,streamFile); + start_offset = get_streamfile_size(streamFile) - data_size; - channel_count = read_32bitLE(0x10,streamFile); - loop_flag = 0; /* all files have loop flags but simply fade out normally and repeat */ + switch(start_offset) { + case 0x18: /* Agent Hugo - Lemoon Twist (PS2)*/ + sample_rate = read_32bitLE(0x0c,streamFile); + channel_count = read_32bitLE(0x10,streamFile); + interleave = read_32bitLE(0x14,streamFile); + break; + + case 0x78: /* Gummy Bears Mini Golf (3DS) */ + sample_rate = read_32bitLE(0x10,streamFile); + channel_count = read_32bitLE(0x14,streamFile); + interleave = read_32bitLE(0x18,streamFile); + coefs_offset = 0x1c; + break; + + case 0x84: /* Mr. Bean's Wacky World (Wii) */ + sample_rate = read_32bitLE(0x10,streamFile); + channel_count = read_32bitLE(0x14,streamFile); + interleave = read_32bitLE(0x18,streamFile); + data_size = read_32bitLE(0x20,streamFile); /* usable size */ + coefs_offset = 0x28; + break; + + default: + goto fail; + } + + loop_flag = 1; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - vgmstream->meta_type = meta_SDF_PS2; - vgmstream->sample_rate = read_32bitLE(0x0c,streamFile); - vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count); + vgmstream->meta_type = meta_SDF; + vgmstream->sample_rate = sample_rate; - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = read_32bitLE(0x14,streamFile); - - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* SDF - from Beyond Reality games [Gummy Bears Mini Golf (3DS)] */ -VGMSTREAM * init_vgmstream_sdf_3ds(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - size_t data_size; - int loop_flag, channel_count; - - - /* checks */ - if ( !check_extensions(streamFile,"sdf") ) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x53444600) /* "SDF\0" */ - goto fail; - if (read_32bitBE(0x04,streamFile) != 0x03000000) /* version? */ - goto fail; - - start_offset = 0x78; /* assumed */ - data_size = get_streamfile_size(streamFile) - start_offset; - if (read_32bitLE(0x08,streamFile) != data_size) - goto fail; - - channel_count = read_32bitLE(0x14,streamFile); - loop_flag = 0; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_SDF_3DS; - vgmstream->sample_rate = read_32bitLE(0x10,streamFile); - vgmstream->num_samples = dsp_bytes_to_samples(data_size,channel_count); - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = data_size / channel_count; - dsp_read_coefs_le(vgmstream,streamFile,0x1c,0x2e); - //todo: there be hist around 0x3c + switch(start_offset) { + case 0x18: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count); + break; + + case 0x78: + case 0x84: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + if (vgmstream->interleave_block_size == 0) /* Gummy Bears Mini Golf */ + vgmstream->interleave_block_size = data_size / channel_count; + + vgmstream->num_samples = dsp_bytes_to_samples(data_size,channel_count); + + dsp_read_coefs_le(vgmstream, streamFile, coefs_offset+0x00,0x2e); + dsp_read_hist_le (vgmstream, streamFile, coefs_offset+0x24,0x2e); + break; + + default: + goto fail; + } + + /* most songs simply repeat; don't loop if too short (in seconds) */ + if (vgmstream->num_samples > 10*sample_rate) { + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = vgmstream->num_samples; + } if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; diff --git a/src/vgmstream.c b/src/vgmstream.c index d26a9db5..fe415d31 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -430,10 +430,9 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_a2m, init_vgmstream_ahv, init_vgmstream_msv, - init_vgmstream_sdf_ps2, + init_vgmstream_sdf, init_vgmstream_svg, init_vgmstream_vis, - init_vgmstream_sdf_3ds, init_vgmstream_vai, init_vgmstream_aif_asobo, init_vgmstream_ao, @@ -2786,7 +2785,6 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s if (!file) goto fail; } - VGM_LOG("ch%i offset=%lx\n", ch,offset); vgmstream->ch[ch].streamfile = file; vgmstream->ch[ch].channel_start_offset = vgmstream->ch[ch].offset = offset; diff --git a/src/vgmstream.h b/src/vgmstream.h index f793130b..fd23a2fb 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -687,11 +687,10 @@ typedef enum { meta_A2M, /* Scooby-Doo! Unmasked (PS2) */ meta_AHV, /* Headhunter (PS2) */ meta_MSV, /* Fight Club (PS2) */ - meta_SDF_PS2, /* Agent Hugo - Lemoon Twist (PS2) */ + meta_SDF, meta_SVG, /* Hunter - The Reckoning - Wayward (PS2) */ meta_VIS, /* AirForce Delta Strike (PS2) */ meta_VAI, /* Ratatouille (GC) */ - meta_SDF_3DS, /* Gummy Bears Mini Golf (3DS) */ meta_AIF_ASOBO, /* Ratatouille (PC) */ meta_AO, /* Cloudphobia (PC) */ meta_APC, /* MegaRace 3 (PC) */ From 4a6df9b3b9e0b423bf10798122c97ed31c777494 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 27 Jan 2019 23:40:40 +0100 Subject: [PATCH 6/8] Add .wxv RIFF [Godzilla - Destroy All Monsters Melee (Xbox)] --- src/formats.c | 1 + src/meta/riff.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/formats.c b/src/formats.c index 8be4032e..f1f06122 100644 --- a/src/formats.c +++ b/src/formats.c @@ -486,6 +486,7 @@ static const char* extension_list[] = { "wv6", "wve", "wvs", + "wvx", "x", "xa", diff --git a/src/meta/riff.c b/src/meta/riff.c index bc3ce390..6830764a 100644 --- a/src/meta/riff.c +++ b/src/meta/riff.c @@ -281,7 +281,8 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { * .adw: Dead Rising 2 (PC) * .wd: Genma Onimusha (Xbox) voices * (extensionless): Myst III (Xbox) - * .sbv: Spongebob Squarepants - The Movie (PC) */ + * .sbv: Spongebob Squarepants - The Movie (PC) + * .wvx: Godzilla - Destroy All Monsters Melee (Xbox) */ if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv") ) { ; } From b81f8a6eed2cb804163c28013ba22101a13c7790 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 27 Jan 2019 23:40:50 +0100 Subject: [PATCH 7/8] Minor fixes --- README.md | 14 ++++++++++++-- src/layout/blocked_mul.c | 2 -- src/vgmstream.c | 17 ++++++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d8c456f6..6b7598ad 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,14 @@ channel mask to allow only certain channels (ex. "song.adx#c1,2"). Creation of those files is meant for advanced users, docs can be found in vgmstream source. +### Plugin conflicts +Since vgmstream supports a huge amount of formats it's possibly that some of +them are also supported in other plugins, and this sometimes causes conflicts. +If a file that should isn't playing or looping, first make sure vgmstream is +really opening it (should show "vgmstream" somewhere in the file info), and +try to remove a few other plugins. foobar's ffmpeg plugin and foo_adpcm are +known to cause issues. + ## Tagging Some of vgmstream's plugins support simple read-only tagging via external files. @@ -282,7 +290,7 @@ are used in few games. - Sony PSX ADPCM a.k.a VAG (standard, badflags, configurable) - Sony HEVAG - Electronic Arts EA-XA (stereo, mono, Maxis) -- Electronic Arts EA-XAS +- Electronic Arts EA-XAS (v0, v1) - DVI/IMA ADPCM (stereo/mono + high/low nibble, 3DS, Omikron, SNDS, etc) - Microsoft MS IMA ADPCM (standard, Xbox, NDS, Radical, Wwise, FSB, WV6, etc) - Microsoft MS ADPCM (standard, Cricket Audio) @@ -298,7 +306,7 @@ are used in few games. - Konami XMD 4-bit ADPCM - Argonaut ASF 4-bit ADPCM - Circus XPCM ADPCM -- PC-FX ADPCM +- OKI 4-bit ADPCM (16-bit output, PC-FX) - SDX2 2:1 Squareroot-Delta-Exact compression DPCM - CBD2 2:1 Cuberoot-Delta-Exact compression DPCM - Activision EXAKT SASSC DPCM @@ -560,6 +568,8 @@ This list is not complete and many other files are supported. - .um3 (Ogg Vorbis) - .xa (CD-ROM XA audio) - .xma (MS XMA/XMA2) + - .sb0/sb1/sb2/sb3/sb4/sb5/sb6/sb7 (many) + - .sm0/sm1/sm2/sm3/sm4/sm5/sm6/sm7 (many) - artificial/generic headers: - .genh (lots) - .txth (lots) diff --git a/src/layout/blocked_mul.c b/src/layout/blocked_mul.c index 716e03c6..e9a4944a 100644 --- a/src/layout/blocked_mul.c +++ b/src/layout/blocked_mul.c @@ -44,7 +44,5 @@ void block_update_mul(off_t block_offset, VGMSTREAM * vgmstream) { for (i = 0; i < vgmstream->channels; i++) { vgmstream->ch[i].offset = block_offset + block_header + data_header + vgmstream->current_block_size*i; - //VGM_LOG("ch%i of=%lx\n", i, vgmstream->ch[i].offset); } - //getchar(); } diff --git a/src/vgmstream.c b/src/vgmstream.c index fe415d31..e94d296a 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -2279,14 +2279,25 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { "encoding: "); concatn(length,desc,temp); switch (vgmstream->coding_type) { + + //todo codec bugs with layout inside layouts (ex. TXTP) #ifdef VGM_USE_FFMPEG case coding_FFmpeg: { - ffmpeg_codec_data *data = (ffmpeg_codec_data *)vgmstream->codec_data; - if (!data && vgmstream->layout_data) { + ffmpeg_codec_data *data = NULL; + + if (vgmstream->layout_type == layout_layered) { layered_layout_data* layout_data = vgmstream->layout_data; if (layout_data->layers[0]->coding_type == coding_FFmpeg) data = layout_data->layers[0]->codec_data; } + else if (vgmstream->layout_type == layout_segmented) { + segmented_layout_data* layout_data = vgmstream->layout_data; + if (layout_data->segments[0]->coding_type == coding_FFmpeg) + data = layout_data->segments[0]->codec_data; + } + else { + data = vgmstream->codec_data; + } if (data) { if (data->codec && data->codec->long_name) { @@ -2636,7 +2647,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) { return get_vgmstream_average_bitrate_from_size(vgmstream->stream_size, sample_rate, length_samples); } - + //todo bitrate bugs with layout inside layouts (ex. TXTP) /* make a list of used streamfiles (repeats will be filtered below) */ if (vgmstream->layout_type==layout_segmented) { segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data; From fc8911b68ab42d7ba32102425efaca3941f49c07 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 27 Jan 2019 23:42:42 +0100 Subject: [PATCH 8/8] Prepare BAO layers/sequences --- src/libvgmstream.vcproj | 4 + src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 + src/meta/ubi_bao.c | 1055 ++++++++++++++++++++++-------- src/meta/ubi_bao_streamfile.h | 11 + 5 files changed, 808 insertions(+), 266 deletions(-) create mode 100644 src/meta/ubi_bao_streamfile.h diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index d3f27ba8..c8793926 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -272,6 +272,10 @@ RelativePath=".\meta\sqex_scd_streamfile.h" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index bca602da..55cf8634 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -110,6 +110,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index f7b49f4d..fa2c2930 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -104,6 +104,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files diff --git a/src/meta/ubi_bao.c b/src/meta/ubi_bao.c index dad17883..238ab80a 100644 --- a/src/meta/ubi_bao.c +++ b/src/meta/ubi_bao.c @@ -1,41 +1,123 @@ #include "meta.h" +#include "../layout/layout.h" #include "../coding/coding.h" +#include "ubi_bao_streamfile.h" -typedef enum { NONE = 0, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2, RAW_AT3, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; +typedef enum { NONE = 0, UBI_IMA, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2, RAW_AT3, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; +typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE } ubi_bao_type; + typedef struct { + size_t header_entry_size; + size_t header_skip; + + off_t header_id; + off_t header_type; + + off_t audio_stream_size; + off_t audio_stream_id; + off_t audio_external_flag; + off_t audio_loop_flag; + off_t audio_channels; + off_t audio_sample_rate; + off_t audio_num_samples; + off_t audio_num_samples2; + off_t audio_stream_type; + off_t audio_prefetch_size; + off_t audio_xma_offset; //todo remove, depends on extra table + off_t audio_dsp_offset; + + int audio_external_and; + int audio_loop_and; + + off_t sequence_sequence_loop; + off_t sequence_sequence_single; + off_t sequence_sequence_count; + off_t sequence_entry_number; + size_t sequence_entry_size; + + off_t layer_layer_count; + off_t layer_stream_id; + off_t layer_stream_size; + off_t layer_prefetch_size; + size_t layer_extra_size; + off_t layer_sample_rate; + off_t layer_channels; + off_t layer_stream_type; + off_t layer_num_samples; + size_t layer_entry_size; + + off_t silence_duration_float; + + ubi_bao_codec codec_map[16]; + +} ubi_bao_config; + +typedef struct { + int version; + ubi_bao_type type; ubi_bao_codec codec; int big_endian; int total_subsongs; - /* stream info */ - size_t header_size; + int is_file; + + /* config */ + ubi_bao_config cfg; + + /* header info */ + off_t header_offset; + uint8_t header_format; + uint32_t header_version; + size_t header_skip; + uint32_t header_id; + uint32_t header_type; + + uint32_t stream_id; size_t stream_size; off_t stream_offset; + off_t prefetch_skip; size_t prefetch_size; off_t prefetch_offset; size_t main_size; off_t main_offset; - uint32_t stream_id; - off_t extradata_offset; - int is_external; + off_t xma_offset; + off_t dsp_offset; + size_t extra_size; int is_prefetched; + int is_external; - int header_codec; + int loop_flag; int num_samples; + int loop_start; int sample_rate; int channels; + int stream_type; + + int layer_count; + int sequence_count; + uint32_t sequence_chain[64]; + int sequence_loop; + int sequence_single; + + float duration; char resource_name[255]; - int types_count[9]; - int subtypes_count[9]; + + char readable_name[255]; + int classes[16]; + int types[16]; + int allowed_types[16]; } ubi_bao_header; static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset, int target_subsong); static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile); static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE *streamFile); static STREAMFILE * setup_bao_streamfile(ubi_bao_header *bao, STREAMFILE *streamFile); +static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile); +static void config_bao_endian(ubi_bao_header * bao, off_t offset, STREAMFILE *streamFile); +static void build_readable_name(char * buf, size_t buf_size, ubi_bao_header * bao); /* .PK - packages with BAOs from Ubisoft's sound engine ("DARE") games in 2008+ */ @@ -82,35 +164,35 @@ fail: } #endif +/* ************************************************************************* */ -static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE * streamFile) { +static VGMSTREAM * init_vgmstream_ubi_bao_base(ubi_bao_header * bao, STREAMFILE *streamHead, STREAMFILE * streamData) { VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamData = NULL; off_t start_offset = 0x00; - int loop_flag = 0; - streamData = setup_bao_streamfile(bao, streamFile); - if (!streamData) goto fail; /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(bao->channels, loop_flag); + vgmstream = allocate_vgmstream(bao->channels, bao->loop_flag); if (!vgmstream) goto fail; - vgmstream->num_samples = bao->num_samples; + vgmstream->meta_type = meta_UBI_BAO; vgmstream->sample_rate = bao->sample_rate; vgmstream->num_streams = bao->total_subsongs; vgmstream->stream_size = bao->stream_size; - vgmstream->meta_type = meta_UBI_BAO; - switch (bao->codec) { - case UBI_ADPCM: { + vgmstream->num_samples = bao->num_samples; + vgmstream->loop_start_sample = bao->loop_start; + vgmstream->loop_end_sample = bao->num_samples; + + switch(bao->codec) { + case UBI_IMA: { vgmstream->coding_type = coding_UBI_IMA; vgmstream->layout_type = layout_none; break; } case RAW_PCM: - vgmstream->coding_type = coding_PCM16LE; /* always LE even on Wii */ + vgmstream->coding_type = coding_PCM16LE; /* always LE */ vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; break; @@ -125,7 +207,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = bao->stream_size / bao->channels; - dsp_read_coefs_be(vgmstream, streamFile, bao->extradata_offset + 0x10, 0x40); + dsp_read_coefs_be(vgmstream, streamHead, bao->dsp_offset + 0x10, 0x40); break; #ifdef VGM_USE_FFMPEG @@ -136,7 +218,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE size_t bytes, chunk_size, frame_size, data_size; int is_xma2_old; STREAMFILE *header_data; - off_t header_offset; + off_t xma_offset; if (bao->version == 0x00230008) { is_xma2_old = 1; @@ -155,6 +237,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE /* 0x08: frame size (not always present?) */ /* then there's a set of rising numbers followed by some weird data?.. */ /* calculate true XMA size and use that get data start offset */ + //todo see Ubi SB num_frames = read_32bitBE(start_offset + chunk_size + 0x04, streamData); //frame_size = read_32bitBE(start_offset + chunk_size + 0x08, streamData); frame_size = 0x800; @@ -170,18 +253,18 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE /* XMA header is stored in 0x20 header for internal sounds and before audio data for external sounds */ if (bao->is_external) { header_data = streamData; - header_offset = 0x00; + xma_offset = 0x00; } else { - header_data = streamFile; - header_offset = bao->extradata_offset; + header_data = streamHead; + xma_offset = bao->xma_offset; } if (is_xma2_old) { - bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf, 0x100, header_offset, chunk_size, data_size, header_data); + bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,sizeof(buf), xma_offset, chunk_size, data_size, header_data); } else { - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, header_data, 1); + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,sizeof(buf), xma_offset, chunk_size, data_size, header_data, 1); } vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, data_size); @@ -244,10 +327,26 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE goto fail; } - /* open the file for reading (can be an external stream, different from the current .pk) */ if (!vgmstream_open_stream(vgmstream, streamData, start_offset)) goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_bao_audio(ubi_bao_header * bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * streamData = NULL; + + streamData = setup_bao_streamfile(bao, streamFile); + if (!streamData) goto fail; + + vgmstream = init_vgmstream_ubi_bao_base(bao, streamFile, streamData); + if (!vgmstream) goto fail; + close_streamfile(streamData); return vgmstream; @@ -257,6 +356,129 @@ fail: return NULL; } +static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + layered_layout_data* data = NULL; + STREAMFILE* temp_streamFile = NULL; + STREAMFILE * streamData = NULL; + int i; + + streamData = setup_bao_streamfile(bao, streamFile); + if (!streamData) goto fail; + + /* init layout */ + data = init_layout_layered(bao->layer_count); + if (!data) goto fail; + + /* open all layers and mix */ + for (i = 0; i < bao->layer_count; i++) { + /* prepare streamfile from a single layer section */ + temp_streamFile = setup_ubi_bao_streamfile(streamData, 0x00, bao->stream_size, i, bao->layer_count, bao->big_endian); + if (!temp_streamFile) goto fail; + + /* build the layer VGMSTREAM (standard sb with custom streamfile) */ + data->layers[i] = init_vgmstream_ubi_bao_base(bao, streamFile, temp_streamFile); + if (!data->layers[i]) goto fail; + + close_streamfile(temp_streamFile); + temp_streamFile = NULL; + } + + if (!setup_layout_layered(data)) + goto fail; + + /* build the base VGMSTREAM */ + vgmstream = allocate_vgmstream(bao->channels * bao->layer_count, bao->loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_SB; + vgmstream->sample_rate = bao->sample_rate; + vgmstream->num_streams = bao->total_subsongs; + vgmstream->stream_size = bao->stream_size; + + vgmstream->num_samples = bao->num_samples; + vgmstream->loop_start_sample = bao->loop_start; + vgmstream->loop_end_sample = bao->num_samples; + + vgmstream->coding_type = data->layers[0]->coding_type; + vgmstream->layout_type = layout_layered; + vgmstream->layout_data = data; + + close_streamfile(streamData); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_streamfile(streamData); + if (vgmstream) + close_vgmstream(vgmstream); + else + free_layout_layered(data); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_bao_sequence(ubi_bao_header *bao, STREAMFILE *streamFile) { + return NULL; +} + +//static VGMSTREAM * init_vgmstream_ubi_bao_silence(ubi_bao_header *bao, STREAMFILE *streamFile) { +// return NULL; +//} + + +static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE * streamFile) { + VGMSTREAM * vgmstream = NULL; + + if (bao->total_subsongs == 0) { + VGM_LOG("UBI BAO: no subsongs\n"); + goto fail; + } + + ;VGM_LOG("UBI BAO: target at %x, id=%x, s_id=%x\n", + (uint32_t)bao->header_offset, bao->header_id, bao->stream_id); + ;VGM_LOG("UBI BAO: stream=%x, size=%x, res=%s\n", + (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); + ;VGM_LOG("UBI BAO: prefetch=%x, size=%x, main=%x, size=%x\n", + (uint32_t)bao->prefetch_offset, bao->prefetch_size, (uint32_t)bao->main_offset, bao->main_size); + + + switch(bao->type) { + + case UBI_AUDIO: + vgmstream = init_vgmstream_ubi_bao_audio(bao, streamFile); + break; + + case UBI_LAYER: + vgmstream = init_vgmstream_ubi_bao_layer(bao, streamFile); + break; + + case UBI_SEQUENCE: + vgmstream = init_vgmstream_ubi_bao_sequence(bao, streamFile); + break; + + //case UBI_SILENCE: + // vgmstream = init_vgmstream_ubi_bao_silence(bao, streamFile); + // break; + + case UBI_NONE: + default: + VGM_LOG("UBI BAO: subsong not found/parsed\n"); + goto fail; + } + + + if (!vgmstream) goto fail; + + strcpy(vgmstream->stream_name, bao->readable_name); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ************************************************************************* */ + /* parse a .pk (package) file: index + BAOs + external .spk resource table. We want header * BAOs pointing to internal/external stream BAOs (.spk is the same, with stream BAOs only). */ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { @@ -269,13 +491,14 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { STREAMFILE *streamTest = NULL; - /* class: 0x01=index, 0x02=BAO */ + /* format: 0x01=index, 0x02=BAO */ if (read_8bit(0x00, streamFile) != 0x01) goto fail; - /* index and resources always LE */ + /* index and resources always LE (except version) */ if (target_subsong == 0) target_subsong = 1; + bao->version = read_32bitBE(0x00, streamFile) & 0x00FFFFFF; /* 0x01(3): version, major/minor/release (numbering continues from .sb0/sm0) */ index_size = read_32bitLE(0x04, streamFile); /* can be 0, not including */ resources_offset = read_32bitLE(0x08, streamFile); /* always found even if not used */ @@ -288,6 +511,10 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { /* 0x30(10): parent GUID? may be same as 0x18, may be shared with other files */ /* (the above values seem ignored by games, probably just info for their tools) */ + if (!config_bao_version(bao, streamFile)) + goto fail; + + index_entries = index_size / 0x08; index_header_size = 0x40; @@ -304,12 +531,14 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { streamTest = reopen_streamfile(streamFile, 0x100); if (!streamTest) goto fail; - /* parse index to get target subsong N = Nth audio header BAO */ + /* parse index to get target subsong N = Nth valid header BAO */ bao_offset = index_header_size + index_size; for (i = 0; i < index_entries; i++) { //uint32_t bao_id = read_32bitLE(index_header_size + 0x08*i + 0x00, streamIndex); size_t bao_size = read_32bitLE(index_header_size + 0x08*i + 0x04, streamIndex); + //;VGM_LOG("UBI BAO: offset=%x, size=%x\n", (uint32_t)bao_offset, bao_size); + /* parse and continue to find out total_subsongs */ if (!parse_bao(bao, streamTest, bao_offset, target_subsong)) goto fail; @@ -317,10 +546,8 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { bao_offset += bao_size; /* files simply concat BAOs */ } - ;VGM_LOG("BAO types: 10=%i,20=%i,30=%i,40=%i,50=%i,70=%i,80=%i\n", - bao->types_count[1],bao->types_count[2],bao->types_count[3],bao->types_count[4],bao->types_count[5],bao->types_count[7],bao->types_count[8]); - ;VGM_LOG("BAO 0x20 subtypes: 01=%i,02=%i,03=%i,04=%i,05=%i,06=%i,07=%i,08=%i\n", - bao->types_count[1],bao->subtypes_count[2],bao->subtypes_count[3],bao->subtypes_count[4],bao->subtypes_count[5],bao->subtypes_count[6],bao->subtypes_count[7],bao->subtypes_count[8]); + ;VGM_LOG("UBI BAO: class "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->classes[i],"%02x=%i ",i,bao->classes[i]); }} VGM_LOG("\n"); + ;VGM_LOG("UBI BAO: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->types[i],"%02x=%i ",i,bao->types[i]); }} VGM_LOG("\n"); if (bao->total_subsongs == 0) { VGM_LOG("UBI BAO: no streams\n"); @@ -328,21 +555,23 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { } if (target_subsong < 0 || target_subsong > bao->total_subsongs || bao->total_subsongs < 1) goto fail; - + //todo move + //todo prefetch in layers /* get stream pointed by header */ if (bao->is_external) { off_t offset; int resources_count; size_t strings_size; - /* some sounds have a prefetched bit stored internally with the remaining streamed part stored externally */ + /* Some sounds have a prefetched part stored internally with the remaining streamed part stored externally. + * Both share stream ID in the .pk and outside, so first we find this prefetch */ bao_offset = index_header_size + index_size; for (i = 0; i < index_entries; i++) { uint32_t bao_id = read_32bitLE(index_header_size + 0x08 * i + 0x00, streamFile); size_t bao_size = read_32bitLE(index_header_size + 0x08 * i + 0x04, streamFile); if (bao_id == bao->stream_id) { - bao->prefetch_offset = bao_offset + bao->header_size; + bao->prefetch_offset = bao_offset + bao->prefetch_skip; break; } @@ -350,14 +579,20 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { } if (bao->prefetch_size) { - if (bao->prefetch_offset == 0) goto fail; + if (bao->prefetch_offset == 0) { + VGM_LOG("UBI BAO: couldn't find expected prefetch\n"); + goto fail; + } bao->is_prefetched = 1; } else { - if (bao->prefetch_offset != 0) goto fail; + if (bao->prefetch_offset != 0) { + VGM_LOG("UBI BAO: unexpected prefetch for stream id found\n"); + goto fail; + } } - /* parse resource table, LE (may be empty, or exist even with nothing in the file) */ + /* parse resource table to external stream (may be empty, or exist even with nothing in the file) */ resources_count = read_32bitLE(resources_offset+0x00, streamFile); strings_size = read_32bitLE(resources_offset+0x04, streamFile); @@ -369,22 +604,22 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { size_t resource_size = read_32bitLE(offset+0x10*i+0x0c, streamFile); if (resource_id == bao->stream_id) { - bao->stream_offset = resource_offset + bao->header_size; + bao->stream_offset = resource_offset + bao->header_skip; read_string(bao->resource_name,255, resources_offset + 0x04+0x04 + name_offset, streamFile); if (bao->is_prefetched) { - bao->main_offset = resource_offset + bao->header_size; - bao->main_size = resource_size - bao->header_size; + bao->main_offset = resource_offset + bao->header_skip; + bao->main_size = resource_size - bao->header_skip; VGM_ASSERT(bao->stream_size != bao->main_size + bao->prefetch_size, "UBI BAO: stream vs resource size mismatch\n"); } else { - VGM_ASSERT(bao->stream_size != resource_size - bao->header_size, "UBI BAO: stream vs resource size mismatch\n"); + VGM_ASSERT(bao->stream_size != resource_size - bao->header_skip, "UBI BAO: stream vs resource size mismatch\n"); } break; } } } - else { + else { //todo find_index_bao /* find within index */ bao_offset = index_header_size + index_size; for (i = 0; i < index_entries; i++) { @@ -394,8 +629,8 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { if (bao_id == bao->stream_id) { /* in some cases, stream size value from 0x20 header can be bigger than */ /* the actual audio chunk o_O [Rayman Raving Rabbids: TV Party (Wii)] */ - bao->stream_size = bao_size - bao->header_size; - bao->stream_offset = bao_offset + bao->header_size; /* relative, adjust to skip descriptor */ + bao->stream_size = bao_size - bao->header_skip; + bao->stream_offset = bao_offset + bao->header_skip; /* relative, adjust to skip descriptor */ break; } @@ -408,7 +643,7 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { goto fail; } - ;VGM_LOG("BAO stream: id=%x, offset=%x, size=%x, res=%s\n", bao->stream_id, (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); + build_readable_name(bao->readable_name, sizeof(bao->readable_name), bao); close_streamfile(streamIndex); close_streamfile(streamTest); @@ -419,240 +654,321 @@ fail: return 0; } -/* parse a single BAO (binary audio object) descriptor */ -static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset, int target_subsong) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - uint32_t bao_version, descriptor_type, descriptor_subtype; - size_t header_size; - +/* ************************************************************************* */ - /* 0x00(1): class? usually 0x02 but older BAOs have 0x01 too */ - bao_version = read_32bitBE(offset+0x00, streamFile) & 0x00FFFFFF; +static void build_readable_name(char * buf, size_t buf_size, ubi_bao_header * bao) { + const char *grp_name; + const char *res_name; + uint32_t id; + uint32_t type; + int index; - /* this could be done once as all BAOs share endianness */ - if (guess_endianness32bit(offset+0x04, streamFile)) { - read_32bit = read_32bitBE; - bao->big_endian = 1; - } else { - read_32bit = read_32bitLE; + /* config */ + if (bao->is_file) + grp_name = "file"; + else + grp_name = "package"; + id = bao->header_id; + type = bao->header_type; + index = -1; //bao->header_index; + + if (bao->type == UBI_SEQUENCE) { + if (bao->sequence_single) { + if (bao->sequence_count == 1) + res_name = "single"; + else + res_name = "multi"; + } + else { + if (bao->sequence_count == 1) + res_name = "single-loop"; + else + res_name = (bao->sequence_loop == 0) ? "multi-loop" : "intro-loop"; + } + } + else { + if (bao->is_external) + res_name = bao->resource_name; + else + res_name = NULL; } - header_size = read_32bit(offset+0x04, streamFile); /* mainly 0x28, rarely 0x24 */ - /* 0x08(10): descriptor GUID? */ - /* 0x18: null */ - /* 0x1c: null */ - descriptor_type = read_32bit(offset+0x20, streamFile); - descriptor_subtype = read_32bit(offset+header_size+0x04, streamFile); - - /* for debugging purposes */ - switch(descriptor_type) { - case 0x10000000: bao->types_count[1]++; break; /* link by id to another descriptor (link or header) */ - case 0x20000000: bao->types_count[2]++; break; /* stream header (and subtypes) */ - case 0x30000000: bao->types_count[3]++; break; /* internal stream (in .pk) */ - case 0x40000000: bao->types_count[4]++; break; /* package info? */ - case 0x50000000: bao->types_count[5]++; break; /* external stream (in .spk) */ - case 0x70000000: bao->types_count[7]++; break; /* project info? (sometimes special id 0x7fffffff) */ - case 0x80000000: bao->types_count[8]++; break; /* unknown (some id/info?) */ - default: - VGM_LOG("UBI BAO: unknown type %x at %x + %x\n", descriptor_type, (uint32_t)offset, 0x20); - goto fail; + /* .pk can contain many subsongs, we need something helpful + * (best done right after subsong detection, since some sequence re-parse types) */ + if (grp_name) { + if (res_name && res_name[0]) { + if (index >= 0) + snprintf(buf,buf_size, "%s/%04d/%02x-%08x/%s", grp_name, index, type, id, res_name); + else + snprintf(buf,buf_size, "%s/%02x-%08x/%s", grp_name, type, id, res_name); + } + else { + if (index >= 0) + snprintf(buf,buf_size, "%s/%04d/%02x-%08x", grp_name, index, type, id); + else + snprintf(buf,buf_size, "%s/%02x-%08x", grp_name, type, id); + } + } + else { + if (res_name && res_name[0]) { + if (index >= 0) + snprintf(buf,buf_size, "%04d/%02x-%08x/%s", index, type, id, res_name); + else + snprintf(buf,buf_size, "%02x-%08x/%s", type, id, res_name); + } else { + if (index >= 0) + snprintf(buf,buf_size, "%04d/%02x-%08x", index, type, id); + else + snprintf(buf,buf_size, "%02x-%08x", type, id); + } + } +} + +static int parse_type_audio(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + + /* audio header */ + bao->type = UBI_AUDIO; + + bao->stream_size = read_32bit(h_offset + bao->cfg.audio_stream_size, streamFile); + bao->stream_id = read_32bit(h_offset + bao->cfg.audio_stream_id, streamFile); + bao->is_external = read_32bit(h_offset + bao->cfg.audio_external_flag, streamFile) & bao->cfg.audio_external_and; + bao->loop_flag = read_32bit(h_offset + bao->cfg.audio_loop_flag, streamFile) & bao->cfg.audio_loop_and; + bao->channels = read_32bit(h_offset + bao->cfg.audio_channels, streamFile); + bao->sample_rate = read_32bit(h_offset + bao->cfg.audio_sample_rate, streamFile); + + /* prefetch data is in another internal BAO right after the base header */ + if (bao->cfg.audio_prefetch_size) { + bao->prefetch_size = read_32bit(h_offset + bao->cfg.audio_prefetch_size, streamFile); + bao->prefetch_skip = bao->header_skip; } - /* only parse headers */ - if (descriptor_type != 0x20000000) - return 1; - - /* for debugging purposes */ - switch(descriptor_subtype) { - case 0x00000001: bao->subtypes_count[1]++; break; /* standard */ - case 0x00000002: bao->subtypes_count[2]++; break; /* related to localized BAOs? (.lpk) */ - case 0x00000003: bao->subtypes_count[3]++; break; /* related to other header BAOs? */ - case 0x00000004: bao->subtypes_count[4]++; break; /* related to other header BAOs? */ - case 0x00000005: bao->subtypes_count[5]++; break; /* related to other header BAOs? */ - case 0x00000006: bao->subtypes_count[6]++; break; /* multilayer with multiple sounds */ - case 0x00000007: bao->subtypes_count[7]++; break; /* related to other header BAOs? */ - case 0x00000008: bao->subtypes_count[8]++; break; /* ? (almost empty with some unknown value) */ - default: - VGM_LOG("UBI BAO: unknown subtype %x at %x + %x\n", descriptor_subtype, (uint32_t)offset, header_size+0x04); - goto fail; + if (bao->loop_flag) { + bao->loop_start = read_32bit(h_offset + bao->cfg.audio_num_samples, streamFile); + bao->num_samples = read_32bit(h_offset + bao->cfg.audio_num_samples2, streamFile) + bao->loop_start; + } + else { + bao->num_samples = read_32bit(h_offset + bao->cfg.audio_num_samples, streamFile); } - //;VGM_ASSERT(descriptor_subtype != 0x01, "UBI BAO: subtype %x at %lx (%lx)\n", descriptor_subtype, offset, offset+header_size+0x04); - if (descriptor_subtype == 0x06) { - ;VGM_LOG("UBI BAO: layer subtype at %lx (%lx)\n", offset, offset+header_size+0x04); - /* todo fix layers - * for scott pilgrim: - * - 0x50: layer count - * - 0x78: layer headers size? - * - 0x7c: prefetch size - * - 0xb4: layer header xN (size 0x30) - * (this header has sample rate, channels, codec, various sizes/num samples) - * - 0x114: good ol' Ubi SB layer header v0x00100009 with classic v0x03 blocked data - * (standard prefetch style, part of data then cut in the middle and links to stream) - */ + bao->stream_type = read_32bit(h_offset + bao->cfg.audio_stream_type, streamFile); + if (bao->stream_type > 0x10) { + VGM_LOG("UBI BAO: unknown stream_type at %x\n", (uint32_t)offset); goto fail; goto fail; } - /* ignore unknown subtypes */ - if (descriptor_subtype != 0x00000001) + bao->codec = bao->cfg.codec_map[bao->stream_type]; + if (bao->codec == 0x00) { + VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; + goto fail; + } + + bao->dsp_offset = bao->cfg.audio_dsp_offset; + bao->xma_offset = bao->cfg.audio_xma_offset; + + return 1; +fail: + return 0; +} + +static int parse_type_sequence(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + off_t table_offset; + int i; + + /* sequence chain */ + bao->type = UBI_SEQUENCE; + if (bao->cfg.sequence_entry_size == 0) { + VGM_LOG("UBI BAO: sequence entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } + + bao->sequence_loop = read_32bit(h_offset + bao->cfg.sequence_sequence_loop, streamFile); + bao->sequence_single = read_32bit(h_offset + bao->cfg.sequence_sequence_single, streamFile); + bao->sequence_count = read_32bit(h_offset + bao->cfg.sequence_sequence_count, streamFile); + + if (bao->sequence_count > sizeof(bao->sequence_chain)) { /* arbitrary max */ + VGM_LOG("UBI BAO: incorrect sequence count\n"); + goto fail; + } + + /* get chain in extra table */ + table_offset = offset + bao->cfg.header_entry_size; + for (i = 0; i < bao->sequence_count; i++) { + uint32_t entry_id = (uint32_t)read_32bit(table_offset + bao->cfg.sequence_entry_number, streamFile); + + bao->sequence_chain[i] = entry_id; + + table_offset += bao->cfg.sequence_entry_size; + } + + return 1; +fail: + return 0; +} + + +static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + off_t table_offset; + int i; + + /* audio header */ + bao->type = UBI_LAYER; + if (bao->cfg.layer_entry_size == 0) { + VGM_LOG("UBI BAO: layer entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } + + bao->layer_count = read_32bit(h_offset + bao->cfg.layer_layer_count, streamFile); + bao->stream_size = read_32bit(h_offset + bao->cfg.layer_stream_size, streamFile); + bao->extra_size = read_32bit(h_offset + bao->cfg.layer_extra_size, streamFile); + + /* prefetch data is inside this BAO and not in other internal stream */ + if (bao->cfg.layer_prefetch_size) { + bao->prefetch_size = read_32bit(h_offset + bao->cfg.layer_prefetch_size, streamFile); + bao->prefetch_skip = bao->cfg.header_entry_size + bao->extra_size; + } + + if (bao->layer_count > 16) { /* arbitrary max */ + VGM_LOG("UBI BAO: incorrect layer count\n"); + goto fail; + } + + /* get 1st layer header in extra table and validate all headers match */ + table_offset = offset + bao->cfg.header_entry_size; + bao->channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); + bao->sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile); + bao->stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile); + bao->num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile); + bao->stream_id = read_32bit(table_offset + bao->cfg.layer_stream_id, streamFile); + + for (i = 0; i < bao->layer_count; i++) { + int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); + int sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile); + int stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile); + int num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile); + int stream_id = read_32bit(table_offset + bao->cfg.layer_stream_id, streamFile); + if (bao->channels != channels || bao->sample_rate != sample_rate || bao->stream_type != stream_type || + bao->num_samples != num_samples || bao->stream_id != stream_id) { + VGM_LOG("UBI BAO: layer headers don't match at %x\n", (uint32_t)table_offset); + goto fail; + } + + table_offset += bao->cfg.layer_entry_size; + } + + + //bao->is_external = 1; //todo flag? + + return 1; +fail: + return 0; +} + + +static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + + + ;VGM_LOG("UBI BAO: header at %x\n", (uint32_t)offset); + + /* parse known headers (see config_bao for info) */ + bao->header_offset = offset; + + bao->header_format = read_8bit (offset + 0x00, streamFile); /* usually 0x02 but older BAOs have 0x01 too */ + bao->header_version = read_32bitBE(offset + 0x00, streamFile) & 0x00FFFFFF; + + if (bao->cfg.header_skip) { + bao->header_skip = bao->cfg.header_skip; + /* 0x04(10): descriptor GUID */ + /* 0x14: class 0x20000000 */ + /* 0x18: v2? */ + } + else { + bao->header_skip = read_32bit(offset + 0x04, streamFile); /* usually 0x28, rarely 0x24 */ + /* 0x08(10): descriptor GUID */ + /* 0x18: null */ + /* 0x1c: null */ + /* 0x20: class 0x20000000 */ + /* 0x24: v2? (later games) */ + } + + bao->header_id = read_32bit(offset + bao->header_skip + 0x00, streamFile); + bao->header_type = read_32bit(offset + bao->header_skip + 0x04, streamFile); + + if (bao->version != bao->header_version) { + VGM_LOG("UBI BAO: mismatched header version at %x: %08x vs %08x\n", (uint32_t)offset, bao->version, bao->header_version); + goto fail; + } + + switch(bao->header_type) { + case 0x01: + if (!parse_type_audio(bao, offset, streamFile)) + goto fail; + break; + case 0x05: + if (!parse_type_sequence(bao, offset, streamFile)) + goto fail; + break; + case 0x06: + if (!parse_type_layer(bao, offset, streamFile)) + goto fail; + break; + default: + VGM_LOG("UBI BAO: unknown header type at %x\n", (uint32_t)offset); + goto fail; + } + + return 1; +fail: + return 0; +} + +static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset, int target_subsong) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + uint32_t bao_class, header_type; + size_t header_skip; + + + /*bao_version =*/ read_32bitBE(offset+0x00, streamFile); /* force buffer read */ + + config_bao_endian(bao, offset, streamFile); + read_32bit = bao->big_endian ? read_32bitBE : read_32bitLE; + + header_skip = read_32bit(offset+0x04, streamFile); + + bao_class = read_32bit(offset+0x20, streamFile); + if (bao_class & 0x0FFFFFFF) { + VGM_LOG("UBI BAO: unknown class %x at %x\n", bao_class, (uint32_t)offset); + goto fail; + } + + bao->classes[(bao_class >> 28) & 0xF]++; + if (bao_class != 0x20000000) /* ignore non-header classes */ + return 1; + + header_type = read_32bit(offset+header_skip+0x04, streamFile); + if (header_type > 8) { + VGM_LOG("UBI BAO: unknown type %x at %x\n", header_type, (uint32_t)offset); + goto fail; + } + + //;VGM_ASSERT(header_type == 0x05 || header_type == 0x06, "UBI BAO: type %x at %x\n", header_type, (uint32_t)offset); + + bao->types[header_type]++; + if (!bao->allowed_types[header_type]) return 1; bao->total_subsongs++; if (target_subsong != bao->total_subsongs) return 1; - /* parse BAO per version. Structure is mostly the same with some extra fields. - * - descriptor id (ignored by game) - * - subtype (may crash on game startup if changed) - * - stream size - * - stream id, corresponding to an internal (0x30) or external (0x50) stream - * - various flags/config fields - * - channels, ?, sample rate, average bit rate?, samples, full stream_size?, codec, etc - * - subtable entries, subtable size (may contain offsets/ids, cues, etc) - * - extra data per codec (ex. XMA header in some versions) */ - ;VGM_LOG("BAO header at %x\n", (uint32_t)offset); - bao->version = bao_version; - - switch(bao->version) { - case 0x001F0008: /* Rayman Raving Rabbids: TV Party (Wii)-pk */ - case 0x001F0011: /* Naruto: The Broken Bond (X360)-pk */ - case 0x0022000D: /* Just Dance (Wii)-pk */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x1c, streamFile); - bao->is_external = read_32bit(offset+header_size+0x28, streamFile); /* maybe 0x30 */ - bao->channels = read_32bit(offset+header_size+0x44, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x4c, streamFile); - if (read_32bit(offset + header_size + 0x34, streamFile) & 0x01) { /* single flag? */ - bao->num_samples = read_32bit(offset + header_size + 0x5c, streamFile); - } - else { - bao->num_samples = read_32bit(offset + header_size + 0x54, streamFile); - } - bao->header_codec = read_32bit(offset+header_size+0x64, streamFile); - - switch(bao->header_codec) { - case 0x01: bao->codec = RAW_PCM; break; - //case 0x02: bao->codec = FMT_OGG; break; - case 0x03: bao->codec = UBI_ADPCM; break; - case 0x05: bao->codec = RAW_XMA1; break; - case 0x09: bao->codec = RAW_DSP; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - bao->prefetch_size = read_32bit(offset + header_size + 0x74, streamFile); - - if (bao->codec == RAW_DSP) { - bao->extradata_offset = offset+header_size+0x80; /* mini DSP header */ - } - if (bao->codec == RAW_XMA1 && !bao->is_external) { - bao->extradata_offset = offset+header_size + 0x7c; /* XMA header */ - } - - break; - - case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-pk */ - case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-pk */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x1c, streamFile); - bao->is_external = read_32bit(offset+header_size+0x20, streamFile) & 0x04; - bao->channels = read_32bit(offset+header_size+0x28, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x30, streamFile); - if (read_32bit(offset+header_size+0x20, streamFile) & 0x20) { - bao->num_samples = read_32bit(offset+header_size+0x40, streamFile); - } - else { - bao->num_samples = read_32bit(offset+header_size+0x38, streamFile); /* from "fact" if AT3 */ - } - bao->header_codec = read_32bit(offset+header_size+0x48, streamFile); - - switch(bao->header_codec) { - case 0x06: bao->codec = RAW_PSX; break; - case 0x07: bao->codec = FMT_AT3; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - if (read_32bit(offset+header_size+0x20, streamFile) & 0x10) { - VGM_LOG("UBI BAO: possible full loop at %x\n", (uint32_t)offset); - /* RIFFs may have "smpl" and this flag, even when data shouldn't loop... */ - } - - break; - - case 0x00230008: /* Splinter Cell: Conviction (X360/PC)-pk */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x24, streamFile); - bao->is_external = read_32bit(offset+header_size+0x38, streamFile); - bao->channels = read_32bit(offset+header_size+0x54, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x5c, streamFile); - if (read_32bit(offset+header_size+0x44, streamFile) & 0x01) { /* single flag? */ - bao->num_samples = read_32bit(offset+header_size+0x6c, streamFile); - } - else { - bao->num_samples = read_32bit(offset+header_size+0x64, streamFile); - } - bao->header_codec = read_32bit(offset+header_size+0x74, streamFile); - - switch (bao->header_codec) { - case 0x01: bao->codec = RAW_PCM; break; - case 0x02: bao->codec = UBI_ADPCM; break; - case 0x03: bao->codec = FMT_OGG; break; - case 0x04: bao->codec = RAW_XMA2; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - bao->prefetch_size = read_32bit(offset+header_size+0x84, streamFile); - - if (bao->codec == RAW_XMA2 && !bao->is_external) { - bao->extradata_offset = offset + header_size + 0x8c; /* XMA header */ - } - - break; - - case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-pk */ - case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-file */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x24, streamFile); - bao->is_external = read_32bit(offset+header_size+0x30, streamFile); - bao->channels = read_32bit(offset+header_size+0x48, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x50, streamFile); - if (read_32bit(offset+header_size+0x38, streamFile) & 0x01) { /* single flag? */ - bao->num_samples = read_32bit(offset+header_size+0x60, streamFile); - } - else { - bao->num_samples = read_32bit(offset+header_size+0x58, streamFile); - } - bao->header_codec = read_32bit(offset+header_size+0x68, streamFile); - /* when is internal+external (flag 0x2c?), 0xa0: internal data size */ - - switch(bao->header_codec) { - case 0x01: bao->codec = RAW_PCM; break; - case 0x02: bao->codec = UBI_ADPCM; break; /* assumed */ - case 0x03: bao->codec = FMT_OGG; break; /* assumed */ - case 0x04: bao->codec = RAW_XMA2; break; - case 0x05: bao->codec = RAW_PSX; break; - case 0x06: bao->codec = RAW_AT3; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - bao->prefetch_size = read_32bit(offset + header_size + 0x78, streamFile); - - if (bao->codec == RAW_XMA2 && !bao->is_external) { - bao->extradata_offset = offset+header_size + 0x8c; /* XMA header */ - } - - break; - - case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-file */ - case 0x001B0200: /* Beowulf (PS3)-file */ - case 0x001F0010: /* Prince of Persia 2008 (PS3/X360)-file, Far Cry 2 (PS3)-file */ - case 0x00280306: /* Far Cry 3: Blood Dragon (X360)-file */ - case 0x00290106: /* Splinter Cell Blacklist? */ - default: /* others possibly using BAO: Avatar X360/PS3/PC, Just Dance, Watch_Dogs, Far Cry Primal, Far Cry 4 */ - VGM_LOG("UBI BAO: unknown BAO version at %x\n", (uint32_t)offset); - goto fail; - } - - bao->header_size = header_size; + if (!parse_header(bao, streamFile, offset)) + goto fail; return 1; fail: @@ -718,3 +1034,210 @@ fail: return NULL; } + + +static void config_bao_endian(ubi_bao_header * bao, off_t offset, STREAMFILE *streamFile) { + //todo this could be done once as all BAOs share endianness + //todo fix for later versions without size field + + bao->big_endian = guess_endianness32bit(offset+0x04, streamFile); +} + + +static void config_bao_sequence(ubi_bao_header * bao, off_t sequence_count, off_t sequence_single, off_t sequence_loop, off_t entry_size) { + /* sequence header and chain table */ + bao->cfg.sequence_sequence_count = sequence_count; + bao->cfg.sequence_sequence_single = sequence_count; + bao->cfg.sequence_sequence_loop = sequence_count; + bao->cfg.sequence_entry_size = entry_size; + bao->cfg.sequence_entry_number = 0x00; +} +static void config_bao_layer_h(ubi_bao_header * bao, off_t layer_count, off_t stream_size, off_t extra_size, off_t prefetch_size) { + /* layer header in the main BAO */ + bao->cfg.layer_layer_count = layer_count; + bao->cfg.layer_stream_size = stream_size; + bao->cfg.layer_extra_size = extra_size; + bao->cfg.layer_prefetch_size = prefetch_size; +} +static void config_bao_layer_s(ubi_bao_header * bao, off_t entry_size, off_t sample_rate, off_t channels, off_t stream_type, off_t num_samples, off_t stream_id) { + /* layer sub-headers in extra table */ + bao->cfg.layer_entry_size = entry_size; + bao->cfg.layer_sample_rate = sample_rate; + bao->cfg.layer_channels = channels; + bao->cfg.layer_stream_type = stream_type; + bao->cfg.layer_num_samples = num_samples; + bao->cfg.layer_stream_id = stream_id; +} + +static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { + + /* Ubi BAO evolved from Ubi SB and are conceptually quite similar, see that first. + * + * BAOs (binary audio objects) are first divided into "classes": + * - 0x10000000: events (links by id to another event or header BAO) + * - 0x20000000: stream header + * - 0x30000000: internal stream (in .pk) + * - 0x40000000: package info? + * - 0x50000000: external stream (in .spk) + * - 0x70000000: project info? (sometimes special id 0x7fffffff) + * - 0x80000000: unknown (some id/info?) + * Class 1/2/3 are roughly equivalent to Ubi SB's section1/2/3. + * + * We want header classes, also similar to SB types: + * - 01: single audio (samples, channels, bitrate, samples+size, etc) + * - 02: unknown chain (has probability?) + * - 03: unknown chain + * - 04: random (count, etc) + BAO IDs and float probability to play + * - 05: sequence (count, etc) + BAO IDs and unknown data + * - 06: layer (count, etc) + layer headers + * - 07: unknown chain + * - 08: silence (duration, etc) + * Right after base BAO size is the extra table for that BAO (what sectionX had). + * + * Most types + tables are pretty much the same (with config styles ported straight) but + * later versions can "prefetch" part of the data (signaled by a size in the header). + * The header points to a stream ID, normally external, but with prefetch enabled it also + * exists as an internal stream too, so must join both internal and external streams. + * For layers though the prefetch part is in the same BAO as the layer header, but the + * ID still references both (layer header with prefetch and external stream). + * I + */ + + bao->allowed_types[0x01] = 1; + //bao->allowed_types[0x05] = 1; + //bao->allowed_types[0x06] = 1; + + + /* all this config is relative to header_skip */ + + bao->cfg.header_id = 0x00; + bao->cfg.header_type = 0x04; + bao->cfg.header_skip = 0x00; /* must set for later versions that removed the field */ + + bao->cfg.audio_external_and = 1; + bao->cfg.audio_loop_and = 1; + + switch(bao->version) { + case 0x001F0008: /* Rayman Raving Rabbids: TV Party (Wii)-pk */ + case 0x001F0011: /* Naruto: The Broken Bond (X360)-pk */ + case 0x0022000D: /* Just Dance (Wii)-pk */ + bao->cfg.header_entry_size = 0xa8;//todo: Naruto = 0xa4, Rayman/JD = 0xa8 + //skip 0x28 + + bao->cfg.audio_stream_size = 0x08; + bao->cfg.audio_stream_id = 0x1c; + bao->cfg.audio_external_flag = 0x28; /* maybe 0x30 */ + bao->cfg.audio_loop_flag = 0x34; + bao->cfg.audio_channels = 0x44; + bao->cfg.audio_sample_rate = 0x4c; + bao->cfg.audio_num_samples = 0x54; + bao->cfg.audio_num_samples2 = 0x5c; + + bao->cfg.audio_stream_type = 0x64; + bao->cfg.audio_prefetch_size = 0x74; + bao->cfg.audio_xma_offset = 0x7c; /* only if internal */ + bao->cfg.audio_dsp_offset = 0x80; + + bao->cfg.codec_map[0x01] = RAW_PCM; + //bao->cfg.codec_map[0x02] = FMT_OGG; + bao->cfg.codec_map[0x03] = UBI_IMA; + bao->cfg.codec_map[0x05] = RAW_XMA1; + bao->cfg.codec_map[0x09] = RAW_DSP; + + return 1; + + case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-pk */ + case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-pk */ + bao->cfg.header_entry_size = 0x84; + //skip 0x28 + + bao->cfg.audio_stream_size = 0x08; + bao->cfg.audio_stream_id = 0x1c; + bao->cfg.audio_external_flag = 0x20; + bao->cfg.audio_loop_flag = 0x20; /* 0x10? */ + bao->cfg.audio_channels = 0x28; + bao->cfg.audio_sample_rate = 0x30; + bao->cfg.audio_num_samples = 0x38; + bao->cfg.audio_num_samples2 = 0x40; + bao->cfg.audio_stream_type = 0x48; + + bao->cfg.audio_external_and = (1 << 2); + bao->cfg.audio_loop_and = (1 << 5); + + bao->cfg.codec_map[0x06] = RAW_PSX; + bao->cfg.codec_map[0x07] = FMT_AT3; + + return 1; + + case 0x00230008: /* Splinter Cell: Conviction (X360/PC)-pk */ + bao->cfg.header_entry_size = 0xb8; + //skip 0x28 + + bao->cfg.audio_stream_size = 0x08; + bao->cfg.audio_stream_id = 0x24; + bao->cfg.audio_external_flag = 0x38; + bao->cfg.audio_loop_flag = 0x44; + bao->cfg.audio_channels = 0x54; + bao->cfg.audio_sample_rate = 0x5c; + bao->cfg.audio_num_samples = 0x64; + bao->cfg.audio_num_samples2 = 0x6c; + bao->cfg.audio_stream_type = 0x74; + bao->cfg.audio_prefetch_size = 0x84; + bao->cfg.audio_xma_offset = 0x8c; /* -1 if null */ + + config_bao_sequence(bao, 0x34, 0x54, 0x58, 0x14); + + config_bao_layer_h(bao, 0x28, 0x50, 0x5c, 0x54); + config_bao_layer_s(bao, 0x30, 0x00, 0x04, 0x08, 0x14, 0x2c); + + bao->cfg.codec_map[0x01] = RAW_PCM; + bao->cfg.codec_map[0x02] = UBI_IMA; + bao->cfg.codec_map[0x03] = FMT_OGG; + bao->cfg.codec_map[0x04] = RAW_XMA2; + + return 1; + + case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-pk */ + case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-file */ + bao->cfg.header_entry_size = 0xb4; + //skip 0x28 + + bao->cfg.audio_stream_size = 0x08; + bao->cfg.audio_stream_id = 0x24; + bao->cfg.audio_external_flag = 0x30; + bao->cfg.audio_loop_flag = 0x38; + bao->cfg.audio_channels = 0x48; + bao->cfg.audio_sample_rate = 0x50; + bao->cfg.audio_num_samples = 0x58; + bao->cfg.audio_num_samples2 = 0x60; + bao->cfg.audio_stream_type = 0x68; + bao->cfg.audio_prefetch_size = 0x78; + bao->cfg.audio_xma_offset = 0x8c; + + config_bao_layer_h(bao, 0x28, 0x48, 0x50, 0x54); + config_bao_layer_s(bao, 0x30, 0x00, 0x04, 0x08, 0x14, 0x2c); + + bao->cfg.codec_map[0x01] = RAW_PCM; + bao->cfg.codec_map[0x02] = UBI_IMA; /* assumed */ + bao->cfg.codec_map[0x03] = FMT_OGG; /* assumed */ + bao->cfg.codec_map[0x04] = RAW_XMA2; + bao->cfg.codec_map[0x05] = RAW_PSX; + bao->cfg.codec_map[0x06] = RAW_AT3; + + return 1; + + case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-file */ + case 0x001B0200: /* Beowulf (PS3)-file */ + case 0x001C0000: /* Lost: Via Domus (PS3)-file */ + case 0x001F0010: /* Prince of Persia 2008 (PS3/X360)-file, Far Cry 2 (PS3)-file */ + case 0x00260102: /* Prince of Persia Trilogy HD (PS3)-pk */ + case 0x00280306: /* Far Cry 3: Blood Dragon (X360)-file */ + case 0x00290106: /* Splinter Cell: Blacklist (PS3)-file */ + default: /* others possibly using BAO: Avatar X360/PS3/PC, Just Dance, Watch_Dogs, Far Cry Primal, Far Cry 4 */ + VGM_LOG("UBI BAO: unknown BAO version %08x\n", bao->version); + return 0; + } + + VGM_LOG("UBI BAO: unknown BAO version %08x\n", bao->version); + return 0; +} diff --git a/src/meta/ubi_bao_streamfile.h b/src/meta/ubi_bao_streamfile.h new file mode 100644 index 00000000..f72298c8 --- /dev/null +++ b/src/meta/ubi_bao_streamfile.h @@ -0,0 +1,11 @@ +#ifndef _UBI_BAO_STREAMFILE_H_ +#define _UBI_BAO_STREAMFILE_H_ + +//todo fix dupe code, but would be nice to keep it all in separate compilation units +#include "ubi_sb_streamfile.h" + +static STREAMFILE* setup_ubi_bao_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) { + return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian); +} + +#endif /* _UBI_BAO_STREAMFILE_H_ */