#include "meta.h" #include "../layout/layout.h" #include "../coding/coding.h" #include "ubi_bao_streamfile.h" #define BAO_MAX_LAYER_COUNT 16 /* arbitrary max */ #define BAO_MAX_CHAIN_COUNT 128 /* POP:TFS goes up to ~100 */ typedef enum { CODEC_NONE = 0, UBI_IMA, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2_OLD, RAW_XMA2_NEW, RAW_AT3, RAW_AT3_105, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; typedef enum { TYPE_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_bao_type; typedef enum { FILE_NONE = 0, UBI_FORGE, UBI_FORGE_b } ubi_bao_file; typedef struct { size_t bao_class; size_t header_base_size; size_t header_skip; int header_less_le_flag; /* horrid but not sure what to do */ 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; size_t audio_interleave; int audio_fix_psx_samples; 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_external_flag; off_t layer_stream_id; off_t layer_stream_size; off_t layer_prefetch_size; off_t layer_extra_size; off_t layer_cue_count; off_t layer_cue_labels; off_t layer_sample_rate; off_t layer_channels; off_t layer_stream_type; off_t layer_num_samples; size_t layer_entry_size; int layer_external_and; int layer_ignore_error; off_t silence_duration_float; ubi_bao_codec codec_map[16]; ubi_bao_file file_type; } ubi_bao_config; typedef struct { int is_atomic; int version; ubi_bao_type type; ubi_bao_codec codec; int big_endian; int total_subsongs; /* config */ ubi_bao_config cfg; /* header info */ off_t header_offset; uint8_t header_format; uint32_t header_version; uint32_t header_id; uint32_t header_type; size_t header_skip; /* common sub-header size */ size_t header_size; /* normal base size (not counting extra tables) */ size_t extra_size; /* extra tables size */ uint32_t stream_id; size_t stream_size; off_t stream_offset; uint32_t prefetch_id; size_t prefetch_size; off_t prefetch_offset; size_t memory_skip; size_t stream_skip; int is_prefetched; int is_external; int loop_flag; int num_samples; int loop_start; int sample_rate; int channels; int stream_type; int layer_count; int layer_channels[BAO_MAX_LAYER_COUNT]; int sequence_count; uint32_t sequence_chain[BAO_MAX_CHAIN_COUNT]; int sequence_loop; int sequence_single; float duration; char resource_name[255]; char readable_name[255]; int classes[16]; int types[16]; int allowed_types[16]; } ubi_bao_header; static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset); static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset, int target_subsong); static int parse_pk(ubi_bao_header * bao, STREAMFILE *streamFile); static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFILE *streamFile); static STREAMFILE * setup_bao_streamfile(ubi_bao_header *bao, STREAMFILE *streamFile); static STREAMFILE * open_atomic_bao(ubi_bao_file file_type, uint32_t file_id, int is_stream, STREAMFILE *streamFile); static int find_package_bao(uint32_t bao_id, STREAMFILE *streamFile, off_t *out_offset, size_t *out_size); 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+ */ VGMSTREAM * init_vgmstream_ubi_bao_pk(STREAMFILE *streamFile) { ubi_bao_header bao = { 0 }; /* checks */ if (!check_extensions(streamFile, "pk,lpk,cpk")) goto fail; /* package .pk+spk (or .lpk+lspk for localized) database-like format, evolved from Ubi sbN/smN. * .pk has an index pointing to memory BAOs and tables with external stream BAOs in .spk. */ /* main parse */ if (!parse_pk(&bao, streamFile)) goto fail; build_readable_name(bao.readable_name, sizeof(bao.readable_name), &bao); return init_vgmstream_ubi_bao_header(&bao, streamFile); fail: return NULL; } /* .BAO - single BAO files from Ubisoft's sound engine ("DARE") games in 2008+ */ VGMSTREAM * init_vgmstream_ubi_bao_atomic(STREAMFILE *streamFile) { ubi_bao_header bao = { 0 }; STREAMFILE * streamData = NULL; /* checks */ if (!check_extensions(streamFile, "bao,")) goto fail; /* atomic .bao+bao/sbao found in .forge and similar bigfiles. The bigfile acts as index, but * since BAOs reference each other by id and are named by it (though the internal BAO id may * be other) we can simulate it. Extension is .bao/sbao or extensionaless in some games. */ /* format: 0x01=AC1, 0x02=POP2008 */ if (read_8bit(0x00, streamFile) != 0x01 && read_8bit(0x00, streamFile) != 0x02) goto fail; bao.is_atomic = 1; bao.version = read_32bitBE(0x00, streamFile) & 0x00FFFFFF; if (!config_bao_version(&bao, streamFile)) goto fail; /* main parse */ if (!parse_bao(&bao, streamFile, 0x00, 1)) goto fail; build_readable_name(bao.readable_name, sizeof(bao.readable_name), &bao); return init_vgmstream_ubi_bao_header(&bao, streamFile); fail: close_streamfile(streamData); return NULL; } #if 0 /* .SPK - special mini package with BAOs [Avatar (PS3)] */ VGMSTREAM * init_vgmstream_ubi_bao_spk(STREAMFILE *streamFile) { ubi_bao_header bao = { 0 }; /* checks */ if (!check_extensions(streamFile, "spk")) goto fail; /* Variation of .pk: * - 0x00: 0x014B5053 ("SPK\01" LE) * - 0x04: BAO count * - 0x08: BAO ids inside (0x04 * BAO count) * - per BAO count * - 0x00: table count * - 0x04: ids related to this BAO? (0x04 * table count) * - 0x08/NN: BAO size * - 0x0c/NN+: BAO data up to size * * BAOs reference .sbao by name (are considered atomic) so perhaps could * be considered a type of bigfile. */ return NULL; } #endif /* ************************************************************************* */ static VGMSTREAM * init_vgmstream_ubi_bao_base(ubi_bao_header * bao, STREAMFILE *streamHead, STREAMFILE * streamData) { VGMSTREAM * vgmstream = NULL; off_t start_offset = 0x00; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(bao->channels, bao->loop_flag); if (!vgmstream) goto fail; 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->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 */ vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; break; case RAW_PSX: vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = (bao->cfg.audio_interleave) ? bao->cfg.audio_interleave : bao->stream_size / bao->channels; break; case RAW_DSP: vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = bao->stream_size / bao->channels; /* mini DSP header (first 0x10 seem to contain DSP header fields like nibbles and format) */ dsp_read_coefs_be(vgmstream, streamHead, bao->header_offset + bao->header_size + 0x10, 0x40); dsp_read_hist_be (vgmstream, streamHead, bao->header_offset + bao->header_size + 0x34, 0x40); /* after gain/initial ps */ break; #ifdef VGM_USE_FFMPEG //todo: some XMA1 decode a bit strangely at certain positions (FFmpeg bug?) case RAW_XMA1: case RAW_XMA2_OLD: case RAW_XMA2_NEW: { uint8_t buf[0x100]; size_t bytes, chunk_size, data_size; off_t chunk_offset; STREAMFILE *streamXMA; switch(bao->codec) { case RAW_XMA1: chunk_size = 0x20; break; case RAW_XMA2_OLD: chunk_size = 0x2c; break; case RAW_XMA2_NEW: chunk_size = 0x34; break; default: goto fail; } //todo improve XMA subheader skip //- audio memory: in header //- audio stream: in data //- layer memory: in layer mem, right before audio (technically in header...) //- layer stream: same? /* XMA header chunk is stored in different places, setup and also find actual data start */ if (bao->is_external || bao->type == UBI_LAYER) { uint8_t flag, bits_per_frame; uint32_t sec1_num, sec2_num, sec3_num; size_t header_size, frame_size; off_t header_offset = start_offset + chunk_size; /* skip custom XMA seek? table after standard XMA/fmt header chunk */ if (bao->codec == RAW_XMA1) { flag = read_8bit(header_offset + 0x00, streamData); sec2_num = read_32bitBE(header_offset + 0x04, streamData); /* number of XMA frames */ frame_size = 0x800; sec1_num = read_32bitBE(header_offset + 0x08, streamData); sec3_num = read_32bitBE(header_offset + 0x0c, streamData); header_size = chunk_size + 0x10; } else { flag = read_8bit(header_offset + 0x00, streamData); sec2_num = read_32bitBE(header_offset + 0x04, streamData); /* number of XMA frames */ frame_size = 0x800; //read_32bitBE(header_offset + 0x08, streamData); /* not always present? */ sec1_num = read_32bitBE(header_offset + 0x0c, streamData); sec3_num = read_32bitBE(header_offset + 0x10, streamData); /* assumed */ header_size = chunk_size + 0x14; } bits_per_frame = 4; if (flag == 0x02 || flag == 0x04) bits_per_frame = 2; else if (flag == 0x08) bits_per_frame = 1; header_size += sec1_num * 0x04; header_size += align_size_to_block(sec2_num * bits_per_frame, 32) / 8; /* bitstream seek table? */ header_size += sec3_num * 0x08; streamXMA = streamData; chunk_offset = 0x00; start_offset += header_size; data_size = sec2_num * frame_size; } else { streamXMA = streamHead; chunk_offset = bao->header_offset + bao->header_size; start_offset = 0x00; data_size = bao->stream_size; } if (bao->codec == RAW_XMA2_OLD) { bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset, chunk_size, data_size, streamXMA); } else { bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset, chunk_size, data_size, streamXMA, 1); } vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, data_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; vgmstream->stream_size = data_size; xma_fix_raw_samples(vgmstream, streamData, start_offset,data_size,0, 0,0); break; } case RAW_AT3_105: case RAW_AT3: { uint8_t buf[0x100]; int32_t bytes, block_size, encoder_delay, joint_stereo; block_size = (bao->codec == RAW_AT3_105 ? 0x98 : 0xc0) * vgmstream->channels; joint_stereo = 0; encoder_delay = 0; /* num_samples is full bytes-to-samples (unlike FMT_AT3) and comparing X360 vs PS3 games seems ok */ bytes = ffmpeg_make_riff_atrac3(buf, 0x100, vgmstream->num_samples, vgmstream->stream_size, vgmstream->channels, vgmstream->sample_rate, block_size, joint_stereo, encoder_delay); vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, bao->stream_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; break; } case FMT_AT3: { vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamData, start_offset, NULL); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; break; } case FMT_OGG: { ffmpeg_codec_data *ffmpeg_data; ffmpeg_data = init_ffmpeg_offset(streamData, start_offset, bao->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 = bao->num_samples; /* ffmpeg_data->totalSamples */ VGM_ASSERT(bao->num_samples != ffmpeg_data->totalSamples, "UBI BAO: header samples %i vs ffmpeg %i differ\n", bao->num_samples, (uint32_t)ffmpeg_data->totalSamples); break; } #endif default: goto fail; } if (!vgmstream_open_stream(vgmstream, streamData, start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); VGM_LOG("UBI BAO: failed init base\n"); 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; fail: close_streamfile(streamData); close_vgmstream(vgmstream); VGM_LOG("UBI BAO: failed init audio\n"); 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; size_t full_stream_size = bao->stream_size; int i, total_channels = 0; 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, full_stream_size, i, bao->layer_count, bao->big_endian); if (!temp_streamFile) goto fail; bao->stream_size = get_streamfile_size(temp_streamFile); bao->channels = bao->layer_channels[i]; total_channels += bao->layer_channels[i]; /* 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(total_channels, bao->loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_UBI_BAO; vgmstream->sample_rate = bao->sample_rate; vgmstream->num_streams = bao->total_subsongs; vgmstream->stream_size = full_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); VGM_LOG("UBI BAO: failed init layer\n"); return NULL; } static VGMSTREAM * init_vgmstream_ubi_bao_sequence(ubi_bao_header *bao, STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE* streamChain = NULL; segmented_layout_data* data = NULL; int i; /* init layout */ data = init_layout_segmented(bao->sequence_count); if (!data) goto fail; bao->channels = 0; bao->num_samples = 0; /* open all segments and mix */ for (i = 0; i < bao->sequence_count; i++) { ubi_bao_header temp_bao = *bao; /* memcpy'ed */ int entry_id = bao->sequence_chain[i]; if (bao->is_atomic) { /* open memory audio BAO */ streamChain = open_atomic_bao(bao->cfg.file_type, entry_id, 0, streamFile); if (!streamChain) { VGM_LOG("UBI BAO: chain BAO %08x not found\n", entry_id); goto fail; } /* parse BAO */ if (!parse_header(&temp_bao, streamChain, 0x00)) goto fail; /* will open its companion BAOs later */ close_streamfile(streamChain); streamChain = NULL; } else { /* find memory audio BAO */ off_t entry_offset; if (!find_package_bao(entry_id, streamFile, &entry_offset, NULL)) { VGM_LOG("UBI BAO: expected chain id %08x not found\n", entry_id); goto fail; } /* parse BAO */ if (!parse_header(&temp_bao, streamFile, entry_offset)) goto fail; } if (temp_bao.type == TYPE_NONE || temp_bao.type == UBI_SEQUENCE) { VGM_LOG("UBI BAO: unexpected sequence entry type\n"); goto fail; /* technically ok but too much recursiveness? */ } /* build the layer VGMSTREAM (current sb entry config) */ data->segments[i] = init_vgmstream_ubi_bao_header(&temp_bao, streamFile); if (!data->segments[i]) goto fail; if (i == bao->sequence_loop) bao->loop_start = bao->num_samples; bao->num_samples += data->segments[i]->num_samples; /* save current (silences don't have values, so this unsures they know when memcpy'ed) */ bao->channels = temp_bao.channels; bao->sample_rate = temp_bao.sample_rate; } //todo Rabbids 0x200000bd.pk#24 mixes 2ch audio with 2ch*3 layers if (!setup_layout_segmented(data)) goto fail; /* build the base VGMSTREAM */ vgmstream = allocate_vgmstream(data->segments[0]->channels, !bao->sequence_single); if (!vgmstream) goto fail; vgmstream->meta_type = meta_UBI_BAO; vgmstream->sample_rate = data->segments[0]->sample_rate; vgmstream->num_streams = bao->total_subsongs; //vgmstream->stream_size = bao->stream_size; /* auto when getting avg br */ vgmstream->num_samples = bao->num_samples; vgmstream->loop_start_sample = bao->loop_start; vgmstream->loop_end_sample = bao->num_samples; vgmstream->coding_type = data->segments[0]->coding_type; vgmstream->layout_type = layout_segmented; vgmstream->layout_data = data; return vgmstream; fail: close_streamfile(streamChain); if (vgmstream) close_vgmstream(vgmstream); else free_layout_segmented(data); VGM_LOG("UBI BAO: failed init sequence\n"); 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_bao_silence(ubi_bao_header *bao, STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE *temp_streamFile = NULL; int channel_count, sample_rate; channel_count = bao->channels; sample_rate = bao->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_BAO; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = bao->duration * sample_rate; vgmstream->num_streams = bao->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_bao_header(ubi_bao_header * bao, STREAMFILE * streamFile) { VGMSTREAM * vgmstream = NULL; if (bao->total_subsongs <= 0) { VGM_LOG("UBI BAO: no subsongs\n"); goto fail; /* not uncommon */ } //;VGM_LOG("UBI BAO: target at %x, h_id=%08x, s_id=%08x, p_id=%08x\n", // (uint32_t)bao->header_offset, bao->header_id, bao->stream_id, bao->prefetch_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: type=%i, header=%x, extra=%x, prefetch=%x, size=%x\n", // bao->header_type, bao->header_size, bao->extra_size, (uint32_t)bao->prefetch_offset, bao->prefetch_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; 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). * A fun feature of .pk is that different BAOs in a .pk can point to different .spk BAOs * that actually hold the same data, with different GUID too, somehow. */ static int parse_pk(ubi_bao_header * bao, STREAMFILE *streamFile) { int i; int index_entries; size_t index_size, index_header_size; off_t bao_offset; int target_subsong = streamFile->stream_index; STREAMFILE *streamIndex = NULL; STREAMFILE *streamTest = NULL; /* format: 0x01=package index, 0x02=package BAO */ if (read_8bit(0x00, streamFile) != 0x01) goto fail; /* index and resources are always LE */ if (target_subsong <= 0) target_subsong = 1; bao->version = read_32bitBE(0x00, streamFile) & 0x00FFFFFF; index_size = read_32bitLE(0x04, streamFile); /* can be 0, not including */ /* 0x08: resource table offset, always found even if not used */ /* 0x0c: always 0? */ /* 0x10: unknown, null if no entries */ /* 0x14: config/flags/time? (changes a bit between files), null if no entries */ /* 0x18(10): file GUID? clones may share it */ /* 0x24: unknown */ /* 0x2c: unknown, may be same as 0x14, can be null */ /* 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; /* pre-load to avoid too much I/O back and forth */ if (index_size > (10000*0x08)) { VGM_LOG("BAO: index too big\n"); goto fail; } /* use smaller I/O buffers for performance, as this read lots of small headers all over the place */ streamIndex = reopen_streamfile(streamFile, index_size); if (!streamIndex) goto fail; streamTest = reopen_streamfile(streamFile, 0x100); if (!streamTest) goto fail; /* 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; bao_offset += bao_size; /* files simply concat BAOs */ } //;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"); close_streamfile(streamIndex); close_streamfile(streamTest); return 1; fail: close_streamfile(streamIndex); close_streamfile(streamTest); return 0; } /* ************************************************************************* */ static void build_readable_name(char * buf, size_t buf_size, ubi_bao_header * bao) { const char *grp_name; const char *pft_name; const char *typ_name; const char *res_name; uint32_t h_id, s_id, type; if (bao->type == TYPE_NONE) return; /* config */ if (bao->is_atomic) grp_name = "atomic"; else grp_name = "package"; pft_name = bao->is_prefetched ? "p" : "n"; typ_name = bao->is_external ? "str" : "mem"; h_id = bao->header_id; s_id = bao->stream_id; type = bao->header_type; 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 { res_name = NULL; //if (!bao->is_atomic && bao->is_external) // res_name = bao->resource_name; /* too big? */ //else // res_name = NULL; } /* .pk can contain many subsongs, we need something helpful * (best done right after subsong detection, since some sequence re-parse types) */ if (res_name && res_name[0]) { snprintf(buf,buf_size, "%s/%s-%s/%02x-%08x/%08x/%s", grp_name, pft_name, typ_name, type, h_id, s_id, res_name); } else { snprintf(buf,buf_size, "%s/%s-%s/%02x-%08x/%08x", grp_name, pft_name, typ_name, type, h_id, s_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->is_prefetched = (bao->prefetch_size > 0); } 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); } bao->stream_type = read_32bit(h_offset + bao->cfg.audio_stream_type, streamFile); 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 > BAO_MAX_CHAIN_COUNT) { VGM_LOG("UBI BAO: incorrect sequence count\n"); goto fail; } /* get chain in extra table */ table_offset = offset + bao->header_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; size_t cues_size = 0; 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); if (bao->layer_count > BAO_MAX_LAYER_COUNT) { VGM_LOG("UBI BAO: incorrect layer count\n"); goto fail; } bao->is_external = read_32bit(h_offset + bao->cfg.layer_external_flag, streamFile) & bao->cfg.layer_external_and; bao->stream_size = read_32bit(h_offset + bao->cfg.layer_stream_size, streamFile); bao->stream_id = read_32bit(h_offset + bao->cfg.layer_stream_id, streamFile); if (bao->cfg.layer_prefetch_size) { bao->prefetch_size = read_32bit(h_offset + bao->cfg.layer_prefetch_size, streamFile); bao->is_prefetched = (bao->prefetch_size > 0); } /* extra cue table (rare, has N variable-sized labels + cue table pointing to them) */ if (bao->cfg.layer_cue_labels) { cues_size += read_32bit(h_offset + bao->cfg.layer_cue_labels, streamFile); } if (bao->cfg.layer_cue_count) { cues_size += read_32bit(h_offset + bao->cfg.layer_cue_count, streamFile) * 0x08; } if (bao->cfg.layer_extra_size) { bao->extra_size = read_32bit(h_offset + bao->cfg.layer_extra_size, streamFile); } else { bao->extra_size = cues_size + bao->layer_count * bao->cfg.layer_entry_size + cues_size; } /* get 1st layer header in extra table and validate all headers match */ table_offset = offset + bao->header_size + cues_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); 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); if (bao->sample_rate != sample_rate || bao->stream_type != stream_type) { VGM_LOG("UBI BAO: layer headers don't match at %x\n", (uint32_t)table_offset); if (bao->cfg.layer_ignore_error) { continue; } goto fail; } /* uncommonly channels may vary per layer [Rayman Raving Rabbids: TV Party (Wii) ex. 0x22000cbc.pk] */ bao->layer_channels[i] = channels; /* can be +-1 */ if (bao->num_samples != num_samples && bao->num_samples + 1 == num_samples) { bao->num_samples -= 1; } table_offset += bao->cfg.layer_entry_size; } return 1; fail: return 0; } static int parse_type_silence(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; uint32_t duration_int; float* duration_float; /* silence header */ bao->type = UBI_SILENCE; if (bao->cfg.silence_duration_float == 0) { VGM_LOG("UBI BAO: silence duration not configured at %x\n", (uint32_t)offset); goto fail; } { duration_int = (uint32_t)read_32bit(h_offset + bao->cfg.silence_duration_float, streamFile); duration_float = (float*)&duration_int; bao->duration = *duration_float; } if (bao->duration <= 0) { VGM_LOG("UBI BAO: bad duration %f at %x\n", bao->duration, (uint32_t)offset); goto fail; } return 1; fail: return 0; } /* adjust some common values */ static int parse_values(ubi_bao_header * bao, STREAMFILE *streamFile) { if (bao->type == UBI_SEQUENCE || bao->type == UBI_SILENCE) return 1; /* common validations */ if (bao->stream_size == 0) { VGM_LOG("UBI BAO: unknown stream_size at %x\n", (uint32_t)bao->header_offset); goto fail; goto fail; } /* set codec */ if (bao->stream_type > 0x10) { VGM_LOG("UBI BAO: unknown stream_type at %x\n", (uint32_t)bao->header_offset); goto fail; goto fail; } bao->codec = bao->cfg.codec_map[bao->stream_type]; if (bao->codec == 0x00) { VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)bao->header_offset); goto fail; goto fail; } if (bao->type == UBI_AUDIO && bao->codec == RAW_PSX && bao->cfg.audio_fix_psx_samples && bao->loop_flag) { //todo: loop flag only? bao->num_samples = bao->num_samples / bao->channels; } /* set prefetch id */ if (bao->is_prefetched) { if (bao->is_atomic && bao->cfg.file_type == UBI_FORGE) { /* AC1's stream BAO are 0x5NNNNNNN and prefetch BAO 0x3NNNNNNN (all filenames include class) */ bao->prefetch_id = (bao->stream_id & 0x0FFFFFFF) | 0x30000000; } else { /* shared id in index and resource table, or named atomic BAOs */ bao->prefetch_id = bao->stream_id; } } /* normalize base skips, as memory data (prefetch or not, atomic or package) can be * in a memory BAO after base header or audio layer BAO after the extra table */ if (bao->stream_id == bao->header_id && (!bao->is_external || bao->is_prefetched)) { /* layers with memory data */ bao->memory_skip = bao->header_size + bao->extra_size; bao->stream_skip = bao->header_skip; } else { bao->memory_skip = bao->header_skip; bao->stream_skip = bao->header_skip; } return 1; fail: return 0; } /* set actual offsets in various places */ static int parse_offsets(ubi_bao_header * bao, STREAMFILE *streamFile) { off_t bao_offset; size_t bao_size; if (bao->type == UBI_SEQUENCE || bao->type == UBI_SILENCE) return 1; if (!bao->is_external && bao->is_prefetched) { VGM_LOG("UBI BAO: unexpected non-streamed prefetch at %x\n", (uint32_t)bao->header_offset); goto fail; } /* Audio headers can point to audio data in multiple forms we must configure here: * - memory part (internal .pk BAO or separate atomic .bao) * - streamed part (external .spk BAO or separate atomic .sbao) * - prefetched memory part + streamed part (must join both during reads) * * Offsets are absolute (ignoring the index table that even .spk has) but point to BAO * base header start, that we must also skip to reach actual audio data. */ if (bao->is_atomic) { if (bao->is_prefetched) { bao->prefetch_offset = bao->memory_skip; } if (bao->is_external) { bao->stream_offset = bao->stream_skip; } else { bao->stream_offset = bao->memory_skip; } } else { if (bao->is_prefetched) { if (!find_package_bao(bao->prefetch_id, streamFile, &bao_offset, &bao_size)) { VGM_LOG("UBI BAO: expected prefetch id %08x not found\n", bao->prefetch_id); goto fail; } bao->prefetch_offset = bao_offset + bao->memory_skip; if (bao->prefetch_size + bao->memory_skip != bao_size) { VGM_LOG("UBI BAO: unexpected prefetch size %x vs %x\n", bao->prefetch_size + bao->memory_skip, bao_size); goto fail; } } if (bao->is_external) { int i; off_t offset; off_t resources_offset = read_32bitLE(0x08, streamFile); int resources_count = read_32bitLE(resources_offset+0x00, streamFile); size_t strings_size = read_32bitLE(resources_offset+0x04, streamFile); /* parse resource table to external stream (may be empty, or exist even with nothing in the file) */ offset = resources_offset + 0x04+0x04 + strings_size; for (i = 0; i < resources_count; i++) { uint32_t resource_id = read_32bitLE(offset+0x10*i+0x00, streamFile); off_t name_offset = read_32bitLE(offset+0x10*i+0x04, streamFile); off_t resource_offset = read_32bitLE(offset+0x10*i+0x08, streamFile); size_t resource_size = read_32bitLE(offset+0x10*i+0x0c, streamFile); if (resource_id == bao->stream_id) { bao->stream_offset = resource_offset + bao->stream_skip; read_string(bao->resource_name,255, resources_offset + 0x04+0x04 + name_offset, streamFile); if (bao->stream_size != resource_size - bao->stream_skip + bao->prefetch_size) { VGM_ASSERT(bao->stream_size != resource_size - bao->stream_skip + bao->prefetch_size, "UBI BAO: stream vs resource size mismatch at %lx\n", offset+0x10*i); goto fail; } break; } } if (bao->stream_offset == 0) { VGM_LOG("UBI BAO: expected external id %08x not found\n", bao->stream_id); goto fail; } } else { if (!find_package_bao(bao->stream_id, streamFile, &bao_offset, &bao_size)) { VGM_LOG("UBI BAO: expected internal id %08x not found\n", bao->stream_id); goto fail; } bao->stream_offset = bao_offset + bao->memory_skip; /* in some cases, stream size value from audio header can be bigger (~0x18) * than actual audio chunk o_O [Rayman Raving Rabbids: TV Party (Wii)] */ if (bao->stream_size > bao_size - bao->memory_skip) { VGM_LOG("UBI BAO: bad stream size found: %x + %x vs %x\n", bao->stream_size, bao->memory_skip, bao_size); /* too big is usually bad config */ if (bao->stream_size > bao_size + bao->header_size) { VGM_LOG("UBI BAO: bad stream config at %x\n", (uint32_t)bao->header_offset); goto fail; } bao->stream_size = bao_size - bao->memory_skip; } } } return 1; fail: return 0; } /* parse a single known header resource at offset (see config_bao for info) */ 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; bao->header_offset = offset; bao->header_format = read_8bit (offset + 0x00, streamFile); /* 0x01: atomic, 0x02: package */ bao->header_version = read_32bitBE(offset + 0x00, streamFile) & 0x00FFFFFF; if (bao->version != bao->header_version) { VGM_LOG("UBI BAO: mismatched header version at %x\n", (uint32_t)offset); goto fail; } /* - base part in early versions: * 0x04: header skip (usually 0x28, rarely 0x24), can be LE unlike other fields (ex. Assassin's Creed PS3) * 0x08(10): GUID, or id-like fields in early versions * 0x18: null * 0x1c: null * 0x20: class * 0x24: config/version? (0x00/0x01/0x02) * * - base part in later versions: * 0x04(10): GUID * 0x14: class * 0x18: config/version? (0x02) * 0x1c: fixed value? */ bao->header_skip = bao->cfg.header_skip; bao->header_id = read_32bit(offset + bao->header_skip + 0x00, streamFile); bao->header_type = read_32bit(offset + bao->header_skip + 0x04, streamFile); bao->header_size = bao->cfg.header_base_size; /* hack for games with smaller than standard * (can't use lowest size as other games also have extra unused field) */ if (bao->cfg.header_less_le_flag && !bao->big_endian) { bao->header_size -= 0x04; } /* detect extra unused field in PC/Wii * (could be improved but no apparent flags or anything useful) */ else if (get_streamfile_size(streamFile) > offset + bao->header_size) { /* may read next BAO version, layer header, cues, resource table size, etc, always > 1 */ int32_t end_field = read_32bit(offset + bao->header_size, streamFile); if (end_field == -1 || end_field == 0 || end_field == 1) /* some count? */ bao->header_size += 0x04; } 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; case 0x08: if (!parse_type_silence(bao, offset, streamFile)) goto fail; break; default: VGM_LOG("UBI BAO: unknown header type at %x\n", (uint32_t)offset); goto fail; } if (!parse_values(bao, streamFile)) goto fail; if (!parse_offsets(bao, streamFile)) 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; /*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; bao_class = read_32bit(offset+bao->cfg.bao_class, 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 + bao->cfg.header_skip + 0x04, streamFile); if (header_type > 9) { 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; if (!parse_header(bao, streamFile, offset)) goto fail; return 1; fail: return 0; } /* ************************************************************************* */ /* opens a file BAO's companion BAO (memory or stream) */ static STREAMFILE * open_atomic_bao(ubi_bao_file file_type, uint32_t file_id, int is_stream, STREAMFILE *streamFile) { STREAMFILE *streamBAO = NULL; char buf[255]; size_t buf_size = 255; /* Get referenced BAOs, in different naming styles for "internal" (=memory) or "external" (=stream). */ switch(file_type) { case UBI_FORGE: case UBI_FORGE_b: /* Try default extensionless (as extracted from .forge bigfile) and with common extension. * .forge data can be uncompressed (stream BAOs) and compressed (subfiles per area with memory BAOs). */ if (is_stream) { snprintf(buf,buf_size, "Common_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; strcat(buf,".sbao"); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "English_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "French_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "Spanish_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "German_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "Italian_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "Japanese_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "Korean_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "Russian_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "Czech_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; snprintf(buf,buf_size, "Polish_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; /* these are all of the languages that were referenced in Assassin's Creed exe (out of each platform), there may be more */ } else { snprintf(buf,buf_size, "BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; strcat(buf,".bao"); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; } goto fail; default: goto fail; } return streamBAO; /* may be NULL */ fail: close_streamfile(streamBAO); VGM_LOG("UBI BAO: failed opening atomic BAO id %08x\n", file_id); return NULL; } static int find_package_bao(uint32_t target_id, STREAMFILE *streamFile, off_t *out_offset, size_t *out_size) { int i; int index_entries; off_t bao_offset; size_t index_size, index_header_size; index_size = read_32bitLE(0x04, streamFile); index_entries = index_size / 0x08; index_header_size = 0x40; /* parse index to get target 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, streamFile); size_t bao_size = read_32bitLE(index_header_size + 0x08*i + 0x04, streamFile); if (bao_id == target_id) { if (out_offset) *out_offset = bao_offset; if (out_size) *out_size = bao_size; return 1; } bao_offset += bao_size; } return 0; } /* create a usable streamfile */ static STREAMFILE * setup_bao_streamfile(ubi_bao_header *bao, STREAMFILE *streamFile) { STREAMFILE *new_streamFile = NULL; STREAMFILE *temp_streamFile = NULL; STREAMFILE *stream_segments[2] = { 0 }; /* Audio comes in "memory" and "streaming" BAOs. When "prefetched" flag is * on we need to join memory and streamed part as they're stored separately. * * The physical location of those depends on the format: * - file .bao: both in separate files, with different names per type * - bank .pk: memory BAO is in the pk, stream is in another file * * For some header BAO audio data can be in the same BAO too, which * can be considered memory BAO with different offset treatment. */ if (bao->is_atomic) { /* file BAOs re-open new STREAMFILEs so no need to wrap them */ if (bao->is_prefetched) { new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->prefetch_id, 0, streamFile); if (!new_streamFile) goto fail; stream_segments[0] = new_streamFile; new_streamFile = open_clamp_streamfile(stream_segments[0], bao->prefetch_offset, bao->prefetch_size); if (!new_streamFile) goto fail; stream_segments[0] = new_streamFile; new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->stream_id, 1, streamFile); if (!new_streamFile) goto fail; stream_segments[1] = new_streamFile; new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); if (!new_streamFile) goto fail; stream_segments[1] = new_streamFile; new_streamFile = open_multifile_streamfile(stream_segments, 2); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; stream_segments[0] = NULL; stream_segments[1] = NULL; } else { new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->stream_id, bao->is_external, streamFile); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; } } else { if (bao->is_prefetched) { new_streamFile = open_wrap_streamfile(streamFile); if (!new_streamFile) goto fail; stream_segments[0] = new_streamFile; new_streamFile = open_clamp_streamfile(stream_segments[0], bao->prefetch_offset, bao->prefetch_size); if (!new_streamFile) goto fail; stream_segments[0] = new_streamFile; new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } stream_segments[1] = new_streamFile; new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); if (!new_streamFile) goto fail; stream_segments[1] = new_streamFile; temp_streamFile = NULL; new_streamFile = open_multifile_streamfile(stream_segments, 2); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; stream_segments[0] = NULL; stream_segments[1] = NULL; } else if (bao->is_external) { new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } temp_streamFile = new_streamFile; new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; } else { new_streamFile = open_wrap_streamfile(streamFile); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; } } return temp_streamFile; fail: close_streamfile(stream_segments[0]); close_streamfile(stream_segments[1]); close_streamfile(temp_streamFile); VGM_LOG("UBI BAO: failed streamfile setup\n"); return NULL; } /* ************************************************************************* */ static void config_bao_endian(ubi_bao_header * bao, off_t offset, STREAMFILE *streamFile) { /* Detect endianness using the 'class' field (the 'header skip' field is LE in early * versions, and was removed in later versions). * This could be done once as all BAOs share endianness */ /* negate as fields looks like LE (0xN0000000) */ bao->big_endian = !guess_endianness32bit(offset+bao->cfg.bao_class, streamFile); } static void config_bao_entry(ubi_bao_header * bao, size_t header_base_size, size_t header_skip) { bao->cfg.header_base_size = header_base_size; bao->cfg.header_skip = header_skip; } static void config_bao_audio_b(ubi_bao_header * bao, off_t stream_size, off_t stream_id, off_t external_flag, off_t loop_flag, int external_and, int loop_and) { /* audio header base */ bao->cfg.audio_stream_size = stream_size; bao->cfg.audio_stream_id = stream_id; bao->cfg.audio_external_flag = external_flag; bao->cfg.audio_loop_flag = loop_flag; bao->cfg.audio_external_and = external_and; bao->cfg.audio_loop_and = loop_and; } static void config_bao_audio_m(ubi_bao_header * bao, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t stream_type, off_t prefetch_size) { /* audio header main */ bao->cfg.audio_channels = channels; bao->cfg.audio_sample_rate = sample_rate; bao->cfg.audio_num_samples = num_samples; bao->cfg.audio_num_samples2 = num_samples2; bao->cfg.audio_stream_type = stream_type; //bao->cfg.audio_cue_count = cue_count; //bao->cfg.audio_cue_labels = cue_labels; bao->cfg.audio_prefetch_size = prefetch_size; } 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_single; bao->cfg.sequence_sequence_loop = sequence_loop; bao->cfg.sequence_entry_size = entry_size; bao->cfg.sequence_entry_number = 0x00; } static void config_bao_layer_m(ubi_bao_header * bao, off_t stream_id, off_t layer_count, off_t external_flag, off_t stream_size, off_t extra_size, off_t prefetch_size, off_t cue_count, off_t cue_labels, int external_and) { /* layer header in the main part */ bao->cfg.layer_stream_id = stream_id; bao->cfg.layer_layer_count = layer_count; bao->cfg.layer_external_flag = external_flag; bao->cfg.layer_stream_size = stream_size; bao->cfg.layer_extra_size = extra_size; bao->cfg.layer_prefetch_size = prefetch_size; bao->cfg.layer_cue_count = cue_count; bao->cfg.layer_cue_labels = cue_labels; bao->cfg.layer_external_and = external_and; } static void config_bao_layer_e(ubi_bao_header * bao, 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 */ 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; } static void config_bao_silence_f(ubi_bao_header * bao, off_t duration) { /* silence headers in float value */ bao->cfg.silence_duration_float = duration; } 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) always start with: * - 0x00(1): format (meaning defined by mode) * - 0x01(3): 8b*3 version, major/minor/release (numbering continues from .sb0/sm0) * - 0x04+: mini header (varies with version, see parse_header) * * Then are divided into "classes": * - 0x10000000: event (links by id to another event or header BAO, may set volume/reverb/filters/etc) * - 0x20000000: header * - 0x30000000: memory audio (in .pk/.bao) * - 0x40000000: project info * - 0x50000000: stream audio (in .spk/.sbao) * - 0x60000000: unused? * - 0x70000000: info? has a count+table of id-things * - 0x80000000: unknown (some floats?) * - 0x90000000: unknown (some kind of command config?), rare [Ghost Recon Future Soldier (PC)] * Class 1/2/3 are roughly equivalent to Ubi SB's section1/2/3, and class 4 is * basically .spN project files. * * The project BAO (usually with special id 0x7FFFFFFF or 0x40000000) has version, * filenames (not complete) and current mode, "PACKAGE" (pk, index + BAOs with * external BAOs) or "ATOMIC" (file, separate BAOs). * * We want header classes, also similar to SB types: * - 01: single audio (samples, channels, bitrate, samples+size, etc) * - 02: play chain with config? (ex. silence + audio, or rarely audio 2ch intro + layer 4ch body) * - 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) * - 09: silence with config? (channels, sample rate, etc), extremely rare [Shaun White Skateboarding (Wii)] * * Right after base BAO size is the extra table for that BAO (what sectionX had, plus * extra crap like cue-like labels, even for type 0x01). * * Just to throw us off, the base BAO size may add +0x04 (with a field value of 0/-1) on * some game versions/platforms (PC/Wii only?). Doesn't look like there is a header field * (comparing many BAOs from different platforms of the same games) so it's autodetected. * * Most types + tables are pretty much the same as SB (with config styles ported straight) but * now can "prefetch" part of the data (signaled by a size in the header, or perhaps a flag but * looks too erratic). The header points to a external/stream ID, and with prefetch enabled part * of the audio is in an internal/memory ID, and must join both during reads to get the full * stream. Prefetch may be used in some platforms of a game only (ex. AC1 PC does while PS3 * doesn't, while Scott Pilgrim always does) */ bao->allowed_types[0x01] = 1; bao->allowed_types[0x05] = 1; bao->allowed_types[0x06] = 1; /* absolute */ bao->cfg.bao_class = 0x20; /* relative to header_skip */ bao->cfg.header_id = 0x00; bao->cfg.header_type = 0x04; /* config per version*/ switch(bao->version) { case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-atomic-forge */ config_bao_entry(bao, 0xA4, 0x28); /* PC: 0xA8, PS3/X360: 0xA4 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); /* 0x2c: prefetch flag? */ config_bao_audio_m(bao, 0x44, 0x48, 0x50, 0x58, 0x64, 0x74); bao->cfg.audio_interleave = 0x10; bao->cfg.audio_fix_psx_samples = 1; config_bao_sequence(bao, 0x2c, 0x20, 0x1c, 0x14); config_bao_layer_m(bao, 0x4c, 0x20, 0x2c, 0x44, 0x00, 0x50, 0x00, 0x00, 1); /* stream size: 0x48? */ config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x10); config_bao_silence_f(bao, 0x1c); bao->cfg.codec_map[0x02] = RAW_PSX; bao->cfg.codec_map[0x03] = UBI_IMA; bao->cfg.codec_map[0x04] = FMT_OGG; bao->cfg.codec_map[0x07] = RAW_AT3_105; bao->cfg.file_type = UBI_FORGE; return 1; case 0x001F0008: /* Rayman Raving Rabbids: TV Party (Wii)-package */ case 0x001F0010: /* Prince of Persia 2008 (PC/PS3/X360)-atomic-forge, Far Cry 2 (PS3)-atomic-dunia? */ case 0x001F0011: /* Naruto: The Broken Bond (X360)-package */ case 0x0022000D: /* Just Dance (Wii)-package */ case 0x0022001B: /* Prince of Persia: The Forgotten Sands (Wii)-package */ config_bao_entry(bao, 0xA4, 0x28); /* PC/Wii: 0xA8 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); config_bao_audio_m(bao, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x74); /* cues: 0x68, 0x6c */ config_bao_sequence(bao, 0x2c, 0x20, 0x1c, 0x14); config_bao_layer_m(bao, 0x00, 0x20, 0x2c, 0x44, 0x4c, 0x50, 0x54, 0x58, 1); /* 0x1c: id-like, 0x3c: prefetch flag? */ config_bao_layer_e(bao, 0x28, 0x00, 0x04, 0x08, 0x10); config_bao_silence_f(bao, 0x1c); bao->cfg.codec_map[0x01] = RAW_PCM; bao->cfg.codec_map[0x03] = UBI_IMA; bao->cfg.codec_map[0x04] = FMT_OGG; bao->cfg.codec_map[0x05] = RAW_XMA1; bao->cfg.codec_map[0x07] = RAW_AT3_105; bao->cfg.codec_map[0x09] = RAW_DSP; bao->cfg.file_type = UBI_FORGE_b; return 1; case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-package */ case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-package */ config_bao_entry(bao, 0x84, 0x28); /* PSP: 0x84 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x20, 0x20, (1 << 2), (1 << 5)); /* (1 << 4): prefetch flag? */ config_bao_audio_m(bao, 0x28, 0x30, 0x38, 0x40, 0x48, 0x58); config_bao_layer_m(bao, 0x00, 0x20, 0x24, 0x34, 0x3c, 0x40, 0x00, 0x00, (1 << 2)); /* 0x1c: id-like */ config_bao_layer_e(bao, 0x28, 0x00, 0x04, 0x08, 0x10); bao->cfg.codec_map[0x06] = RAW_PSX; bao->cfg.codec_map[0x07] = FMT_AT3; return 1; case 0x00230008: /* Splinter Cell: Conviction (X360/PC)-package */ config_bao_entry(bao, 0xB4, 0x28); /* PC: 0xB8, X360: 0xB4 */ config_bao_audio_b(bao, 0x08, 0x24, 0x38, 0x44, 1, 1); config_bao_audio_m(bao, 0x54, 0x5c, 0x64, 0x6c, 0x74, 0x84); config_bao_sequence(bao, 0x34, 0x28, 0x24, 0x14); config_bao_layer_m(bao, 0x00, 0x28, 0x3c, 0x54, 0x5c, 0x00 /*0x60?*/, 0x00, 0x00, 1); /* 0x24: id-like */ config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x18); bao->cfg.layer_ignore_error = 1; //todo last sfx layer (bass) may have smaller sample rate 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_OLD; return 1; case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-package */ case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-atomic-forge */ case 0x00250119: /* Shaun White Skateboarding (Wii)-package */ case 0x0025011D: /* Shaun White Skateboarding (PC/PS3)-atomic-forge */ config_bao_entry(bao, 0xB4, 0x28); /* PC: 0xB0, PS3/X360: 0xB4, Wii: 0xB8 */ if (bao->version == 0x0025011D) bao->cfg.header_less_le_flag = 1; config_bao_audio_b(bao, 0x08, 0x24, 0x2c, 0x38, 1, 1); config_bao_audio_m(bao, 0x48, 0x50, 0x58, 0x60, 0x68, 0x78); bao->cfg.audio_interleave = 0x10; config_bao_sequence(bao, 0x34, 0x28, 0x24, 0x14); config_bao_layer_m(bao, 0x00, 0x28, 0x30, 0x48, 0x50, 0x54, 0x58, 0x5c, 1); /* 0x24: id-like */ config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x18); //todo some SPvsTW layers look like should loop (0x30 flag?) //todo some POP layers have different sample rates (ambience) config_bao_silence_f(bao, 0x24); 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_NEW; bao->cfg.codec_map[0x05] = RAW_PSX; bao->cfg.codec_map[0x06] = RAW_AT3; if (bao->version == 0x0025010A) /* no apparent flag */ bao->cfg.codec_map[0x06] = RAW_AT3_105; bao->cfg.file_type = UBI_FORGE_b; return 1; case 0x00280303: /* Tom Clancy's Ghost Recon Future Soldier (PC/PS3)-pk */ config_bao_entry(bao, 0xBC, 0x28); /* PC/PS3: 0xBC */ config_bao_audio_b(bao, 0x08, 0x38, 0x3c, 0x48, 1, 1); config_bao_audio_m(bao, 0x54, 0x5c, 0x64, 0x6c, 0x74, 0x80); config_bao_sequence(bao, 0x48, 0x3c, 0x38, 0x14); config_bao_layer_m(bao, 0x00, 0x3c, 0x44, 0x58, 0x60, 0x64, 0x00, 0x00, 1); config_bao_layer_e(bao, 0x2c, 0x00, 0x04, 0x08, 0x1c); bao->cfg.layer_ignore_error = 1; //todo some layer sample rates don't match //todo some files have strange prefetch+stream of same size (2 segments?), ex. CEND_30_VOX.lpk config_bao_silence_f(bao, 0x38); bao->cfg.codec_map[0x01] = RAW_PCM; bao->cfg.codec_map[0x02] = UBI_IMA; /* v6 */ bao->cfg.codec_map[0x04] = FMT_OGG; bao->cfg.codec_map[0x07] = RAW_AT3; //todo some layers use AT3_105 bao->cfg.file_type = UBI_FORGE_b; return 1; case 0x001B0200: /* Beowulf (PS3)-atomic-bin+fat */ /* same as 0x001B0100 except: * - base 0xA0, skip 0x24, name style %08x (.bao/sbao?) */ case 0x001C0000: /* Lost: Via Domus (PS3)-atomic-gear */ /* same as 0x001B0100 except: * - base 0xA0, skip 0x24, name style %08x.bao (not .sbao?) */ case 0x001D0A00: /* Shaun White Snowboarding (PSP)-atomic-opal */ case 0x00220017: /* Avatar (PS3)-atomic/spk */ case 0x00220018: /* Avatar (PS3)-atomic/spk */ case 0x00260102: /* Prince of Persia Trilogy HD (PS3)-package-gear */ /* similar to 0x00250108 but most values are moved +4 * - base 0xB8, skip 0x28 */ case 0x00280306: /* Far Cry 3: Blood Dragon (X360)-atomic-hashed */ case 0x00290106: /* Splinter Cell: Blacklist (PS3)-atomic-gear */ /* quite different, lots of flags and random values * - base varies per type (0xF0=audio), skip 0x20 * - 0x74: type, 0x78: channels, 0x7c: sample rate, 0x80: num_samples * - 0x94: stream id? 0x9C: extra size */ case 0x002A0300: /* Watch Dogs (Wii U) */ /* similar to SC:B */ default: /* others possibly using BAO: 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; }