Merge pull request #220 from bnnm/bao-downmix-etc

BAO, downmix, etc
This commit is contained in:
Christopher Snowhill 2018-04-29 17:04:19 -07:00 committed by GitHub
commit 35cd14fa04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1047 additions and 656 deletions

View File

@ -23,6 +23,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
// //
// Dialog // Dialog
// //
//elements: text, id, x, y, width, height [, style [, extended-style]]
IDD_CONFIG DIALOGEX 0, 0, 187, 156 IDD_CONFIG DIALOGEX 0, 0, 187, 156
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD 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 "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 "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 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 END

View File

@ -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_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_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_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_LoopForever(guid_cfg_LoopForever, DEFAULT_LOOP_FOREVER);
static cfg_bool cfg_IgnoreLoop(guid_cfg_IgnoreLoop, DEFAULT_IGNORE_LOOP); 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_FadeLength(guid_cfg_FadeLength, DEFAULT_FADE_SECONDS);
static cfg_string cfg_FadeDelay(guid_cfg_FadeDelay, DEFAULT_FADE_DELAY_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_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 // Needs to be here in rder to access the static config
void input_vgmstream::load_settings() void input_vgmstream::load_settings()
@ -38,6 +40,7 @@ void input_vgmstream::load_settings()
loop_forever = cfg_LoopForever; loop_forever = cfg_LoopForever;
ignore_loop = cfg_IgnoreLoop; ignore_loop = cfg_IgnoreLoop;
disable_subsongs = cfg_DisableSubsongs; disable_subsongs = cfg_DisableSubsongs;
sscanf(cfg_DownmixChannels.get_ptr(),"%d",&downmix_channels);
} }
const char * vgmstream_prefs::get_name() 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); CheckDlgButton(IDC_DISABLE_SUBSONGS, cfg_DisableSubsongs?BST_CHECKED:BST_UNCHECKED);
uSetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS, cfg_DownmixChannels);
return TRUE; return TRUE;
} }
@ -93,6 +98,8 @@ void vgmstreamPreferences::reset()
uSetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS, DEFAULT_FADE_DELAY_SECONDS); uSetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS, DEFAULT_FADE_DELAY_SECONDS);
CheckDlgButton(IDC_DISABLE_SUBSONGS, DEFAULT_DISABLE_SUBSONGS?BST_CHECKED:BST_UNCHECKED); 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_fade_delay_seconds;
double temp_loop_count; double temp_loop_count;
int consumed; int consumed;
int temp_downmix_channels;
pfc::string buf; pfc::string buf;
buf = uGetDlgItemText(m_hWnd, IDC_FADE_SECONDS); buf = uGetDlgItemText(m_hWnd, IDC_FADE_SECONDS);
@ -141,6 +149,18 @@ void vgmstreamPreferences::apply()
"Error",MB_OK|MB_ICONERROR); "Error",MB_OK|MB_ICONERROR);
return; return;
} else cfg_FadeDelay = buf.get_ptr(); } 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(FadeDelay != uGetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS)) return true;
if(LoopCount != uGetDlgItemText(m_hWnd, IDC_LOOP_COUNT)) 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; return FALSE;
} }

View File

@ -15,6 +15,7 @@
#define DEFAULT_LOOP_FOREVER false #define DEFAULT_LOOP_FOREVER false
#define DEFAULT_IGNORE_LOOP false #define DEFAULT_IGNORE_LOOP false
#define DEFAULT_DISABLE_SUBSONGS false #define DEFAULT_DISABLE_SUBSONGS false
#define DEFAULT_DOWNMIX_CHANNELS "8"
class vgmstreamPreferences : public CDialogImpl<vgmstreamPreferences>, public preferences_page_instance { class vgmstreamPreferences : public CDialogImpl<vgmstreamPreferences>, public preferences_page_instance {
public: public:
@ -42,6 +43,7 @@ public:
COMMAND_HANDLER_EX(IDC_FADE_DELAY_SECONDS, EN_CHANGE, OnEditChange) COMMAND_HANDLER_EX(IDC_FADE_DELAY_SECONDS, EN_CHANGE, OnEditChange)
COMMAND_HANDLER_EX(IDC_LOOP_COUNT, 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_DISABLE_SUBSONGS, BN_CLICKED, OnEditChange)
COMMAND_HANDLER_EX(IDC_DOWNMIX_CHANNELS, EN_CHANGE, OnEditChange)
END_MSG_MAP() END_MSG_MAP()
private: private:
BOOL OnInitDialog(CWindow, LPARAM); BOOL OnInitDialog(CWindow, LPARAM);

View File

@ -56,6 +56,7 @@ input_vgmstream::input_vgmstream() {
loop_forever = false; loop_forever = false;
ignore_loop = 0; ignore_loop = 0;
disable_subsongs = false; disable_subsongs = false;
downmix_channels = 0;
load_settings(); 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])); /* downmix enabled (foobar refuses to do more than 8 channels) */
p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, vgmstream->channels, 16, audio_chunk::g_guess_channel_config(vgmstream->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_samples+=samples_to_do;
decode_pos_ms=decode_pos_samples*1000LL/vgmstream->sample_rate; 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); title.set_string(p, e - p);
if (!disable_subsongs && infostream && infostream->num_streams > 1) { 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; title += temp;
if (infostream->stream_name[0] != '\0') { if (infostream->stream_name[0] != '\0') {

View File

@ -61,6 +61,7 @@ class input_vgmstream : public input_stubs {
bool force_ignore_loop; bool force_ignore_loop;
int ignore_loop; int ignore_loop;
bool disable_subsongs; bool disable_subsongs;
int downmix_channels;
/* helpers */ /* helpers */
VGMSTREAM * init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort); VGMSTREAM * init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort);

View File

@ -14,6 +14,7 @@
#define IDC_THREAD_PRIORITY_TEXT 1007 #define IDC_THREAD_PRIORITY_TEXT 1007
#define IDC_DEFAULT_BUTTON 1008 #define IDC_DEFAULT_BUTTON 1008
#define IDC_DISABLE_SUBSONGS 1009 #define IDC_DISABLE_SUBSONGS 1009
#define IDC_DOWNMIX_CHANNELS 1010
// Next default values for new objects // Next default values for new objects
// //

View File

@ -179,6 +179,7 @@ static const char* extension_list[] = {
"logg", //fake extension, for OGGs "logg", //fake extension, for OGGs
"lopus", //fake extension, for OPUS "lopus", //fake extension, for OPUS
"lpcm", "lpcm",
"lpk",
"lps", "lps",
"lsf", "lsf",
"lstm", //fake extension, for STMs "lstm", //fake extension, for STMs
@ -243,6 +244,7 @@ static const char* extension_list[] = {
"past", "past",
"pcm", "pcm",
"pdt", "pdt",
"pk",
"pnb", "pnb",
"pona", "pona",
"pos", "pos",
@ -337,6 +339,7 @@ static const char* extension_list[] = {
"swag", "swag",
"swav", "swav",
"swd", "swd",
"switch_audio"
"sx", "sx",
"sxd", "sxd",
"sxd2", "sxd2",
@ -345,6 +348,8 @@ static const char* extension_list[] = {
"thp", "thp",
"tk5", "tk5",
"tra", "tra",
"trj",
"trm",
"tun", "tun",
"txtp", "txtp",
"tydsp", "tydsp",
@ -484,7 +489,7 @@ static const coding_info coding_info_list[] = {
{coding_PSX, "Playstation 4-bit ADPCM"}, {coding_PSX, "Playstation 4-bit ADPCM"},
{coding_PSX_badflags, "Playstation 4-bit ADPCM (bad flags)"}, {coding_PSX_badflags, "Playstation 4-bit ADPCM (bad flags)"},
{coding_PSX_cfg, "Playstation 4-bit ADPCM (configurable)"}, {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, "Electronic Arts EA-XA 4-bit ADPCM v1"},
{coding_EA_XA_int, "Electronic Arts EA-XA 4-bit ADPCM v1 (mono/interleave)"}, {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_OGG_YS8, "Ogg Vorbis (Ys VIII header)"},
{meta_PPST, "Parappa PPST header"}, {meta_PPST, "Parappa PPST header"},
{meta_OPUS_PPP, "AT9 OPUS 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 #ifdef VGM_USE_FFMPEG
{meta_FFmpeg, "FFmpeg supported file format"}, {meta_FFmpeg, "FFmpeg supported file format"},

View File

@ -93,6 +93,14 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) {
break; 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 */ /* id, size, samples, hists-per-channel, stereo/interleaved data */
case coding_EA_XA: case coding_EA_XA:
//case coding_EA_XA_V2: /* handled in default */ //case coding_EA_XA_V2: /* handled in default */

View File

@ -1305,6 +1305,10 @@
<File <File
RelativePath=".\meta\txtp.c" RelativePath=".\meta\txtp.c"
> >
</File>
<File
RelativePath=".\meta\ubi_bao.c"
>
</File> </File>
<File <File
RelativePath=".\meta\ubi_ckd.c" RelativePath=".\meta\ubi_ckd.c"

View File

@ -406,6 +406,7 @@
<ClCompile Include="meta\ta_aac.c" /> <ClCompile Include="meta\ta_aac.c" />
<ClCompile Include="meta\thp.c" /> <ClCompile Include="meta\thp.c" />
<ClCompile Include="meta\vgs.c" /> <ClCompile Include="meta\vgs.c" />
<ClCompile Include="meta\ubi_bao.c" />
<ClCompile Include="meta\ubi_ckd.c" /> <ClCompile Include="meta\ubi_ckd.c" />
<ClCompile Include="meta\ubi_lyn.c" /> <ClCompile Include="meta\ubi_lyn.c" />
<ClCompile Include="meta\ubi_raki.c" /> <ClCompile Include="meta\ubi_raki.c" />

View File

@ -793,6 +793,9 @@
<ClCompile Include="meta\vgs.c"> <ClCompile Include="meta\vgs.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\ubi_bao.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ubi_ckd.c"> <ClCompile Include="meta\ubi_ckd.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>

View File

@ -26,7 +26,7 @@ static const adxkey_info adxkey8_list[] = {
{0x49e1,0x4a57,0x553d, "karaage",0}, {0x49e1,0x4a57,0x553d, "karaage",0},
/* Grasshopper Manufacture 0 (Blood+) */ /* Grasshopper Manufacture 0 (Blood+) */
{0x5f5d,0x58bd,0x55ed, NULL,0}, // estimated {0x5f5d,0x58bd,0x55ed, NULL,0}, // estimated (keystring not in ELF?)
/* Grasshopper Manufacture 1 (Killer7) */ /* Grasshopper Manufacture 1 (Killer7) */
{0x50fb,0x5803,0x5701, "GHM",0}, {0x50fb,0x5803,0x5701, "GHM",0},
@ -132,7 +132,7 @@ static const adxkey_info adxkey8_list[] = {
{0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx {0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx
/* Shakugan no Shana (2006)(Vridge)(Media Works)[PS2] */ /* 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] */ /* Uragiri wa Boku no Namae o Shitteiru (2010)(Kadokawa Shoten)[PS2] */
{0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx {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] // Super Robot Wars X-Omega (voices) [iOS/Android]
{0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96 {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]); static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]);

View File

@ -82,12 +82,12 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_
VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) {
off_t start_offset, header_offset; off_t start_offset, header_offset;
size_t header_size; size_t header_size;
ea_header ea; ea_header ea = {0};
/* check extension; exts don't seem enforced by EA's tools, but usually: /* 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) */ * 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; goto fail;
/* check header */ /* check header */
@ -121,7 +121,7 @@ fail:
VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) {
off_t start_offset, header_offset, offset, table_offset; off_t start_offset, header_offset, offset, table_offset;
size_t header_size; size_t header_size;
ea_header ea; ea_header ea = {0};
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
int i, bnk_version; int i, bnk_version;
@ -285,7 +285,11 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_
break; break;
case EA_CODEC2_S16LE: /* PCM16LE */ 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; break;
case EA_CODEC2_VAG: /* PS-ADPCM */ 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; uint32_t platform_id;
int is_header_end = 0; int is_header_end = 0;
memset(ea,0,sizeof(ea_header));
/* null defaults as 0 can be valid */ /* null defaults as 0 can be valid */
ea->version = EA_VERSION_NONE; 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 */ offset += 4 + 4; /* GSTRs have an extra field (config?): ex. 0x01000000, 0x010000D8 BE */
} }
else if ((platform_id & 0xFFFF0000) == 0x50540000) { /* "PT" = PlaTform */ 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; offset += 4;
} }
else { else {

View File

@ -747,4 +747,7 @@ VGMSTREAM * init_vgmstream_ppst(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_opus_ppp(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*/ #endif /*_META_H*/

View File

@ -24,9 +24,10 @@ struct dsp_header {
uint16_t loop_ps; uint16_t loop_ps;
int16_t loop_hist1; int16_t loop_hist1;
int16_t loop_hist2; int16_t loop_hist2;
/* later/mdsp extension */ int16_t channel_count; /* DSPADPCM.exe ~v2.7 extension */
int16_t channel_count;
int16_t block_size; 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 */ /* 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; int32_t (*get_32bit)(uint8_t *) = big_endian ? get_32bitBE : get_32bitLE;
int16_t (*get_16bit)(uint8_t *) = big_endian ? get_16bitBE : get_16bitLE; int16_t (*get_16bit)(uint8_t *) = big_endian ? get_16bitBE : get_16bitLE;
int i; int i;
uint8_t buf[0x4e]; /* usually padded out to 0x60 */ uint8_t buf[0x4e];
if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e) if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e)
return 1; return 1;
@ -131,20 +132,22 @@ static int check_dsp_samples(struct dsp_header* ch_header, int channels) {
fail: fail:
return 0; 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; int i;
/* check initial predictor/scale */ /* check initial predictor/scale */
for (i = 0; i < channels; i++) { 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; goto fail;
}
} }
return 1; return 1;
fail: fail:
return 0; 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; int i;
if (!ch_header[0].loop_flag) 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 */ /* check loop predictor/scale */
for (i = 0; i < channels; i++) { for (i = 0; i < channels; i++) {
off_t loop_offset = ch_header[i].loop_start_offset / 16 * 8; off_t loop_offset = ch_header[i].loop_start_offset;
if (ch_header[i].loop_ps != (uint8_t)read_8bit(offset + i*spacing + loop_offset,streamFile)) 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; goto fail;
} }
@ -172,9 +180,10 @@ typedef struct {
int force_loop; /* force full loop */ int force_loop; /* force full loop */
int fix_looping; /* fix loop end going past num_samples */ 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 single_header; /* all channels share header, thus totals are off */
int ignore_header_agreement; /* sometimes there are minor differences between headers */ 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; off_t header_offset;
size_t header_spacing; 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)) if (!dsp_load_header_endian(ch_header, dspm->channel_count, streamFile,dspm->header_offset,dspm->header_spacing, !dspm->little_endian))
goto fail; 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)) if (!check_dsp_format(ch_header, dspm->channel_count))
goto fail; goto fail;
@ -274,7 +292,7 @@ VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) {
int i, channel_count; int i, channel_count;
/* checks */ /* 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")) if (!check_extensions(streamFile, "dsp,adp"))
goto fail; goto fail;
@ -600,122 +618,49 @@ fail:
return NULL; return NULL;
} }
/* IDSP with multiple standard DSP headers - from SSB4 (3DS), Tekken Tag Tournament 2 (Wii U) */ /* IDSP - Namco header + interleaved dsp [SSB4 (3DS), Tekken Tag Tournament 2 (WiiU)] */
#define MULTI_IDSP_MAX_CHANNELS 8
VGMSTREAM * init_vgmstream_3ds_idsp(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_3ds_idsp(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; dsp_meta dspm = {0};
off_t offset;
off_t idsp_offset = 0; /* checks */
off_t start_offset; if (!check_extensions(streamFile, "idsp,nus3bank"))
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) )
goto fail; 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: fail:
close_vgmstream(vgmstream);
return NULL; return NULL;
} }
@ -731,9 +676,6 @@ VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile) {
dspm.channel_count = read_8bit(0x32, streamFile); dspm.channel_count = read_8bit(0x32, streamFile);
dspm.max_channels = 2; 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_offset = 0x80;
dspm.header_spacing = 0x60; 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 * init_vgmstream_ngc_swd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; dsp_meta dspm = {0};
char filename[PATH_LIMIT];
off_t start_offset;
off_t interleave;
struct dsp_header ch0_header, ch1_header; /* checks */
int i; if (!check_extensions(streamFile, "swd"))
/* 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 */
goto fail; goto fail;
start_offset = 0xC8; //todo blocked layout when first chunk is 0x50534631 (count + table of 0x0c with offset/sizes)
interleave = 0x8;
#if 0 if (read_32bitBE(0x00,streamFile) != 0x505346d1) /* PSF\0xd1 */
/* 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)
goto fail; goto fail;
/* check for agreement */ dspm.channel_count = 2;
if ( dspm.max_channels = 2;
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 0 dspm.header_offset = 0x08;
if (ch0_header.loop_flag) { dspm.header_spacing = 0x60;
off_t loop_off; dspm.start_offset = dspm.header_offset + 0x60 * dspm.channel_count;
/* check loop predictor/scale */ dspm.interleave = 0x08;
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.meta_type = meta_NGC_SWD;
return init_vgmstream_dsp_common(streamFile, &dspm);
fail: fail:
/* clean up anything we may have opened */
if (vgmstream) close_vgmstream(vgmstream);
return NULL; return NULL;
} }
@ -1182,119 +1042,29 @@ fail:
return NULL; return NULL;
} }
/* .dsp - Ubisoft raw interleaved dsp [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */ /* .dsp - Ubisoft interleaved dsp with bad loop start [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */
//todo unusual loop values (set at the end)
VGMSTREAM * init_vgmstream_dsp_xiii(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_dsp_xiii(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; dsp_meta dspm = {0};
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;
/* check extension */ /* checks */
if (!check_extensions(streamFile, "dsp")) if (!check_extensions(streamFile, "dsp"))
goto fail; goto fail;
channel_count = 2; dspm.channel_count = 2;
dspm.max_channels = 2;
ch1_header_start = 0x00; dspm.fix_loop_start = 1; /* loop flag but strange loop start instead of 0 (maybe shouldn't loop) */
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.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: fail:
/* clean up anything we may have opened */
if (vgmstream) close_vgmstream(vgmstream);
return NULL; return NULL;
} }
/* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */ /* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */
VGMSTREAM * init_vgmstream_wii_ndp(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_wii_ndp(STREAMFILE *streamFile) {
dsp_meta dspm = {0}; dsp_meta dspm = {0};
@ -1570,3 +1340,31 @@ VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile) {
fail: fail:
return NULL; 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;
}

View File

@ -1,362 +1,338 @@
#include "meta.h" #include "meta.h"
#include "../util.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 * init_vgmstream_ps2_mib(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
VGMSTREAM * vgmstream = NULL;
STREAMFILE * streamFileMIH = NULL; STREAMFILE * streamFileMIH = NULL;
off_t start_offset = 0x00;
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
uint8_t mibBuffer[0x10];
uint8_t testBuffer[0x10];
uint8_t doChannelUpdate=1;
uint8_t bDoUpdateInterleave=1;
size_t fileLength; uint8_t mibBuffer[0x10];
uint8_t testBuffer[0x10];
off_t loopStart = 0;
off_t loopEnd = 0;
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] = {0};
off_t loopStartPoints[0x10]; int loopStartPointsCount=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]; uint8_t gotMIH=0;
int loopEndPointsCount=0;
int loopToEnd=0; int i, channel_count=0;
int forceNoLoop=0;
int gotEmptyLine=0;
uint8_t gotMIH=0;
int i, channel_count=0; /* checks
* .mib: common, but many ext-less files are renamed to this.
// Initialize loop point to 0 * .mi4: fake .mib to force another sample rate
for(i=0; i<0x10; i++) { * .cvs: Aladdin - Nasira's Revenge (PS1)
loopStartPoints[i]=0; * .snds: The Incredibles (PS2)
loopEndPoints[i]=0; * .vb: Tantei Jinguuji Saburo - Mikan no Rupo (PS1)
} * .xag: Hagane no Renkinjutsushi - Dream Carnival (PS2)
* */
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename)); streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("cvs",filename_extension(filename)) && if (strcasecmp("cvs",filename_extension(filename)) &&
strcasecmp("mib",filename_extension(filename)) && strcasecmp("mib",filename_extension(filename)) &&
strcasecmp("mi4",filename_extension(filename)) && strcasecmp("mi4",filename_extension(filename)) &&
strcasecmp("snds",filename_extension(filename))&& strcasecmp("snds",filename_extension(filename))&&
strcasecmp("vb",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 */ /* test if raw PS-ADPCM */
strcpy(filenameMIH,filename); if (!check_psadpcm(streamFile))
strcpy(filenameMIH+strlen(filenameMIH)-3,"MIH"); goto fail;
streamFileMIH = streamFile->open(streamFile,filenameMIH,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (streamFileMIH) gotMIH = 1;
/* Search for interleave value & loop points */ /* .MIB may come with a .MIH header file */
/* Get the first 16 values */ if (strcasecmp("mib",filename_extension(filename))==0) {
fileLength = get_streamfile_size(streamFile); streamFileMIH = open_streamfile_by_ext(streamFile,"mih");
if (streamFileMIH)
readOffset+=(off_t)read_streamfile(mibBuffer,0,0x10,streamFile); gotMIH = 1;
readOffset=0; }
mibBuffer[0]=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)) {
if(memcmp(testBuffer+2, mibBuffer+2,0x0e)) { fileLength = get_streamfile_size(streamFile);
if(doChannelUpdate) {
doChannelUpdate=0;
channel_count++;
}
if(channel_count<2)
bDoUpdateInterleave=1;
}
testBuffer[0]=0; /* Search for interleave value (checking channel starts) and loop points (using PS-ADPCM flags).
if(!memcmp(testBuffer,mibBuffer,0x10)) { * 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.
gotEmptyLine=1; * 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. */
if(bDoUpdateInterleave) { readOffset+=(off_t)read_streamfile(mibBuffer,0,0x10,streamFile);
bDoUpdateInterleave=0; mibBuffer[0]=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 */
{ {
for (i=0;i<channel_count;i++) { uint8_t doChannelUpdate=1;
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,0x8000); 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= if(memcmp(testBuffer+2, mibBuffer+2,0x0e)) {
vgmstream->ch[i].offset=i*vgmstream->interleave_block_size; 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; return vgmstream;
/* clean up anything we may have opened */
fail: fail:
if (streamFileMIH) close_streamfile(streamFileMIH); close_streamfile(streamFileMIH);
if (vgmstream) close_vgmstream(vgmstream); close_vgmstream(vgmstream);
return NULL; 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;
}

517
src/meta/ubi_bao.c Normal file
View File

@ -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;
}

View File

@ -405,6 +405,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_ea_sps_fb, init_vgmstream_ea_sps_fb,
init_vgmstream_ppst, init_vgmstream_ppst,
init_vgmstream_opus_ppp, init_vgmstream_opus_ppp,
init_vgmstream_ubi_bao_pk,
init_vgmstream_dsp_switch_audio,
init_vgmstream_txth, /* should go at the end (lower priority) */ init_vgmstream_txth, /* should go at the end (lower priority) */
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG

View File

@ -673,6 +673,8 @@ typedef enum {
meta_OGG_YS8, /* Ogg Vorbis with encryption (Ys VIII PC) */ meta_OGG_YS8, /* Ogg Vorbis with encryption (Ys VIII PC) */
meta_PPST, /* PPST [Parappa the Rapper (PSP)] */ meta_PPST, /* PPST [Parappa the Rapper (PSP)] */
meta_OPUS_PPP, /* .at9 Opus [Penny-Punching Princess (Switch)] */ 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 #ifdef VGM_USE_FFMPEG
meta_FFmpeg, meta_FFmpeg,