From 47e45d42dad7d2b6dee09f2b537a2cfc1fb8e7b8 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 21 Dec 2018 17:17:10 +0300 Subject: [PATCH 1/8] Ubi SB: Added support for some games using MAPS.SMx --- src/meta/ubi_sb.c | 310 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 271 insertions(+), 39 deletions(-) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index fc01a990..c94ff301 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -2,7 +2,7 @@ #include "../coding/coding.h" -typedef enum { UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_XMA1, FMT_OGG } ubi_sb_codec; +typedef enum { UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG } ubi_sb_codec; typedef enum { UBI_PC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_3DS, UBI_PS3, UBI_WII, UBI_PSP } ubi_sb_platform; typedef struct { ubi_sb_platform platform; @@ -41,6 +41,7 @@ typedef struct { int has_internal_names; int has_extra_name_flag; int has_rotating_ids; + int is_map; /* derived */ size_t main_size; @@ -62,6 +63,8 @@ typedef struct { int channels; uint32_t stream_type; char stream_name[255]; + int header_idx; + off_t header_offset; } ubi_sb_header; @@ -156,7 +159,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { case RAW_PSX: vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = sb.stream_size / sb.channels; + vgmstream->interleave_block_size = (sb.stream_type == 0x00) ? sb.stream_size / sb.channels : 0x10; /* TODO: needs testing */ vgmstream->num_samples = ps_bytes_to_samples(sb.stream_size, sb.channels) ; break; @@ -223,14 +226,71 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { break; } + case RAW_AT3: { + uint8_t buf[0x100]; + int32_t bytes, block_size, encoder_delay, joint_stereo; + + block_size = 0x98 * sb.channels; + joint_stereo = 0; + encoder_delay = 0x00; /* TODO: this is incorrect */ + + bytes = ffmpeg_make_riff_atrac3(buf, 0x100, sb.stream_samples, sb.stream_size, sb.channels, sb.sample_rate, block_size, joint_stereo, encoder_delay); + vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb.stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->num_samples = sb.stream_samples; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + break; + } + + case FMT_XMA1: { + ffmpeg_codec_data *ffmpeg_data; + uint8_t buf[0x100]; + uint32_t num_frames; + size_t bytes, frame_size, chunk_size, data_size; + off_t header_offset; + + chunk_size = 0x20; + + /* formatted XMA sounds have a strange custom header */ + /* first there's XMA2/FMT chunk, after that: */ + /* 0x00: some low number like 0x01 or 0x04 */ + /* 0x04: number of frames */ + /* 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 */ + num_frames = read_32bitBE(start_offset + chunk_size + 0x04, streamData); + //frame_size = read_32bitBE(start_offset + chunk_size + 0x08, streamData); + frame_size = 0x800; + + header_offset = start_offset; + data_size = num_frames * frame_size; + start_offset += sb.stream_size - data_size; + + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, streamData, 1); + + ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, data_size); + if (!ffmpeg_data) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + vgmstream->num_samples = sb.stream_samples; + vgmstream->stream_size = data_size; + break; + } + case RAW_XMA1: { ffmpeg_codec_data *ffmpeg_data; uint8_t buf[0x100]; - size_t bytes; + size_t bytes, chunk_size; off_t header_offset; + + VGM_ASSERT(sb.is_external, "Ubi SB: Raw XMA used for external sound\n"); + /* Get XMA header from extra section */ + chunk_size = 0x20; header_offset = sb.main_size + sb.section1_size + sb.section2_size + sb.xma_header_offset; - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, 0x20, sb.stream_size, streamFile, 1); + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb.stream_size, streamFile, 1); ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb.stream_size); if (!ffmpeg_data) goto fail; @@ -238,7 +298,6 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; vgmstream->num_samples = sb.stream_samples; - VGM_ASSERT(sb.stream_samples != ffmpeg_data->totalSamples, "UBI SB: header samples differ\n"); break; } @@ -285,7 +344,7 @@ fail: static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; - int i, ok, current_type = -1, current_id = -1; + int i, j, k, ok, current_type = -1, current_id = -1; int target_stream = streamFile->stream_index; if (target_stream == 0) target_stream = 1; @@ -360,7 +419,28 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->main_size = 0x1c; sb->section1_size = sb->section1_entry_size * sb->section1_num; sb->section2_size = sb->section2_entry_size * sb->section2_num; - sb->section3_size = sb->section3_entry_size * sb->section3_num; + + /* detect if this a maps bank */ + if (read_32bit(sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size, streamFile) == 0xFFFFFFFF) { + sb->is_map = 1; + } + + if (sb->is_map) { + sb->section3_size = 0; + off_t sec3_offset = sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size; + + for (i = 0; i < sb->section3_num; i++) { + off_t offset = sec3_offset + 0x14 * i; + off_t table_offset = read_32bit(offset + 0x04, streamFile) + sec3_offset; + off_t table_num = read_32bit(offset + 0x08, streamFile); + off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sec3_offset; + off_t table2_num = read_32bit(offset + 0x10, streamFile); + + sb->section3_size += 0x14 + table_num * 0x08 + table2_num * 0x10; + } + } else { + sb->section3_size = sb->section3_entry_size * sb->section3_num; + } /* find target stream info in section2 */ for (i = 0; i < sb->section2_num; i++) { @@ -405,6 +485,9 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { continue; //;VGM_LOG("target at offset=%lx (size=%x)\n", offset, sb->section2_entry_size); + sb->header_offset = offset; + sb->header_idx = i; + sb->header_id = read_32bit(offset + 0x00, streamFile); /* 16b+16b group+sound id */ sb->header_type = read_32bit(offset + 0x04, streamFile); sb->stream_size = read_32bit(offset + 0x08, streamFile); @@ -453,11 +536,6 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { if (sb->extra_size > 0 && sb->stream_name_offset > sb->extra_size) sb->autodetect_external = 0; /* name outside extra table == is internal */ } - - /* this field is only seen in X360 games, points at XMA1 header in extra section */ - if (sb->xma_pointer_offset) { - sb->xma_header_offset = read_32bit(offset + sb->xma_pointer_offset, streamFile); - } } if (sb->total_streams == 0) { VGM_LOG("UBI SB: no streams\n"); @@ -468,8 +546,8 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { goto fail; } - if (!(sb->stream_id_offset || sb->has_rotating_ids) && sb->section3_num > 1) { - VGM_LOG("UBI SB: unexpected number of internal streams %i\n", sb->section3_num); + if (!(sb->stream_id_offset || sb->has_rotating_ids || sb->is_map) && sb->section3_num > 1) { + VGM_LOG("UBI SB: unexpected number of internal stream groups %i\n", sb->section3_num); goto fail; } @@ -515,7 +593,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { #if 0 case UBI_PS3: /* Need to confirm */ - sb->codec = FMT_AT3; + sb->codec = RAW_AT3; break; #endif default: @@ -541,8 +619,23 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->codec = FMT_OGG; break; - case 0x05: /* AT3 (PSP, PS3) */ - sb->codec = FMT_AT3; + case 0x05: /* AT3 (PSP, PS3) or XMA1 (X360) */ + switch (sb->platform) { + case UBI_X360: + sb->codec = FMT_XMA1; + break; + case UBI_PS3: + case UBI_PSP: + sb->codec = FMT_AT3; + break; + default: + VGM_LOG("UBI SB: unknown codec for stream_type %x\n", sb->stream_type); + goto fail; + } + break; + + case 0x07: + sb->codec = RAW_AT3; break; default: @@ -550,6 +643,13 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { goto fail; } + if (sb->codec == RAW_XMA1) { + if (!sb->is_external) { + /* this field is only seen in X360 games, points at XMA1 header in extra section */ + sb->xma_header_offset = read_32bit(sb->header_offset + sb->xma_pointer_offset, streamFile); + } + } + /* uncommon but possible */ //VGM_ASSERT(sb->is_external && sb->section3_num != 0, "UBI SS: mixed external and internal streams\n"); @@ -557,14 +657,50 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { //VGM_ASSERT(sb->is_external && sb->stream_id_offset && sb->stream_id > 0, "UBI SB: unexpected external stream with stream id\n"); /* section 3: substreams within the file, adjust stream offset (rarely used but table is always present) */ - if (!sb->is_external && (sb->stream_id_offset || sb->has_rotating_ids) && sb->section3_num > 1) { - for (i = 0; i < sb->section3_num; i++) { - off_t offset = sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size + sb->section3_entry_size * i; + if (!sb->is_external) { + off_t sec3_offset = sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size; - /* 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->stream_id) - break; - sb->stream_offset += read_32bit(offset + 0x04, streamFile); + if (sb->is_map) { + /* maps banks store internal sounds offsets in a separate table, find the matching entry */ + for (i = 0; i < sb->section3_num; i++) { + off_t offset = sec3_offset + 0x14 * i; + off_t table_offset = read_32bit(offset + 0x04, streamFile) + sec3_offset; + off_t table_num = read_32bit(offset + 0x08, streamFile); + off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sec3_offset; + off_t table2_num = read_32bit(offset + 0x10, streamFile); + + for (j = 0; j < table_num; j++) { + int idx = read_32bit(table_offset + 0x08 * j + 0x00, streamFile) & 0x0000FFFF; + + if (idx == sb->header_idx) { + if (!sb->stream_id_offset && table2_num > 1) { + VGM_LOG("UBI SB: unexpected number of internal stream groups %i\n", sb->section3_num); + goto fail; + } + + sb->stream_offset = read_32bit(table_offset + 0x08 * j + 0x04, streamFile); + for (k = 0; k < table2_num; k++) { + uint32_t id = read_32bit(table2_offset + 0x10 * k + 0x00, streamFile); + if (id == sb->stream_id) + break; + sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x08, streamFile); + } + break; + } + } + + if (sb->stream_offset) + break; + } + } else if ((sb->stream_id_offset || sb->has_rotating_ids) && sb->section3_num > 1) { + for (i = 0; i < sb->section3_num; i++) { + off_t offset = sec3_offset + sb->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->stream_id) + break; + sb->stream_offset += read_32bit(offset + 0x04, streamFile); + } } } @@ -598,6 +734,26 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section3_entry_size = 0x08; sb->stream_name_size = 0x24; /* maybe 0x28 or 0x20 for some but ok enough (null terminated) */ +#if 0 + /* Splinter Cell (2002)(PC) */ + if (sb->version == 0x00000007 && sb->platform == UBI_PC) { + sb->section1_entry_size = 0x58; + sb->section2_entry_size = 0x80; + + sb->external_flag_offset = 0x24; /* maybe 0x28 */ + sb->num_samples_offset = 0x30; + sb->sample_rate_offset = 0x44; + sb->channels_offset = 0x4a; + sb->stream_type_offset = 0x4c; + sb->stream_name_offset = 0x50; + + sb->has_short_channels = 1; + return 1; + } + + /* Splinter Cell has all the common values placed 0x04 bytes earlier */ +#endif + /* Prince of Persia: Sands of Time (2003)(PC) */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PC) || /* (not sure if exists, just in case) */ (sb->version == 0x000A0004 && sb->platform == UBI_PC)) { /* main game */ @@ -867,6 +1023,23 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } + + /* Splinter Cell: Chaos Theory (2005)(PC) */ + if (sb->version == 0x00120012 && sb->platform == UBI_PC) { + sb->section1_entry_size = 0x68; + sb->section2_entry_size = 0x60; + + sb->external_flag_offset = 0x24; + sb->num_samples_offset = 0x30; + sb->sample_rate_offset = 0x44; + sb->channels_offset = 0x4c; + sb->stream_type_offset = 0x50; + sb->extra_name_offset = 0x54; + + sb->has_extra_name_flag = 1; + return 1; + } + #if 0 /* Far cry: Instincts - Evolution (2006)(Xbox) */ if (sb->version == 0x00170000 && sb->platform == UBI_XBOX) { @@ -885,6 +1058,77 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) } #endif + /* Red Steel (2006)(Wii) */ + if (sb->version == 0x00180006 && sb->platform == UBI_WII) { /* same as 0x00150000 */ + sb->section1_entry_size = 0x68; + sb->section2_entry_size = 0x6c; + + sb->external_flag_offset = 0x28; /* maybe 0x2c */ + sb->num_samples_offset = 0x3c; + sb->sample_rate_offset = 0x50; + sb->channels_offset = 0x58; + sb->stream_type_offset = 0x5c; + sb->extra_name_offset = 0x60; + + return 1; + } + + /* Splinter Cell: Double Agent (2006)(PC) */ + if (sb->version == 0x00180006 && sb->platform == UBI_PC) { + sb->section1_entry_size = 0x68; + sb->section2_entry_size = 0x7c; + + sb->external_flag_offset = 0x2c; + sb->stream_id_offset = 0x34; + sb->channels_offset = 0x5c; + sb->sample_rate_offset = 0x54; + sb->num_samples_offset = 0x40; + sb->num_samples_offset2 = 0x48; + sb->stream_type_offset = 0x60; + sb->extra_name_offset = 0x64; + + sb->has_extra_name_flag = 1; + return 1; + } + + /* Splinter Cell: Double Agent (2006)(X360) */ + if (sb->version == 0x00180006 && sb->platform == UBI_X360) { + sb->section1_entry_size = 0x68; + sb->section2_entry_size = 0x78; + + sb->external_flag_offset = 0x2c; + sb->stream_id_offset = 0x30; + sb->samples_flag_offset = 0x34; + sb->channels_offset = 0x5c; + sb->sample_rate_offset = 0x54; + sb->num_samples_offset = 0x40; + sb->num_samples_offset2 = 0x48; + sb->stream_type_offset = 0x60; + sb->extra_name_offset = 0x64; + sb->xma_pointer_offset = 0x70; + + sb->has_extra_name_flag = 1; + return 1; + } + + /* Splinter Cell: Double Agent (2006)(Xbox) */ + if (sb->version == 0x00160002 && sb->platform == UBI_XBOX) { + sb->section1_entry_size = 0x48; + sb->section2_entry_size = 0x58; + + sb->external_flag_offset = 0; + sb->num_samples_offset = 0x28; + sb->stream_id_offset = 0; + sb->sample_rate_offset = 0x3c; + sb->channels_offset = 0x44; + sb->stream_type_offset = 0x48; + sb->extra_name_offset = 0x4c; + + sb->has_extra_name_flag = 1; + //sb->has_rotating_ids = 1; + return 1; + } + /* Prince of Persia: Rival Swords (2007)(PSP) */ if (sb->version == 0x00180005 && sb->platform == UBI_PSP) { sb->section1_entry_size = 0x48; @@ -931,21 +1175,6 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) */ #endif - /* Red Steel (2006)(Wii) */ - if (sb->version == 0x00180006 && sb->platform == UBI_WII) { /* same as 0x00150000 */ - sb->section1_entry_size = 0x68; - sb->section2_entry_size = 0x6c; - - sb->external_flag_offset = 0x28; /* maybe 0x2c */ - sb->num_samples_offset = 0x3c; - sb->sample_rate_offset = 0x50; - sb->channels_offset = 0x58; - sb->stream_type_offset = 0x5c; - sb->extra_name_offset = 0x60; - - return 1; - } - /* Prince of Persia: Rival Swords (2007)(Wii) */ if (sb->version == 0x00190003 && sb->platform == UBI_WII) { sb->section1_entry_size = 0x68; @@ -1023,6 +1252,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->extra_name_offset = 0x58; sb->xma_pointer_offset = 0x6c; + sb->has_extra_name_flag = 1; return 1; } @@ -1043,11 +1273,13 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) } /* Surf's Up (2007)(PS3) */ + /* Splinter Cell: Double Agent (2007)(PS3) */ if (sb->version == 0x00190005 && sb->platform == UBI_PS3) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x70; sb->external_flag_offset = 0x28; + sb->stream_id_offset = 0x2c; sb->channels_offset = 0x3c; sb->sample_rate_offset = 0x40; sb->num_samples_offset = 0x48; From 6f0db94553eb9cbd9c6bad4a188f0927b26297b6 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 21 Dec 2018 19:00:59 +0300 Subject: [PATCH 2/8] Ubi SB: Added Splinter Cell: Chaos Theory (Xbox) --- src/meta/ubi_sb.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index c94ff301..1b462285 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -1040,6 +1040,24 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } + /* Splinter Cell: Chaos Theory (2005)(Xbox) */ + if (sb->version == 0x00120012 && sb->platform == UBI_XBOX) { + sb->section1_entry_size = 0x48; + sb->section2_entry_size = 0x4c; + + sb->external_flag_offset = 0; + sb->num_samples_offset = 0x18; + sb->stream_id_offset = 0; + sb->sample_rate_offset = 0x30; + sb->channels_offset = 0x38; + sb->stream_type_offset = 0x3c; + sb->extra_name_offset = 0x40; + + sb->has_extra_name_flag = 1; + //sb->has_rotating_ids = 1; + return 1; + } + #if 0 /* Far cry: Instincts - Evolution (2006)(Xbox) */ if (sb->version == 0x00170000 && sb->platform == UBI_XBOX) { From 562624410890a8943db38ee1a25559599c3b0aa4 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 23 Dec 2018 00:47:24 +0300 Subject: [PATCH 3/8] Ubi SB: Full SMx format support --- src/formats.c | 8 + src/meta/meta.h | 1 + src/meta/ubi_sb.c | 591 +++++++++++++++++++++++++++++----------------- src/vgmstream.c | 1 + 4 files changed, 388 insertions(+), 213 deletions(-) diff --git a/src/formats.c b/src/formats.c index 9db2151d..0f3f7a5b 100644 --- a/src/formats.c +++ b/src/formats.c @@ -330,6 +330,14 @@ static const char* extension_list[] = { "sb5", "sb6", "sb7", + "sm0", + "sm1", + "sm2", + "sm3", + "sm4", + "sm5", + "sm6", + "sm7", "sbin", "sc", "scd", diff --git a/src/meta/meta.h b/src/meta/meta.h index a068fc96..1c38f08e 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -673,6 +673,7 @@ VGMSTREAM * init_vgmstream_pc_ast(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_naac(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ezw(STREAMFILE * streamFile); diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 1b462285..b5199239 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -21,6 +21,18 @@ typedef struct { int flag1; int flag2; + /* maps data config */ + int is_map; + size_t map_header_entry_size; + off_t map_sec1_pointer_offset; + off_t map_sec1_num_offset; + off_t map_sec2_pointer_offset; + off_t map_sec2_num_offset; + off_t map_sec3_pointer_offset; + off_t map_sec3_num_offset; + off_t map_extra_pointer_offset; + off_t map_extra_size_offset; + /* stream info config (format varies slightly per game) */ size_t section1_entry_size; size_t section2_entry_size; @@ -41,13 +53,19 @@ typedef struct { int has_internal_names; int has_extra_name_flag; int has_rotating_ids; - int is_map; /* derived */ - size_t main_size; - size_t section1_size; - size_t section2_size; - size_t section3_size; + size_t section1_offset; + size_t section2_offset; + size_t section3_offset; + size_t extra_section_offset; + size_t sounds_offset; + + /* map info */ + off_t map_header_offset; + off_t map_num; + off_t map_offset; + char map_name[255]; /* stream info */ uint32_t header_id; @@ -69,51 +87,223 @@ typedef struct { } ubi_sb_header; +static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *streamFile); static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile); static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile); - /* .SBx - banks from Ubisoft's sound engine ("DARE" / "UbiSound Driver") games in ~2000-2008 */ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *streamData = NULL; - off_t start_offset; - int loop_flag = 0; - ubi_sb_header sb = {0}; + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL; + ubi_sb_header sb = { 0 }; + int ok; /* check extension (number represents the platform, see later) */ - if ( !check_extensions(streamFile,"sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7") ) + if (!check_extensions(streamFile, "sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7")) goto fail; - /* .sb0 (sound bank) is a small multisong format (loaded in memory?) that contains SFX data * but can also reference .ss0/ls0 (sound stream) external files for longer streams. * A companion .sp0 (sound project) describes files and if it uses BANKs (.sb0) or MAPs (.sm0). */ + /* sigh... PSP hijacks not one but *two* platform indexes */ + /* please add any PSP game versions under sb4 and sb5 sections so we can properly identify platform */ + sb.version = read_32bitLE(0x00, streamFile); + + if (check_extensions(streamFile, "sb0")) { + sb.platform = UBI_PC; + } else if (check_extensions(streamFile, "sb1")) { + sb.platform = UBI_PS2; + } else if (check_extensions(streamFile, "sb2")) { + sb.platform = UBI_XBOX; + } else if (check_extensions(streamFile, "sb3")) { + sb.platform = UBI_GC; + } else if (check_extensions(streamFile, "sb4")) { + switch (sb.version) { + case 0x0012000C: /* Prince of Persia: Revelations (2005)(PSP) */ + sb.platform = UBI_PSP; + break; + default: + sb.platform = UBI_X360; + break; + } + } else if (check_extensions(streamFile, "sb5")) { + switch (sb.version) { + case 0x00180005: /* Prince of Persia: Rival Swords (2007)(PSP) */ + case 0x00180006: /* Rainbow Six Vegas (2007)(PSP) */ + sb.platform = UBI_PSP; + break; + default: + sb.platform = UBI_3DS; + break; + } + } else if (check_extensions(streamFile, "sb6")) { + sb.platform = UBI_PS3; + } else if (check_extensions(streamFile, "sb7")) { + sb.platform = UBI_WII; + } else { + goto fail; + } + + sb.big_endian = (sb.platform == UBI_GC || + sb.platform == UBI_PS3 || + sb.platform == UBI_X360 || + sb.platform == UBI_WII); + if (sb.big_endian) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + /* file layout is: base header, section1, section2, extra section, section3, data (all except base header can be null) */ + + sb.version = read_32bit(0x00, streamFile); /* 16b+16b major/minor version */ + sb.section1_num = read_32bit(0x04, streamFile); /* group headers? */ + sb.section2_num = read_32bit(0x08, streamFile); /* streams headers (internal or external) */ + sb.section3_num = read_32bit(0x0c, streamFile); /* internal streams table */ + sb.extra_size = read_32bit(0x10, streamFile); /* extra table, unknown (config for non-audio types) except with DSP = coefs */ + sb.flag1 = read_32bit(0x14, streamFile); /* unknown, usually -1 but can be others (0/1/2/etc) */ + sb.flag2 = read_32bit(0x18, streamFile); /* unknown, usually -1 but can be others */ + + ok = config_sb_header_version(&sb, streamFile); + if (!ok) { + VGM_LOG("UBI SB: unknown SB version+platform\n"); + goto fail; + } + + sb.section1_offset = 0x1c; + sb.section2_offset = sb.section1_offset + sb.section1_entry_size * sb.section1_num; + sb.extra_section_offset = sb.section2_offset + sb.section2_entry_size * sb.section2_num; + sb.section3_offset = sb.extra_section_offset + sb.extra_size; + sb.sounds_offset = sb.section3_offset + sb.section3_entry_size * sb.section3_num; + sb.is_map = 0; + /* main parse */ - if ( !parse_sb_header(&sb, streamFile) ) + if (!parse_sb_header(&sb, streamFile)) goto fail; + return init_vgmstream_ubi_sb_main(&sb, streamFile); + +fail: + return NULL; +} + +/* .SMx - essentially a set of SBx files, one per map, compiled into one file */ +VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL; + ubi_sb_header sb = { 0 }; + int ok, i; + + /* check extension (number represents the platform, see later) */ + if (!check_extensions(streamFile, "sm0,sm1,sm2,sm3,sm4,sm5,sm6,sm7")) + goto fail; + + /* sigh... PSP hijacks not one but *two* platform indexes */ + /* please add any PSP game versions under sb4 and sb5 sections so we can properly identify platform */ + sb.version = read_32bitLE(0x00, streamFile); + + if (check_extensions(streamFile, "sm0")) { + sb.platform = UBI_PC; + } else if (check_extensions(streamFile, "sm1")) { + sb.platform = UBI_PS2; + } else if (check_extensions(streamFile, "sm2")) { + sb.platform = UBI_XBOX; + } else if (check_extensions(streamFile, "sm3")) { + sb.platform = UBI_GC; + } else if (check_extensions(streamFile, "sm4")) { + sb.platform = UBI_X360; + } else if (check_extensions(streamFile, "sm5")) { + sb.platform = UBI_3DS; + } else if (check_extensions(streamFile, "sm6")) { + sb.platform = UBI_PS3; + } else if (check_extensions(streamFile, "sm7")) { + sb.platform = UBI_WII; + } else { + goto fail; + } + + sb.big_endian = (sb.platform == UBI_GC || + sb.platform == UBI_PS3 || + sb.platform == UBI_X360 || + sb.platform == UBI_WII); + if (sb.big_endian) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + sb.is_map = 1; + sb.version = read_32bit(0x00, streamFile); + sb.map_header_offset = read_32bit(0x04, streamFile); + sb.map_num = read_32bit(0x08, streamFile); + + ok = config_sb_header_version(&sb, streamFile); + if (!ok) { + VGM_LOG("UBI SB: unknown SB version+platform\n"); + goto fail; + } + + for (i = 0; i < sb.map_num; i++) { + /* basic layout: + * 0x00 - map type + * 0x04 - zero + * 0x08 - map section offset + * 0x0c - map section size + * 0x10 - map name (20 byte) */ + off_t offset = sb.map_header_offset + i * sb.map_header_entry_size; + sb.map_offset = read_32bit(offset + 0x08, streamFile); + read_string(sb.map_name, sizeof(sb.map_name), offset + 0x10, streamFile); + + /* parse map section header */ + sb.section1_offset = read_32bit(sb.map_offset + sb.map_sec1_pointer_offset, streamFile) + sb.map_offset; + sb.section1_num = read_32bit(sb.map_offset + sb.map_sec1_num_offset, streamFile); + sb.section2_offset = read_32bit(sb.map_offset + sb.map_sec2_pointer_offset, streamFile) + sb.map_offset; + sb.section2_num = read_32bit(sb.map_offset + sb.map_sec2_num_offset, streamFile); + sb.extra_section_offset = read_32bit(sb.map_offset + sb.map_extra_pointer_offset, streamFile) + sb.map_offset; + sb.extra_size = read_32bit(sb.map_offset + sb.map_extra_size_offset, streamFile); + sb.section3_offset = read_32bit(sb.map_offset + sb.map_sec3_pointer_offset, streamFile) + sb.map_offset; + sb.section3_num = read_32bit(sb.map_offset + sb.map_sec3_num_offset, streamFile); + + if (!parse_sb_header(&sb, streamFile)) + goto fail; + } + + return init_vgmstream_ubi_sb_main(&sb, streamFile); + +fail: + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *streamData = NULL; + off_t start_offset; + int loop_flag = 0; /* open external stream if needed */ - if (sb.autodetect_external) { /* works most of the time but could give false positives */ - VGM_LOG("UBI SB: autodetecting external stream '%s'\n", sb.stream_name); + if (sb->autodetect_external) { /* works most of the time but could give false positives */ + VGM_LOG("UBI SB: autodetecting external stream '%s'\n", sb->stream_name); - streamData = open_streamfile_by_filename(streamFile,sb.stream_name); + streamData = open_streamfile_by_filename(streamFile,sb->stream_name); if (!streamData) { streamData = streamFile; /* assume internal */ - if (sb.stream_size > get_streamfile_size(streamData)) { + if (sb->stream_size > get_streamfile_size(streamData)) { VGM_LOG("UBI SB: expected external stream\n"); goto fail; } } else { - sb.is_external = 1; + sb->is_external = 1; } } - else if (sb.is_external) { - streamData = open_streamfile_by_filename(streamFile,sb.stream_name); + else if (sb->is_external) { + streamData = open_streamfile_by_filename(streamFile,sb->stream_name); if (!streamData) { - VGM_LOG("UBI SB: external stream '%s' not found\n", sb.stream_name); + VGM_LOG("UBI SB: external stream '%s' not found\n", sb->stream_name); goto fail; } } @@ -122,61 +312,63 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { } /* final offset */ - if (sb.is_external) { - start_offset = sb.stream_offset; + if (sb->is_external) { + start_offset = sb->stream_offset; } else { - start_offset = sb.main_size + sb.section1_size + sb.section2_size + sb.extra_size + sb.section3_size; - start_offset += sb.stream_offset; + if (sb->is_map) { + start_offset = sb->stream_offset; + } else { + start_offset = sb->sounds_offset + sb->stream_offset; + } } - //;VGM_LOG("start offset=%lx, external=%i\n", start_offset, sb.is_external); + //;VGM_LOG("start offset=%lx, external=%i\n", start_offset, sb->is_external); /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(sb.channels,loop_flag); + vgmstream = allocate_vgmstream(sb->channels,loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = sb.sample_rate; - vgmstream->num_streams = sb.total_streams; - vgmstream->stream_size = sb.stream_size; + vgmstream->sample_rate = sb->sample_rate; + vgmstream->num_streams = sb->total_streams; + vgmstream->stream_size = sb->stream_size; vgmstream->meta_type = meta_UBI_SB; - switch(sb.codec) { - case UBI_ADPCM: { + switch(sb->codec) { + case UBI_ADPCM: vgmstream->coding_type = coding_UBI_IMA; vgmstream->layout_type = layout_none; - vgmstream->num_samples = ubi_ima_bytes_to_samples(sb.stream_size, sb.channels, streamData, start_offset); + vgmstream->num_samples = ubi_ima_bytes_to_samples(sb->stream_size, sb->channels, streamData, start_offset); break; - } case RAW_PCM: vgmstream->coding_type = coding_PCM16LE; /* always LE even on Wii */ vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; - vgmstream->num_samples = pcm_bytes_to_samples(sb.stream_size, sb.channels, 16); + vgmstream->num_samples = pcm_bytes_to_samples(sb->stream_size, sb->channels, 16); break; 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 */ - vgmstream->num_samples = ps_bytes_to_samples(sb.stream_size, sb.channels) ; + vgmstream->interleave_block_size = (sb->stream_type == 0x00) ? sb->stream_size / sb->channels : 0x10; /* TODO: needs testing */ + vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels) ; break; case RAW_XBOX: vgmstream->coding_type = coding_XBOX_IMA; vgmstream->layout_type = layout_none; - vgmstream->num_samples = xbox_ima_bytes_to_samples(sb.stream_size, sb.channels); + vgmstream->num_samples = xbox_ima_bytes_to_samples(sb->stream_size, sb->channels); break; case RAW_DSP: vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = sb.stream_size / sb.channels; - vgmstream->num_samples = dsp_bytes_to_samples(sb.stream_size, sb.channels); + vgmstream->interleave_block_size = sb->stream_size / sb->channels; + vgmstream->num_samples = dsp_bytes_to_samples(sb->stream_size, sb->channels); { - off_t coefs_offset = sb.main_size + sb.section1_size + sb.section2_size + sb.extra_offset; + off_t coefs_offset = sb->extra_section_offset + sb->extra_offset; coefs_offset += 0x10; /* entry size is 0x40 (first/last 0x10 = unknown), per channel */ dsp_read_coefs_be(vgmstream,streamFile,coefs_offset, 0x40); @@ -187,13 +379,13 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { /* 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; + sb->stream_size -= 0x30; } vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = sb.stream_size / sb.channels; - vgmstream->num_samples = ps_bytes_to_samples(sb.stream_size, sb.channels); + vgmstream->interleave_block_size = sb->stream_size / sb->channels; + vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels); break; @@ -204,20 +396,20 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { /* skip weird value (3, 4) in Brothers in Arms: D-Day (PSP) */ if (read_32bitBE(start_offset+0x04,streamData) == 0x52494646) { start_offset += 0x04; - sb.stream_size -= 0x04; + sb->stream_size -= 0x04; } - ffmpeg_data = init_ffmpeg_offset(streamData, start_offset, sb.stream_size); + ffmpeg_data = init_ffmpeg_offset(streamData, start_offset, sb->stream_size); if ( !ffmpeg_data ) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - if (sb.stream_samples == 0) /* sometimes not known */ - sb.stream_samples = ffmpeg_data->totalSamples; - vgmstream->num_samples = sb.stream_samples; - if (sb.stream_samples != ffmpeg_data->totalSamples) { - VGM_LOG("UBI SB: header samples differ (%i vs %i)\n", sb.stream_samples, (size_t)ffmpeg_data->totalSamples); + if (sb->stream_samples == 0) /* sometimes not known */ + sb->stream_samples = ffmpeg_data->totalSamples; + vgmstream->num_samples = sb->stream_samples; + if (sb->stream_samples != ffmpeg_data->totalSamples) { + VGM_LOG("UBI SB: header samples differ (%i vs %i)\n", sb->stream_samples, (size_t)ffmpeg_data->totalSamples); goto fail; } @@ -230,14 +422,14 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { uint8_t buf[0x100]; int32_t bytes, block_size, encoder_delay, joint_stereo; - block_size = 0x98 * sb.channels; + block_size = 0x98 * sb->channels; joint_stereo = 0; encoder_delay = 0x00; /* TODO: this is incorrect */ - bytes = ffmpeg_make_riff_atrac3(buf, 0x100, sb.stream_samples, sb.stream_size, sb.channels, sb.sample_rate, block_size, joint_stereo, encoder_delay); - vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb.stream_size); + bytes = ffmpeg_make_riff_atrac3(buf, 0x100, sb->stream_samples, sb->stream_size, sb->channels, sb->sample_rate, block_size, joint_stereo, encoder_delay); + vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); if (!vgmstream->codec_data) goto fail; - vgmstream->num_samples = sb.stream_samples; + vgmstream->num_samples = sb->stream_samples; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; break; @@ -265,7 +457,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { header_offset = start_offset; data_size = num_frames * frame_size; - start_offset += sb.stream_size - data_size; + start_offset += sb->stream_size - data_size; bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, streamData, 1); @@ -274,7 +466,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - vgmstream->num_samples = sb.stream_samples; + vgmstream->num_samples = sb->stream_samples; vgmstream->stream_size = data_size; break; } @@ -285,33 +477,33 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { size_t bytes, chunk_size; off_t header_offset; - VGM_ASSERT(sb.is_external, "Ubi SB: Raw XMA used for external sound\n"); + VGM_ASSERT(sb->is_external, "Ubi SB: Raw XMA used for external sound\n"); /* Get XMA header from extra section */ chunk_size = 0x20; - header_offset = sb.main_size + sb.section1_size + sb.section2_size + sb.xma_header_offset; - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb.stream_size, streamFile, 1); + header_offset = sb->extra_section_offset + sb->xma_header_offset; + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamFile, 1); - ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb.stream_size); + ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); if (!ffmpeg_data) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - vgmstream->num_samples = sb.stream_samples; + vgmstream->num_samples = sb->stream_samples; break; } case FMT_OGG: { ffmpeg_codec_data *ffmpeg_data; - ffmpeg_data = init_ffmpeg_offset(streamData, start_offset, sb.stream_size); + ffmpeg_data = init_ffmpeg_offset(streamData, start_offset, sb->stream_size); if ( !ffmpeg_data ) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - vgmstream->num_samples = sb.stream_samples; /* ffmpeg_data->totalSamples */ - VGM_ASSERT(sb.stream_samples != ffmpeg_data->totalSamples, "UBI SB: header samples differ\n"); + vgmstream->num_samples = sb->stream_samples; /* ffmpeg_data->totalSamples */ + VGM_ASSERT(sb->stream_samples != ffmpeg_data->totalSamples, "UBI SB: header samples differ\n"); break; } @@ -321,8 +513,8 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { goto fail; } - if (sb.has_internal_names || sb.is_external) { - strcpy(vgmstream->stream_name, sb.stream_name); + if (sb->has_internal_names || sb->is_external) { + strcpy(vgmstream->stream_name, sb->stream_name); } @@ -330,11 +522,11 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { if ( !vgmstream_open_stream(vgmstream, streamData, start_offset) ) goto fail; - if (sb.is_external && streamData) close_streamfile(streamData); + if (sb->is_external && streamData) close_streamfile(streamData); return vgmstream; fail: - if (sb.is_external && streamData) close_streamfile(streamData); + if (sb->is_external && streamData) close_streamfile(streamData); close_vgmstream(vgmstream); return NULL; @@ -344,54 +536,9 @@ fail: static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; - int i, j, k, ok, current_type = -1, current_id = -1; + int i, j, k, current_type = -1, current_id = -1; int target_stream = streamFile->stream_index; - if (target_stream == 0) target_stream = 1; - - /* sigh... PSP hijacks not one but *two* platform indexes */ - /* please add any PSP game versions under sb4 and sb5 sections so we can properly identify platform */ - sb->version = read_32bitLE(0x00, streamFile); - - if (check_extensions(streamFile, "sb0")) { - sb->platform = UBI_PC; - } else if (check_extensions(streamFile, "sb1")) { - sb->platform = UBI_PS2; - } else if (check_extensions(streamFile, "sb2")) { - sb->platform = UBI_XBOX; - } else if (check_extensions(streamFile, "sb3")) { - sb->platform = UBI_GC; - } else if (check_extensions(streamFile, "sb4")) { - switch (sb->version) { - case 0x0012000C: /* Prince of Persia: Revelations (2005)(PSP) */ - sb->platform = UBI_PSP; - break; - default: - sb->platform = UBI_X360; - break; - } - } else if (check_extensions(streamFile, "sb5")) { - switch (sb->version) { - case 0x00180005: /* Prince of Persia: Rival Swords (2007)(PSP) */ - case 0x00180006: /* Rainbow Six Vegas (2007)(PSP) */ - sb->platform = UBI_PSP; - break; - default: - sb->platform = UBI_3DS; - break; - } - } else if (check_extensions(streamFile, "sb6")) { - sb->platform = UBI_PS3; - } else if (check_extensions(streamFile, "sb7")) { - sb->platform = UBI_WII; - } else { - goto fail; - } - - sb->big_endian = (sb->platform == UBI_GC || - sb->platform == UBI_PS3 || - sb->platform == UBI_X360 || - sb->platform == UBI_WII); if (sb->big_endian) { read_32bit = read_32bitBE; read_16bit = read_16bitBE; @@ -400,51 +547,11 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { read_16bit = read_16bitLE; } - /* file layout is: base header, section1, section2, extra section, section3, data (all except base header can be null) */ - - sb->version = read_32bit(0x00, streamFile); /* 16b+16b major/minor version */ - sb->section1_num = read_32bit(0x04, streamFile); /* group headers? */ - sb->section2_num = read_32bit(0x08, streamFile); /* streams headers (internal or external) */ - sb->section3_num = read_32bit(0x0c, streamFile); /* internal streams table */ - sb->extra_size = read_32bit(0x10, streamFile); /* extra table, unknown (config for non-audio types) except with DSP = coefs */ - sb->flag1 = read_32bit(0x14, streamFile); /* unknown, usually -1 but can be others (0/1/2/etc) */ - sb->flag2 = read_32bit(0x18, streamFile); /* unknown, usually -1 but can be others */ - - ok = config_sb_header_version(sb, streamFile); - if (!ok) { - VGM_LOG("UBI SB: unknown SB version+platform\n"); - goto fail; - } - - sb->main_size = 0x1c; - sb->section1_size = sb->section1_entry_size * sb->section1_num; - sb->section2_size = sb->section2_entry_size * sb->section2_num; - - /* detect if this a maps bank */ - if (read_32bit(sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size, streamFile) == 0xFFFFFFFF) { - sb->is_map = 1; - } - - if (sb->is_map) { - sb->section3_size = 0; - off_t sec3_offset = sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size; - - for (i = 0; i < sb->section3_num; i++) { - off_t offset = sec3_offset + 0x14 * i; - off_t table_offset = read_32bit(offset + 0x04, streamFile) + sec3_offset; - off_t table_num = read_32bit(offset + 0x08, streamFile); - off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sec3_offset; - off_t table2_num = read_32bit(offset + 0x10, streamFile); - - sb->section3_size += 0x14 + table_num * 0x08 + table2_num * 0x10; - } - } else { - sb->section3_size = sb->section3_entry_size * sb->section3_num; - } + if (target_stream == 0) target_stream = 1; /* find target stream info in section2 */ for (i = 0; i < sb->section2_num; i++) { - off_t offset = sb->main_size + sb->section1_size + sb->section2_entry_size*i; + off_t offset = sb->section2_offset + sb->section2_entry_size*i; /* ignore non-audio entry (other types seem to have config data) */ if (read_32bit(offset + 0x04, streamFile) != 0x01) @@ -459,7 +566,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { if (current_type == -1) current_type = type; if (current_id == -1) /* use first ID in section3 */ - current_id = read_32bit(sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size + 0x00, streamFile); + current_id = read_32bit(sb->section3_offset + 0x00, streamFile); if (sb->external_flag_offset) { current_is_external = read_32bit(offset + sb->external_flag_offset, streamFile); @@ -518,7 +625,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { read_string(sb->stream_name, sb->stream_name_size, offset + sb->stream_name_offset, streamFile); } else { sb->stream_name_offset = read_32bit(offset + sb->extra_name_offset, streamFile); - read_string(sb->stream_name, sb->stream_name_size, sb->main_size + sb->section1_size + sb->section2_size + sb->stream_name_offset, streamFile); + read_string(sb->stream_name, sb->stream_name_size, sb->extra_section_offset + sb->stream_name_offset, streamFile); } /* not always set and must be derived */ @@ -643,30 +750,21 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { goto fail; } - if (sb->codec == RAW_XMA1) { - if (!sb->is_external) { - /* this field is only seen in X360 games, points at XMA1 header in extra section */ - sb->xma_header_offset = read_32bit(sb->header_offset + sb->xma_pointer_offset, streamFile); - } - } - /* uncommon but possible */ //VGM_ASSERT(sb->is_external && sb->section3_num != 0, "UBI SS: mixed external and internal streams\n"); /* seems that can be safely ignored */ //VGM_ASSERT(sb->is_external && sb->stream_id_offset && sb->stream_id > 0, "UBI SB: unexpected external stream with stream id\n"); - /* section 3: substreams within the file, adjust stream offset (rarely used but table is always present) */ + /* section 3: internal stream info */ if (!sb->is_external) { - off_t sec3_offset = sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size; - if (sb->is_map) { - /* maps banks store internal sounds offsets in a separate table, find the matching entry */ + /* maps store internal sounds offsets in a separate table, find the matching entry */ for (i = 0; i < sb->section3_num; i++) { - off_t offset = sec3_offset + 0x14 * i; - off_t table_offset = read_32bit(offset + 0x04, streamFile) + sec3_offset; + off_t offset = sb->section3_offset + 0x14 * i; + off_t table_offset = read_32bit(offset + 0x04, streamFile) + sb->section3_offset; off_t table_num = read_32bit(offset + 0x08, streamFile); - off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sec3_offset; + off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sb->section3_offset; off_t table2_num = read_32bit(offset + 0x10, streamFile); for (j = 0; j < table_num; j++) { @@ -680,10 +778,15 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->stream_offset = read_32bit(table_offset + 0x08 * j + 0x04, streamFile); for (k = 0; k < table2_num; k++) { + /* entry layout: + * 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); - if (id == sb->stream_id) - break; - sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x08, streamFile); + if (id == sb->stream_id) { + sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x0c, streamFile); + } } break; } @@ -693,8 +796,10 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { break; } } else if ((sb->stream_id_offset || sb->has_rotating_ids) && sb->section3_num > 1) { + /* internal sounds are split into groups based on their type with their offsets being relative to group start + * this table contains sizes of each group, adjust offset based on group ID of our sound */ for (i = 0; i < sb->section3_num; i++) { - off_t offset = sec3_offset + sb->section3_entry_size * i; + off_t offset = sb->section3_offset + sb->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->stream_id) @@ -735,7 +840,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->stream_name_size = 0x24; /* maybe 0x28 or 0x20 for some but ok enough (null terminated) */ #if 0 - /* Splinter Cell (2002)(PC) */ + /* Splinter Cell (2002)(PC)-map */ if (sb->version == 0x00000007 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x58; sb->section2_entry_size = 0x80; @@ -754,7 +859,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) /* Splinter Cell has all the common values placed 0x04 bytes earlier */ #endif - /* Prince of Persia: Sands of Time (2003)(PC) */ + /* Prince of Persia: Sands of Time (2003)(PC)-bank */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PC) || /* (not sure if exists, just in case) */ (sb->version == 0x000A0004 && sb->platform == UBI_PC)) { /* main game */ sb->section1_entry_size = 0x64; @@ -772,7 +877,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Sands of Time (2003)(PS2) */ + /* Prince of Persia: Sands of Time (2003)(PS2)-bank */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PS2) || /* Prince of Persia 1 port */ (sb->version == 0x000A0004 && sb->platform == UBI_PS2)) { /* main game */ sb->section1_entry_size = 0x48; @@ -788,7 +893,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Sands of Time (2003)(Xbox) */ + /* Prince of Persia: Sands of Time (2003)(Xbox)-bank */ if ((sb->version == 0x000A0002 && sb->platform == UBI_XBOX) || /* Prince of Persia 1 port */ (sb->version == 0x000A0004 && sb->platform == UBI_XBOX)) { /* main game */ sb->section1_entry_size = 0x64; @@ -806,7 +911,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Tom Clancy's Rainbow Six 3 (2003)(PS2) */ + /* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank */ if (sb->version == 0x000A0007 && sb->platform == UBI_PS2) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x6c; @@ -821,7 +926,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Sands of Time (2003)(GC) */ + /* Prince of Persia: Sands of Time (2003)(GC)-bank */ if ((sb->version == 0x000A0002 && sb->platform == UBI_GC) || /* Prince of Persia 1 port */ (sb->version == 0x000A0004 && sb->platform == UBI_GC)) { /* main game */ sb->section1_entry_size = 0x64; @@ -838,7 +943,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Myst IV Demo (2004)(PC) (final game is different) */ + /* Myst IV Demo (2004)(PC) (final game is different)-bank */ if (sb->version == 0x00100000 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0xa4; @@ -854,7 +959,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Warrior Within (2004)(PC) */ + /* Prince of Persia: Warrior Within (2004)(PC)-bank */ if (sb->version == 0x00120009 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x6c; sb->section2_entry_size = 0x84; @@ -870,7 +975,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Warrior Within (2004)(PS2) */ + /* Prince of Persia: Warrior Within (2004)(PS2)-bank */ if (sb->version == 0x00120009 && sb->platform == UBI_PS2) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x6c; @@ -885,7 +990,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Warrior Within (2004)(Xbox) */ + /* Prince of Persia: Warrior Within (2004)(Xbox)-bank */ if (sb->version == 0x00120009 && sb->platform == UBI_XBOX) { sb->section1_entry_size = 0x6c; sb->section2_entry_size = 0x90; @@ -901,7 +1006,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Warrior Within (2004)(GC) */ + /* Prince of Persia: Warrior Within (2004)(GC)-bank */ if (sb->version == 0x00120009 && sb->platform == UBI_GC) { sb->section1_entry_size = 0x6c; sb->section2_entry_size = 0x78; @@ -925,7 +1030,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) } } - /* Prince of Persia: Revelations (2005)(PSP) */ + /* Prince of Persia: Revelations (2005)(PSP)-bank */ if (sb->version == 0x0012000C && sb->platform == UBI_PSP && !is_biadd_psp) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x84; @@ -941,7 +1046,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Brothers in Arms - D-Day (2006)(PSP) */ + /* Brothers in Arms - D-Day (2006)(PSP)-bank */ if (sb->version == 0x0012000C && sb->platform == UBI_PSP && is_biadd_psp) { sb->section1_entry_size = 0x80; sb->section2_entry_size = 0x94; @@ -958,7 +1063,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: The Two Thrones (2005)(PC) */ + /* Prince of Persia: The Two Thrones (2005)(PC)-bank */ if (sb->version == 0x00150000 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x78; @@ -975,7 +1080,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: The Two Thrones (2005)(PS2) */ + /* Prince of Persia: The Two Thrones (2005)(PS2)-bank */ if (sb->version == 0x00150000 && sb->platform == UBI_PS2) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x5c; @@ -991,7 +1096,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: The Two Thrones (2005)(Xbox) */ + /* Prince of Persia: The Two Thrones (2005)(Xbox)-bank */ if (sb->version == 0x00150000 && sb->platform == UBI_XBOX) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x58; @@ -1009,7 +1114,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: The Two Thrones (2005)(GC) */ + /* Prince of Persia: The Two Thrones (2005)(GC)-bank */ if (sb->version == 0x00150000 && sb->platform == UBI_GC) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x6c; @@ -1024,11 +1129,21 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Splinter Cell: Chaos Theory (2005)(PC) */ + /* Splinter Cell: Chaos Theory (2005)(PC)-map */ if (sb->version == 0x00120012 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x60; + sb->map_header_entry_size = 0x24; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x14; + sb->map_sec3_num_offset = 0x18; + sb->map_extra_pointer_offset = 0x1c; + sb->map_extra_size_offset = 0x20; + sb->external_flag_offset = 0x24; sb->num_samples_offset = 0x30; sb->sample_rate_offset = 0x44; @@ -1040,11 +1155,21 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Splinter Cell: Chaos Theory (2005)(Xbox) */ + /* Splinter Cell: Chaos Theory (2005)(Xbox)-map */ if (sb->version == 0x00120012 && sb->platform == UBI_XBOX) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x4c; + sb->map_header_entry_size = 0x24; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x14; + sb->map_sec3_num_offset = 0x18; + sb->map_extra_pointer_offset = 0x1c; + sb->map_extra_size_offset = 0x20; + sb->external_flag_offset = 0; sb->num_samples_offset = 0x18; sb->stream_id_offset = 0; @@ -1059,7 +1184,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) } #if 0 - /* Far cry: Instincts - Evolution (2006)(Xbox) */ + /* Far cry: Instincts - Evolution (2006)(Xbox)-bank */ if (sb->version == 0x00170000 && sb->platform == UBI_XBOX) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x6c; @@ -1076,7 +1201,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) } #endif - /* Red Steel (2006)(Wii) */ + /* Red Steel (2006)(Wii)-bank */ if (sb->version == 0x00180006 && sb->platform == UBI_WII) { /* same as 0x00150000 */ sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x6c; @@ -1091,11 +1216,21 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Splinter Cell: Double Agent (2006)(PC) */ + /* Splinter Cell: Double Agent (2006)(PC)-map */ if (sb->version == 0x00180006 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x7c; + sb->map_header_entry_size = 0x24; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x1c; + sb->map_sec3_num_offset = 0x20; + sb->map_extra_pointer_offset = 0x14; + sb->map_extra_size_offset = 0x28; + sb->external_flag_offset = 0x2c; sb->stream_id_offset = 0x34; sb->channels_offset = 0x5c; @@ -1109,11 +1244,21 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Splinter Cell: Double Agent (2006)(X360) */ + /* Splinter Cell: Double Agent (2006)(X360)-map */ if (sb->version == 0x00180006 && sb->platform == UBI_X360) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x78; + sb->map_header_entry_size = 0x24; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x1c; + sb->map_sec3_num_offset = 0x20; + sb->map_extra_pointer_offset = 0x14; + sb->map_extra_size_offset = 0x28; + sb->external_flag_offset = 0x2c; sb->stream_id_offset = 0x30; sb->samples_flag_offset = 0x34; @@ -1129,11 +1274,21 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Splinter Cell: Double Agent (2006)(Xbox) */ + /* Splinter Cell: Double Agent (2006)(Xbox)-map */ if (sb->version == 0x00160002 && sb->platform == UBI_XBOX) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x58; + sb->map_header_entry_size = 0x24; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x1c; + sb->map_sec3_num_offset = 0x20; + sb->map_extra_pointer_offset = 0x14; + sb->map_extra_size_offset = 0x28; + sb->external_flag_offset = 0; sb->num_samples_offset = 0x28; sb->stream_id_offset = 0; @@ -1147,7 +1302,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Prince of Persia: Rival Swords (2007)(PSP) */ + /* Prince of Persia: Rival Swords (2007)(PSP)-bank */ if (sb->version == 0x00180005 && sb->platform == UBI_PSP) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x54; @@ -1164,7 +1319,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) } #if 0 - /* Rainbow Six Vegas (2007)(PSP) */ + /* Rainbow Six Vegas (2007)(PSP)-bank */ if (sb->version == 0x00180006 && sb->platform == UBI_PSP) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x54; @@ -1193,7 +1348,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) */ #endif - /* Prince of Persia: Rival Swords (2007)(Wii) */ + /* Prince of Persia: Rival Swords (2007)(Wii)-bank */ if (sb->version == 0x00190003 && sb->platform == UBI_WII) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x70; @@ -1208,7 +1363,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* TMNT (2007)(PC) */ + /* TMNT (2007)(PC)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x74; @@ -1224,7 +1379,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* TMNT (2007)(PS2) */ + /* TMNT (2007)(PS2)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_PS2) { sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x5c; @@ -1240,7 +1395,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* TMNT (2007)(GC) */ + /* TMNT (2007)(GC)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_GC) { /* same as 0x00190003 */ sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x6c; @@ -1255,7 +1410,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* TMNT (2007)(X360) */ + /* TMNT (2007)(X360)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_X360) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x70; @@ -1274,7 +1429,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Surf's Up (2007)(PC) */ + /* Surf's Up (2007)(PC)-bank */ if (sb->version == 0x00190005 && sb->platform == UBI_PC) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x74; @@ -1290,12 +1445,22 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Surf's Up (2007)(PS3) */ - /* Splinter Cell: Double Agent (2007)(PS3) */ + /* Surf's Up (2007)(PS3)-bank */ + /* Splinter Cell: Double Agent (2007)(PS3)-map */ if (sb->version == 0x00190005 && sb->platform == UBI_PS3) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x70; + sb->map_header_entry_size = 0x24; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x1c; + sb->map_sec3_num_offset = 0x20; + sb->map_extra_pointer_offset = 0x14; + sb->map_extra_size_offset = 0x28; + sb->external_flag_offset = 0x28; sb->stream_id_offset = 0x2c; sb->channels_offset = 0x3c; @@ -1308,7 +1473,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } - /* Surf's Up (2007)(X360) */ + /* Surf's Up (2007)(X360)-bank */ if (sb->version == 0x00190005 && sb->platform == UBI_X360) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x70; diff --git a/src/vgmstream.c b/src/vgmstream.c index 47aba049..fe8f9191 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -371,6 +371,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_pc_ast, init_vgmstream_naac, init_vgmstream_ubi_sb, + init_vgmstream_ubi_sm, init_vgmstream_ezw, init_vgmstream_vxn, init_vgmstream_ea_snr_sns, From 3c8f17a977dde8e4986d1af01a2493e02aaddf6a Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 23 Dec 2018 04:15:50 +0300 Subject: [PATCH 4/8] Ubi SB: Added Splinter Cell: Essentials (PSP) --- src/meta/ubi_sb.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index b5199239..9c868afb 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -214,7 +214,14 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { } else if (check_extensions(streamFile, "sm3")) { sb.platform = UBI_GC; } else if (check_extensions(streamFile, "sm4")) { - sb.platform = UBI_X360; + switch (sb.version) { + case 0x0012000C: /* Splinter Cell: Essentials (2006)(PSP) */ + sb.platform = UBI_PSP; + break; + default: + sb.platform = UBI_X360; + break; + } } else if (check_extensions(streamFile, "sm5")) { sb.platform = UBI_3DS; } else if (check_extensions(streamFile, "sm6")) { @@ -243,8 +250,8 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.map_num = read_32bit(0x08, streamFile); ok = config_sb_header_version(&sb, streamFile); - if (!ok) { - VGM_LOG("UBI SB: unknown SB version+platform\n"); + if (!ok || sb.map_header_entry_size == 0) { + VGM_LOG("UBI SB: unknown SM version+platform\n"); goto fail; } @@ -479,7 +486,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *str VGM_ASSERT(sb->is_external, "Ubi SB: Raw XMA used for external sound\n"); - /* Get XMA header from extra section */ + /* get XMA header from extra section */ chunk_size = 0x20; header_offset = sb->extra_section_offset + sb->xma_header_offset; bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamFile, 1); @@ -678,7 +685,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { break; case UBI_PSP: - if (check_extensions(streamFile, "sb4")) { + if (check_extensions(streamFile, "sb4,sm4")) { sb->codec = FMT_VAG; } else { sb->codec = RAW_PSX; @@ -786,6 +793,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { uint32_t id = read_32bit(table2_offset + 0x10 * k + 0x00, streamFile); if (id == sb->stream_id) { sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x0c, streamFile); + break; } } break; @@ -1031,10 +1039,21 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) } /* Prince of Persia: Revelations (2005)(PSP)-bank */ + /* Splinter Cell: Essentials (2006)(PSP)-map */ if (sb->version == 0x0012000C && sb->platform == UBI_PSP && !is_biadd_psp) { sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x84; + sb->map_header_entry_size = 0x24; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x14; + sb->map_sec3_num_offset = 0x18; + sb->map_extra_pointer_offset = 0x1c; + sb->map_extra_size_offset = 0x20; + sb->external_flag_offset = 0x24; sb->num_samples_offset = 0x30; sb->sample_rate_offset = 0x44; From 518e47ca5f1935c96a297e48211b8b309d48f671 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 23 Dec 2018 07:20:36 +0300 Subject: [PATCH 5/8] Ubi SB: SMx fixes --- src/meta/ubi_sb.c | 65 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 9c868afb..597e99be 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -88,7 +88,7 @@ typedef struct { } ubi_sb_header; static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *streamFile); -static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile); +static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int target_stream); static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile); /* .SBx - banks from Ubisoft's sound engine ("DARE" / "UbiSound Driver") games in ~2000-2008 */ @@ -97,11 +97,14 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL; ubi_sb_header sb = { 0 }; int ok; + int target_stream = streamFile->stream_index; /* check extension (number represents the platform, see later) */ if (!check_extensions(streamFile, "sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7")) goto fail; + if (target_stream == 0) target_stream = 1; + /* .sb0 (sound bank) is a small multisong format (loaded in memory?) that contains SFX data * but can also reference .ss0/ls0 (sound stream) external files for longer streams. * A companion .sp0 (sound project) describes files and if it uses BANKs (.sb0) or MAPs (.sm0). */ @@ -181,7 +184,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { sb.is_map = 0; /* main parse */ - if (!parse_sb_header(&sb, streamFile)) + if (!parse_sb_header(&sb, streamFile, target_stream)) goto fail; return init_vgmstream_ubi_sb_main(&sb, streamFile); @@ -196,11 +199,14 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL; ubi_sb_header sb = { 0 }; int ok, i; + int target_stream = streamFile->stream_index; /* check extension (number represents the platform, see later) */ if (!check_extensions(streamFile, "sm0,sm1,sm2,sm3,sm4,sm5,sm6,sm7")) goto fail; + if (target_stream == 0) target_stream = 1; + /* sigh... PSP hijacks not one but *two* platform indexes */ /* please add any PSP game versions under sb4 and sb5 sections so we can properly identify platform */ sb.version = read_32bitLE(0x00, streamFile); @@ -276,10 +282,20 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.section3_offset = read_32bit(sb.map_offset + sb.map_sec3_pointer_offset, streamFile) + sb.map_offset; sb.section3_num = read_32bit(sb.map_offset + sb.map_sec3_num_offset, streamFile); - if (!parse_sb_header(&sb, streamFile)) + if (!parse_sb_header(&sb, streamFile, target_stream)) goto fail; } + if (sb.total_streams == 0) { + VGM_LOG("UBI SB: no streams\n"); + goto fail; + } + + if (target_stream < 0 || target_stream > sb.total_streams) { + VGM_LOG("UBI SB: wrong target stream (target=%i, total=%i)\n", target_stream, sb.total_streams); + goto fail; + } + return init_vgmstream_ubi_sb_main(&sb, streamFile); fail: @@ -540,11 +556,10 @@ fail: } -static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { +static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int target_stream) { int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; - int i, j, k, current_type = -1, current_id = -1; - int target_stream = streamFile->stream_index; + int i, j, k, current_type = -1, current_id = -1, bank_streams = 0, prev_streams; if (sb->big_endian) { read_32bit = read_32bitBE; @@ -554,7 +569,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { read_16bit = read_16bitLE; } - if (target_stream == 0) target_stream = 1; + prev_streams = sb->total_streams; /* find target stream info in section2 */ for (i = 0; i < sb->section2_num; i++) { @@ -595,6 +610,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { /* update streams (total_stream also doubles as current) */ sb->total_streams++; + bank_streams++; if (sb->total_streams != target_stream) continue; //;VGM_LOG("target at offset=%lx (size=%x)\n", offset, sb->section2_entry_size); @@ -651,13 +667,20 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->autodetect_external = 0; /* name outside extra table == is internal */ } } - if (sb->total_streams == 0) { - VGM_LOG("UBI SB: no streams\n"); - goto fail; - } - if (target_stream < 0 || target_stream > sb->total_streams || sb->total_streams < 1) { - VGM_LOG("UBI SB: wrong target stream (target=%i, total=%i)\n", target_stream, sb->total_streams); - goto fail; + + if (sb->is_map) { + if (bank_streams == 0 || target_stream <= prev_streams || target_stream > sb->total_streams) + return 1; /* Target stream is not in this map */ + } else { + if (sb->total_streams == 0) { + VGM_LOG("UBI SB: no streams\n"); + goto fail; + } + + if (target_stream < 0 || target_stream > sb->total_streams) { + VGM_LOG("UBI SB: wrong target stream (target=%i, total=%i)\n", target_stream, sb->total_streams); + goto fail; + } } if (!(sb->stream_id_offset || sb->has_rotating_ids || sb->is_map) && sb->section3_num > 1) { @@ -1044,7 +1067,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x84; - sb->map_header_entry_size = 0x24; + sb->map_header_entry_size = 0x34; sb->map_sec1_pointer_offset = 0x04; sb->map_sec1_num_offset = 0x08; sb->map_sec2_pointer_offset = 0x0c; @@ -1153,7 +1176,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x60; - sb->map_header_entry_size = 0x24; + sb->map_header_entry_size = 0x34; sb->map_sec1_pointer_offset = 0x04; sb->map_sec1_num_offset = 0x08; sb->map_sec2_pointer_offset = 0x0c; @@ -1179,7 +1202,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x4c; - sb->map_header_entry_size = 0x24; + sb->map_header_entry_size = 0x34; sb->map_sec1_pointer_offset = 0x04; sb->map_sec1_num_offset = 0x08; sb->map_sec2_pointer_offset = 0x0c; @@ -1240,7 +1263,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x7c; - sb->map_header_entry_size = 0x24; + sb->map_header_entry_size = 0x34; sb->map_sec1_pointer_offset = 0x04; sb->map_sec1_num_offset = 0x08; sb->map_sec2_pointer_offset = 0x0c; @@ -1268,7 +1291,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x78; - sb->map_header_entry_size = 0x24; + sb->map_header_entry_size = 0x34; sb->map_sec1_pointer_offset = 0x04; sb->map_sec1_num_offset = 0x08; sb->map_sec2_pointer_offset = 0x0c; @@ -1298,7 +1321,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section1_entry_size = 0x48; sb->section2_entry_size = 0x58; - sb->map_header_entry_size = 0x24; + sb->map_header_entry_size = 0x34; sb->map_sec1_pointer_offset = 0x04; sb->map_sec1_num_offset = 0x08; sb->map_sec2_pointer_offset = 0x0c; @@ -1470,7 +1493,7 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) sb->section1_entry_size = 0x68; sb->section2_entry_size = 0x70; - sb->map_header_entry_size = 0x24; + sb->map_header_entry_size = 0x34; sb->map_sec1_pointer_offset = 0x04; sb->map_sec1_num_offset = 0x08; sb->map_sec2_pointer_offset = 0x0c; From a9624f6f7195269b3500d76ffcb4d61e00a17e23 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 23 Dec 2018 11:03:50 +0300 Subject: [PATCH 6/8] Ubi SB: Restored XMA header offset parameter --- src/meta/ubi_sb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 597e99be..aecd1d43 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -780,6 +780,11 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe goto fail; } + if (sb->codec == RAW_XMA1) { + /* this field is only seen in X360 games, points at XMA1 header in extra section */ + sb->xma_header_offset = read_32bit(sb->header_offset + sb->xma_pointer_offset, streamFile); + } + /* uncommon but possible */ //VGM_ASSERT(sb->is_external && sb->section3_num != 0, "UBI SS: mixed external and internal streams\n"); From ab18f074bd17f7f6cf2dfb69a5ac6b6173c89497 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 23 Dec 2018 12:49:54 +0300 Subject: [PATCH 7/8] Ubi SB: Added Splinter Cell: Double Agent (GC) --- src/meta/ubi_sb.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index aecd1d43..e066ee19 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -1349,6 +1349,32 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) return 1; } + /* Splinter Cell: Double Agent (2006)(GC)-map */ + if (sb->version == 0x00160002 && sb->platform == UBI_GC) { + sb->section1_entry_size = 0x68; + sb->section2_entry_size = 0x6c; + + sb->map_header_entry_size = 0x34; + sb->map_sec1_pointer_offset = 0x04; + sb->map_sec1_num_offset = 0x08; + sb->map_sec2_pointer_offset = 0x0c; + sb->map_sec2_num_offset = 0x10; + sb->map_sec3_pointer_offset = 0x1c; + sb->map_sec3_num_offset = 0x20; + sb->map_extra_pointer_offset = 0x14; + sb->map_extra_size_offset = 0x28; + + sb->external_flag_offset = 0x28; + sb->stream_id_offset = 0x2c; + sb->num_samples_offset = 0x3c; + sb->sample_rate_offset = 0x50; + sb->channels_offset = 0x58; + sb->stream_type_offset = 0x5c; + sb->extra_name_offset = 0x60; + + return 1; + } + /* Prince of Persia: Rival Swords (2007)(PSP)-bank */ if (sb->version == 0x00180005 && sb->platform == UBI_PSP) { sb->section1_entry_size = 0x48; From 960a13a6f213f3ea95f0bcb87b7df7db16c78858 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 23 Dec 2018 18:55:20 +0300 Subject: [PATCH 8/8] Ubi SB: More SMx fixes --- src/meta/ubi_sb.c | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index e066ee19..788ed673 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -334,19 +334,9 @@ static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *str streamData = streamFile; } - /* final offset */ - if (sb->is_external) { - start_offset = sb->stream_offset; - } else { - if (sb->is_map) { - start_offset = sb->stream_offset; - } else { - start_offset = sb->sounds_offset + sb->stream_offset; - } - } + start_offset = sb->stream_offset; //;VGM_LOG("start offset=%lx, external=%i\n", start_offset, sb->is_external); - /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(sb->channels,loop_flag); if (!vgmstream) goto fail; @@ -391,7 +381,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *str vgmstream->num_samples = dsp_bytes_to_samples(sb->stream_size, sb->channels); { - off_t coefs_offset = sb->extra_section_offset + sb->extra_offset; + off_t coefs_offset = sb->extra_offset; coefs_offset += 0x10; /* entry size is 0x40 (first/last 0x10 = unknown), per channel */ dsp_read_coefs_be(vgmstream,streamFile,coefs_offset, 0x40); @@ -504,7 +494,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *str /* get XMA header from extra section */ chunk_size = 0x20; - header_offset = sb->extra_section_offset + sb->xma_header_offset; + header_offset = sb->xma_header_offset; bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamFile, 1); ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); @@ -621,7 +611,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe sb->header_id = read_32bit(offset + 0x00, streamFile); /* 16b+16b group+sound id */ sb->header_type = read_32bit(offset + 0x04, streamFile); sb->stream_size = read_32bit(offset + 0x08, streamFile); - sb->extra_offset = read_32bit(offset + 0x0c, streamFile); /* within the extra section */ + sb->extra_offset = read_32bit(offset + 0x0c, streamFile) + sb->extra_section_offset; /* within the extra section */ sb->stream_offset = read_32bit(offset + 0x10, streamFile); /* within the data section */ sb->channels = (sb->has_short_channels) ? (uint16_t)read_16bit(offset + sb->channels_offset, streamFile) : @@ -782,7 +772,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe if (sb->codec == RAW_XMA1) { /* this field is only seen in X360 games, points at XMA1 header in extra section */ - sb->xma_header_offset = read_32bit(sb->header_offset + sb->xma_pointer_offset, streamFile); + sb->xma_header_offset = read_32bit(sb->header_offset + sb->xma_pointer_offset, streamFile) + sb->extra_section_offset; } /* uncommon but possible */ @@ -831,16 +821,20 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe if (sb->stream_offset) break; } - } else if ((sb->stream_id_offset || sb->has_rotating_ids) && sb->section3_num > 1) { - /* internal sounds are split into groups based on their type with their offsets being relative to group start - * this table contains sizes of each group, adjust offset based on group ID of our sound */ - for (i = 0; i < sb->section3_num; i++) { - off_t offset = sb->section3_offset + sb->section3_entry_size * i; + } else { + sb->stream_offset += sb->sounds_offset; - /* 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->stream_id) - break; - sb->stream_offset += read_32bit(offset + 0x04, streamFile); + if ((sb->stream_id_offset || sb->has_rotating_ids) && sb->section3_num > 1) { + /* internal sounds are split into groups based on their type with their offsets being relative to group start + * this table contains sizes of each group, adjust offset based on group ID of our sound */ + for (i = 0; i < sb->section3_num; i++) { + off_t offset = sb->section3_offset + sb->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->stream_id) + break; + sb->stream_offset += read_32bit(offset + 0x04, streamFile); + } } } }