diff --git a/fb2k/foo_input_vgmstream.rc b/fb2k/foo_input_vgmstream.rc index 96edda9a..2bc0b28d 100755 --- a/fb2k/foo_input_vgmstream.rc +++ b/fb2k/foo_input_vgmstream.rc @@ -23,6 +23,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL // // Dialog // +//elements: text, id, x, y, width, height [, style [, extended-style]] IDD_CONFIG DIALOGEX 0, 0, 187, 156 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD @@ -40,6 +41,8 @@ BEGIN CONTROL "Loop forever",IDC_LOOP_FOREVER,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,70,77,10 CONTROL "Ignore looping",IDC_IGNORE_LOOP,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,83,77,10 CONTROL "Disable subsongs",IDC_DISABLE_SUBSONGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,99,87,10 + LTEXT "Downmix",IDC_STATIC,7,115,48,12 + EDITTEXT IDC_DOWNMIX_CHANNELS,52,112,37,14,ES_AUTOHSCROLL END diff --git a/fb2k/foo_prefs.cpp b/fb2k/foo_prefs.cpp index d0cc0c31..72910a06 100755 --- a/fb2k/foo_prefs.cpp +++ b/fb2k/foo_prefs.cpp @@ -20,6 +20,7 @@ static const GUID guid_cfg_LoopCount = { 0xfc8dfd72, 0xfae8, 0x44cc, { 0xbe, 0x9 static const GUID guid_cfg_FadeLength = { 0x61da7ef1, 0x56a5, 0x4368, { 0xae, 0x6, 0xec, 0x6f, 0xd7, 0xe6, 0x15, 0x5d } }; static const GUID guid_cfg_FadeDelay = { 0x73907787, 0xaf49, 0x4659, { 0x96, 0x8e, 0x9f, 0x70, 0xa1, 0x62, 0x49, 0xc4 } }; static const GUID guid_cfg_DisableSubsongs = { 0xa8cdd664, 0xb32b, 0x4a36, { 0x83, 0x07, 0xa0, 0x4c, 0xcd, 0x52, 0xa3, 0x7c } }; +static const GUID guid_cfg_DownmixChannels = { 0x5a0e65dd, 0xeb37, 0x4c67, { 0x9a, 0xb1, 0x3f, 0xb0, 0xc9, 0x7e, 0xb0, 0xe0 } }; static cfg_bool cfg_LoopForever(guid_cfg_LoopForever, DEFAULT_LOOP_FOREVER); static cfg_bool cfg_IgnoreLoop(guid_cfg_IgnoreLoop, DEFAULT_IGNORE_LOOP); @@ -27,6 +28,7 @@ static cfg_string cfg_LoopCount(guid_cfg_LoopCount, DEFAULT_LOOP_COUNT); static cfg_string cfg_FadeLength(guid_cfg_FadeLength, DEFAULT_FADE_SECONDS); static cfg_string cfg_FadeDelay(guid_cfg_FadeDelay, DEFAULT_FADE_DELAY_SECONDS); static cfg_bool cfg_DisableSubsongs(guid_cfg_DisableSubsongs, DEFAULT_DISABLE_SUBSONGS); +static cfg_string cfg_DownmixChannels(guid_cfg_DownmixChannels, DEFAULT_DOWNMIX_CHANNELS); // Needs to be here in rder to access the static config void input_vgmstream::load_settings() @@ -38,6 +40,7 @@ void input_vgmstream::load_settings() loop_forever = cfg_LoopForever; ignore_loop = cfg_IgnoreLoop; disable_subsongs = cfg_DisableSubsongs; + sscanf(cfg_DownmixChannels.get_ptr(),"%d",&downmix_channels); } const char * vgmstream_prefs::get_name() @@ -70,6 +73,8 @@ BOOL vgmstreamPreferences::OnInitDialog(CWindow, LPARAM) CheckDlgButton(IDC_DISABLE_SUBSONGS, cfg_DisableSubsongs?BST_CHECKED:BST_UNCHECKED); + uSetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS, cfg_DownmixChannels); + return TRUE; } @@ -93,6 +98,8 @@ void vgmstreamPreferences::reset() uSetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS, DEFAULT_FADE_DELAY_SECONDS); CheckDlgButton(IDC_DISABLE_SUBSONGS, DEFAULT_DISABLE_SUBSONGS?BST_CHECKED:BST_UNCHECKED); + + uSetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS, DEFAULT_DOWNMIX_CHANNELS); } @@ -107,6 +114,7 @@ void vgmstreamPreferences::apply() double temp_fade_delay_seconds; double temp_loop_count; int consumed; + int temp_downmix_channels; pfc::string buf; buf = uGetDlgItemText(m_hWnd, IDC_FADE_SECONDS); @@ -141,6 +149,18 @@ void vgmstreamPreferences::apply() "Error",MB_OK|MB_ICONERROR); return; } else cfg_FadeDelay = buf.get_ptr(); + + buf = uGetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS); + if (sscanf(buf.get_ptr(),"%d%n",&temp_downmix_channels,&consumed)<1 + || consumed!=strlen(buf.get_ptr()) || + temp_downmix_channels<0) { + uMessageBox(m_hWnd, + "Invalid value for Downmix Channels\n" + "Must be a number greater than or equal to zero", + "Error",MB_OK|MB_ICONERROR); + return; + } else cfg_DownmixChannels = buf.get_ptr(); + } @@ -166,6 +186,9 @@ bool vgmstreamPreferences::HasChanged() if(FadeDelay != uGetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS)) return true; if(LoopCount != uGetDlgItemText(m_hWnd, IDC_LOOP_COUNT)) return true; + pfc::string DownmixChannels(cfg_DownmixChannels); + if(DownmixChannels != uGetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS)) return true; + return FALSE; } diff --git a/fb2k/foo_prefs.h b/fb2k/foo_prefs.h index c5cc6589..77e22662 100755 --- a/fb2k/foo_prefs.h +++ b/fb2k/foo_prefs.h @@ -15,6 +15,7 @@ #define DEFAULT_LOOP_FOREVER false #define DEFAULT_IGNORE_LOOP false #define DEFAULT_DISABLE_SUBSONGS false +#define DEFAULT_DOWNMIX_CHANNELS "8" class vgmstreamPreferences : public CDialogImpl, public preferences_page_instance { public: @@ -42,6 +43,7 @@ public: COMMAND_HANDLER_EX(IDC_FADE_DELAY_SECONDS, EN_CHANGE, OnEditChange) COMMAND_HANDLER_EX(IDC_LOOP_COUNT, EN_CHANGE, OnEditChange) COMMAND_HANDLER_EX(IDC_DISABLE_SUBSONGS, BN_CLICKED, OnEditChange) + COMMAND_HANDLER_EX(IDC_DOWNMIX_CHANNELS, EN_CHANGE, OnEditChange) END_MSG_MAP() private: BOOL OnInitDialog(CWindow, LPARAM); diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index 12639074..2192f681 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -56,6 +56,7 @@ input_vgmstream::input_vgmstream() { loop_forever = false; ignore_loop = 0; disable_subsongs = false; + downmix_channels = 0; load_settings(); } @@ -228,8 +229,38 @@ bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort) } } - bytes = (samples_to_do*vgmstream->channels * sizeof(sample_buffer[0])); - p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, vgmstream->channels, 16, audio_chunk::g_guess_channel_config(vgmstream->channels)); + /* downmix enabled (foobar refuses to do more than 8 channels) */ + if (downmix_channels > 0 && downmix_channels < vgmstream->channels) { + short temp_buffer[OUTBUF_SIZE]; + int s, ch; + + for (s = 0; s < samples_to_do; s++) { + /* copy channels up to max */ + for (ch = 0; ch < downmix_channels; ch++) { + temp_buffer[s*downmix_channels + ch] = sample_buffer[s*vgmstream->channels + ch]; + } + /* then mix the rest */ + for (ch = downmix_channels; ch < vgmstream->channels; ch++) { + int downmix_ch = ch % downmix_channels; + int new_sample = ((int)temp_buffer[s*downmix_channels + downmix_ch] + (int)sample_buffer[s*vgmstream->channels + ch]); + new_sample = (int)(new_sample * 0.7); /* limit clipping without removing too much loudness... hopefully */ + if (new_sample > 32767) new_sample = 32767; + else if (new_sample < -32768) new_sample = -32768; + temp_buffer[s*downmix_channels + downmix_ch] = (short)new_sample; + } + } + + /* copy back to global buffer... in case of multithreading stuff? */ + memcpy(sample_buffer,temp_buffer, samples_to_do*downmix_channels*sizeof(short)); + + bytes = (samples_to_do*downmix_channels * sizeof(sample_buffer[0])); + p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, downmix_channels, 16, audio_chunk::g_guess_channel_config(downmix_channels)); + } + else { + bytes = (samples_to_do*vgmstream->channels * sizeof(sample_buffer[0])); + p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, vgmstream->channels, 16, audio_chunk::g_guess_channel_config(vgmstream->channels)); + } + decode_pos_samples+=samples_to_do; decode_pos_ms=decode_pos_samples*1000LL/vgmstream->sample_rate; @@ -407,7 +438,10 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti title.set_string(p, e - p); if (!disable_subsongs && infostream && infostream->num_streams > 1) { - sprintf(temp,"#%d",infostream->stream_index); + int info_subsong = infostream->stream_index; + if (info_subsong==0) + info_subsong = 1; + sprintf(temp,"#%d",info_subsong); title += temp; if (infostream->stream_name[0] != '\0') { diff --git a/fb2k/foo_vgmstream.h b/fb2k/foo_vgmstream.h index df347115..ba3eb738 100644 --- a/fb2k/foo_vgmstream.h +++ b/fb2k/foo_vgmstream.h @@ -61,6 +61,7 @@ class input_vgmstream : public input_stubs { bool force_ignore_loop; int ignore_loop; bool disable_subsongs; + int downmix_channels; /* helpers */ VGMSTREAM * init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort); diff --git a/fb2k/resource.h b/fb2k/resource.h index ec646540..57549610 100755 --- a/fb2k/resource.h +++ b/fb2k/resource.h @@ -14,6 +14,7 @@ #define IDC_THREAD_PRIORITY_TEXT 1007 #define IDC_DEFAULT_BUTTON 1008 #define IDC_DISABLE_SUBSONGS 1009 +#define IDC_DOWNMIX_CHANNELS 1010 // Next default values for new objects // diff --git a/src/formats.c b/src/formats.c index 0fde6525..8ba3bb0f 100644 --- a/src/formats.c +++ b/src/formats.c @@ -179,6 +179,7 @@ static const char* extension_list[] = { "logg", //fake extension, for OGGs "lopus", //fake extension, for OPUS "lpcm", + "lpk", "lps", "lsf", "lstm", //fake extension, for STMs @@ -243,6 +244,7 @@ static const char* extension_list[] = { "past", "pcm", "pdt", + "pk", "pnb", "pona", "pos", @@ -337,6 +339,7 @@ static const char* extension_list[] = { "swag", "swav", "swd", + "switch_audio" "sx", "sxd", "sxd2", @@ -345,6 +348,8 @@ static const char* extension_list[] = { "thp", "tk5", "tra", + "trj", + "trm", "tun", "txtp", "tydsp", @@ -484,7 +489,7 @@ static const coding_info coding_info_list[] = { {coding_PSX, "Playstation 4-bit ADPCM"}, {coding_PSX_badflags, "Playstation 4-bit ADPCM (bad flags)"}, {coding_PSX_cfg, "Playstation 4-bit ADPCM (configurable)"}, - {coding_HEVAG, "Playstation Vita HEVAG 4-bit ADPCM"}, + {coding_HEVAG, "Sony HEVAG 4-bit ADPCM"}, {coding_EA_XA, "Electronic Arts EA-XA 4-bit ADPCM v1"}, {coding_EA_XA_int, "Electronic Arts EA-XA 4-bit ADPCM v1 (mono/interleave)"}, @@ -1005,6 +1010,8 @@ static const meta_info meta_info_list[] = { {meta_OGG_YS8, "Ogg Vorbis (Ys VIII header)"}, {meta_PPST, "Parappa PPST header"}, {meta_OPUS_PPP, "AT9 OPUS header"}, + {meta_UBI_BAO, "Ubisoft BAO header"}, + {meta_DSP_SWITCH_AUDIO, "UE4 Switch Audio header"}, #ifdef VGM_USE_FFMPEG {meta_FFmpeg, "FFmpeg supported file format"}, diff --git a/src/layout/blocked_ea_schl.c b/src/layout/blocked_ea_schl.c index 9a9c5f3b..169bbaec 100644 --- a/src/layout/blocked_ea_schl.c +++ b/src/layout/blocked_ea_schl.c @@ -93,6 +93,14 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { break; + /* id, size, samples */ + case coding_PCM16_int: + for (i = 0; i < vgmstream->channels; i++) { + vgmstream->ch[i].offset = block_offset + 0x0c + (i*0x02); + } + + break; + /* id, size, samples, hists-per-channel, stereo/interleaved data */ case coding_EA_XA: //case coding_EA_XA_V2: /* handled in default */ diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index e5ab4562..3d5993c0 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1305,6 +1305,10 @@ + + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 9a71fdb7..2b32c125 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -793,6 +793,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/adx_keys.h b/src/meta/adx_keys.h index 9d5b750e..d3556486 100644 --- a/src/meta/adx_keys.h +++ b/src/meta/adx_keys.h @@ -26,7 +26,7 @@ static const adxkey_info adxkey8_list[] = { {0x49e1,0x4a57,0x553d, "karaage",0}, /* Grasshopper Manufacture 0 (Blood+) */ - {0x5f5d,0x58bd,0x55ed, NULL,0}, // estimated + {0x5f5d,0x58bd,0x55ed, NULL,0}, // estimated (keystring not in ELF?) /* Grasshopper Manufacture 1 (Killer7) */ {0x50fb,0x5803,0x5701, "GHM",0}, @@ -132,7 +132,7 @@ static const adxkey_info adxkey8_list[] = { {0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx /* Shakugan no Shana (2006)(Vridge)(Media Works)[PS2] */ - {0x5fc5,0x63d9,0x599f, NULL,0}, // confirmed unique with guessadx + {0x5fc5,0x63d9,0x599f, "FUZETSU",0}, /* Uragiri wa Boku no Namae o Shitteiru (2010)(Kadokawa Shoten)[PS2] */ {0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx @@ -207,6 +207,9 @@ static const adxkey_info adxkey9_list[] = { // Super Robot Wars X-Omega (voices) [iOS/Android] {0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96 + // AKA to BLUE (Android) + {0x03fc,0x0749,0x12EF, NULL,0}, // guessed with VGAudio (possible key: 1FE0748978 / 136909719928) + //{0x0c03,0x0749,0x1459, NULL,0}, // 2nd guess (possible key: 6018748A2D / 412727151149) }; static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); diff --git a/src/meta/ea_schl.c b/src/meta/ea_schl.c index 85adbf67..a2b78408 100644 --- a/src/meta/ea_schl.c +++ b/src/meta/ea_schl.c @@ -82,12 +82,12 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { off_t start_offset, header_offset; size_t header_size; - ea_header ea; + ea_header ea = {0}; /* check extension; exts don't seem enforced by EA's tools, but usually: * STR/ASF/MUS ~early, EAM ~mid, SNG/AUD ~late, rest uncommon/one game (ex. STRM: MySims Kingdom Wii) */ - if (!check_extensions(streamFile,"str,asf,mus,eam,sng,aud,sx,strm,xa,xsf,exa,stm,ast")) + if (!check_extensions(streamFile,"str,asf,mus,eam,sng,aud,sx,strm,xa,xsf,exa,stm,ast,trj,trm")) goto fail; /* check header */ @@ -121,7 +121,7 @@ fail: VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { off_t start_offset, header_offset, offset, table_offset; size_t header_size; - ea_header ea; + ea_header ea = {0}; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; int i, bnk_version; @@ -285,7 +285,11 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ break; case EA_CODEC2_S16LE: /* PCM16LE */ - vgmstream->coding_type = coding_PCM16LE; + if (ea->version > 0) { + vgmstream->coding_type = coding_PCM16LE; + } else { /* Need for Speed III: Hot Pursuit (PC) */ + vgmstream->coding_type = coding_PCM16_int; + } break; case EA_CODEC2_VAG: /* PS-ADPCM */ @@ -450,7 +454,6 @@ static int parse_variable_header(STREAMFILE* streamFile, ea_header* ea, off_t be uint32_t platform_id; int is_header_end = 0; - memset(ea,0,sizeof(ea_header)); /* null defaults as 0 can be valid */ ea->version = EA_VERSION_NONE; @@ -468,7 +471,7 @@ static int parse_variable_header(STREAMFILE* streamFile, ea_header* ea, off_t be offset += 4 + 4; /* GSTRs have an extra field (config?): ex. 0x01000000, 0x010000D8 BE */ } else if ((platform_id & 0xFFFF0000) == 0x50540000) { /* "PT" = PlaTform */ - ea->platform = (uint8_t)read_16bitLE(offset + 2,streamFile); + ea->platform = (uint16_t)read_16bitLE(offset + 2,streamFile); offset += 4; } else { diff --git a/src/meta/meta.h b/src/meta/meta.h index d44a6fac..c32d4715 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -747,4 +747,7 @@ VGMSTREAM * init_vgmstream_ppst(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_opus_ppp(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_ubi_bao_pk(STREAMFILE *streamFile); + +VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile); #endif /*_META_H*/ diff --git a/src/meta/ngc_dsp_std.c b/src/meta/ngc_dsp_std.c index 3e02bbc8..990a1c21 100644 --- a/src/meta/ngc_dsp_std.c +++ b/src/meta/ngc_dsp_std.c @@ -24,9 +24,10 @@ struct dsp_header { uint16_t loop_ps; int16_t loop_hist1; int16_t loop_hist2; - /* later/mdsp extension */ - int16_t channel_count; + int16_t channel_count; /* DSPADPCM.exe ~v2.7 extension */ int16_t block_size; + /* padding/reserved up to 0x60 */ + /* DSPADPCM.exe from GC adds some extra data here (uninitialized MSVC memory?) */ }; /* read the above struct; returns nonzero on failure */ @@ -34,7 +35,7 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA int32_t (*get_32bit)(uint8_t *) = big_endian ? get_32bitBE : get_32bitLE; int16_t (*get_16bit)(uint8_t *) = big_endian ? get_16bitBE : get_16bitLE; int i; - uint8_t buf[0x4e]; /* usually padded out to 0x60 */ + uint8_t buf[0x4e]; if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e) return 1; @@ -131,20 +132,22 @@ static int check_dsp_samples(struct dsp_header* ch_header, int channels) { fail: return 0; } -static int check_dsp_initial_ps(struct dsp_header* ch_header, int channels, STREAMFILE *streamFile, off_t offset, size_t spacing) { +static int check_dsp_initial_ps(struct dsp_header* ch_header, int channels, STREAMFILE *streamFile, off_t offset, size_t interleave) { int i; /* check initial predictor/scale */ for (i = 0; i < channels; i++) { - if (ch_header[i].initial_ps != (uint8_t)read_8bit(offset + i*spacing, streamFile)) + off_t start_offset = offset + i*interleave; + if (ch_header[i].initial_ps != (uint8_t)read_8bit(start_offset, streamFile)){ goto fail; + } } return 1; fail: return 0; } -static int check_dsp_loop_ps(struct dsp_header* ch_header, int channels, STREAMFILE *streamFile, off_t offset, size_t spacing) { +static int check_dsp_loop_ps(struct dsp_header* ch_header, int channels, STREAMFILE *streamFile, off_t offset, size_t interleave) { int i; if (!ch_header[0].loop_flag) @@ -152,8 +155,13 @@ static int check_dsp_loop_ps(struct dsp_header* ch_header, int channels, STREAMF /* check loop predictor/scale */ for (i = 0; i < channels; i++) { - off_t loop_offset = ch_header[i].loop_start_offset / 16 * 8; - if (ch_header[i].loop_ps != (uint8_t)read_8bit(offset + i*spacing + loop_offset,streamFile)) + off_t loop_offset = ch_header[i].loop_start_offset; + if (interleave) { + loop_offset = loop_offset / 16 * 8; + loop_offset = (loop_offset / interleave * interleave * channels) + (loop_offset % interleave); + } + + if (ch_header[i].loop_ps != (uint8_t)read_8bit(offset + i*interleave + loop_offset,streamFile)) goto fail; } @@ -172,9 +180,10 @@ typedef struct { int force_loop; /* force full loop */ int fix_looping; /* fix loop end going past num_samples */ + int fix_loop_start; /* weird files with bad loop start */ int single_header; /* all channels share header, thus totals are off */ int ignore_header_agreement; /* sometimes there are minor differences between headers */ - int ignore_loop_check; /* loop info in header should match data, but sometimes it's weird */ + int ignore_loop_check; /* loop info in header should match data, but sometimes it's weird */ //todo check if needed anymore off_t header_offset; size_t header_spacing; @@ -200,6 +209,15 @@ static VGMSTREAM * init_vgmstream_dsp_common(STREAMFILE *streamFile, dsp_meta *d if (!dsp_load_header_endian(ch_header, dspm->channel_count, streamFile,dspm->header_offset,dspm->header_spacing, !dspm->little_endian)) goto fail; + if (dspm->fix_loop_start) { + int i; + for (i = 0; i < dspm->channel_count; i++) { + /* bad/fixed value in loop start */ + if (ch_header[i].loop_flag) + ch_header[i].loop_start_offset = 0x00; + } + } + if (!check_dsp_format(ch_header, dspm->channel_count)) goto fail; @@ -274,7 +292,7 @@ VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) { int i, channel_count; /* checks */ - /* .dsp: standard, .adp: Dr. Muto (GC) mono files */ + /* .dsp: standard, .adp: Dr. Muto/Battalion Wars (GC) mono files */ if (!check_extensions(streamFile, "dsp,adp")) goto fail; @@ -600,122 +618,49 @@ fail: return NULL; } -/* IDSP with multiple standard DSP headers - from SSB4 (3DS), Tekken Tag Tournament 2 (Wii U) */ -#define MULTI_IDSP_MAX_CHANNELS 8 +/* IDSP - Namco header + interleaved dsp [SSB4 (3DS), Tekken Tag Tournament 2 (WiiU)] */ VGMSTREAM * init_vgmstream_3ds_idsp(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; + dsp_meta dspm = {0}; + off_t offset; - off_t idsp_offset = 0; - off_t start_offset; - off_t interleave; - - struct dsp_header ch_headers[MULTI_IDSP_MAX_CHANNELS]; - int i, ch; - int channel_count; - - /* check extension, case insensitive */ - //if (check_extensions(streamFile,"idsp,nus3bank")) goto fail; - - /* check header magic */ - if( read_32bitBE(0x0,streamFile) != 0x49445350 ) /* "IDSP" */ - { - /* try NUS3 format instead */ - if (read_32bitBE(0,streamFile) != 0x4E555333) goto fail; /* "NUS3" */ - - /* Header size */ - idsp_offset = 0x14 + read_32bitLE( 0x10, streamFile ); - - idsp_offset += read_32bitLE( 0x1C, streamFile ) + 8; - idsp_offset += read_32bitLE( 0x24, streamFile ) + 8; - idsp_offset += read_32bitLE( 0x2C, streamFile ) + 8; - idsp_offset += read_32bitLE( 0x34, streamFile ) + 8; - idsp_offset += read_32bitLE( 0x3C, streamFile ) + 8; - idsp_offset += read_32bitLE( 0x44, streamFile ) + 8; - idsp_offset += 8; - - /* check magic */ - if (read_32bitBE(idsp_offset,streamFile) != 0x49445350) goto fail; /* "IDSP" */ - } - - channel_count = read_32bitBE(idsp_offset+0x8, streamFile); - if (channel_count > MULTI_IDSP_MAX_CHANNELS) goto fail; - - start_offset = read_32bitBE(idsp_offset+0x28,streamFile) + idsp_offset; - interleave = 0x10; - - /* read standard dsp header per channel and do some validations */ - for (ch=0; ch < channel_count; ch++) { - /* read 0x60 header per channel */ - if (read_dsp_header(&ch_headers[ch], idsp_offset + 0x40 + 0x60*ch, streamFile)) goto fail; - - /* check initial values */ - if (ch_headers[ch].initial_ps != (uint8_t)read_8bit(start_offset + interleave*ch, streamFile)) goto fail; - if (ch_headers[ch].format || ch_headers[ch].gain) goto fail; - - /* check for agreement with prev channel*/ - if (ch > 0 && ( - ch_headers[ch].sample_count != ch_headers[ch-1].sample_count || - ch_headers[ch].nibble_count != ch_headers[ch-1].nibble_count || - ch_headers[ch].sample_rate != ch_headers[ch-1].sample_rate || - ch_headers[ch].loop_flag != ch_headers[ch-1].loop_flag || - ch_headers[ch].loop_start_offset != ch_headers[ch-1].loop_start_offset || - ch_headers[ch].loop_end_offset != ch_headers[ch-1].loop_end_offset - )) goto fail; - - -#if 0 //this is wrong for >2ch and will fail - /* check loop predictor/scale */ - if (ch_headers[ch].loop_flag) { - off_t loop_off; - loop_off = ch_headers[ch].loop_start_offset / 8 / channel_count * 8; - loop_off = (loop_off / interleave * interleave * channel_count) + (loop_off%interleave); - if (ch_headers[ch].loop_ps != (uint8_t)read_8bit(start_offset + loop_off + interleave*ch, streamFile)) goto fail; - } -#endif - } - /* check first channel (implicitly all ch) agree with main sample rate */ - if (ch_headers[0].sample_rate != read_32bitBE(idsp_offset+0xc, streamFile)) goto fail; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,ch_headers[0].loop_flag); - if (!vgmstream) goto fail; - - vgmstream->num_samples = ch_headers[0].sample_count; - vgmstream->sample_rate = ch_headers[0].sample_rate; - - /* TODO: adjust for interleave? */ - vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch_headers[0].loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_headers[0].loop_end_offset) + 1; - /* games will ignore loop_end and use num_samples if going over it - * only needed for user-created IDSPs, but it's possible loop_end_sample shouldn't add +1 above */ - if (vgmstream->loop_end_sample > vgmstream->num_samples) - vgmstream->loop_end_sample = vgmstream->num_samples; - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = channel_count > 1 ? layout_interleave : layout_none; - vgmstream->interleave_block_size = interleave; - vgmstream->meta_type = meta_3DS_IDSP; - - - /* set DSP coefs/history */ - for (ch=0; ch < channel_count; ch++) { - for (i=0;i<16;i++) { - vgmstream->ch[ch].adpcm_coef[i] = ch_headers[ch].coef[i]; - } - /* always 0 that I've ever seen, but for completeness... */ - vgmstream->ch[ch].adpcm_history1_16 = ch_headers[ch].initial_hist1; - vgmstream->ch[ch].adpcm_history2_16 = ch_headers[ch].initial_hist2; - } - - /* open the file for reading */ - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + /* checks */ + if (!check_extensions(streamFile, "idsp,nus3bank")) goto fail; - return vgmstream; + /* try NUS3BANK container */ + if (read_32bitBE(0x00,streamFile) == 0x4E555333) { /* "NUS3" */ + offset = 0x14 + read_32bitLE(0x10, streamFile); /* header size */ + offset += read_32bitLE(0x1C, streamFile) + 0x08; + offset += read_32bitLE(0x24, streamFile) + 0x08; + offset += read_32bitLE(0x2C, streamFile) + 0x08; + offset += read_32bitLE(0x34, streamFile) + 0x08; + offset += read_32bitLE(0x3C, streamFile) + 0x08; + offset += read_32bitLE(0x44, streamFile) + 0x08; + offset += 0x08; + } + else { + offset = 0x00; + } + if (read_32bitBE(offset,streamFile) != 0x49445350) /* "IDSP" */ + goto fail; + /* 0x0c: sample rate, 0x10: num_samples, 0x14: loop_start_sample, 0x18: loop_start_sample */ + + dspm.channel_count = read_32bitBE(offset+0x08, streamFile); + dspm.max_channels = 8; + /* games do adjust loop_end if bigger than num_samples (only happens in user-created IDSPs) */ + dspm.fix_looping = 1; + + dspm.header_offset = read_32bitBE(offset+0x20,streamFile) + offset; + dspm.header_spacing = read_32bitBE(offset+0x24,streamFile); + dspm.start_offset = read_32bitBE(offset+0x28,streamFile) + offset; + dspm.interleave = read_32bitBE(offset+0x1c,streamFile); /* usually 0x10 */ + if (dspm.interleave == 0) /* Taiko no Tatsujin: Atsumete Tomodachi Daisakusen (WiiU) */ + dspm.interleave = read_32bitBE(offset+0x2c,streamFile); /* half interleave, use channel size */ + + dspm.meta_type = meta_3DS_IDSP; + return init_vgmstream_dsp_common(streamFile, &dspm); fail: - close_vgmstream(vgmstream); return NULL; } @@ -731,9 +676,6 @@ VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile) { dspm.channel_count = read_8bit(0x32, streamFile); dspm.max_channels = 2; - //todo: loop check fails unless adjusted: - // loop_offset = (loop_offset / spacing * spacing * channels) + (loop_offset % spacing); - dspm.ignore_loop_check = 1; dspm.header_offset = 0x80; dspm.header_spacing = 0x60; @@ -847,112 +789,30 @@ fail: } -/* SWD (found in Conflict - Desert Storm 1 & 2 */ +/* SWD - PSF chunks + interleaved dsps [Conflict: Desert Storm 1 & 2] */ VGMSTREAM * init_vgmstream_ngc_swd(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t start_offset; - off_t interleave; + dsp_meta dspm = {0}; - struct dsp_header ch0_header, ch1_header; - int i; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("swd",filename_extension(filename))) goto fail; - - if (read_dsp_header(&ch0_header, 0x08, streamFile)) goto fail; - if (read_dsp_header(&ch1_header, 0x68, streamFile)) goto fail; - - /* check header magic */ - if (read_32bitBE(0x00,streamFile) != 0x505346D1) /* PSF\0xD1 */ + /* checks */ + if (!check_extensions(streamFile, "swd")) goto fail; - start_offset = 0xC8; - interleave = 0x8; + //todo blocked layout when first chunk is 0x50534631 (count + table of 0x0c with offset/sizes) -#if 0 - /* check initial predictor/scale */ - if (ch0_header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) - goto fail; - if (ch1_header.initial_ps != (uint8_t)read_8bit(start_offset+interleave,streamFile)) - goto fail; -#endif - - /* check type==0 and gain==0 */ - if (ch0_header.format || ch0_header.gain || - ch1_header.format || ch1_header.gain) + if (read_32bitBE(0x00,streamFile) != 0x505346d1) /* PSF\0xd1 */ goto fail; - /* check for agreement */ - if ( - ch0_header.sample_count != ch1_header.sample_count || - ch0_header.nibble_count != ch1_header.nibble_count || - ch0_header.sample_rate != ch1_header.sample_rate || - ch0_header.loop_flag != ch1_header.loop_flag || - ch0_header.loop_start_offset != ch1_header.loop_start_offset || - ch0_header.loop_end_offset != ch1_header.loop_end_offset - ) goto fail; + dspm.channel_count = 2; + dspm.max_channels = 2; -#if 0 - if (ch0_header.loop_flag) { - off_t loop_off; - /* check loop predictor/scale */ - loop_off = ch0_header.loop_start_offset/16*8; - loop_off = (loop_off/interleave*interleave*2) + (loop_off%interleave); - if (ch0_header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) - goto fail; - if (ch1_header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off+interleave,streamFile)) - goto fail; - } -#endif - - /* build the VGMSTREAM */ - - vgmstream = allocate_vgmstream(2,ch0_header.loop_flag); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - vgmstream->num_samples = ch0_header.sample_count; - vgmstream->sample_rate = ch0_header.sample_rate; - - /* TODO: adjust for interleave? */ - vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch0_header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch0_header.loop_end_offset)+1; - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave; - vgmstream->meta_type = meta_NGC_SWD; - - /* coeffs */ - for (i=0;i<16;i++) { - vgmstream->ch[0].adpcm_coef[i] = ch0_header.coef[i]; - vgmstream->ch[1].adpcm_coef[i] = ch1_header.coef[i]; - } - - /* initial history */ - /* always 0 that I've ever seen, but for completeness... */ - vgmstream->ch[0].adpcm_history1_16 = ch0_header.initial_hist1; - vgmstream->ch[0].adpcm_history2_16 = ch0_header.initial_hist2; - vgmstream->ch[1].adpcm_history1_16 = ch1_header.initial_hist1; - vgmstream->ch[1].adpcm_history2_16 = ch1_header.initial_hist2; - - vgmstream->ch[0].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - vgmstream->ch[1].streamfile = vgmstream->ch[0].streamfile; - - if (!vgmstream->ch[0].streamfile) goto fail; - /* open the file for reading */ - for (i=0;i<2;i++) { - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=start_offset+i*interleave; - } - - return vgmstream; + dspm.header_offset = 0x08; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + 0x60 * dspm.channel_count; + dspm.interleave = 0x08; + dspm.meta_type = meta_NGC_SWD; + return init_vgmstream_dsp_common(streamFile, &dspm); fail: - /* clean up anything we may have opened */ - if (vgmstream) close_vgmstream(vgmstream); return NULL; } @@ -1182,119 +1042,29 @@ fail: return NULL; } -/* .dsp - Ubisoft raw interleaved dsp [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */ -//todo unusual loop values (set at the end) +/* .dsp - Ubisoft interleaved dsp with bad loop start [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */ VGMSTREAM * init_vgmstream_dsp_xiii(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - struct dsp_header ch0_header,ch1_header; - off_t ch1_header_start, ch2_header_start, ch1_start, ch2_start; - size_t interleave; - int channel_count; - int i; + dsp_meta dspm = {0}; - /* check extension */ + /* checks */ if (!check_extensions(streamFile, "dsp")) goto fail; - channel_count = 2; - - ch1_header_start = 0x00; - ch2_header_start = 0x60; - interleave = 0x08; - ch1_start = 0xC0; - ch2_start = ch1_start + interleave; - - - /* get DSP headers */ - if (read_dsp_header(&ch0_header, ch1_header_start, streamFile)) goto fail; - if (read_dsp_header(&ch1_header, ch2_header_start, streamFile)) goto fail; - - /* check initial predictor/scale */ - if (ch0_header.initial_ps != (uint8_t)read_8bit(ch1_start, streamFile)) - goto fail; - if (ch1_header.initial_ps != (uint8_t)read_8bit(ch2_start, streamFile)) - goto fail; - - /* check type==0 and gain==0 */ - if (ch0_header.format || ch0_header.gain) - goto fail; - if (ch1_header.format || ch1_header.gain) - goto fail; - - /* check for agreement */ - if ( - ch0_header.sample_count != ch1_header.sample_count || - ch0_header.nibble_count != ch1_header.nibble_count || - ch0_header.sample_rate != ch1_header.sample_rate || - ch0_header.loop_flag != ch1_header.loop_flag || - //ch0_header.loop_start_offset != ch1_header.loop_start_offset || - ch0_header.loop_end_offset != ch1_header.loop_end_offset - ) goto fail; - - if (ch0_header.loop_flag) - { - off_t loop_off; - /* check loop predictor/scale */ - loop_off = 0x0; //ch0_header.loop_start_offset/16*8; - - if (ch0_header.loop_ps != (uint8_t)read_8bit(ch1_start+loop_off,streamFile)) - goto fail; - if (ch1_header.loop_ps != (uint8_t)read_8bit(ch2_start+loop_off,streamFile)) - goto fail; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, ch1_header.loop_flag); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - vgmstream->num_samples = ch0_header.sample_count; - vgmstream->sample_rate = ch0_header.sample_rate; - - vgmstream->loop_start_sample = 0x0; //dsp_nibbles_to_samples(ch0_header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch0_header.loop_end_offset)+1; - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave; - vgmstream->meta_type = meta_DSP_XIII; - - /* coeffs */ - for (i=0;i<16;i++) { - vgmstream->ch[0].adpcm_coef[i] = ch0_header.coef[i]; - vgmstream->ch[1].adpcm_coef[i] = ch1_header.coef[i]; - } - - /* initial history */ - /* always 0 that I've ever seen, but for completeness... */ - vgmstream->ch[0].adpcm_history1_16 = ch0_header.initial_hist1; - vgmstream->ch[0].adpcm_history2_16 = ch0_header.initial_hist2; - vgmstream->ch[1].adpcm_history1_16 = ch1_header.initial_hist1; - vgmstream->ch[1].adpcm_history2_16 = ch1_header.initial_hist2; - - /* open the file for reading */ - vgmstream->ch[0].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!vgmstream->ch[0].streamfile) - goto fail; - vgmstream->ch[0].channel_start_offset = vgmstream->ch[0].offset=ch1_start; - - vgmstream->ch[1].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!vgmstream->ch[1].streamfile) - goto fail; - vgmstream->ch[1].channel_start_offset = vgmstream->ch[1].offset=ch2_start; - - return vgmstream; + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.fix_loop_start = 1; /* loop flag but strange loop start instead of 0 (maybe shouldn't loop) */ + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing * dspm.channel_count; + dspm.interleave = 0x08; + dspm.meta_type = meta_DSP_XIII; + return init_vgmstream_dsp_common(streamFile, &dspm); fail: - /* clean up anything we may have opened */ - if (vgmstream) close_vgmstream(vgmstream); return NULL; } - /* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */ VGMSTREAM * init_vgmstream_wii_ndp(STREAMFILE *streamFile) { dsp_meta dspm = {0}; @@ -1570,3 +1340,31 @@ VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile) { fail: return NULL; } + +/* .switch_audio - UE4 standard LE header + full interleaved dsp [Gal Gun 2 (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .switch_audio: possibly UE4 class name rather than extension, .dsp: assumed */ + if (!check_extensions(streamFile, "switch_audio,dsp")) + goto fail; + + /* manual double header test */ + if (read_32bitLE(0x00, streamFile) == read_32bitLE(get_streamfile_size(streamFile) / 2, streamFile)) + dspm.channel_count = 2; + else + dspm.channel_count = 1; + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = get_streamfile_size(streamFile) / dspm.channel_count; + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_SWITCH_AUDIO; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} diff --git a/src/meta/ps2_mib.c b/src/meta/ps2_mib.c index aca59564..dfb23f4f 100644 --- a/src/meta/ps2_mib.c +++ b/src/meta/ps2_mib.c @@ -1,362 +1,338 @@ #include "meta.h" #include "../util.h" -/* MIB +static int check_psadpcm(STREAMFILE *streamFile); - PS2 MIB format is a headerless format. - The interleave value can be found by checking the body of the data. - - The interleave start allways at offset 0 with a int value (which can have - many values : 0x0000, 0x0002, 0x0006 etc...) follow by 12 empty (zero) values. - - The interleave value is the offset where you found the same 16 bytes. - - The n° of channels can be found by checking each time you found this 16 bytes. - - The interleave value can be very "large" (up to 0x20000 found so far) and is allways - a 0x10 multiply value. - - The loop values can be found by checking the 'tags' offset (found @ 0x02 each 0x10 bytes). - 06 = start of the loop point (can be found for each channel) - 03 - end of the loop point (can be found for each channel) - - The .MIH header contains all informations about frequency, numbers of channels, interleave - but has, afaik, no loop values. - - known extensions : MIB (MIH for the header) MIC (concatenation of MIB+MIH) - Nota : the MIC stuff is not supported here as there is - another MIC format which can be found in Koei Games. - - 2008-05-14 - Fastelbja : First version ... - 2008-05-20 - Fastelbja : Fix loop value when loopEnd==0 -*/ +/* MIB+MIH - from namCollection: Ace Combat 2 (PS2), Rampage - Total Destruction (PS2) */ +/* headerless PS-ADPCM - from Katamary Damacy (PS2), Air (PS2), Aladdin: Nasira's Revenge (PS1) + * (guesses interleave and channels by testing data and using the file extension, and finds + * loops in PS-ADPCM flags; this is a crutch for convenience, consider using GENH/TXTH instead). */ VGMSTREAM * init_vgmstream_ps2_mib(STREAMFILE *streamFile) { - - VGMSTREAM * vgmstream = NULL; + VGMSTREAM * vgmstream = NULL; STREAMFILE * streamFileMIH = NULL; + off_t start_offset = 0x00; char filename[PATH_LIMIT]; - - uint8_t mibBuffer[0x10]; - uint8_t testBuffer[0x10]; - uint8_t doChannelUpdate=1; - uint8_t bDoUpdateInterleave=1; - size_t fileLength; - - off_t loopStart = 0; - off_t loopEnd = 0; + uint8_t mibBuffer[0x10]; + uint8_t testBuffer[0x10]; - off_t interleave = 0; + size_t fileLength; + off_t loopStart = 0; + off_t loopEnd = 0; + off_t interleave = 0; - off_t readOffset = 0; + off_t readOffset = 0; - char filenameMIH[PATH_LIMIT]; - off_t loopStartPoints[0x10]; - int loopStartPointsCount=0; + off_t loopStartPoints[0x10] = {0}; + int loopStartPointsCount=0; + off_t loopEndPoints[0x10] = {0}; + int loopEndPointsCount=0; + int loopToEnd=0; + int forceNoLoop=0; + int gotEmptyLine=0; - off_t loopEndPoints[0x10]; - int loopEndPointsCount=0; + uint8_t gotMIH=0; - int loopToEnd=0; - int forceNoLoop=0; - int gotEmptyLine=0; + int i, channel_count=0; - uint8_t gotMIH=0; - int i, channel_count=0; - - // Initialize loop point to 0 - for(i=0; i<0x10; i++) { - loopStartPoints[i]=0; - loopEndPoints[i]=0; - } - - /* check extension, case insensitive */ + /* checks + * .mib: common, but many ext-less files are renamed to this. + * .mi4: fake .mib to force another sample rate + * .cvs: Aladdin - Nasira's Revenge (PS1) + * .snds: The Incredibles (PS2) + * .vb: Tantei Jinguuji Saburo - Mikan no Rupo (PS1) + * .xag: Hagane no Renkinjutsushi - Dream Carnival (PS2) + * */ streamFile->get_name(streamFile,filename,sizeof(filename)); if (strcasecmp("cvs",filename_extension(filename)) && strcasecmp("mib",filename_extension(filename)) && strcasecmp("mi4",filename_extension(filename)) && strcasecmp("snds",filename_extension(filename))&& strcasecmp("vb",filename_extension(filename)) && - strcasecmp("xag",filename_extension(filename))) goto fail; + strcasecmp("xag",filename_extension(filename))) + goto fail; - /* check for .MIH file */ - strcpy(filenameMIH,filename); - strcpy(filenameMIH+strlen(filenameMIH)-3,"MIH"); + /* test if raw PS-ADPCM */ + if (!check_psadpcm(streamFile)) + goto fail; - streamFileMIH = streamFile->open(streamFile,filenameMIH,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (streamFileMIH) gotMIH = 1; - /* Search for interleave value & loop points */ - /* Get the first 16 values */ - fileLength = get_streamfile_size(streamFile); - - readOffset+=(off_t)read_streamfile(mibBuffer,0,0x10,streamFile); - readOffset=0; - mibBuffer[0]=0; + /* .MIB may come with a .MIH header file */ + if (strcasecmp("mib",filename_extension(filename))==0) { + streamFileMIH = open_streamfile_by_ext(streamFile,"mih"); + if (streamFileMIH) + gotMIH = 1; + } - do { - readOffset+=(off_t)read_streamfile(testBuffer,readOffset,0x10,streamFile); - // be sure to point to an interleave value - if(readOffset<(int32_t)(fileLength*0.5)) { - if(memcmp(testBuffer+2, mibBuffer+2,0x0e)) { - if(doChannelUpdate) { - doChannelUpdate=0; - channel_count++; - } - if(channel_count<2) - bDoUpdateInterleave=1; - } + fileLength = get_streamfile_size(streamFile); - testBuffer[0]=0; - if(!memcmp(testBuffer,mibBuffer,0x10)) { - - gotEmptyLine=1; - - if(bDoUpdateInterleave) { - bDoUpdateInterleave=0; - interleave=readOffset-0x10; - } - if(((readOffset-0x10)==(channel_count*interleave))) { - doChannelUpdate=1; - } - } - } - - // Loop Start ... - if(testBuffer[0x01]==0x06) - { - if(loopStartPointsCount<0x10) - { - loopStartPoints[loopStartPointsCount] = readOffset-0x10; - loopStartPointsCount++; - } - } - - // Loop End ... - if((testBuffer[0x01]==0x03) && (testBuffer[0x03]!=0x77)) { - if(loopEndPointsCount<0x10) - { - loopEndPoints[loopEndPointsCount] = readOffset; - loopEndPointsCount++; - } - } - - if(testBuffer[0x01]==0x04) - { - // 0x04 loop points flag can't be with a 0x03 loop points flag - if(loopStartPointsCount<0x10) - { - loopStartPoints[loopStartPointsCount] = readOffset-0x10; - loopStartPointsCount++; - - // Loop end value is not set by flags ... - // go until end of file - loopToEnd=1; - } - } - - } while (streamFile->get_offset(streamFile)<((int32_t)fileLength)); - - if((testBuffer[0]==0x0c) && (testBuffer[1]==0)) - forceNoLoop=1; - - if(channel_count==0) - channel_count=1; - - if(gotMIH) - channel_count=read_32bitLE(0x08,streamFileMIH); - - // force no loop - if(!strcasecmp("vb",filename_extension(filename))) - loopStart=0; - - if(!strcasecmp("xag",filename_extension(filename))) - channel_count=2; - - // Calc Loop Points & Interleave ... - if(loopStartPointsCount>=2) - { - // can't get more then 0x10 loop point ! - if(loopStartPointsCount<=0x0F) { - // Always took the first 2 loop points - interleave=loopStartPoints[1]-loopStartPoints[0]; - loopStart=loopStartPoints[1]; - - // Can't be one channel .mib with interleave values - if((interleave>0) && (channel_count==1)) - channel_count=2; - } else - loopStart=0; - } - - if(loopEndPointsCount>=2) - { - // can't get more then 0x10 loop point ! - if(loopEndPointsCount<=0x0F) { - // No need to recalculate interleave value ... - loopEnd=loopEndPoints[loopEndPointsCount-1]; - - // Can't be one channel .mib with interleave values - if(channel_count==1) channel_count=2; - } else { - loopToEnd=0; - loopEnd=0; - } - } - - if (loopToEnd) - loopEnd=fileLength; - - // force no loop - if(forceNoLoop) - loopEnd=0; - - if((interleave>0x10) && (channel_count==1)) - channel_count=2; - - if(interleave==0) interleave=0x10; - - // further check on channel_count ... - if(gotEmptyLine) - { - int newChannelCount = 0; - - readOffset=0; - - do - { - newChannelCount++; - read_streamfile(testBuffer,readOffset,0x10,streamFile); - readOffset+=interleave; - } while(!memcmp(testBuffer,mibBuffer,16)); - - newChannelCount--; - - if(newChannelCount>channel_count) - channel_count=newChannelCount; - } - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,(loopEnd!=0)); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - - if(gotMIH) { - // Read stuff from the MIH file - vgmstream->channels = read_32bitLE(0x08,streamFileMIH); - vgmstream->sample_rate = read_32bitLE(0x0C,streamFileMIH); - vgmstream->interleave_block_size = read_32bitLE(0x10,streamFileMIH); - vgmstream->num_samples=((read_32bitLE(0x10,streamFileMIH)* - (read_32bitLE(0x14,streamFileMIH)-1)*2)+ - ((read_32bitLE(0x04,streamFileMIH)>>8)*2))/16*28/2; - } else { - vgmstream->channels = channel_count; - vgmstream->interleave_block_size = interleave; - - if(!strcasecmp("mib",filename_extension(filename))) - vgmstream->sample_rate = 44100; - - if(!strcasecmp("mi4",filename_extension(filename))) - vgmstream->sample_rate = 48000; - - //Heavy Iron Studios SNDS (The Incredibles) - //Do a bogus check to avoid clashing with PC_SNDS IMA - if(!strcasecmp("snds", filename_extension(filename)) && - (read_32bitBE(0x0, streamFile) == 0x00000000)) - vgmstream->sample_rate = 48000; - - if(!strcasecmp("xag",filename_extension(filename))) { - vgmstream->channels=2; - vgmstream->sample_rate = 44100; - } - - if (!strcasecmp("cvs", filename_extension(filename)) || - !strcasecmp("vb",filename_extension(filename))) - { - vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size=0; - vgmstream->sample_rate = 22050; - vgmstream->channels = 1; - } - - vgmstream->num_samples = (int32_t)(fileLength/16/channel_count*28); - } - - if(loopEnd!=0) { - if(vgmstream->channels==1) { - vgmstream->loop_start_sample = loopStart/16*18; - vgmstream->loop_end_sample = loopEnd/16*28; - } else { - vgmstream->loop_start_sample = ((((loopStart/vgmstream->interleave_block_size)-1)*vgmstream->interleave_block_size)/16*14*channel_count)/channel_count; - if(loopStart%vgmstream->interleave_block_size) { - vgmstream->loop_start_sample += (((loopStart%vgmstream->interleave_block_size)-1)/16*14*channel_count); - } - - if(loopEnd==fileLength) - { - vgmstream->loop_end_sample=(loopEnd/16*28)/channel_count; - } else { - vgmstream->loop_end_sample = ((((loopEnd/vgmstream->interleave_block_size)-1)*vgmstream->interleave_block_size)/16*14*channel_count)/channel_count; - - if(loopEnd%vgmstream->interleave_block_size) { - vgmstream->loop_end_sample += (((loopEnd%vgmstream->interleave_block_size)-1)/16*14*channel_count); - } - } - } - } - - if(loopToEnd) - { - // try to find if there's no empty line ... - int emptySamples=0; - - for(i=0; i<16;i++) { - mibBuffer[i]=0; - } - - readOffset=fileLength-0x10; - - do { - read_streamfile(testBuffer,readOffset,0x10,streamFile); - if(!memcmp(mibBuffer,testBuffer,16)) - { - emptySamples+=28; - } - readOffset-=0x10; - } while(!memcmp(testBuffer,mibBuffer,16)); - - vgmstream->loop_end_sample-=(emptySamples*channel_count); - } - vgmstream->meta_type = meta_PS2_MIB; - - if (gotMIH) { - vgmstream->meta_type = meta_PS2_MIB_MIH; - close_streamfile(streamFileMIH); streamFileMIH=NULL; - } - - /* open the file for reading by each channel */ + /* Search for interleave value (checking channel starts) and loop points (using PS-ADPCM flags). + * Channel start will by 0x0000, 0x0002, 0x0006 followed by 12 zero values. + * Interleave value is the offset where those repeat, and channels the number of times. + * Loop flags in second byte are: 0x06 = start, 0x03 = end (per channel). + * Interleave can be large (up to 0x20000 found so far) and is always a 0x10 multiple value. */ + readOffset+=(off_t)read_streamfile(mibBuffer,0,0x10,streamFile); + mibBuffer[0]=0; { - for (i=0;ich[i].streamfile = streamFile->open(streamFile,filename,0x8000); + uint8_t doChannelUpdate=1; + uint8_t bDoUpdateInterleave=1; - if (!vgmstream->ch[i].streamfile) goto fail; + readOffset=0; + do { + readOffset+=(off_t)read_streamfile(testBuffer,readOffset,0x10,streamFile); + // be sure to point to an interleave value + if(readOffset<(int32_t)(fileLength*0.5)) { - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=i*vgmstream->interleave_block_size; + if(memcmp(testBuffer+2, mibBuffer+2,0x0e)) { + if(doChannelUpdate) { + doChannelUpdate=0; + channel_count++; + } + if(channel_count<2) + bDoUpdateInterleave=1; + } + + testBuffer[0]=0; + if(!memcmp(testBuffer,mibBuffer,0x10)) { + gotEmptyLine=1; + + if(bDoUpdateInterleave) { + bDoUpdateInterleave=0; + interleave=readOffset-0x10; + } + if(readOffset-0x10 == channel_count*interleave) { + doChannelUpdate=1; + } + } + } + + // Loop Start ... + if(testBuffer[0x01]==0x06) { + if(loopStartPointsCount<0x10) { + loopStartPoints[loopStartPointsCount] = readOffset-0x10; + loopStartPointsCount++; + } + } + + // Loop End ... + if(testBuffer[0x01]==0x03 && testBuffer[0x03]!=0x77) { + if(loopEndPointsCount<0x10) { + loopEndPoints[loopEndPointsCount] = readOffset; + loopEndPointsCount++; + } + } + + if(testBuffer[0x01]==0x04) { + // 0x04 loop points flag can't be with a 0x03 loop points flag + if(loopStartPointsCount<0x10) { + loopStartPoints[loopStartPointsCount] = readOffset-0x10; + loopStartPointsCount++; + + // Loop end value is not set by flags ... + // go until end of file + loopToEnd=1; + } + } + + } while (streamFile->get_offset(streamFile)<((int32_t)fileLength)); + } + + if(testBuffer[0]==0x0c && testBuffer[1]==0) + forceNoLoop=1; + + if(channel_count==0) + channel_count=1; + + if(gotMIH) + channel_count=read_32bitLE(0x08,streamFileMIH); + + // force no loop + if(!strcasecmp("vb",filename_extension(filename))) + loopStart=0; + + if(!strcasecmp("xag",filename_extension(filename))) + channel_count=2; + + // Calc Loop Points & Interleave ... + if(loopStartPointsCount>=2) { + // can't get more then 0x10 loop point ! + if(loopStartPointsCount<=0x0F) { + // Always took the first 2 loop points + interleave=loopStartPoints[1]-loopStartPoints[0]; + loopStart=loopStartPoints[1]; + + // Can't be one channel .mib with interleave values + if(interleave>0 && channel_count==1) + channel_count=2; + } else { + loopStart=0; } } + if(loopEndPointsCount>=2) { + // can't get more then 0x10 loop point ! + if(loopEndPointsCount<=0x0F) { + // No need to recalculate interleave value ... + loopEnd=loopEndPoints[loopEndPointsCount-1]; + + // Can't be one channel .mib with interleave values + if(channel_count==1) + channel_count=2; + } else { + loopToEnd=0; + loopEnd=0; + } + } + + if (loopToEnd) + loopEnd=fileLength; + + if(forceNoLoop) + loopEnd=0; + + if(interleave>0x10 && channel_count==1) + channel_count=2; + + if(interleave==0) + interleave=0x10; + + // further check on channel_count ... + if(gotEmptyLine) { + int newChannelCount = 0; + + readOffset=0; + + /* count empty lines at interleave = channels */ + do { + newChannelCount++; + read_streamfile(testBuffer,readOffset,0x10,streamFile); + readOffset+=interleave; + } while(!memcmp(testBuffer,mibBuffer,16)); + + newChannelCount--; + if(newChannelCount>channel_count) + channel_count=newChannelCount; + } + + if (!strcasecmp("cvs", filename_extension(filename)) || + !strcasecmp("vb",filename_extension(filename))) + channel_count=1; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,(loopEnd!=0)); + if (!vgmstream) goto fail; + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = (channel_count == 1) ? layout_none : layout_interleave; + + if(gotMIH) { + // Read stuff from the MIH file + vgmstream->sample_rate = read_32bitLE(0x0C,streamFileMIH); + vgmstream->interleave_block_size = read_32bitLE(0x10,streamFileMIH); + vgmstream->num_samples=((read_32bitLE(0x10,streamFileMIH)* + (read_32bitLE(0x14,streamFileMIH)-1)*2)+ + ((read_32bitLE(0x04,streamFileMIH)>>8)*2))/16*28/2; + } else { + vgmstream->interleave_block_size = interleave; + + if(!strcasecmp("mib",filename_extension(filename))) + vgmstream->sample_rate = 44100; + + if(!strcasecmp("mi4",filename_extension(filename))) + vgmstream->sample_rate = 48000; + + if(!strcasecmp("snds", filename_extension(filename))) + vgmstream->sample_rate = 48000; + + if(!strcasecmp("xag",filename_extension(filename))) + vgmstream->sample_rate = 44100; + + if (!strcasecmp("cvs", filename_extension(filename)) || + !strcasecmp("vb",filename_extension(filename))) + vgmstream->sample_rate = 22050; + + vgmstream->num_samples = (int32_t)(fileLength/16/channel_count*28); + } + + if(loopEnd!=0) { + if(vgmstream->channels==1) { + vgmstream->loop_start_sample = loopStart/16*18; //todo 18 instead of 28 probably a bug + vgmstream->loop_end_sample = loopEnd/16*28; + } else { + vgmstream->loop_start_sample = ((((loopStart/vgmstream->interleave_block_size)-1)*vgmstream->interleave_block_size)/16*14*channel_count)/channel_count; + if(loopStart%vgmstream->interleave_block_size) { + vgmstream->loop_start_sample += (((loopStart%vgmstream->interleave_block_size)-1)/16*14*channel_count); + } + + if(loopEnd==fileLength) { + vgmstream->loop_end_sample=(loopEnd/16*28)/channel_count; + } else { + vgmstream->loop_end_sample = ((((loopEnd/vgmstream->interleave_block_size)-1)*vgmstream->interleave_block_size)/16*14*channel_count)/channel_count; + if(loopEnd%vgmstream->interleave_block_size) { + vgmstream->loop_end_sample += (((loopEnd%vgmstream->interleave_block_size)-1)/16*14*channel_count); + } + } + } + } + + if(loopToEnd) { + // try to find if there's no empty line ... + int emptySamples=0; + + for(i=0; i<16;i++) { + mibBuffer[i]=0; //memset + } + + readOffset=fileLength-0x10; + do { + read_streamfile(testBuffer,readOffset,0x10,streamFile); + if(!memcmp(mibBuffer,testBuffer,16)) { + emptySamples+=28; + } + readOffset-=0x10; + } while(!memcmp(testBuffer,mibBuffer,16)); + + vgmstream->loop_end_sample-=(emptySamples*channel_count); + } + + vgmstream->meta_type = gotMIH ? meta_PS2_MIB_MIH : meta_PS2_MIB; + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + + + close_streamfile(streamFileMIH); return vgmstream; - /* clean up anything we may have opened */ fail: - if (streamFileMIH) close_streamfile(streamFileMIH); - if (vgmstream) close_vgmstream(vgmstream); + close_streamfile(streamFileMIH); + close_vgmstream(vgmstream); return NULL; } + +/* tests some PS-ADPCM frames */ +static int check_psadpcm(STREAMFILE *streamFile) { + off_t offset, max_offset; + + max_offset = get_streamfile_size(streamFile); + if (max_offset > 0x2000) + max_offset = 0x2000; + + offset = 0x00; + while (offset < max_offset) { + uint8_t predictor = (read_8bit(offset+0x00,streamFile) >> 4) & 0x0f; + uint8_t flags = read_8bit(offset+0x01,streamFile); + + if (predictor > 5 || flags > 7) + goto fail; + offset += 0x10; + } + + return 1; +fail: + return 0; +} diff --git a/src/meta/ubi_bao.c b/src/meta/ubi_bao.c new file mode 100644 index 00000000..1ce698cc --- /dev/null +++ b/src/meta/ubi_bao.c @@ -0,0 +1,517 @@ +#include "meta.h" +#include "../coding/coding.h" + + +typedef enum { NONE = 0, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2, RAW_AT3, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; +typedef struct { + ubi_bao_codec codec; + int big_endian; + int total_subsongs; + + /* stream info */ + size_t header_size; + size_t stream_size; + off_t stream_offset; + uint32_t stream_id; + off_t extradata_offset; + int is_external; + + int header_codec; + int num_samples; + int sample_rate; + int channels; + + char resource_name[255]; + int types_count[9]; +} ubi_bao_header; + +static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset); +static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile); +static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE *streamFile); + + +/* .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")) + goto fail; + + /* .pk+spk (or .lpk+lspk) is a database-like format, evolved from Ubi sb0/sm0+sp0. + * .pk has "BAO" headers pointing to internal or external .spk resources (also BAOs). */ + + /* main parse */ + if ( !parse_pk_header(&bao, streamFile) ) + goto fail; + + return init_vgmstream_ubi_bao_main(&bao, streamFile); +fail: + return NULL; +} + +#if 0 +/* .BAO - files with a single BAO from Ubisoft's sound engine ("DARE") games in 2008+ */ +VGMSTREAM * init_vgmstream_ubi_bao_file(STREAMFILE *streamFile) { + ubi_bao_header bao = {0}; + + /* checks */ + if (!check_extensions(streamFile, "bao")) + goto fail; + + /* single .bao+sbao found in .forge and similar bigfiles (containing compressed + * "BAO_0xNNNNNNNN" headers/links, or "Common/English/(etc)_BAO_0xNNNNNNNN" streams). + * The bigfile acts as index, but external files can be opened as are named after their id. + * Extension isn't always given but is .bao in some games. */ + + /* main parse */ + if ( !parse_bao_header(&bao, streamFile) ) + goto fail; + + return init_vgmstream_ubi_bao_main(&bao, streamFile); +fail: + return NULL; +} +#endif + + +static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *streamData = NULL; + off_t start_offset; + int loop_flag = 0; + + + /* open external stream if needed */ + if (bao->is_external) { + streamData = open_streamfile_by_filename(streamFile,bao->resource_name); + if (!streamData) { + VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); + goto fail; + } + } + else { + streamData = streamFile; + } + + start_offset = bao->stream_offset; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(bao->channels,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->num_samples = bao->num_samples; + vgmstream->sample_rate = bao->sample_rate; + vgmstream->num_streams = bao->total_subsongs; + vgmstream->stream_size = bao->stream_size; + vgmstream->meta_type = meta_UBI_BAO; + + switch(bao->codec) { +#if 0 + case UBI_ADPCM: { + vgmstream->coding_type = coding_UBI_IMA; + vgmstream->layout_type = layout_none; + break; + } +#endif + + case RAW_PCM: + vgmstream->coding_type = coding_PCM16LE; /* always LE even on Wii */ + 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->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; + dsp_read_coefs_be(vgmstream,streamFile,bao->extradata_offset+0x10, 0x40); + break; + +#ifdef VGM_USE_FFMPEG + case RAW_XMA1: + case RAW_XMA2: { + uint8_t buf[0x100]; + size_t bytes, chunk_size; + + chunk_size = (bao->codec == RAW_XMA1) ? 0x20 : 0x34; + + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, bao->extradata_offset,chunk_size, bao->stream_size, streamFile, 1); + vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, 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 RAW_AT3: { + uint8_t buf[0x100]; + int32_t bytes, block_size, encoder_delay, joint_stereo; + + block_size = 0xc0 * vgmstream->channels; + joint_stereo = 0; + encoder_delay = 0x00;//todo not correct + + bytes = ffmpeg_make_riff_atrac3(buf,0x100, vgmstream->num_samples, bao->stream_size, vgmstream->channels, vgmstream->sample_rate, block_size, joint_stereo, encoder_delay); + vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, 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: { + 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; + + /* manually read skip_samples if FFmpeg didn't do it */ + if (ffmpeg_data->skipSamples <= 0) { + off_t chunk_offset; + size_t chunk_size, fact_skip_samples = 0; + if (!find_chunk_le(streamData, 0x66616374,start_offset+0xc,0, &chunk_offset,&chunk_size)) /* find "fact" */ + goto fail; + if (chunk_size == 0x8) { + fact_skip_samples = read_32bitLE(chunk_offset+0x4, streamData); + } else if (chunk_size == 0xc) { + fact_skip_samples = read_32bitLE(chunk_offset+0x8, streamData); + } + ffmpeg_set_skip_samples(ffmpeg_data, fact_skip_samples); + } + 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 differ\n"); + break; + } + +#endif + default: + goto fail; + } + + /* open the file for reading (can be an external stream, different from the current .pk) */ + if ( !vgmstream_open_stream(vgmstream, streamData, start_offset) ) + goto fail; + + if (bao->is_external && streamData) close_streamfile(streamData); + return vgmstream; + +fail: + if (bao->is_external && streamData) close_streamfile(streamData); + close_vgmstream(vgmstream); + return NULL; +} + +/* parse a .pk (package) file: index + BAOs + external .spk resource table. We want header + * BAOs pointing to internal/external stream BAOs (.spk is the same, with stream BAOs only). */ +static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { + int i; + int index_entries; + size_t index_size, index_header_size; + off_t bao_offset, resources_offset; + int target_subsong = streamFile->stream_index; + + + /* class: 0x01=index, 0x02=BAO */ + if (read_8bit(0x00, streamFile) != 0x01) + goto fail; + /* index and resources always LE */ + + /* 0x01(3): version, major/minor/release (numbering continues from .sb0/sm0) */ + index_size = read_32bitLE(0x04, streamFile); /* can be 0 */ + resources_offset = read_32bitLE(0x08, streamFile); /* 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) */ + + index_entries = index_size / 0x08; + index_header_size = 0x40; + + /* parse index to get target subsong N = Nth 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, streamFile); + size_t bao_size = read_32bitLE(index_header_size+0x08*i+0x04, streamFile); + + /* parse and continue to find out total_subsongs */ + if (!parse_bao(bao, streamFile, bao_offset)) + goto fail; + + bao_offset += bao_size; /* files simply concat BAOs */ + } + + ;VGM_LOG("BAO types: 10=%i,20=%i,30=%i,40=%i,50=%i,70=%i,80=%i\n", + bao->types_count[1],bao->types_count[2],bao->types_count[3],bao->types_count[4],bao->types_count[5],bao->types_count[7],bao->types_count[8]); + + if (bao->total_subsongs == 0) { + VGM_LOG("UBI BAO: no streams\n"); + goto fail; /* not uncommon */ + } + if (target_subsong < 0 || target_subsong > bao->total_subsongs || bao->total_subsongs < 1) goto fail; + + + /* get stream pointed by header */ + if (bao->is_external) { + /* parse resource table, LE (may be empty, or exist even with nothing in the file) */ + off_t offset; + int resources_count = read_32bitLE(resources_offset+0x00, streamFile); + size_t strings_size = read_32bitLE(resources_offset+0x04, streamFile); + + 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->header_size; + read_string(bao->resource_name,255, resources_offset + 0x04+0x04 + name_offset, streamFile); + + VGM_ASSERT(bao->stream_size != resource_size - bao->header_size, "UBI BAO: stream vs resource size mismatch\n"); + break; + } + } + + + //todo find flag and fix + /* some songs divide data in internal+external resource and data may be split arbitrarily, + * must join on reads (needs multifile_streamfile); resources may use block layout in XMA too */ + bao_offset = index_header_size + index_size; + for (i = 0; i < index_entries; i++) { + uint32_t bao_id = read_32bitLE(index_header_size+0x08*i+0x00, streamFile); + size_t bao_size = read_32bitLE(index_header_size+0x08*i+0x04, streamFile); + + if (bao_id == bao->stream_id) { + VGM_LOG("UBI BAO: found internal+external at offset=%lx\n",bao_offset); + goto fail; + } + + bao_offset += bao_size; + } + } + else { + /* find within index */ + + bao_offset = index_header_size + index_size; + for (i = 0; i < index_entries; i++) { + uint32_t bao_id = read_32bitLE(index_header_size+0x08*i+0x00, streamFile); + size_t bao_size = read_32bitLE(index_header_size+0x08*i+0x04, streamFile); + + if (bao_id == bao->stream_id) { + bao->stream_offset = bao_offset + bao->header_size; /* relative, adjust to skip descriptor */ + break; + } + + bao_offset += bao_size; + } + } + + if (!bao->stream_offset) { + VGM_LOG("UBI BAO: stream not found (id=%08x, external=%i)\n", bao->stream_id, bao->is_external); + goto fail; + } + + ;VGM_LOG("BAO stream: id=%x, offset=%lx, size=%x, res=%s\n", bao->stream_id, bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); + + return 1; +fail: + return 0; +} + +/* parse a single BAO (binary audio object) descriptor */ +static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + uint32_t bao_version, descriptor_type; + size_t header_size; + int target_subsong = streamFile->stream_index; + + + /* 0x00(1): class? usually 0x02 but older BAOs have 0x01 too */ + bao_version = read_32bitBE(offset + 0x00, streamFile) & 0x00FFFFFF; + + /* detect endianness */ + if (read_32bitLE(offset+0x04, streamFile) < 0x0000FFFF) { + read_32bit = read_32bitLE; + } else { + read_32bit = read_32bitBE; + bao->big_endian = 1; + } + + header_size = read_32bit(offset+0x04, streamFile); /* mainly 0x28, rarely 0x24 */ + /* 0x08(10): descriptor GUID? */ + /* 0x18: null */ + /* 0x1c: null */ + descriptor_type = read_32bit(offset+0x20, streamFile); + /* 0x28: subtype? usually 0x02/0x01, games may crash if changed */ + + /* for debugging purposes */ + switch(descriptor_type) { + case 0x10000000: bao->types_count[1]++; break; /* link by id to another descriptor (link or header) */ + case 0x20000000: bao->types_count[2]++; break; /* stream header (and subtypes) */ + case 0x30000000: bao->types_count[3]++; break; /* internal stream (in .pk) */ + case 0x40000000: bao->types_count[4]++; break; /* package info? */ + case 0x50000000: bao->types_count[5]++; break; /* external stream (in .spk) */ + case 0x70000000: bao->types_count[7]++; break; /* project info? (sometimes special id 0x7fffffff)*/ + case 0x80000000: bao->types_count[8]++; break; /* unknown (some id/info?) */ + default: + VGM_LOG("UBI BAO: unknown descriptor type at %lx\n", offset); + goto fail; + } + + /* only parse headers */ + if (descriptor_type != 0x20000000) + return 1; + /* ignore other header subtypes, 0x01=sound header, 0x04=info? (like Ubi .sb0) */ + if (read_32bit(offset+header_size+0x04, streamFile) != 0x01) + return 1; + + bao->total_subsongs++; + if (target_subsong == 0) target_subsong = 1; + + if (target_subsong != bao->total_subsongs) + return 1; + + /* parse BAO per version. Structure is mostly the same with some extra fields. + * - descriptor id (ignored by game) + * - type (may crash on game startup if changed) + * - stream size + * - stream id, corresponding to an internal (0x30) or external (0x50) stream + * - various flags/config fields + * - channels, ?, sample rate, average bit rate?, samples, full stream_size?, codec, etc + * - subtable entries, subtable size (may contain offsets/ids, cues, etc) + * - extra data per codec (ex. XMA header in some versions) */ + //todo skip tables when getting extradata + ;VGM_LOG("BAO header at %lx\n", offset); + + switch(bao_version) { + + case 0x001F0011: /* Naruto: The Broken Bond (X360)-pk */ + case 0x0022000D: /* Just Dance (Wii)-pk */ + bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); + bao->stream_id = read_32bit(offset+header_size+0x1c, streamFile); + bao->is_external = read_32bit(offset+header_size+0x28, streamFile); /* maybe 0x30 */ + bao->channels = read_32bit(offset+header_size+0x44, streamFile); + bao->sample_rate = read_32bit(offset+header_size+0x4c, streamFile); + bao->num_samples = read_32bit(offset+header_size+0x54, streamFile); + bao->header_codec = read_32bit(offset+header_size+0x64, streamFile); + + switch(bao->header_codec) { + case 0x01: bao->codec = RAW_PCM; break; + case 0x05: bao->codec = RAW_XMA1; break; + case 0x09: bao->codec = RAW_DSP; break; + default: VGM_LOG("UBI BAO: unknown codec at %lx\n", offset); goto fail; + } + + //todo use flags? + if (bao->header_codec == 0x09) { + bao->extradata_offset = offset+header_size+0x80; /* mini DSP header */ + } + if (bao->header_codec == 0x05 && !bao->is_external) { + bao->extradata_offset = offset+header_size + 0x7c; /* XMA header */ + } + //todo external XMA may use blocked layout + layered layout + + break; + + case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-pk */ + case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-pk */ + bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); + bao->stream_id = read_32bit(offset+header_size+0x1c, streamFile); + bao->is_external = read_32bit(offset+header_size+0x20, streamFile) & 0x04; + bao->channels = read_32bit(offset+header_size+0x28, streamFile); + bao->sample_rate = read_32bit(offset+header_size+0x30, streamFile); + if (read_32bit(offset+header_size+0x20, streamFile) & 0x20) { + bao->num_samples = read_32bit(offset+header_size+0x40, streamFile); + } + else { + bao->num_samples = read_32bit(offset+header_size+0x38, streamFile); /* from "fact" if AT3 */ + } + bao->header_codec = read_32bit(offset+header_size+0x48, streamFile); + + switch(bao->header_codec) { + case 0x06: bao->codec = RAW_PSX; break; + case 0x07: bao->codec = FMT_AT3; break; + default: VGM_LOG("UBI BAO: unknown codec at %lx\n", offset); goto fail; + } + + if (read_32bit(offset+header_size+0x20, streamFile) & 0x10) { + VGM_LOG("UBI BAO: possible full loop at %lx\n", offset); + /* RIFFs may have "smpl" and this flag, even when data shouldn't loop... */ + } + + break; + + case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-pk */ + case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-file */ + bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); + bao->stream_id = read_32bit(offset+header_size+0x24, streamFile); + bao->is_external = read_32bit(offset+header_size+0x30, streamFile); + bao->channels = read_32bit(offset+header_size+0x48, streamFile); + bao->sample_rate = read_32bit(offset+header_size+0x50, streamFile); + if (read_32bit(offset+header_size+0x38, streamFile) & 0x01) { /* single flag? */ + bao->num_samples = read_32bit(offset+header_size+0x60, streamFile); + } + else { + bao->num_samples = read_32bit(offset+header_size+0x58, streamFile); + } + bao->header_codec = read_32bit(offset+header_size+0x68, streamFile); + /* when is internal+external (flag 0x2c?), 0xa0: internal data size */ + + switch(bao->header_codec) { + case 0x01: bao->codec = RAW_PCM; break; + case 0x04: bao->codec = RAW_XMA2; break; + case 0x05: bao->codec = RAW_PSX; break; + case 0x06: bao->codec = RAW_AT3; break; + default: VGM_LOG("UBI BAO: unknown codec at %lx\n", offset); goto fail; + } + + if (bao->header_codec == 0x04 && !bao->is_external) { + bao->extradata_offset = offset+header_size + 0x8c; /* XMA header */ + } + + break; + + case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-file */ + case 0x001B0200: /* Beowulf (PS3)-file */ + case 0x001F0010: /* Prince of Persia 2008 (PS3/X360)-file, Far Cry 2 (PS3)-file */ + case 0x00280306: /* Far Cry 3: Blood Dragon (X360)-file */ + case 0x00290106: /* Splinter Cell Blacklist? */ + default: + VGM_LOG("UBI BAO: unknown BAO version at %lx\n", offset); + goto fail; + } + + bao->header_size = header_size; + + return 1; +fail: + return 0; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 14cc245d..832b36b1 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -405,6 +405,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ea_sps_fb, init_vgmstream_ppst, init_vgmstream_opus_ppp, + init_vgmstream_ubi_bao_pk, + init_vgmstream_dsp_switch_audio, init_vgmstream_txth, /* should go at the end (lower priority) */ #ifdef VGM_USE_FFMPEG diff --git a/src/vgmstream.h b/src/vgmstream.h index e16eb930..3d285125 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -673,6 +673,8 @@ typedef enum { meta_OGG_YS8, /* Ogg Vorbis with encryption (Ys VIII PC) */ meta_PPST, /* PPST [Parappa the Rapper (PSP)] */ meta_OPUS_PPP, /* .at9 Opus [Penny-Punching Princess (Switch)] */ + meta_UBI_BAO, /* Ubisoft BAO */ + meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */ #ifdef VGM_USE_FFMPEG meta_FFmpeg,