mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-18 15:54:05 +01:00
commit
35cd14fa04
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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') {
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
//
|
//
|
||||||
|
@ -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"},
|
||||||
|
@ -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 */
|
||||||
|
@ -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"
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
||||||
|
@ -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]);
|
||||||
|
@ -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 {
|
||||||
|
@ -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*/
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
517
src/meta/ubi_bao.c
Normal 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;
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user