Merge pull request #1175 from bnnm/winamp-akb-swav-etc

- Fix some .swav [Face Training (DSi)]
- txtp_maker: fix subsongs with unicode
- Add .3ds extension [F1 2021 (3DS)]
- Fix some WayForward .wave [Happy Feet Two (3DS)]
- Fix some Koei .wbd+whd [Nights of Azure 2 (PS4)]
- Add encrypted .akb [Final Fantasy Agito (Android)]
- Fix winamp's format detection hijacking .vgm
This commit is contained in:
bnnm 2022-07-23 15:55:10 +02:00 committed by GitHub
commit 449bb5e083
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 601 additions and 378 deletions

View File

@ -183,7 +183,11 @@ class TxtpInfo(object):
return str_cut.split()[0].strip() return str_cut.split()[0].strip()
def _get_text(self, str): def _get_text(self, str):
return self._get_string(str, full=True) text = self._get_string(str, full=True)
# stream names in CLI is printed as UTF-8 using '\xNN', so detect and transform
if text and '\\' in text:
return text.encode('ascii').decode('unicode-escape').encode('iso-8859-1').decode('utf-8')
return text
def _get_value(self, str): def _get_value(self, str):
res = self._get_string(str) res = self._get_string(str)

View File

@ -1132,6 +1132,7 @@ struct g7221_handle {
/* control */ /* control */
int bit_rate; int bit_rate;
int frame_size; int frame_size;
int test_errors;
/* AES setup/state */ /* AES setup/state */
s14aes_handle* aes; s14aes_handle* aes;
/* state */ /* state */
@ -1179,7 +1180,7 @@ int g7221_decode_frame(g7221_handle* handle, uint8_t* data, int16_t* out_samples
* so we could avoid one extra buffer, but for clarity we'll leave as is */ * so we could avoid one extra buffer, but for clarity we'll leave as is */
/* unpack data into MLT spectrum coefs */ /* unpack data into MLT spectrum coefs */
res = unpack_frame(handle->bit_rate, data, handle->frame_size, &mag_shift, handle->mlt_coefs, &handle->random_value, encrypted); res = unpack_frame(handle->bit_rate, data, handle->frame_size, &mag_shift, handle->mlt_coefs, &handle->random_value, handle->test_errors);
if (res < 0) goto fail; if (res < 0) goto fail;
/* convert coefs to samples using reverse (inverse) MLT */ /* convert coefs to samples using reverse (inverse) MLT */
@ -1254,6 +1255,7 @@ int g7221_set_key(g7221_handle* handle, const uint8_t* key) {
if (key == NULL) { if (key == NULL) {
s14aes_close(handle->aes); s14aes_close(handle->aes);
handle->aes = NULL; handle->aes = NULL;
handle->test_errors = 1; /* force? */
return 1; return 1;
} }
@ -1263,6 +1265,8 @@ int g7221_set_key(g7221_handle* handle, const uint8_t* key) {
if (!handle->aes) goto fail; if (!handle->aes) goto fail;
} }
handle->test_errors = 1;
/* Base key is XORed probably against memdumps, as plain key would be part of the final AES key. However /* Base key is XORed probably against memdumps, as plain key would be part of the final AES key. However
* roundkey is still in memdumps near AES state (~0x1310 from sbox table, that starts with 0x63,0x7c,0x77,0x7b...) * roundkey is still in memdumps near AES state (~0x1310 from sbox table, that starts with 0x63,0x7c,0x77,0x7b...)
* so it isn't too effective. XORing was originally done inside aes_expand_key during S14/S22 init. */ * so it isn't too effective. XORing was originally done inside aes_expand_key during S14/S22 init. */

View File

@ -25,6 +25,7 @@ static const char* extension_list[] = {
"2dx9", "2dx9",
"2pfs", "2pfs",
"3do", "3do",
"3ds", //txth/reserved [F1 2011 (3DS)]
"4", //for Game.com audio "4", //for Game.com audio
"8", //txth/reserved [Gungage (PS1)] "8", //txth/reserved [Gungage (PS1)]
"800", "800",

View File

@ -137,7 +137,7 @@
<ClInclude Include="meta\sab_streamfile.h" /> <ClInclude Include="meta\sab_streamfile.h" />
<ClInclude Include="meta\riff_ogg_streamfile.h" /> <ClInclude Include="meta\riff_ogg_streamfile.h" />
<ClInclude Include="meta\sfh_streamfile.h" /> <ClInclude Include="meta\sfh_streamfile.h" />
<ClInclude Include="meta\sqex_sead_streamfile.h" /> <ClInclude Include="meta\sqex_streamfile.h" />
<ClInclude Include="meta\txth_streamfile.h" /> <ClInclude Include="meta\txth_streamfile.h" />
<ClInclude Include="meta\ubi_bao_streamfile.h" /> <ClInclude Include="meta\ubi_bao_streamfile.h" />
<ClInclude Include="meta\ubi_sb_streamfile.h" /> <ClInclude Include="meta\ubi_sb_streamfile.h" />

View File

@ -173,7 +173,7 @@
<ClInclude Include="meta\sfh_streamfile.h"> <ClInclude Include="meta\sfh_streamfile.h">
<Filter>meta\Header Files</Filter> <Filter>meta\Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="meta\sqex_sead_streamfile.h"> <ClInclude Include="meta\sqex_streamfile.h">
<Filter>meta\Header Files</Filter> <Filter>meta\Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="meta\txth_streamfile.h"> <ClInclude Include="meta\txth_streamfile.h">

View File

@ -259,7 +259,7 @@ static const adxkey_info adxkey9_list[] = {
{0x0000,0x0000,0x0000, NULL,1991062320101111}, // 000712DC5250B6F7 {0x0000,0x0000,0x0000, NULL,1991062320101111}, // 000712DC5250B6F7
/* Shin Megami Tensei V (Switch) */ /* Shin Megami Tensei V (Switch) */
{0x000c,0x13b5,0x1fdb, NULL,0}, // guessed with VGAudio (possible key: 613B4FEE / 1631277038) {0x0000,0x0000,0x0000, NULL,1731948526}, // 00000000673B6FEE
}; };

View File

@ -1,48 +1,51 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../util.h" #include "../util.h"
#if 0
#include "adx_keys.h"
#endif
/* AHX - CRI format mainly for voices, contains MPEG-2 Layer 2 audio with lying frame headers */ /* AHX - CRI voice format */
VGMSTREAM * init_vgmstream_ahx(STREAMFILE *streamFile) { VGMSTREAM* init_vgmstream_ahx(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset; uint32_t start_offset;
int channel_count = 1, loop_flag = 0, type; int channels = 1, loop_flag = 0, type;
/* check extension, case insensitive */ /* checks */
if ( !check_extensions(streamFile, "ahx") ) goto fail; if (read_u16be(0x00,sf) != 0x8000)
goto fail;
/* check first 2 bytes */ if (!check_extensions(sf, "ahx") )
if ((uint16_t)read_16bitBE(0,streamFile)!=0x8000) goto fail; goto fail;
/* get stream offset, check for CRI signature just before */ start_offset = read_u16be(0x02,sf) + 0x04;
start_offset = (uint16_t)read_16bitBE(0x02,streamFile) + 0x04; if (read_u16be(start_offset - 0x06,sf) != 0x2863 || /* "(c" */
read_u32be(start_offset - 0x04,sf) != 0x29435249) /* ")CRI" */
if ((uint16_t)read_16bitBE(start_offset-0x06,streamFile)!=0x2863 || /* "(c" */
(uint32_t)read_32bitBE(start_offset-0x04,streamFile)!=0x29435249) /* ")CRI" */
goto fail; goto fail;
/* check for encoding type (0x10 is AHX for DC with bigger frames, 0x11 is AHX, 0x0N are ADX) */ /* types: 0x10 = AHX for DC with bigger frames, 0x11 = AHX, 0x0N = ADX */
type = read_8bit(0x04,streamFile); type = read_u8(0x04,sf);
if (type != 0x10 && type != 0x11) goto fail; if (type != 0x10 && type != 0x11) goto fail;
/* check for frame size (0 for AHX) */ /* frame size (0 for AHX) */
if (read_8bit(0x05,streamFile) != 0) goto fail; if (read_u8(0x05,sf) != 0) goto fail;
/* check for bits per sample? (0 for AHX) */ /* check for bits per sample? (0 for AHX) */
if (read_8bit(0x06,streamFile) != 0) goto fail; if (read_u8(0x06,sf) != 0) goto fail;
/* check channel count (only mono AHXs can be created by the encoder) */ /* check channel count (only mono AHXs can be created by the encoder) */
if (read_8bit(0x07,streamFile) != 1) goto fail; if (read_u8(0x07,sf) != 1) goto fail;
/* check version signature */ /* check version signature */
if (read_8bit(0x12,streamFile) != 0x06) goto fail; if (read_u8(0x12,sf) != 0x06) goto fail;
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag); vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->sample_rate = read_32bitBE(0x08,streamFile); /* real sample rate */ vgmstream->sample_rate = read_s32be(0x08,sf); /* real sample rate */
vgmstream->num_samples = read_32bitBE(0x0c,streamFile); /* doesn't include encoder_delay (handled in decoder) */ vgmstream->num_samples = read_s32be(0x0c,sf); /* doesn't include encoder_delay (handled in decoder) */
vgmstream->meta_type = meta_AHX; vgmstream->meta_type = meta_AHX;
@ -50,30 +53,51 @@ VGMSTREAM * init_vgmstream_ahx(STREAMFILE *streamFile) {
#ifdef VGM_USE_MPEG #ifdef VGM_USE_MPEG
mpeg_custom_config cfg = {0}; mpeg_custom_config cfg = {0};
cfg.encryption = read_8bit(0x13,streamFile); /* 0x08 = keyword encryption */ cfg.encryption = read_u8(0x13,sf); /* 0x08 = keyword encryption */
cfg.cri_type = type; cfg.cri_type = type;
if (cfg.encryption) { if (cfg.encryption) {
uint8_t keybuf[6]; uint8_t keybuf[0x10+1] = {0}; /* approximate max for keystrings, +1 extra null for keystrings */
if (read_key_file(keybuf, 6, streamFile) == 6) { size_t key_size;
cfg.cri_key1 = get_16bitBE(keybuf+0);
cfg.cri_key2 = get_16bitBE(keybuf+2); key_size = read_key_file(keybuf, sizeof(keybuf), sf);
cfg.cri_key3 = get_16bitBE(keybuf+4); if (key_size > 0) {
#if 0
int i, is_ascii;
is_ascii = 1;
for (i = 0; i < key_size; i++) {
if (keybuf[i] < 0x20 || keybuf[i] > 0x7f) {
is_ascii = 0;
break;
}
}
#endif
if (key_size == 0x06 /*&& !is_ascii*/) {
cfg.cri_key1 = get_u16be(keybuf + 0x00);
cfg.cri_key2 = get_u16be(keybuf + 0x02);
cfg.cri_key3 = get_u16be(keybuf + 0x04);
}
#if 0
else if (is_ascii) {
const char* keystring = (const char*)keybuf;
derive_adx_key8(keystring, &cfg.cri_key1, &cfg.cri_key2, &cfg.cri_key3);
VGM_LOG("ok: %x, %x, %x\n", cfg.cri_key1, cfg.cri_key2, cfg.cri_key3 );
}
#endif
} }
} }
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, channel_count, MPEG_AHX, &cfg); vgmstream->codec_data = init_mpeg_custom(sf, start_offset, &vgmstream->coding_type, channels, MPEG_AHX, &cfg);
if (!vgmstream->codec_data) goto fail; if (!vgmstream->codec_data) goto fail;
#else #else
goto fail; goto fail;
#endif #endif
} }
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail; goto fail;
return vgmstream; return vgmstream;
fail: fail:

View File

@ -1,5 +1,7 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "sqex_streamfile.h"
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
/* AKB (AAC only) - found in SQEX iOS games */ /* AKB (AAC only) - found in SQEX iOS games */
@ -11,8 +13,8 @@ VGMSTREAM * init_vgmstream_akb_mp4(STREAMFILE *sf) {
if ((uint32_t)read_32bitBE(0, sf) != 0x414b4220) goto fail; if ((uint32_t)read_32bitBE(0, sf) != 0x414b4220) goto fail;
loop_start = read_32bitLE(0x14, sf); loop_start = read_s32le(0x14, sf);
loop_end = read_32bitLE(0x18, sf); loop_end = read_s32le(0x18, sf);
filesize = get_streamfile_size( sf ); filesize = get_streamfile_size( sf );
@ -33,40 +35,59 @@ fail:
#endif #endif
/* AKB - found in SQEX iOS games */ /* AKB - found in SQEX 'sdlib' iOS/Android games */
VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) { VGMSTREAM* init_vgmstream_akb(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset, extradata_offset = 0; off_t start_offset, extradata_offset = 0;
size_t stream_size, header_size, subheader_size = 0, extradata_size = 0; size_t stream_size, header_size, subheader_size = 0, extradata_size = 0;
int loop_flag = 0, channel_count, codec, sample_rate; int loop_flag = 0, channels, codec, sample_rate, version, flags = 0;
int num_samples, loop_start, loop_end; int num_samples, loop_start, loop_end;
/* checks */ /* checks */
if ( !check_extensions(sf, "akb") ) if (!is_id32be(0x00,sf, "AKB "))
goto fail;
if (read_32bitBE(0x00,sf) != 0x414B4220) /* "AKB " */
goto fail;
if (read_32bitLE(0x08,sf) != get_streamfile_size(sf))
goto fail; goto fail;
/* 0x04(1): version */ if (!check_extensions(sf, "akb"))
header_size = read_16bitLE(0x06,sf); goto fail;
codec = read_8bit(0x0c,sf); version = read_u8(0x04,sf); /* 00=TWEWY, 02=DQs, 03=FFAgito */
channel_count = read_8bit(0x0d,sf); /* 0x05(1); unused? */
sample_rate = (uint16_t)read_16bitLE(0x0e,sf); header_size = read_u16le(0x06,sf);
num_samples = read_32bitLE(0x10,sf); if (read_u32le(0x08,sf) != get_streamfile_size(sf))
loop_start = read_32bitLE(0x14,sf); goto fail;
loop_end = read_32bitLE(0x18,sf);
/* material info, though can only hold 1 */
codec = read_u8(0x0c,sf);
channels = read_u8(0x0d,sf);
sample_rate = read_u16le(0x0e,sf);
num_samples = read_s32le(0x10,sf);
loop_start = read_s32le(0x14,sf);
loop_end = read_s32le(0x18,sf);
/* possibly more complex, see AKB2 */ /* possibly more complex, see AKB2 */
if (header_size >= 0x44) { /* v2+ */ if (header_size >= 0x44) { /* v2+ */
extradata_size = read_16bitLE(0x1c,sf); extradata_size = read_u16le(0x1c,sf);
/* 0x20+: config? (pan, volume) */ /* 0x20+: config? (pan, volume) */
subheader_size = read_16bitLE(0x28,sf); subheader_size = read_u16le(0x28,sf);
/* 0x24: file_id? */ /* 0x24: file_id? */
/* 0x2b: encryption bitflag if version > 2? */ /* 0x28: */
/* 0x29: */
/* 0x2a: */
/* flags:
* 1: (v2+) enable random volume
* 2: (v2+) enable random pitch
* 4: (v2+) enable random pan
* 8: (v3+) encryption (for MS-ADPCM / Ogg) [Final Fantasy Agito (Android)-ogg bgm only, other sounds don't use AKB] */
flags = read_u8(0x2B,sf);
/* 0x2c: max random volume */
/* 0x30: min random volume */
/* 0x34: max random pitch */
/* 0x38: min random pitch */
/* 0x3c: max random pan */
/* 0x40: min random pan */
extradata_offset = header_size + subheader_size; extradata_offset = header_size + subheader_size;
start_offset = extradata_offset + extradata_size; start_offset = extradata_offset + extradata_size;
} }
@ -79,30 +100,34 @@ VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) {
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag); vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->meta_type = meta_AKB; vgmstream->meta_type = meta_AKB;
vgmstream->sample_rate = sample_rate;
switch (codec) { switch (codec) {
case 0x02: { /* MSADPCM [Dragon Quest II (iOS) sfx] */ case 0x02: { /* MSADPCM [Dragon Quest II (iOS) sfx] */
vgmstream->coding_type = coding_MSADPCM; vgmstream->coding_type = coding_MSADPCM;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
vgmstream->frame_size = read_16bitLE(extradata_offset + 0x02,sf); vgmstream->frame_size = read_u16le(extradata_offset + 0x02,sf);
/* encryption, untested but should be the same as Ogg */
if (version >= 3 && (flags & 8))
goto fail;
/* adjusted samples; bigger or smaller than base samples, akb lib uses these fields instead /* adjusted samples; bigger or smaller than base samples, akb lib uses these fields instead
* (base samples may have more than possible and read over file size otherwise, very strange) * (base samples may have more than possible and read over file size otherwise, very strange)
* loop_end seems to exist even with loop disabled */ * loop_end seems to exist even with loop disabled */
vgmstream->num_samples = read_32bitLE(extradata_offset + 0x04, sf); vgmstream->num_samples = read_s32le(extradata_offset + 0x04, sf);
vgmstream->loop_start_sample = read_32bitLE(extradata_offset + 0x08, sf); vgmstream->loop_start_sample = read_s32le(extradata_offset + 0x08, sf);
vgmstream->loop_end_sample = read_32bitLE(extradata_offset + 0x0c, sf); vgmstream->loop_end_sample = read_s32le(extradata_offset + 0x0c, sf);
break; break;
} }
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
case 0x05: { /* Ogg Vorbis [Final Fantasy VI (iOS), Dragon Quest II-VI (iOS)] */ case 0x05: { /* Ogg Vorbis [Final Fantasy VI (iOS), Dragon Quest II-VI (iOS)] */
STREAMFILE* temp_sf;
VGMSTREAM *ogg_vgmstream = NULL; VGMSTREAM *ogg_vgmstream = NULL;
ogg_vorbis_meta_info_t ovmi = {0}; ogg_vorbis_meta_info_t ovmi = {0};
@ -111,7 +136,20 @@ VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) {
/* extradata + 0x04: Ogg loop start offset */ /* extradata + 0x04: Ogg loop start offset */
/* oggs have loop info in the comments */ /* oggs have loop info in the comments */
ogg_vgmstream = init_vgmstream_ogg_vorbis_config(sf, start_offset, &ovmi); /* enable encryption */
if (version >= 3 && (flags & 8)) {
VGM_LOG("temp1\n");
temp_sf = setup_sqex_streamfile(sf, start_offset, stream_size, 1, 0x00, 0x00, "ogg");
if (!temp_sf) goto fail;
VGM_LOG("temp2\n");
ogg_vgmstream = init_vgmstream_ogg_vorbis_config(temp_sf, 0x00, &ovmi);
close_streamfile(temp_sf);
}
else {
ogg_vgmstream = init_vgmstream_ogg_vorbis_config(sf, start_offset, &ovmi);
}
if (ogg_vgmstream) { if (ogg_vgmstream) {
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
return ogg_vgmstream; return ogg_vgmstream;
@ -161,13 +199,13 @@ VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) {
} }
#endif #endif
case 0x01: /* PCM16LE */ case 0x01: /* PCM16LE (from debugging, not seen) */
default: default:
goto fail; goto fail;
} }
/* open the file for reading */ /* open the file for reading */
if ( !vgmstream_open_stream(vgmstream, sf, start_offset) ) if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail; goto fail;
return vgmstream; return vgmstream;
@ -178,67 +216,69 @@ fail:
} }
/* AKB2 - found in later SQEX iOS games */ /* AKB2 - found in later SQEX 'sdlib' iOS/Android games */
VGMSTREAM * init_vgmstream_akb2(STREAMFILE *sf) { VGMSTREAM* init_vgmstream_akb2(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset, material_offset, extradata_offset; off_t start_offset, material_offset, extradata_offset;
size_t material_size, extradata_size, stream_size; size_t material_size, extradata_size, stream_size;
int loop_flag = 0, channel_count, encryption_flag, codec, sample_rate, num_samples, loop_start, loop_end; int loop_flag = 0, channel_count, flags, codec, sample_rate, num_samples, loop_start, loop_end;
int total_subsongs, target_subsong = sf->stream_index; int total_subsongs, target_subsong = sf->stream_index;
/* check extensions */
if ( !check_extensions(sf, "akb") )
goto fail;
/* checks */ /* checks */
if (read_32bitBE(0x00,sf) != 0x414B4232) /* "AKB2" */ if (!is_id32be(0x00,sf, "AKB2"))
goto fail; goto fail;
if (read_32bitLE(0x08,sf) != get_streamfile_size(sf))
if (!check_extensions(sf, "akb"))
goto fail; goto fail;
/* 0x04: version */ /* 0x04: version */
if (read_u32le(0x08,sf) != get_streamfile_size(sf))
goto fail;
/* parse tables */ /* parse tables */
{ {
off_t table_offset; off_t table_offset;
size_t table_size, entry_size; size_t table_size, entry_size;
off_t akb_header_size = read_16bitLE(0x06, sf); off_t akb_header_size = read_u16le(0x06, sf);
int table_count = read_8bit(0x0c, sf); int table_count = read_u8(0x0c, sf);
/* probably each table has its type somewhere, but only seen last table = sound table */ /* probably each table has its type somewhere, but only seen last table = sound table */
if (table_count > 2) /* 2 only seen in some Mobius FF sound banks */ if (table_count > 2) /* 2 only seen in some Mobius FF sound banks */
goto fail; goto fail;
entry_size = 0x10; /* technically every entry/table has its own size but to simplify... */ entry_size = 0x10; /* technically every entry/table has its own size but to simplify... */
table_offset = read_32bitLE(akb_header_size + (table_count-1)*entry_size + 0x04, sf); table_offset = read_u32le(akb_header_size + (table_count-1)*entry_size + 0x04, sf);
table_size = read_16bitLE(table_offset + 0x02, sf); table_size = read_u16le(table_offset + 0x02, sf);
total_subsongs = read_8bit(table_offset + 0x0f, sf); /* can contain 0 entries too */ total_subsongs = read_u8(table_offset + 0x0f, sf); /* can contain 0 entries too */
if (target_subsong == 0) target_subsong = 1; if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
material_offset = table_offset + read_32bitLE(table_offset + table_size + (target_subsong-1)*entry_size + 0x04, sf); material_offset = table_offset + read_u32le(table_offset + table_size + (target_subsong-1)*entry_size + 0x04, sf);
} }
/** stream header (material) **/ /** stream header (material) **/
/* 0x00: 0? */ /* 0x00: 0? */
codec = read_8bit(material_offset+0x01,sf); codec = read_u8(material_offset+0x01,sf);
channel_count = read_8bit(material_offset+0x02,sf); channel_count = read_u8(material_offset+0x02,sf);
encryption_flag = read_8bit(material_offset+0x03,sf); flags = read_u8(material_offset+0x03,sf);
material_size = read_16bitLE(material_offset+0x04,sf); material_size = read_u16le(material_offset+0x04,sf);
sample_rate = (uint16_t)read_16bitLE(material_offset+0x06,sf); sample_rate = read_u16le(material_offset+0x06,sf);
stream_size = read_32bitLE(material_offset+0x08,sf); stream_size = read_u32le(material_offset+0x08,sf);
num_samples = read_32bitLE(material_offset+0x0c,sf); num_samples = read_s32le(material_offset+0x0c,sf);
loop_start = read_32bitLE(material_offset+0x10,sf); loop_start = read_s32le(material_offset+0x10,sf);
loop_end = read_32bitLE(material_offset+0x14,sf); loop_end = read_s32le(material_offset+0x14,sf);
extradata_size = read_32bitLE(material_offset+0x18,sf); extradata_size = read_u32le(material_offset+0x18,sf);
/* rest: ? (empty or 0x3f80) */ /* rest: ? (empty, floats or 0x3f80) */
loop_flag = (loop_end > loop_start); loop_flag = (loop_end > loop_start);
extradata_offset = material_offset + material_size; extradata_offset = material_offset + material_size;
start_offset = material_offset + material_size + extradata_size; start_offset = material_offset + material_size + extradata_size;
if (encryption_flag & 0x08) /* encrypted, not seen (see AKB flags) */
if (flags & 0x08)
goto fail; goto fail;
@ -267,14 +307,14 @@ VGMSTREAM * init_vgmstream_akb2(STREAMFILE *sf) {
case 0x02: { /* MSADPCM [The Irregular at Magic High School Lost Zero (Android)] */ case 0x02: { /* MSADPCM [The Irregular at Magic High School Lost Zero (Android)] */
vgmstream->coding_type = coding_MSADPCM; vgmstream->coding_type = coding_MSADPCM;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
vgmstream->frame_size = read_16bitLE(extradata_offset + 0x02, sf); vgmstream->frame_size = read_u16le(extradata_offset + 0x02, sf);
/* adjusted samples; bigger or smaller than base samples, akb lib uses these fields instead /* adjusted samples; bigger or smaller than base samples, akb lib uses these fields instead
* (base samples may have more than possible and read over file size otherwise, very strange) * (base samples may have more than possible and read over file size otherwise, very strange)
* loop_end seems to exist even with loop disabled */ * loop_end seems to exist even with loop disabled */
vgmstream->num_samples = read_32bitLE(extradata_offset + 0x04, sf); vgmstream->num_samples = read_s32le(extradata_offset + 0x04, sf);
vgmstream->loop_start_sample = read_32bitLE(extradata_offset + 0x08, sf); vgmstream->loop_start_sample = read_s32le(extradata_offset + 0x08, sf);
vgmstream->loop_end_sample = read_32bitLE(extradata_offset + 0x0c, sf); vgmstream->loop_end_sample = read_s32le(extradata_offset + 0x0c, sf);
break; break;
} }
@ -317,8 +357,8 @@ VGMSTREAM * init_vgmstream_akb2(STREAMFILE *sf) {
/* When loop_flag num_samples may be much larger than real num_samples (it's fine when looping is off) /* When loop_flag num_samples may be much larger than real num_samples (it's fine when looping is off)
* Actual num_samples would be loop_end_sample+1, but more testing is needed */ * Actual num_samples would be loop_end_sample+1, but more testing is needed */
vgmstream->num_samples = read_32bitLE(material_offset+0x0c,sf);//num_samples; vgmstream->num_samples = read_s32le(material_offset+0x0c,sf);//num_samples;
vgmstream->loop_start_sample = read_32bitLE(material_offset+0x10,sf);//loop_start; vgmstream->loop_start_sample = read_s32le(material_offset+0x10,sf);//loop_start;
vgmstream->loop_end_sample = loop_end; vgmstream->loop_end_sample = loop_end;
break; break;
} }

View File

@ -1,48 +1,48 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
/* APC - from Cryo games [MegaRace 3 (PC)] */ /* APC - from Cryo games [MegaRace 3 (PC)] */
VGMSTREAM * init_vgmstream_apc(STREAMFILE *streamFile) { VGMSTREAM* init_vgmstream_apc(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset; uint32_t start_offset, data_size;
size_t data_size; int loop_flag, channels, sample_rate;
int loop_flag, channel_count;
/* checks */
/* checks */ if (!is_id32be(0x00,sf, "CRYO"))
if ( !check_extensions(streamFile,"apc") ) goto fail;
goto fail; if (!is_id32be(0x04,sf, "_APC"))
if (read_32bitBE(0x00,streamFile) != 0x4352594F) /* "CRYO" */ goto fail;
goto fail; //if (!is_id32be(0x04,sf, "1.20"))
if (read_32bitBE(0x04,streamFile) != 0x5F415043) /* "_APC" */ // goto fail;
goto fail;
//if (read_32bitBE(0x08,streamFile) != 0x312E3230) /* "1.20" */ if (!check_extensions(sf,"apc"))
// goto fail; goto fail;
/* 0x14/18: L/R hist sample? */ sample_rate = read_s32le(0x10,sf);
/* 0x14/18: L/R hist sample? */
start_offset = 0x20; channels = read_s32le(0x1c,sf) == 0 ? 1 : 2;
data_size = get_streamfile_size(streamFile) - start_offset; loop_flag = 0;
channel_count = read_32bitLE(0x1c,streamFile) == 0 ? 1 : 2; start_offset = 0x20;
loop_flag = 0; data_size = get_streamfile_size(sf) - start_offset;
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag); vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->meta_type = meta_APC; vgmstream->meta_type = meta_APC;
vgmstream->sample_rate = read_32bitLE(0x10,streamFile); vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = ima_bytes_to_samples(data_size,channel_count); vgmstream->num_samples = ima_bytes_to_samples(data_size, channels);
vgmstream->coding_type = coding_IMA; vgmstream->coding_type = coding_IMA;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail; goto fail;
return vgmstream; return vgmstream;
fail: fail:
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
return NULL; return NULL;
} }

View File

@ -1,58 +1,63 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
/* ASF - Argonaut PC games [Croc 2 (PC), Aladdin: Nasira's Revenge (PC)] */ /* ASF - Argonaut PC games [Croc 2 (PC), Aladdin: Nasira's Revenge (PC)] */
VGMSTREAM * init_vgmstream_asf(STREAMFILE *streamFile) { VGMSTREAM* init_vgmstream_asf(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset; uint32_t start_offset;
int loop_flag, channel_count, version; int loop_flag, channels, type, sample_rate;
/* checks */ /* checks */
/* .asf: original if (!is_id32be(0x00,sf, "ASF\0"))
* .lasf: fake for plugins */ goto fail;
if (!check_extensions(streamFile, "asf,lasf"))
goto fail; /* .asf: original
* .lasf: fake for plugins */
if (read_32bitBE(0x00,streamFile) != 0x41534600) /* "ASF\0" */ if (!check_extensions(sf, "asf,lasf"))
goto fail; goto fail;
if (read_32bitBE(0x04,streamFile) != 0x02000100)
goto fail; if (read_u32le(0x04,sf) != 0x00010002) /* v1.002? */
if (read_32bitLE(0x08,streamFile) != 0x01 && goto fail;
read_32bitLE(0x0c,streamFile) != 0x18 && if (read_u32le(0x08,sf) != 0x01 &&
read_32bitLE(0x1c,streamFile) != 0x20) read_u32le(0x0c,sf) != 0x18)
goto fail; goto fail;
/* 0x10~18: stream name (same as filename) */
version = read_32bitLE(0x28,streamFile); /* assumed? */ /* 0x18: non-full size? */
switch(version){ if (read_u32le(0x1c,sf) != 0x20) /* samples per frame? */
case 0x0d: channel_count = 1; break; /* Aladdin: Nasira's Revenge (PC) */ goto fail;
case 0x0f: channel_count = 2; break; /* Croc 2 (PC), The Emperor's New Groove (PC) */ sample_rate = read_u16le(0x24, sf);
default: goto fail;
} type = read_u32le(0x28,sf); /* assumed? */
switch(type){
loop_flag = 0; case 0x0d: channels = 1; break; /* Aladdin: Nasira's Revenge (PC) */
start_offset = 0x2c; case 0x0f: channels = 2; break; /* Croc 2 (PC), The Emperor's New Groove (PC) */
default: goto fail;
/* build the VGMSTREAM */ }
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail; loop_flag = 0;
start_offset = 0x2c;
vgmstream->sample_rate = (uint16_t)read_16bitLE(0x24, streamFile);
vgmstream->meta_type = meta_ASF; /* build the VGMSTREAM */
vgmstream->coding_type = coding_ASF; vgmstream = allocate_vgmstream(channels, loop_flag);
vgmstream->layout_type = layout_interleave; if (!vgmstream) goto fail;
vgmstream->interleave_block_size = 0x11;
vgmstream->num_samples = (get_streamfile_size(streamFile)-start_offset)/(0x11*channel_count)*32; /* bytes_to_samples */ vgmstream->sample_rate = sample_rate;
//vgmstream->num_samples = read_32bitLE(0x18,streamFile) * (0x20<<channel_count); /* something like this? */ vgmstream->meta_type = meta_ASF;
vgmstream->coding_type = coding_ASF;
read_string(vgmstream->stream_name,0x10, 0x08+1,streamFile); vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x11;
vgmstream->num_samples = (get_streamfile_size(sf) - start_offset) / (0x11 * channels) * 32; /* bytes_to_samples */
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) //vgmstream->num_samples = read_32bitLE(0x18,sf) * (32 << channels); /* something like this? */
goto fail;
return vgmstream; read_string(vgmstream->stream_name,0x10, 0x08+1,sf);
fail:
close_vgmstream(vgmstream); if (!vgmstream_open_stream(vgmstream, sf, start_offset))
return NULL; goto fail;
} return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -39,7 +39,13 @@ VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf) {
/* checks */ /* checks */
if (!is_id32be(0x00, sf, "WBD_") &&
!is_id32le(0x00, sf, "WBD_") &&
!is_id32be(0x00, sf, "WHD1"))
goto fail;
/* .wbd+wbh: common [Bladestorm Nightmare (PC)] /* .wbd+wbh: common [Bladestorm Nightmare (PC)]
* .wbd+whd: uncommon [Nights of Azure 2 (PS4)]
* .wb2+wh2: newer [Nights of Azure 2 (PC)] * .wb2+wh2: newer [Nights of Azure 2 (PC)]
* .sed: mixed header+data [Dissidia NT (PC)] */ * .sed: mixed header+data [Dissidia NT (PC)] */
if (!check_extensions(sf, "wbd,wb2,sed")) if (!check_extensions(sf, "wbd,wb2,sed"))
@ -47,18 +53,20 @@ VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf) {
/* open companion header */ /* open companion header */
if (check_extensions(sf, "wbd")) { if (is_id32be(0x00, sf, "WHD1")) { /* .sed */
sf_h = sf;
sf_b = sf;
}
else if (check_extensions(sf, "wbd")) {
sf_h = open_streamfile_by_ext(sf, "wbh"); sf_h = open_streamfile_by_ext(sf, "wbh");
if (!sf_h)
sf_h = open_streamfile_by_ext(sf, "whd");
sf_b = sf; sf_b = sf;
} }
else if (check_extensions(sf, "wb2")) { else if (check_extensions(sf, "wb2")) {
sf_h = open_streamfile_by_ext(sf, "wh2"); sf_h = open_streamfile_by_ext(sf, "wh2");
sf_b = sf; sf_b = sf;
} }
else if (check_extensions(sf, "sed")) {
sf_h = sf;
sf_b = sf;
}
else { else {
goto fail; goto fail;
} }
@ -378,14 +386,14 @@ fail:
return 0; return 0;
} }
static int parse_type_k4hd(kwb_header* kwb, off_t offset, off_t body_offset, STREAMFILE* sf_h) { static int parse_type_k4hd_pvhd(kwb_header* kwb, off_t offset, off_t body_offset, STREAMFILE* sf_h) {
off_t ppva_offset, header_offset; off_t ppva_offset, header_offset;
int entries, current_subsongs, relative_subsong; int entries, current_subsongs, relative_subsong;
size_t entry_size; size_t entry_size;
/* a format mimicking PSVita's hd4+bd4 format */ /* a format mimicking PSVita's hd4+bd4 format */
/* 00: K4HD id */ /* 00: K4HD/PVHD id */
/* 04: chunk size */ /* 04: chunk size */
/* 08: ? */ /* 08: ? */
/* 0c: ? */ /* 0c: ? */
@ -565,7 +573,7 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL; uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
if (read_u32be(0x00, sf_h) == 0x57484431) { /* "WHD1" */ if (is_id32be(0x00, sf_h, "WHD1")) {
/* container of fused .wbh+wbd */ /* container of fused .wbh+wbd */
/* 0x04: fixed value? */ /* 0x04: fixed value? */
kwb->big_endian = read_u8(0x08, sf_h) == 0xFF; kwb->big_endian = read_u8(0x08, sf_h) == 0xFF;
@ -617,7 +625,8 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
break; break;
case 0x4B344844: /* "K4HD" [Dissidia NT (PS4), (Vita) */ case 0x4B344844: /* "K4HD" [Dissidia NT (PS4), (Vita) */
if (!parse_type_k4hd(kwb, head_offset, body_offset, sf_h)) case 0x50564844: /* "PVHD" [Nights of Azure 2 (PS4)] */
if (!parse_type_k4hd_pvhd(kwb, head_offset, body_offset, sf_h))
goto fail; goto fail;
break; break;
@ -632,6 +641,7 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
break; break;
default: default:
vgm_logi("KWB: unknown type\n");
goto fail; goto fail;
} }

View File

@ -3,48 +3,45 @@
#include "../coding/coding.h" #include "../coding/coding.h"
#include <string.h> #include <string.h>
/* .s14 and .sss - headerless siren14 stream (The Idolm@ster DS, Korogashi Puzzle Katamari Damacy DS) */ static int test_interleave(STREAMFILE* sf, int channels, int interleave);
VGMSTREAM * init_vgmstream_s14_sss(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; /* .s14/.sss - headerless siren14 stream [The Idolm@ster (DS), Korogashi Puzzle Katamari Damacy (DS), Taiko no Tatsujin DS 1/2 (DS)] */
VGMSTREAM* init_vgmstream_s14_sss(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset = 0; off_t start_offset = 0;
int channel_count, loop_flag = 0, interleave; int channels, loop_flag = 0, interleave;
/* check extension */ /* check extension */
if (check_extensions(streamFile,"sss")) { if (check_extensions(sf,"sss")) {
channel_count = 2; channels = 2;
} else if (check_extensions(streamFile,"s14")) { } else if (check_extensions(sf,"s14")) {
channel_count = 1; //todo missing dual _0ch.s14 _1ch.s14, but dual_ext thing doesn't work properly with siren14 decoder channels = 1; /* may have dual _0ch.s14 + _1ch.s14, needs .txtp */
} else { } else {
goto fail; goto fail;
} }
/* raw siren comes in 3 frame sizes, try to guess the correct one /* raw siren comes in 3 frame sizes, try to guess the correct one */
* (should try to decode and check the error flag but it isn't currently reported) */
{ {
char filename[PATH_LIMIT]; /* horrid but ain't losing sleep over it (besides the header is often incrusted in-code as some tracks loop)
streamFile->get_name(streamFile,filename,sizeof(filename)); * Katamari, Taiko = 0x78/0x50, idolmaster=0x3c (usually but can be any) */
if (test_interleave(sf, channels, 0x78))
/* horrid but I ain't losing sleep over it (besides the header is often incrusted in-code as some tracks loop) */
if (strstr(filename,"S037")==filename || strstr(filename,"b06")==filename || /* Korogashi Puzzle Katamari Damacy */
strstr(filename,"_48kbps")!=NULL) /* Taiko no Tatsujin DS 1/2 */
interleave = 0x78; interleave = 0x78;
else if (strstr(filename,"32700")==filename || /* Hottarake no Shima - Kanata to Nijiiro no Kagami */ else if (test_interleave(sf, channels, 0x50))
strstr(filename,"b0")==filename || strstr(filename,"puzzle")==filename || strstr(filename,"M09")==filename || /* Korogashi Puzzle Katamari Damacy */ interleave = 0x50;
strstr(filename,"_32kbps")!=NULL) /* Taiko no Tatsujin DS 1/2 */ else if (test_interleave(sf, channels, 0x3c))
interleave = 0x50; interleave = 0x3c;
else else
interleave = 0x3c; /* The Idolm@ster - Dearly Stars */ goto fail;
} }
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag); vgmstream = allocate_vgmstream(channels,loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->num_samples = get_streamfile_size(streamFile) / (interleave * channel_count) * (32000/50); vgmstream->num_samples = get_streamfile_size(sf) / (interleave * channels) * (32000/50);
vgmstream->sample_rate = 32768; /* maybe 32700? */ vgmstream->sample_rate = 32768;
vgmstream->meta_type = channel_count==1 ? meta_S14 : meta_SSS; vgmstream->meta_type = channels==1 ? meta_S14 : meta_SSS;
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave; vgmstream->interleave_block_size = interleave;
@ -58,7 +55,7 @@ VGMSTREAM * init_vgmstream_s14_sss(STREAMFILE *streamFile) {
#endif #endif
} }
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail; goto fail;
return vgmstream; return vgmstream;
@ -66,3 +63,27 @@ fail:
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
return NULL; return NULL;
} }
/* pretty gross (should use TXTH), but codec info seems to be in hard-to-locate places/exe
* and varies per file, so for now autodetect possible types. could also check if data_size matches interleave */
static int test_interleave(STREAMFILE* sf, int channels, int interleave) {
#ifdef VGM_USE_G7221
int res;
g7221_codec_data* data = init_g7221(channels, interleave);
if (!data) goto fail;
set_key_g7221(data, NULL); /* force test key */
/* though this is mainly for key testing, with no key can be used to test frames too */
res = test_key_g7221(data, 0x00, sf);
if (res <= 0) goto fail;
free_g7221(data);
return 1;
fail:
free_g7221(data);
return 0;
#else
return 0;
#endif
}

View File

@ -32,7 +32,7 @@ VGMSTREAM* init_vgmstream_sgxd(STREAMFILE* sf) {
/* checks */ /* checks */
/* .sgx: header+data (Genji) /* .sgx: header+data (Genji)
* .sgd: header+data (common) * .sgd: header+data (common)
* .sgh+sgd: header+data */ * .sgh+sgd: header+data (streams) */
if (!check_extensions(sf,"sgx,sgd,sgb")) if (!check_extensions(sf,"sgx,sgd,sgb"))
goto fail; goto fail;
@ -91,7 +91,9 @@ VGMSTREAM* init_vgmstream_sgxd(STREAMFILE* sf) {
* - 0x00: sub-id? * - 0x00: sub-id?
* - 0x02: type? (possibly: 0000=bank, 0x2xxx=SEQD/WAVE, 0x3xxx=WSUR, 0x4xxx=BUSS, 0x6xxx=CONF) * - 0x02: type? (possibly: 0000=bank, 0x2xxx=SEQD/WAVE, 0x3xxx=WSUR, 0x4xxx=BUSS, 0x6xxx=CONF)
* - 0x04: absolute offset * - 0x04: absolute offset
* - SEQD: related to SFX (sequences?), entries seem to be offsets to name offset + seq (ps1?) offset * - SEQD: related to SFX (sequences?), entries seem to be offsets to name offset + sequence offset
* > sequence format seems to be 1 byte type (0=sfx, 1=music) + midi without header
* (default tick resolution of 960 pulses per quarter note)
* - WSUR: ? * - WSUR: ?
* - WMKR: ? * - WMKR: ?
* - CONF: ? (name offset + config offset) * - CONF: ? (name offset + config offset)

View File

@ -6,7 +6,7 @@
VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
STREAMFILE* sf_b = NULL; STREAMFILE* sf_b = NULL;
uint32_t stream_offset, stream_size, name_offset, head_size, data_size; uint32_t stream_offset, stream_size, name_offset, data_size;
int channels, loop_flag, sample_rate, codec, streamed; int channels, loop_flag, sample_rate, codec, streamed;
int32_t num_samples, loop_start, loop_end; int32_t num_samples, loop_start, loop_end;
uint32_t at9_config; uint32_t at9_config;
@ -16,7 +16,7 @@ VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf) {
if (!is_id32be(0x00, sf, "SNDZ")) if (!is_id32be(0x00, sf, "SNDZ"))
goto fail; goto fail;
head_size = read_u32le(0x04, sf); //head_size = read_u32le(0x04, sf);
data_size = read_u32le(0x08, sf); data_size = read_u32le(0x08, sf);
/* 0x0c: version? (0x00010001) */ /* 0x0c: version? (0x00010001) */
/* 0x10: size size? */ /* 0x10: size size? */
@ -95,7 +95,6 @@ VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf) {
loop_flag = loop_end > 0; loop_flag = loop_end > 0;
} }
VGM_LOG("%i, %x, %x, %x\n", streamed, head_size, data_size, get_streamfile_size(sf));
/* szd3 is streamed but has header+data together, with padding between (data_size is the same as file size)*/ /* szd3 is streamed but has header+data together, with padding between (data_size is the same as file size)*/
if (streamed && get_streamfile_size(sf) < data_size) { if (streamed && get_streamfile_size(sf) < data_size) {
sf_b = open_streamfile_by_ext(sf, "szd2"); sf_b = open_streamfile_by_ext(sf, "szd2");

View File

@ -1,6 +1,6 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "sqex_sead_streamfile.h" #include "sqex_streamfile.h"
typedef struct { typedef struct {
@ -278,7 +278,7 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) {
/* 0x0e: reserved x2 */ /* 0x0e: reserved x2 */
/* 0x10+ HCA header */ /* 0x10+ HCA header */
temp_sf = setup_sqex_sead_streamfile(sf, subfile_offset, subfile_size, encryption, header_size, key_start); temp_sf = setup_sqex_streamfile(sf, subfile_offset, subfile_size, encryption, header_size, key_start, "hca");
if (!temp_sf) goto fail; if (!temp_sf) goto fail;
temp_vgmstream = init_vgmstream_hca(temp_sf); temp_vgmstream = init_vgmstream_hca(temp_sf);

View File

@ -6,11 +6,11 @@
typedef struct { typedef struct {
size_t start; size_t start;
size_t key_start; size_t key_start;
} sqex_sead_io_data; } sqex_io_data;
static size_t sqex_sead_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, sqex_sead_io_data* data) { static size_t sqex_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, sqex_io_data* data) {
/* Found in FFXII_TZA.exe (same key in SCD Ogg V3) */ /* Found in FFXII_TZA.exe (same key in SCD Ogg V3), also in AKB's sdlib as "EncKey" */
static const uint8_t key[0x100] = { static const uint8_t key[0x100] = {
0x3A,0x32,0x32,0x32,0x03,0x7E,0x12,0xF7,0xB2,0xE2,0xA2,0x67,0x32,0x32,0x22,0x32, // 00-0F 0x3A,0x32,0x32,0x32,0x03,0x7E,0x12,0xF7,0xB2,0xE2,0xA2,0x67,0x32,0x32,0x22,0x32, // 00-0F
0x32,0x52,0x16,0x1B,0x3C,0xA1,0x54,0x7B,0x1B,0x97,0xA6,0x93,0x1A,0x4B,0xAA,0xA6, // 10-1F 0x32,0x52,0x16,0x1B,0x3C,0xA1,0x54,0x7B,0x1B,0x97,0xA6,0x93,0x1A,0x4B,0xAA,0xA6, // 10-1F
@ -45,22 +45,22 @@ static size_t sqex_sead_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, siz
} }
/* decrypts subfile if needed */ /* decrypts subfile if needed */
static STREAMFILE* setup_sqex_sead_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size, int encryption, size_t header_size, size_t key_start) { static STREAMFILE* setup_sqex_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size, int encryption, size_t header_size, size_t key_start, const char* ext) {
STREAMFILE* new_sf = NULL; STREAMFILE* new_sf = NULL;
/* setup sf */ /* setup sf */
new_sf = open_wrap_streamfile(sf); new_sf = open_wrap_streamfile(sf);
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset, subfile_size); new_sf = open_clamp_streamfile_f(new_sf, subfile_offset, subfile_size);
if (encryption) { if (encryption) {
sqex_sead_io_data io_data = {0}; sqex_io_data io_data = {0};
io_data.start = header_size; io_data.start = header_size;
io_data.key_start = key_start; io_data.key_start = key_start;
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(sqex_sead_io_data), sqex_sead_io_read, NULL); new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(sqex_io_data), sqex_io_read, NULL);
} }
new_sf = open_fakename_streamfile_f(new_sf, NULL, "hca"); new_sf = open_fakename_streamfile_f(new_sf, NULL, ext);
return new_sf; return new_sf;
} }

View File

@ -5,35 +5,52 @@
/* SWAV - wave files generated by the DS SDK */ /* SWAV - wave files generated by the DS SDK */
VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
int channel_count, loop_flag; uint32_t start_offset, data_size, file_size;
off_t start_offset; int channels, loop_flag, codec, bits_per_sample, sample_rate;
int codec_number, bits_per_sample; int32_t loop_start, loop_end;
coding_t coding_type; coding_t coding_type;
/* checks */ /* checks */
if (!is_id32be(0x00,sf, "SWAV"))
goto fail;
/* .swav: standard /* .swav: standard
* .adpcm: Merlin - A Servant of Two Masters (DS) */ * .adpcm: Merlin - A Servant of Two Masters (DS) */
if (!check_extensions(sf, "swav,adpcm")) if (!check_extensions(sf, "swav,adpcm"))
goto fail; goto fail;
if (read_u32be(0x00,sf) != 0x53574156) /* "SWAV" */ /* 0x04: BOM mark */
goto fail; /* 0x06: version? (1.00) */
if (read_u32be(0x10,sf) != 0x44415441) /* "DATA" */ file_size = read_u32le(0x08,sf);
goto fail; /* 0x0c: always 16? */
/* 0x0e: always 1? */
/* check type details */ if (!is_id32be(0x10,sf, "DATA"))
codec_number = read_8bit(0x18,sf); goto fail;
loop_flag = read_8bit(0x19,sf); data_size = read_u32le(0x14,sf);
codec = read_u8(0x18,sf);
loop_flag = read_u8(0x19,sf);
sample_rate = read_u16le(0x1A,sf);
/* 0x1c: related to size? */
loop_start = read_u16le(0x1E,sf);
loop_end = read_s32le(0x20,sf);
channel_count = 1; start_offset = 0x24;
if (get_streamfile_size(sf) != read_s32le(0x08,sf)) {
if (get_streamfile_size(sf) != (read_s32le(0x08,sf) - 0x24) * 2 + 0x24) /* strange values found in Face Training (DSi) samples, may be pitch/etc reference info? (samples sounds ok like this) */
if (sample_rate < 0x2000)
sample_rate = 44100;
channels = 1;
if (get_streamfile_size(sf) != file_size) {
if (get_streamfile_size(sf) != (file_size - 0x24) * 2 + 0x24)
goto fail; goto fail;
channel_count = 2; channels = 2;
} }
switch (codec_number) { switch (codec) {
case 0: case 0:
coding_type = coding_PCM8; coding_type = coding_PCM8;
bits_per_sample = 8; bits_per_sample = 8;
@ -49,18 +66,17 @@ VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
default: default:
goto fail; goto fail;
} }
start_offset = 0x24;
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag); vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->num_samples = (read_s32le(0x14,sf) - 0x14) * 8 / bits_per_sample; vgmstream->num_samples = (data_size - 0x14) * 8 / bits_per_sample;
vgmstream->sample_rate = read_u16le(0x1A,sf); vgmstream->sample_rate = sample_rate;
if (loop_flag) { if (loop_flag) {
vgmstream->loop_start_sample = read_u16le(0x1E,sf) * 32 / bits_per_sample; vgmstream->loop_start_sample = loop_start * 32 / bits_per_sample;
vgmstream->loop_end_sample = read_s32le(0x20,sf) * 32 / bits_per_sample + vgmstream->loop_start_sample; vgmstream->loop_end_sample = loop_end * 32 / bits_per_sample + vgmstream->loop_start_sample;
} }
if (coding_type == coding_IMA_int) { if (coding_type == coding_IMA_int) {
@ -71,18 +87,18 @@ VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
{ {
int i; int i;
for (i = 0; i < channel_count; i++) { for (i = 0; i < channels; i++) {
vgmstream->ch[i].adpcm_history1_32 = read_s16le(start_offset + 0 + 4*i, sf); vgmstream->ch[i].adpcm_history1_32 = read_s16le(start_offset + 0 + 4*i, sf);
vgmstream->ch[i].adpcm_step_index = read_s16le(start_offset + 2 + 4*i, sf); vgmstream->ch[i].adpcm_step_index = read_s16le(start_offset + 2 + 4*i, sf);
} }
} }
start_offset += 4 * channel_count; start_offset += 4 * channels;
} }
vgmstream->coding_type = coding_type; vgmstream->coding_type = coding_type;
vgmstream->meta_type = meta_SWAV; vgmstream->meta_type = meta_SWAV;
if (channel_count == 2) { if (channels == 2) {
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 1; vgmstream->interleave_block_size = 1;
} else { } else {

View File

@ -52,8 +52,8 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
/* check variation */ /* check variation */
switch(vag_id) { switch(vag_id) {
case 0x56414731: /* "VAG1" [Metal Gear Solid 3 (PS2), Cabela's African Safari (PSP)] */ case 0x56414731: /* "VAG1" [Metal Gear Solid 3 (PS2), Cabela's African Safari (PSP), Shamu's Deep Sea Adventures (PS2)] */
meta_type = meta_PS2_VAG1; //TODO not always Konami meta_type = meta_PS2_VAG1; //TODO not always Konami (Sand Grain Studios)
start_offset = 0x40; /* 0x30 is extra data in VAG1 */ start_offset = 0x40; /* 0x30 is extra data in VAG1 */
interleave = 0x10; interleave = 0x10;
loop_flag = 0; loop_flag = 0;

View File

@ -1,71 +1,71 @@
#include "meta.h" #include "meta.h"
#include "../util/endianness.h"
#include "../coding/coding.h" #include "../coding/coding.h"
/* .WAVE - WayForward "EngineBlack" games [Mighty Switch Force! (3DS), Adventure Time: Hey Ice King! Why'd You Steal Our Garbage?! (3DS)] */ /* .WAVE - WayForward "EngineBlack" games [Mighty Switch Force! (3DS), Adventure Time: Hey Ice King! Why'd You Steal Our Garbage?! (3DS)] */
VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) { VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset, extradata_offset; uint32_t start_offset, extradata_offset, interleave;
int loop_flag = 0, channel_count, sample_rate, codec; int channels, loop_flag, sample_rate, codec;
int32_t num_samples, loop_start = 0, loop_end = 0; int32_t num_samples, loop_start, loop_end;
size_t interleave;
int big_endian; int big_endian;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; read_u32_t read_u32;
float (*read_f32)(off_t,STREAMFILE*) = NULL; read_s32_t read_s32;
read_f32_t read_f32;
/* checks */ /* checks */
if (!check_extensions(streamFile, "wave")) if (!is_id32be(0x00,sf, "VAW3") && /* Happy Feet Two (3DS) */
read_u32le(0x00,sf) != 0xE5B7ECFE && /* common (LE) */
read_u32be(0x00,sf) != 0xE5B7ECFE) /* used? */
goto fail; goto fail;
/* 0x04: version? common=0, VAW3=2 */
if (read_32bitLE(0x00,streamFile) != 0xE5B7ECFE && /* header id */ if (!check_extensions(sf, "wave"))
read_32bitBE(0x00,streamFile) != 0xE5B7ECFE)
goto fail;
if (read_32bitBE(0x04,streamFile) != 0x00) /* version? */
goto fail; goto fail;
/* assumed */ /* assumed */
big_endian = read_32bitBE(0x00,streamFile) == 0xE5B7ECFE; big_endian = read_u32be(0x00,sf) == 0xE5B7ECFE;
if (big_endian) { if (big_endian) {
read_32bit = read_32bitBE; read_u32 = read_u32be;
read_s32 = read_s32be;
read_f32 = read_f32be; read_f32 = read_f32be;
} else { } else {
read_32bit = read_32bitLE; read_u32 = read_u32le;
read_s32 = read_s32le;
read_f32 = read_f32le; read_f32 = read_f32le;
} }
channel_count = read_8bit(0x05,streamFile); if (read_u32(0x08,sf) != get_streamfile_size(sf))
if (read_32bit(0x08,streamFile) != get_streamfile_size(streamFile))
goto fail;
if (read_8bit(0x0c,streamFile) != 0x00) /* ? */
goto fail; goto fail;
sample_rate = (int)read_f32(0x0c, streamFile); /* sample rate in 32b float (WHY?) */ sample_rate = (int)read_f32(0x0c, sf); /* WHY */
num_samples = read_32bit(0x10, streamFile); num_samples = read_s32(0x10, sf);
loop_start = read_32bit(0x14, streamFile); loop_start = read_s32(0x14, sf);
loop_end = read_32bit(0x18, streamFile); loop_end = read_s32(0x18, sf);
codec = read_8bit(0x1c, streamFile); codec = read_u8(0x1c, sf);
channel_count = read_8bit(0x1d, streamFile); channels = read_u8(0x1d, sf);
if (read_8bit(0x1e, streamFile) != 0x00) goto fail; /* unknown */ if (read_u8(0x1e, sf) != 0x00) goto fail; /* unknown */
if (read_8bit(0x1f, streamFile) != 0x00) goto fail; /* unknown */ if (read_u8(0x1f, sf) != 0x00) goto fail; /* unknown */
start_offset = read_32bit(0x20, streamFile); start_offset = read_u32(0x20, sf);
interleave = read_32bit(0x24, streamFile); /* typically half data_size */ interleave = read_u32(0x24, sf); /* typically half data_size */
extradata_offset = read_32bit(0x28, streamFile); /* OR: extradata size (0x2c) */ extradata_offset = read_u32(0x28, sf); /* OR: extradata size (always 0x2c) */
loop_flag = (loop_start > 0); loop_flag = (loop_start > 0);
/* some songs (ex. Adventure Time's m_candykingdom_overworld.wave) do full loops, but there is no way /* some songs (ex. Adventure Time's m_candykingdom_overworld.wave) do full loops, but there is no way
* to tell them apart from sfx/voices, so we try to detect if it's long enough. */ * to tell them apart from sfx/voices, so we try to detect if it's long enough. */
if(!loop_flag if(!loop_flag
&& loop_start == 0 && loop_end == num_samples /* full loop */ && loop_start == 0 && loop_end == num_samples /* full loop */
&& channel_count > 1 && channels > 1
&& num_samples > 20*sample_rate) { /* in seconds */ && num_samples > 20*sample_rate) { /* in seconds */
loop_flag = 1; loop_flag = 1;
} }
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag); vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate; vgmstream->sample_rate = sample_rate;
@ -74,6 +74,7 @@ VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
vgmstream->loop_end_sample = loop_end; vgmstream->loop_end_sample = loop_end;
vgmstream->meta_type = meta_WAVE; vgmstream->meta_type = meta_WAVE;
/* not sure if there are other codecs but anyway */ /* not sure if there are other codecs but anyway */
switch(codec) { switch(codec) {
case 0x02: case 0x02:
@ -82,14 +83,14 @@ VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
vgmstream->interleave_block_size = interleave; vgmstream->interleave_block_size = interleave;
/* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2, per channel */ /* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2, per channel */
dsp_read_coefs(vgmstream, streamFile, extradata_offset+0x00, 0x2c, big_endian); dsp_read_coefs(vgmstream, sf, extradata_offset+0x00, 0x2c, big_endian);
dsp_read_hist(vgmstream, streamFile, extradata_offset+0x22, 0x2c, big_endian); dsp_read_hist(vgmstream, sf, extradata_offset+0x22, 0x2c, big_endian);
break; break;
default: default:
goto fail; goto fail;
} }
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) if (!vgmstream_open_stream(vgmstream,sf,start_offset))
goto fail; goto fail;
return vgmstream; return vgmstream;

View File

@ -21,8 +21,13 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
extension = filename_extension(filename); extension = filename_extension(filename);
} }
/* some metas accept extensionless files */ /* some metas accept extensionless files, but make sure it's not a path */
if (strlen(extension) <= 0) { if (strlen(extension) <= 0) {
int len = strlen(filename);
if (len <= 0)
return 0;
if (filename[len - 1] == '/' || filename[len - 1] == '\\')
return 0;
return !cfg->reject_extensionless; return !cfg->reject_extensionless;
} }

View File

@ -57,11 +57,45 @@ winamp_settings_t settings;
winamp_state_t state; winamp_state_t state;
short sample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS]; //todo maybe should be dynamic short sample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS]; //todo maybe should be dynamic
/* info cache (optimization) */
in_char info_fn[PATH_LIMIT] = {0};
in_char info_title[GETFILEINFO_TITLE_LENGTH];
int info_time;
int info_valid;
/* ***************************************** */ /* ***************************************** */
/* IN_VGMSTREAM UTILS */ /* IN_VGMSTREAM UTILS */
/* ***************************************** */ /* ***************************************** */
/* parses a modified filename ('fakename') extracting tags parameters (NULL tag for first = filename) */
static int parse_fn_string(const in_char* fn, const in_char* tag, in_char* dst, int dst_size) {
const in_char* end = wa_strchr(fn,'|');
if (tag==NULL) {
wa_strcpy(dst,fn);
if (end)
dst[end - fn] = '\0';
return 1;
}
dst[0] = '\0';
return 0;
}
static int parse_fn_int(const in_char* fn, const in_char* tag, int* num) {
const in_char* start = wa_strchr(fn,'|');
if (start > 0) {
wa_sscanf(start+1, wa_L("$s=%i "), num);
return 1;
} else {
*num = 0;
return 0;
}
}
/* opens vgmstream for winamp */ /* opens vgmstream for winamp */
static VGMSTREAM* init_vgmstream_winamp(const in_char* fn, int stream_index) { static VGMSTREAM* init_vgmstream_winamp(const in_char* fn, int stream_index) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
@ -79,6 +113,19 @@ static VGMSTREAM* init_vgmstream_winamp(const in_char* fn, int stream_index) {
return vgmstream; return vgmstream;
} }
/* opens vgmstream with (possibly) an index */
static VGMSTREAM* init_vgmstream_winamp_fileinfo(const in_char* fn) {
in_char filename[PATH_LIMIT];
int stream_index = 0;
/* check for info encoded in the filename */
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
parse_fn_int(fn, wa_L("$s"), &stream_index);
return init_vgmstream_winamp(filename, stream_index);
}
/* makes a modified filename, suitable to pass parameters around */ /* makes a modified filename, suitable to pass parameters around */
static void make_fn_subsong(in_char* dst, int dst_size, const in_char* filename, int stream_index) { static void make_fn_subsong(in_char* dst, int dst_size, const in_char* filename, int stream_index) {
/* Follows "(file)(config)(ext)". Winamp needs to "see" (ext) to validate, and file goes first so relative /* Follows "(file)(config)(ext)". Winamp needs to "see" (ext) to validate, and file goes first so relative
@ -133,31 +180,6 @@ static int split_subsongs(const in_char* filename, int stream_index, VGMSTREAM *
return 1; return 1;
} }
/* parses a modified filename ('fakename') extracting tags parameters (NULL tag for first = filename) */
static int parse_fn_string(const in_char* fn, const in_char* tag, in_char* dst, int dst_size) {
const in_char* end = wa_strchr(fn,'|');
if (tag==NULL) {
wa_strcpy(dst,fn);
if (end)
dst[end - fn] = '\0';
return 1;
}
dst[0] = '\0';
return 0;
}
static int parse_fn_int(const in_char* fn, const in_char* tag, int* num) {
const in_char* start = wa_strchr(fn,'|');
if (start > 0) {
wa_sscanf(start+1, wa_L("$s=%i "), num);
return 1;
} else {
*num = 0;
return 0;
}
}
/* try to detect XMPlay, which can't interact with the playlist = no splitting */ /* try to detect XMPlay, which can't interact with the playlist = no splitting */
static int is_xmplay() { static int is_xmplay() {
@ -367,21 +389,88 @@ void winamp_Quit() {
/* called before extension checks, to allow detection of mms://, etc */ /* called before extension checks, to allow detection of mms://, etc */
int winamp_IsOurFile(const in_char *fn) { int winamp_IsOurFile(const in_char *fn) {
VGMSTREAM* infostream;
vgmstream_ctx_valid_cfg cfg = {0}; vgmstream_ctx_valid_cfg cfg = {0};
char filename_utf8[PATH_LIMIT]; char filename_utf8[PATH_LIMIT];
int valid;
/* Winamp has a bizarre behavior that seemingly retries files twice (not when subsongs are added to the playlist?).
* Then passes "hi.mp3" (no path) as a last resort if no plugin plays the file. This makes non-playable files
* show time 0:00 and use Winamp's dialog (kinda annoying). Subsongs' fake filenames that remain blank (good).
*
* When allowing common_exts pretend to accept that fake mp3, so later fails on winamp_open/getfileinfo. Worked like
* this before when infostream wasn't tested, by mistake though, but who wants unplayable files reporting 0:00. */
//TODO may need to check file size to invalidate cache
if (wa_strcmp(fn, info_fn) == 0) {
//;vgm_logi("winamp_IsOurFile: repeated call\n");
return info_valid;
}
if (settings.exts_common_on && wa_strcmp(fn, wa_L("hi.mp3")) == 0) {
//vgm_logi("winamp_IsOurFile: ignored fakefile\n");
return 1;
}
wa_ichar_to_char(filename_utf8, PATH_LIMIT, fn);
cfg.skip_standard = 1; /* validated by Winamp */ cfg.skip_standard = 1; /* validated by Winamp */
cfg.accept_unknown = settings.exts_unknown_on; cfg.accept_unknown = settings.exts_unknown_on;
cfg.accept_common = settings.exts_common_on; cfg.accept_common = settings.exts_common_on;
/* Winamp seem to have bizarre handling of MP3 without standard names (ex song.mp3a), wa_ichar_to_char(filename_utf8, PATH_LIMIT, fn);
* in that it'll try to open normally, rejected if unknown_exts_on is not set, and
* finally retry with "hi.mp3", accepted if exts_common_on is set. */
/* returning 0 here means it only accepts the extensions in working_extension_list */ //;vgm_logi("winamp_IsOurFile: %s\n", filename_utf8);
return vgmstream_ctx_is_valid(filename_utf8, &cfg);
/* Returning 1 here means we'll handle the format (even if getinfo/play fail later), while 0
* means "default", that being: let other plugins check the file; if no plugin claims it by
* returning 1 Winamp will try to match file<>plugin via extension_list. So it's common for
* other plugins to just return 0 here (a few do check the file's header, like in_vgm).
*
* This generally works but plugins may hijack one of vgmstream's extensions (.wav would never
* be playable even with exts_common_on). Also, we can't just check the extension, to avoid
* hijacking stuff like in_vgm's *.vgm. So, vgmstream should try to check the file's format (slower).
*
* Somehow Winamp calls with "cda://" protocol on init, but should be ignored by is_valid */
info_valid = 0; /* may not be playable */
wa_strncpy(info_fn, fn, PATH_LIMIT); /* copy now for repeat calls */
/* basic extension check */
valid = vgmstream_ctx_is_valid(filename_utf8, &cfg);
if (!valid) {
//;vgm_logi("winamp_IsOurFile: invalid extension\n");
return 0;
}
/* format check */
infostream = init_vgmstream_winamp_fileinfo(fn);
if (!infostream) {
//;vgm_logi("winamp_IsOurFile: invalid infostream\n");
return 0;
}
//TODO simplify, variations used in other places (or rather, fix the API)
/* first thing winamp does after accepting a file is asking for time/title, so keep those around to avoid re-parsing the file */
{
int32_t num_samples;
apply_config(infostream, &settings);
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL, NULL);
num_samples = vgmstream_get_samples(infostream);
info_time = num_samples * 1000LL /infostream->sample_rate;
get_title(info_title,GETFILEINFO_TITLE_LENGTH, fn, infostream);
}
info_valid = 1;
close_vgmstream(infostream);
return 1;
} }
@ -391,6 +480,8 @@ int winamp_Play(const in_char *fn) {
in_char filename[PATH_LIMIT]; in_char filename[PATH_LIMIT];
int stream_index = 0; int stream_index = 0;
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("winamp_Play: file %s\n", f8); }
/* shouldn't happen */ /* shouldn't happen */
if (vgmstream) if (vgmstream)
return 1; return 1;
@ -400,7 +491,7 @@ int winamp_Play(const in_char *fn) {
parse_fn_int(fn, wa_L("$s"), &stream_index); parse_fn_int(fn, wa_L("$s"), &stream_index);
/* open the stream */ /* open the stream */
vgmstream = init_vgmstream_winamp(filename,stream_index); vgmstream = init_vgmstream_winamp(filename, stream_index);
if (!vgmstream) if (!vgmstream)
return 1; return 1;
@ -552,14 +643,8 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
else { else {
/* some other file in playlist given by filename */ /* some other file in playlist given by filename */
VGMSTREAM* infostream = NULL; VGMSTREAM* infostream = NULL;
in_char filename[PATH_LIMIT];
int stream_index = 0;
/* check for info encoded in the filename */ infostream = init_vgmstream_winamp_fileinfo(fn);
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
parse_fn_int(fn, wa_L("$s"), &stream_index);
infostream = init_vgmstream_winamp(filename, stream_index);
if (!infostream) return 0; if (!infostream) return 0;
apply_config(infostream, &settings); apply_config(infostream, &settings);
@ -590,9 +675,17 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
if (!fn || !*fn) { if (!fn || !*fn) {
/* no filename = current playing file */ /* no filename = current playing file */
//;vgm_logi("winamp_GetFileInfo: current\n");
if (!vgmstream) /* Sometimes called even if no file is playing (usually when hovering "O" > preferences... submenu).
* No idea what's that about so try to reuse last info */
if (!vgmstream) {
if (info_valid) {
if (title) wa_strncpy(title, info_title, GETFILEINFO_TITLE_LENGTH);
if (length_in_ms) *length_in_ms = info_time;
}
return; return;
}
if (title) { if (title) {
get_title(title,GETFILEINFO_TITLE_LENGTH, lastfn, vgmstream); get_title(title,GETFILEINFO_TITLE_LENGTH, lastfn, vgmstream);
@ -603,16 +696,18 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
} }
} }
else { else {
/* some other file in playlist given by filename */
VGMSTREAM* infostream = NULL; VGMSTREAM* infostream = NULL;
in_char filename[PATH_LIMIT]; //;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("winamp_GetFileInfo: file %s\n", f8); }
int stream_index = 0;
/* check for info encoded in the filename */ /* not changed from last IsOurFile (most common) */
parse_fn_string(fn, NULL, filename,PATH_LIMIT); if (info_valid && wa_strcmp(fn, info_fn) == 0) {
parse_fn_int(fn, wa_L("$s"), &stream_index); if (title) wa_strncpy(title, info_title, GETFILEINFO_TITLE_LENGTH);
if (length_in_ms) *length_in_ms = info_time;
return;
}
infostream = init_vgmstream_winamp(filename, stream_index); /* some other file in playlist given by filename */
infostream = init_vgmstream_winamp_fileinfo(fn);
if (!infostream) return; if (!infostream) return;
apply_config(infostream, &settings); apply_config(infostream, &settings);
@ -1021,16 +1116,12 @@ short xsample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS];
/* open the file and prepares to decode */ /* open the file and prepares to decode */
static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps, int *nch, int *srate) { static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps, int *nch, int *srate) {
VGMSTREAM *xvgmstream = NULL; VGMSTREAM* xvgmstream = NULL;
in_char filename[PATH_LIMIT];
int stream_index = 0;
/* check for info encoded in the filename */ //;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("Winamp: open common file %s\n", f8); }
parse_fn_string(fn, NULL, filename, PATH_LIMIT);
parse_fn_int(fn, wa_L("$s"), &stream_index);
/* open the stream */ /* open the stream */
xvgmstream = init_vgmstream_winamp(filename, stream_index); xvgmstream = init_vgmstream_winamp_fileinfo(fn);
if (!xvgmstream) { if (!xvgmstream) {
return NULL; return NULL;
} }