mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-31 20:25:23 +01:00
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:
commit
449bb5e083
@ -183,7 +183,11 @@ class TxtpInfo(object):
|
||||
return str_cut.split()[0].strip()
|
||||
|
||||
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):
|
||||
res = self._get_string(str)
|
||||
|
@ -1132,6 +1132,7 @@ struct g7221_handle {
|
||||
/* control */
|
||||
int bit_rate;
|
||||
int frame_size;
|
||||
int test_errors;
|
||||
/* AES setup/state */
|
||||
s14aes_handle* aes;
|
||||
/* 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 */
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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) {
|
||||
s14aes_close(handle->aes);
|
||||
handle->aes = NULL;
|
||||
handle->test_errors = 1; /* force? */
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1263,6 +1265,8 @@ int g7221_set_key(g7221_handle* handle, const uint8_t* key) {
|
||||
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
|
||||
* 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. */
|
||||
|
@ -25,6 +25,7 @@ static const char* extension_list[] = {
|
||||
"2dx9",
|
||||
"2pfs",
|
||||
"3do",
|
||||
"3ds", //txth/reserved [F1 2011 (3DS)]
|
||||
"4", //for Game.com audio
|
||||
"8", //txth/reserved [Gungage (PS1)]
|
||||
"800",
|
||||
|
@ -137,7 +137,7 @@
|
||||
<ClInclude Include="meta\sab_streamfile.h" />
|
||||
<ClInclude Include="meta\riff_ogg_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\ubi_bao_streamfile.h" />
|
||||
<ClInclude Include="meta\ubi_sb_streamfile.h" />
|
||||
|
@ -173,7 +173,7 @@
|
||||
<ClInclude Include="meta\sfh_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\sqex_sead_streamfile.h">
|
||||
<ClInclude Include="meta\sqex_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\txth_streamfile.h">
|
||||
|
@ -259,7 +259,7 @@ static const adxkey_info adxkey9_list[] = {
|
||||
{0x0000,0x0000,0x0000, NULL,1991062320101111}, // 000712DC5250B6F7
|
||||
|
||||
/* Shin Megami Tensei V (Switch) */
|
||||
{0x000c,0x13b5,0x1fdb, NULL,0}, // guessed with VGAudio (possible key: 613B4FEE / 1631277038)
|
||||
{0x0000,0x0000,0x0000, NULL,1731948526}, // 00000000673B6FEE
|
||||
|
||||
};
|
||||
|
||||
|
@ -1,48 +1,51 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.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 */
|
||||
VGMSTREAM * init_vgmstream_ahx(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int channel_count = 1, loop_flag = 0, type;
|
||||
/* AHX - CRI voice format */
|
||||
VGMSTREAM* init_vgmstream_ahx(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset;
|
||||
int channels = 1, loop_flag = 0, type;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
if ( !check_extensions(streamFile, "ahx") ) goto fail;
|
||||
/* checks */
|
||||
if (read_u16be(0x00,sf) != 0x8000)
|
||||
goto fail;
|
||||
|
||||
/* check first 2 bytes */
|
||||
if ((uint16_t)read_16bitBE(0,streamFile)!=0x8000) goto fail;
|
||||
if (!check_extensions(sf, "ahx") )
|
||||
goto fail;
|
||||
|
||||
/* get stream offset, check for CRI signature just before */
|
||||
start_offset = (uint16_t)read_16bitBE(0x02,streamFile) + 0x04;
|
||||
|
||||
if ((uint16_t)read_16bitBE(start_offset-0x06,streamFile)!=0x2863 || /* "(c" */
|
||||
(uint32_t)read_32bitBE(start_offset-0x04,streamFile)!=0x29435249) /* ")CRI" */
|
||||
start_offset = read_u16be(0x02,sf) + 0x04;
|
||||
if (read_u16be(start_offset - 0x06,sf) != 0x2863 || /* "(c" */
|
||||
read_u32be(start_offset - 0x04,sf) != 0x29435249) /* ")CRI" */
|
||||
goto fail;
|
||||
|
||||
/* check for encoding type (0x10 is AHX for DC with bigger frames, 0x11 is AHX, 0x0N are ADX) */
|
||||
type = read_8bit(0x04,streamFile);
|
||||
/* types: 0x10 = AHX for DC with bigger frames, 0x11 = AHX, 0x0N = ADX */
|
||||
type = read_u8(0x04,sf);
|
||||
if (type != 0x10 && type != 0x11) goto fail;
|
||||
|
||||
/* check for frame size (0 for AHX) */
|
||||
if (read_8bit(0x05,streamFile) != 0) goto fail;
|
||||
/* frame size (0 for AHX) */
|
||||
if (read_u8(0x05,sf) != 0) goto fail;
|
||||
|
||||
/* 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) */
|
||||
if (read_8bit(0x07,streamFile) != 1) goto fail;
|
||||
if (read_u8(0x07,sf) != 1) goto fail;
|
||||
|
||||
/* check version signature */
|
||||
if (read_8bit(0x12,streamFile) != 0x06) goto fail;
|
||||
if (read_u8(0x12,sf) != 0x06) goto fail;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitBE(0x08,streamFile); /* real sample rate */
|
||||
vgmstream->num_samples = read_32bitBE(0x0c,streamFile); /* doesn't include encoder_delay (handled in decoder) */
|
||||
vgmstream->sample_rate = read_s32be(0x08,sf); /* real sample rate */
|
||||
vgmstream->num_samples = read_s32be(0x0c,sf); /* doesn't include encoder_delay (handled in decoder) */
|
||||
|
||||
vgmstream->meta_type = meta_AHX;
|
||||
|
||||
@ -50,30 +53,51 @@ VGMSTREAM * init_vgmstream_ahx(STREAMFILE *streamFile) {
|
||||
#ifdef VGM_USE_MPEG
|
||||
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;
|
||||
|
||||
if (cfg.encryption) {
|
||||
uint8_t keybuf[6];
|
||||
if (read_key_file(keybuf, 6, streamFile) == 6) {
|
||||
cfg.cri_key1 = get_16bitBE(keybuf+0);
|
||||
cfg.cri_key2 = get_16bitBE(keybuf+2);
|
||||
cfg.cri_key3 = get_16bitBE(keybuf+4);
|
||||
uint8_t keybuf[0x10+1] = {0}; /* approximate max for keystrings, +1 extra null for keystrings */
|
||||
size_t key_size;
|
||||
|
||||
key_size = read_key_file(keybuf, sizeof(keybuf), sf);
|
||||
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->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;
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
|
170
src/meta/akb.c
170
src/meta/akb.c
@ -1,5 +1,7 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "sqex_streamfile.h"
|
||||
|
||||
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
/* 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;
|
||||
|
||||
loop_start = read_32bitLE(0x14, sf);
|
||||
loop_end = read_32bitLE(0x18, sf);
|
||||
loop_start = read_s32le(0x14, sf);
|
||||
loop_end = read_s32le(0x18, sf);
|
||||
|
||||
filesize = get_streamfile_size( sf );
|
||||
|
||||
@ -33,40 +35,59 @@ fail:
|
||||
#endif
|
||||
|
||||
|
||||
/* AKB - found in SQEX iOS games */
|
||||
VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
/* AKB - found in SQEX 'sdlib' iOS/Android games */
|
||||
VGMSTREAM* init_vgmstream_akb(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset, extradata_offset = 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;
|
||||
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(sf, "akb") )
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x414B4220) /* "AKB " */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x08,sf) != get_streamfile_size(sf))
|
||||
if (!is_id32be(0x00,sf, "AKB "))
|
||||
goto fail;
|
||||
|
||||
/* 0x04(1): version */
|
||||
header_size = read_16bitLE(0x06,sf);
|
||||
if (!check_extensions(sf, "akb"))
|
||||
goto fail;
|
||||
|
||||
codec = read_8bit(0x0c,sf);
|
||||
channel_count = read_8bit(0x0d,sf);
|
||||
sample_rate = (uint16_t)read_16bitLE(0x0e,sf);
|
||||
num_samples = read_32bitLE(0x10,sf);
|
||||
loop_start = read_32bitLE(0x14,sf);
|
||||
loop_end = read_32bitLE(0x18,sf);
|
||||
version = read_u8(0x04,sf); /* 00=TWEWY, 02=DQs, 03=FFAgito */
|
||||
/* 0x05(1); unused? */
|
||||
header_size = read_u16le(0x06,sf);
|
||||
if (read_u32le(0x08,sf) != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
|
||||
/* 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 */
|
||||
if (header_size >= 0x44) { /* v2+ */
|
||||
extradata_size = read_16bitLE(0x1c,sf);
|
||||
extradata_size = read_u16le(0x1c,sf);
|
||||
/* 0x20+: config? (pan, volume) */
|
||||
subheader_size = read_16bitLE(0x28,sf);
|
||||
subheader_size = read_u16le(0x28,sf);
|
||||
/* 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;
|
||||
start_offset = extradata_offset + extradata_size;
|
||||
}
|
||||
@ -79,30 +100,34 @@ VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) {
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->meta_type = meta_AKB;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
|
||||
switch (codec) {
|
||||
case 0x02: { /* MSADPCM [Dragon Quest II (iOS) sfx] */
|
||||
vgmstream->coding_type = coding_MSADPCM;
|
||||
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
|
||||
* (base samples may have more than possible and read over file size otherwise, very strange)
|
||||
* loop_end seems to exist even with loop disabled */
|
||||
vgmstream->num_samples = read_32bitLE(extradata_offset + 0x04, sf);
|
||||
vgmstream->loop_start_sample = read_32bitLE(extradata_offset + 0x08, sf);
|
||||
vgmstream->loop_end_sample = read_32bitLE(extradata_offset + 0x0c, sf);
|
||||
vgmstream->num_samples = read_s32le(extradata_offset + 0x04, sf);
|
||||
vgmstream->loop_start_sample = read_s32le(extradata_offset + 0x08, sf);
|
||||
vgmstream->loop_end_sample = read_s32le(extradata_offset + 0x0c, sf);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x05: { /* Ogg Vorbis [Final Fantasy VI (iOS), Dragon Quest II-VI (iOS)] */
|
||||
STREAMFILE* temp_sf;
|
||||
VGMSTREAM *ogg_vgmstream = NULL;
|
||||
ogg_vorbis_meta_info_t ovmi = {0};
|
||||
|
||||
@ -111,7 +136,20 @@ VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) {
|
||||
/* extradata + 0x04: Ogg loop start offset */
|
||||
/* 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) {
|
||||
close_vgmstream(vgmstream);
|
||||
return ogg_vgmstream;
|
||||
@ -161,13 +199,13 @@ VGMSTREAM * init_vgmstream_akb(STREAMFILE *sf) {
|
||||
}
|
||||
#endif
|
||||
|
||||
case 0x01: /* PCM16LE */
|
||||
case 0x01: /* PCM16LE (from debugging, not seen) */
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open the file for reading */
|
||||
if ( !vgmstream_open_stream(vgmstream, sf, start_offset) )
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
@ -178,67 +216,69 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
/* AKB2 - found in later SQEX iOS games */
|
||||
VGMSTREAM * init_vgmstream_akb2(STREAMFILE *sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
/* AKB2 - found in later SQEX 'sdlib' iOS/Android games */
|
||||
VGMSTREAM* init_vgmstream_akb2(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset, material_offset, extradata_offset;
|
||||
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;
|
||||
|
||||
/* check extensions */
|
||||
if ( !check_extensions(sf, "akb") )
|
||||
goto fail;
|
||||
|
||||
/* checks */
|
||||
if (read_32bitBE(0x00,sf) != 0x414B4232) /* "AKB2" */
|
||||
if (!is_id32be(0x00,sf, "AKB2"))
|
||||
goto fail;
|
||||
if (read_32bitLE(0x08,sf) != get_streamfile_size(sf))
|
||||
|
||||
if (!check_extensions(sf, "akb"))
|
||||
goto fail;
|
||||
|
||||
/* 0x04: version */
|
||||
if (read_u32le(0x08,sf) != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
|
||||
/* parse tables */
|
||||
{
|
||||
off_t table_offset;
|
||||
size_t table_size, entry_size;
|
||||
off_t akb_header_size = read_16bitLE(0x06, sf);
|
||||
int table_count = read_8bit(0x0c, sf);
|
||||
off_t akb_header_size = read_u16le(0x06, sf);
|
||||
int table_count = read_u8(0x0c, sf);
|
||||
|
||||
/* 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 */
|
||||
goto fail;
|
||||
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_size = read_16bitLE(table_offset + 0x02, sf);
|
||||
table_offset = read_u32le(akb_header_size + (table_count-1)*entry_size + 0x04, 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 > 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) **/
|
||||
/* 0x00: 0? */
|
||||
codec = read_8bit(material_offset+0x01,sf);
|
||||
channel_count = read_8bit(material_offset+0x02,sf);
|
||||
encryption_flag = read_8bit(material_offset+0x03,sf);
|
||||
material_size = read_16bitLE(material_offset+0x04,sf);
|
||||
sample_rate = (uint16_t)read_16bitLE(material_offset+0x06,sf);
|
||||
stream_size = read_32bitLE(material_offset+0x08,sf);
|
||||
num_samples = read_32bitLE(material_offset+0x0c,sf);
|
||||
codec = read_u8(material_offset+0x01,sf);
|
||||
channel_count = read_u8(material_offset+0x02,sf);
|
||||
flags = read_u8(material_offset+0x03,sf);
|
||||
material_size = read_u16le(material_offset+0x04,sf);
|
||||
sample_rate = read_u16le(material_offset+0x06,sf);
|
||||
stream_size = read_u32le(material_offset+0x08,sf);
|
||||
num_samples = read_s32le(material_offset+0x0c,sf);
|
||||
|
||||
loop_start = read_32bitLE(material_offset+0x10,sf);
|
||||
loop_end = read_32bitLE(material_offset+0x14,sf);
|
||||
extradata_size = read_32bitLE(material_offset+0x18,sf);
|
||||
/* rest: ? (empty or 0x3f80) */
|
||||
loop_start = read_s32le(material_offset+0x10,sf);
|
||||
loop_end = read_s32le(material_offset+0x14,sf);
|
||||
extradata_size = read_u32le(material_offset+0x18,sf);
|
||||
/* rest: ? (empty, floats or 0x3f80) */
|
||||
|
||||
loop_flag = (loop_end > loop_start);
|
||||
extradata_offset = material_offset + material_size;
|
||||
start_offset = material_offset + material_size + extradata_size;
|
||||
|
||||
if (encryption_flag & 0x08)
|
||||
/* encrypted, not seen (see AKB flags) */
|
||||
if (flags & 0x08)
|
||||
goto fail;
|
||||
|
||||
|
||||
@ -267,14 +307,14 @@ VGMSTREAM * init_vgmstream_akb2(STREAMFILE *sf) {
|
||||
case 0x02: { /* MSADPCM [The Irregular at Magic High School Lost Zero (Android)] */
|
||||
vgmstream->coding_type = coding_MSADPCM;
|
||||
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
|
||||
* (base samples may have more than possible and read over file size otherwise, very strange)
|
||||
* loop_end seems to exist even with loop disabled */
|
||||
vgmstream->num_samples = read_32bitLE(extradata_offset + 0x04, sf);
|
||||
vgmstream->loop_start_sample = read_32bitLE(extradata_offset + 0x08, sf);
|
||||
vgmstream->loop_end_sample = read_32bitLE(extradata_offset + 0x0c, sf);
|
||||
vgmstream->num_samples = read_s32le(extradata_offset + 0x04, sf);
|
||||
vgmstream->loop_start_sample = read_s32le(extradata_offset + 0x08, sf);
|
||||
vgmstream->loop_end_sample = read_s32le(extradata_offset + 0x0c, sf);
|
||||
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)
|
||||
* 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->loop_start_sample = read_32bitLE(material_offset+0x10,sf);//loop_start;
|
||||
vgmstream->num_samples = read_s32le(material_offset+0x0c,sf);//num_samples;
|
||||
vgmstream->loop_start_sample = read_s32le(material_offset+0x10,sf);//loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
break;
|
||||
}
|
||||
|
@ -1,48 +1,48 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* APC - from Cryo games [MegaRace 3 (PC)] */
|
||||
VGMSTREAM * init_vgmstream_apc(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
size_t data_size;
|
||||
int loop_flag, channel_count;
|
||||
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"apc") )
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4352594F) /* "CRYO" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x04,streamFile) != 0x5F415043) /* "_APC" */
|
||||
goto fail;
|
||||
//if (read_32bitBE(0x08,streamFile) != 0x312E3230) /* "1.20" */
|
||||
// goto fail;
|
||||
|
||||
/* 0x14/18: L/R hist sample? */
|
||||
|
||||
start_offset = 0x20;
|
||||
data_size = get_streamfile_size(streamFile) - start_offset;
|
||||
channel_count = read_32bitLE(0x1c,streamFile) == 0 ? 1 : 2;
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_APC;
|
||||
vgmstream->sample_rate = read_32bitLE(0x10,streamFile);
|
||||
vgmstream->num_samples = ima_bytes_to_samples(data_size,channel_count);
|
||||
|
||||
vgmstream->coding_type = coding_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* APC - from Cryo games [MegaRace 3 (PC)] */
|
||||
VGMSTREAM* init_vgmstream_apc(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset, data_size;
|
||||
int loop_flag, channels, sample_rate;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "CRYO"))
|
||||
goto fail;
|
||||
if (!is_id32be(0x04,sf, "_APC"))
|
||||
goto fail;
|
||||
//if (!is_id32be(0x04,sf, "1.20"))
|
||||
// goto fail;
|
||||
|
||||
if (!check_extensions(sf,"apc"))
|
||||
goto fail;
|
||||
|
||||
sample_rate = read_s32le(0x10,sf);
|
||||
/* 0x14/18: L/R hist sample? */
|
||||
channels = read_s32le(0x1c,sf) == 0 ? 1 : 2;
|
||||
loop_flag = 0;
|
||||
start_offset = 0x20;
|
||||
data_size = get_streamfile_size(sf) - start_offset;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_APC;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = ima_bytes_to_samples(data_size, channels);
|
||||
|
||||
vgmstream->coding_type = coding_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
121
src/meta/asf.c
121
src/meta/asf.c
@ -1,58 +1,63 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* ASF - Argonaut PC games [Croc 2 (PC), Aladdin: Nasira's Revenge (PC)] */
|
||||
VGMSTREAM * init_vgmstream_asf(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, version;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .asf: original
|
||||
* .lasf: fake for plugins */
|
||||
if (!check_extensions(streamFile, "asf,lasf"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x41534600) /* "ASF\0" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x04,streamFile) != 0x02000100)
|
||||
goto fail;
|
||||
if (read_32bitLE(0x08,streamFile) != 0x01 &&
|
||||
read_32bitLE(0x0c,streamFile) != 0x18 &&
|
||||
read_32bitLE(0x1c,streamFile) != 0x20)
|
||||
goto fail;
|
||||
|
||||
version = read_32bitLE(0x28,streamFile); /* assumed? */
|
||||
switch(version){
|
||||
case 0x0d: channel_count = 1; break; /* Aladdin: Nasira's Revenge (PC) */
|
||||
case 0x0f: channel_count = 2; break; /* Croc 2 (PC), The Emperor's New Groove (PC) */
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
loop_flag = 0;
|
||||
start_offset = 0x2c;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = (uint16_t)read_16bitLE(0x24, streamFile);
|
||||
vgmstream->meta_type = meta_ASF;
|
||||
vgmstream->coding_type = coding_ASF;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x11;
|
||||
vgmstream->num_samples = (get_streamfile_size(streamFile)-start_offset)/(0x11*channel_count)*32; /* bytes_to_samples */
|
||||
//vgmstream->num_samples = read_32bitLE(0x18,streamFile) * (0x20<<channel_count); /* something like this? */
|
||||
|
||||
read_string(vgmstream->stream_name,0x10, 0x08+1,streamFile);
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* ASF - Argonaut PC games [Croc 2 (PC), Aladdin: Nasira's Revenge (PC)] */
|
||||
VGMSTREAM* init_vgmstream_asf(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset;
|
||||
int loop_flag, channels, type, sample_rate;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "ASF\0"))
|
||||
goto fail;
|
||||
|
||||
/* .asf: original
|
||||
* .lasf: fake for plugins */
|
||||
if (!check_extensions(sf, "asf,lasf"))
|
||||
goto fail;
|
||||
|
||||
if (read_u32le(0x04,sf) != 0x00010002) /* v1.002? */
|
||||
goto fail;
|
||||
if (read_u32le(0x08,sf) != 0x01 &&
|
||||
read_u32le(0x0c,sf) != 0x18)
|
||||
goto fail;
|
||||
/* 0x10~18: stream name (same as filename) */
|
||||
/* 0x18: non-full size? */
|
||||
if (read_u32le(0x1c,sf) != 0x20) /* samples per frame? */
|
||||
goto fail;
|
||||
sample_rate = read_u16le(0x24, sf);
|
||||
|
||||
type = read_u32le(0x28,sf); /* assumed? */
|
||||
switch(type){
|
||||
case 0x0d: channels = 1; break; /* Aladdin: Nasira's Revenge (PC) */
|
||||
case 0x0f: channels = 2; break; /* Croc 2 (PC), The Emperor's New Groove (PC) */
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
loop_flag = 0;
|
||||
start_offset = 0x2c;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->meta_type = meta_ASF;
|
||||
vgmstream->coding_type = coding_ASF;
|
||||
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 */
|
||||
//vgmstream->num_samples = read_32bitLE(0x18,sf) * (32 << channels); /* something like this? */
|
||||
|
||||
read_string(vgmstream->stream_name,0x10, 0x08+1,sf);
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -39,7 +39,13 @@ VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf) {
|
||||
|
||||
|
||||
/* 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+whd: uncommon [Nights of Azure 2 (PS4)]
|
||||
* .wb2+wh2: newer [Nights of Azure 2 (PC)]
|
||||
* .sed: mixed header+data [Dissidia NT (PC)] */
|
||||
if (!check_extensions(sf, "wbd,wb2,sed"))
|
||||
@ -47,18 +53,20 @@ VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf) {
|
||||
|
||||
|
||||
/* 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");
|
||||
if (!sf_h)
|
||||
sf_h = open_streamfile_by_ext(sf, "whd");
|
||||
sf_b = sf;
|
||||
}
|
||||
else if (check_extensions(sf, "wb2")) {
|
||||
sf_h = open_streamfile_by_ext(sf, "wh2");
|
||||
sf_b = sf;
|
||||
}
|
||||
else if (check_extensions(sf, "sed")) {
|
||||
sf_h = sf;
|
||||
sf_b = sf;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
@ -378,14 +386,14 @@ fail:
|
||||
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;
|
||||
int entries, current_subsongs, relative_subsong;
|
||||
size_t entry_size;
|
||||
|
||||
|
||||
/* a format mimicking PSVita's hd4+bd4 format */
|
||||
/* 00: K4HD id */
|
||||
/* 00: K4HD/PVHD id */
|
||||
/* 04: chunk size */
|
||||
/* 08: ? */
|
||||
/* 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;
|
||||
|
||||
|
||||
if (read_u32be(0x00, sf_h) == 0x57484431) { /* "WHD1" */
|
||||
if (is_id32be(0x00, sf_h, "WHD1")) {
|
||||
/* container of fused .wbh+wbd */
|
||||
/* 0x04: fixed value? */
|
||||
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;
|
||||
|
||||
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;
|
||||
break;
|
||||
|
||||
@ -632,6 +641,7 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
break;
|
||||
|
||||
default:
|
||||
vgm_logi("KWB: unknown type\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -3,48 +3,45 @@
|
||||
#include "../coding/coding.h"
|
||||
#include <string.h>
|
||||
|
||||
/* .s14 and .sss - headerless siren14 stream (The Idolm@ster DS, Korogashi Puzzle Katamari Damacy DS) */
|
||||
VGMSTREAM * init_vgmstream_s14_sss(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
static int test_interleave(STREAMFILE* sf, int channels, int interleave);
|
||||
|
||||
/* .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;
|
||||
int channel_count, loop_flag = 0, interleave;
|
||||
int channels, loop_flag = 0, interleave;
|
||||
|
||||
|
||||
/* check extension */
|
||||
if (check_extensions(streamFile,"sss")) {
|
||||
channel_count = 2;
|
||||
} else if (check_extensions(streamFile,"s14")) {
|
||||
channel_count = 1; //todo missing dual _0ch.s14 _1ch.s14, but dual_ext thing doesn't work properly with siren14 decoder
|
||||
if (check_extensions(sf,"sss")) {
|
||||
channels = 2;
|
||||
} else if (check_extensions(sf,"s14")) {
|
||||
channels = 1; /* may have dual _0ch.s14 + _1ch.s14, needs .txtp */
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 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) */
|
||||
/* raw siren comes in 3 frame sizes, try to guess the correct one */
|
||||
{
|
||||
char filename[PATH_LIMIT];
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
|
||||
/* 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 */
|
||||
/* horrid but ain't losing sleep over it (besides the header is often incrusted in-code as some tracks loop)
|
||||
* Katamari, Taiko = 0x78/0x50, idolmaster=0x3c (usually but can be any) */
|
||||
if (test_interleave(sf, channels, 0x78))
|
||||
interleave = 0x78;
|
||||
else if (strstr(filename,"32700")==filename || /* Hottarake no Shima - Kanata to Nijiiro no Kagami */
|
||||
strstr(filename,"b0")==filename || strstr(filename,"puzzle")==filename || strstr(filename,"M09")==filename || /* Korogashi Puzzle Katamari Damacy */
|
||||
strstr(filename,"_32kbps")!=NULL) /* Taiko no Tatsujin DS 1/2 */
|
||||
interleave = 0x50;
|
||||
else if (test_interleave(sf, channels, 0x50))
|
||||
interleave = 0x50;
|
||||
else if (test_interleave(sf, channels, 0x3c))
|
||||
interleave = 0x3c;
|
||||
else
|
||||
interleave = 0x3c; /* The Idolm@ster - Dearly Stars */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
vgmstream->num_samples = get_streamfile_size(streamFile) / (interleave * channel_count) * (32000/50);
|
||||
vgmstream->sample_rate = 32768; /* maybe 32700? */
|
||||
vgmstream->num_samples = get_streamfile_size(sf) / (interleave * channels) * (32000/50);
|
||||
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->interleave_block_size = interleave;
|
||||
|
||||
@ -58,7 +55,7 @@ VGMSTREAM * init_vgmstream_s14_sss(STREAMFILE *streamFile) {
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
@ -66,3 +63,27 @@ fail:
|
||||
close_vgmstream(vgmstream);
|
||||
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
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ VGMSTREAM* init_vgmstream_sgxd(STREAMFILE* sf) {
|
||||
/* checks */
|
||||
/* .sgx: header+data (Genji)
|
||||
* .sgd: header+data (common)
|
||||
* .sgh+sgd: header+data */
|
||||
* .sgh+sgd: header+data (streams) */
|
||||
if (!check_extensions(sf,"sgx,sgd,sgb"))
|
||||
goto fail;
|
||||
|
||||
@ -91,7 +91,9 @@ VGMSTREAM* init_vgmstream_sgxd(STREAMFILE* sf) {
|
||||
* - 0x00: sub-id?
|
||||
* - 0x02: type? (possibly: 0000=bank, 0x2xxx=SEQD/WAVE, 0x3xxx=WSUR, 0x4xxx=BUSS, 0x6xxx=CONF)
|
||||
* - 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: ?
|
||||
* - WMKR: ?
|
||||
* - CONF: ? (name offset + config offset)
|
||||
|
@ -6,7 +6,7 @@
|
||||
VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = 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;
|
||||
int32_t num_samples, loop_start, loop_end;
|
||||
uint32_t at9_config;
|
||||
@ -16,7 +16,7 @@ VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf) {
|
||||
|
||||
if (!is_id32be(0x00, sf, "SNDZ"))
|
||||
goto fail;
|
||||
head_size = read_u32le(0x04, sf);
|
||||
//head_size = read_u32le(0x04, sf);
|
||||
data_size = read_u32le(0x08, sf);
|
||||
/* 0x0c: version? (0x00010001) */
|
||||
/* 0x10: size size? */
|
||||
@ -95,7 +95,6 @@ VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf) {
|
||||
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)*/
|
||||
if (streamed && get_streamfile_size(sf) < data_size) {
|
||||
sf_b = open_streamfile_by_ext(sf, "szd2");
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "sqex_sead_streamfile.h"
|
||||
#include "sqex_streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
@ -278,7 +278,7 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) {
|
||||
/* 0x0e: reserved x2 */
|
||||
/* 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;
|
||||
|
||||
temp_vgmstream = init_vgmstream_hca(temp_sf);
|
||||
|
@ -6,11 +6,11 @@
|
||||
typedef struct {
|
||||
size_t 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) {
|
||||
/* Found in FFXII_TZA.exe (same key in SCD Ogg V3) */
|
||||
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), also in AKB's sdlib as "EncKey" */
|
||||
static const uint8_t key[0x100] = {
|
||||
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
|
||||
@ -45,22 +45,22 @@ static size_t sqex_sead_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, siz
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/* setup sf */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset, subfile_size);
|
||||
if (encryption) {
|
||||
sqex_sead_io_data io_data = {0};
|
||||
sqex_io_data io_data = {0};
|
||||
|
||||
io_data.start = header_size;
|
||||
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;
|
||||
}
|
||||
|
@ -5,35 +5,52 @@
|
||||
/* SWAV - wave files generated by the DS SDK */
|
||||
VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int channel_count, loop_flag;
|
||||
off_t start_offset;
|
||||
int codec_number, bits_per_sample;
|
||||
uint32_t start_offset, data_size, file_size;
|
||||
int channels, loop_flag, codec, bits_per_sample, sample_rate;
|
||||
int32_t loop_start, loop_end;
|
||||
coding_t coding_type;
|
||||
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "SWAV"))
|
||||
goto fail;
|
||||
|
||||
/* .swav: standard
|
||||
* .adpcm: Merlin - A Servant of Two Masters (DS) */
|
||||
if (!check_extensions(sf, "swav,adpcm"))
|
||||
goto fail;
|
||||
|
||||
if (read_u32be(0x00,sf) != 0x53574156) /* "SWAV" */
|
||||
goto fail;
|
||||
if (read_u32be(0x10,sf) != 0x44415441) /* "DATA" */
|
||||
goto fail;
|
||||
/* 0x04: BOM mark */
|
||||
/* 0x06: version? (1.00) */
|
||||
file_size = read_u32le(0x08,sf);
|
||||
/* 0x0c: always 16? */
|
||||
/* 0x0e: always 1? */
|
||||
|
||||
/* check type details */
|
||||
codec_number = read_8bit(0x18,sf);
|
||||
loop_flag = read_8bit(0x19,sf);
|
||||
if (!is_id32be(0x10,sf, "DATA"))
|
||||
goto fail;
|
||||
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;
|
||||
if (get_streamfile_size(sf) != read_s32le(0x08,sf)) {
|
||||
if (get_streamfile_size(sf) != (read_s32le(0x08,sf) - 0x24) * 2 + 0x24)
|
||||
start_offset = 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;
|
||||
channel_count = 2;
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
switch (codec_number) {
|
||||
switch (codec) {
|
||||
case 0:
|
||||
coding_type = coding_PCM8;
|
||||
bits_per_sample = 8;
|
||||
@ -49,18 +66,17 @@ VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
start_offset = 0x24;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->num_samples = (read_s32le(0x14,sf) - 0x14) * 8 / bits_per_sample;
|
||||
vgmstream->sample_rate = read_u16le(0x1A,sf);
|
||||
vgmstream->num_samples = (data_size - 0x14) * 8 / bits_per_sample;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = read_u16le(0x1E,sf) * 32 / bits_per_sample;
|
||||
vgmstream->loop_end_sample = read_s32le(0x20,sf) * 32 / bits_per_sample + vgmstream->loop_start_sample;
|
||||
vgmstream->loop_start_sample = loop_start * 32 / bits_per_sample;
|
||||
vgmstream->loop_end_sample = loop_end * 32 / bits_per_sample + vgmstream->loop_start_sample;
|
||||
}
|
||||
|
||||
if (coding_type == coding_IMA_int) {
|
||||
@ -71,18 +87,18 @@ VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
|
||||
|
||||
{
|
||||
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_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->meta_type = meta_SWAV;
|
||||
if (channel_count == 2) {
|
||||
if (channels == 2) {
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 1;
|
||||
} else {
|
||||
|
@ -52,8 +52,8 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
|
||||
/* check variation */
|
||||
switch(vag_id) {
|
||||
|
||||
case 0x56414731: /* "VAG1" [Metal Gear Solid 3 (PS2), Cabela's African Safari (PSP)] */
|
||||
meta_type = meta_PS2_VAG1; //TODO not always Konami
|
||||
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 (Sand Grain Studios)
|
||||
start_offset = 0x40; /* 0x30 is extra data in VAG1 */
|
||||
interleave = 0x10;
|
||||
loop_flag = 0;
|
||||
|
@ -1,71 +1,71 @@
|
||||
#include "meta.h"
|
||||
#include "../util/endianness.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)] */
|
||||
VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, extradata_offset;
|
||||
int loop_flag = 0, channel_count, sample_rate, codec;
|
||||
int32_t num_samples, loop_start = 0, loop_end = 0;
|
||||
size_t interleave;
|
||||
VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset, extradata_offset, interleave;
|
||||
int channels, loop_flag, sample_rate, codec;
|
||||
int32_t num_samples, loop_start, loop_end;
|
||||
int big_endian;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
float (*read_f32)(off_t,STREAMFILE*) = NULL;
|
||||
read_u32_t read_u32;
|
||||
read_s32_t read_s32;
|
||||
read_f32_t read_f32;
|
||||
|
||||
|
||||
/* 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;
|
||||
/* 0x04: version? common=0, VAW3=2 */
|
||||
|
||||
if (read_32bitLE(0x00,streamFile) != 0xE5B7ECFE && /* header id */
|
||||
read_32bitBE(0x00,streamFile) != 0xE5B7ECFE)
|
||||
goto fail;
|
||||
if (read_32bitBE(0x04,streamFile) != 0x00) /* version? */
|
||||
if (!check_extensions(sf, "wave"))
|
||||
goto fail;
|
||||
|
||||
/* assumed */
|
||||
big_endian = read_32bitBE(0x00,streamFile) == 0xE5B7ECFE;
|
||||
big_endian = read_u32be(0x00,sf) == 0xE5B7ECFE;
|
||||
if (big_endian) {
|
||||
read_32bit = read_32bitBE;
|
||||
read_u32 = read_u32be;
|
||||
read_s32 = read_s32be;
|
||||
read_f32 = read_f32be;
|
||||
} else {
|
||||
read_32bit = read_32bitLE;
|
||||
read_u32 = read_u32le;
|
||||
read_s32 = read_s32le;
|
||||
read_f32 = read_f32le;
|
||||
}
|
||||
|
||||
channel_count = read_8bit(0x05,streamFile);
|
||||
|
||||
if (read_32bit(0x08,streamFile) != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
if (read_8bit(0x0c,streamFile) != 0x00) /* ? */
|
||||
if (read_u32(0x08,sf) != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
|
||||
sample_rate = (int)read_f32(0x0c, streamFile); /* sample rate in 32b float (WHY?) */
|
||||
num_samples = read_32bit(0x10, streamFile);
|
||||
loop_start = read_32bit(0x14, streamFile);
|
||||
loop_end = read_32bit(0x18, streamFile);
|
||||
sample_rate = (int)read_f32(0x0c, sf); /* WHY */
|
||||
num_samples = read_s32(0x10, sf);
|
||||
loop_start = read_s32(0x14, sf);
|
||||
loop_end = read_s32(0x18, sf);
|
||||
|
||||
codec = read_8bit(0x1c, streamFile);
|
||||
channel_count = read_8bit(0x1d, streamFile);
|
||||
if (read_8bit(0x1e, streamFile) != 0x00) goto fail; /* unknown */
|
||||
if (read_8bit(0x1f, streamFile) != 0x00) goto fail; /* unknown */
|
||||
codec = read_u8(0x1c, sf);
|
||||
channels = read_u8(0x1d, sf);
|
||||
if (read_u8(0x1e, sf) != 0x00) goto fail; /* unknown */
|
||||
if (read_u8(0x1f, sf) != 0x00) goto fail; /* unknown */
|
||||
|
||||
start_offset = read_32bit(0x20, streamFile);
|
||||
interleave = read_32bit(0x24, streamFile); /* typically half data_size */
|
||||
extradata_offset = read_32bit(0x28, streamFile); /* OR: extradata size (0x2c) */
|
||||
start_offset = read_u32(0x20, sf);
|
||||
interleave = read_u32(0x24, sf); /* typically half data_size */
|
||||
extradata_offset = read_u32(0x28, sf); /* OR: extradata size (always 0x2c) */
|
||||
|
||||
loop_flag = (loop_start > 0);
|
||||
/* 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. */
|
||||
if(!loop_flag
|
||||
&& loop_start == 0 && loop_end == num_samples /* full loop */
|
||||
&& channel_count > 1
|
||||
&& channels > 1
|
||||
&& num_samples > 20*sample_rate) { /* in seconds */
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
@ -74,6 +74,7 @@ VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
vgmstream->meta_type = meta_WAVE;
|
||||
|
||||
/* not sure if there are other codecs but anyway */
|
||||
switch(codec) {
|
||||
case 0x02:
|
||||
@ -82,14 +83,14 @@ VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
/* 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_hist(vgmstream, streamFile, extradata_offset+0x22, 0x2c, big_endian);
|
||||
dsp_read_coefs(vgmstream, sf, extradata_offset+0x00, 0x2c, big_endian);
|
||||
dsp_read_hist(vgmstream, sf, extradata_offset+0x22, 0x2c, big_endian);
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
|
@ -21,8 +21,13 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
|
||||
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) {
|
||||
int len = strlen(filename);
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
if (filename[len - 1] == '/' || filename[len - 1] == '\\')
|
||||
return 0;
|
||||
return !cfg->reject_extensionless;
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,45 @@ winamp_settings_t settings;
|
||||
winamp_state_t state;
|
||||
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 */
|
||||
/* ***************************************** */
|
||||
|
||||
/* 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 */
|
||||
static VGMSTREAM* init_vgmstream_winamp(const in_char* fn, int stream_index) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
@ -79,6 +113,19 @@ static VGMSTREAM* init_vgmstream_winamp(const in_char* fn, int stream_index) {
|
||||
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 */
|
||||
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
|
||||
@ -133,31 +180,6 @@ static int split_subsongs(const in_char* filename, int stream_index, VGMSTREAM *
|
||||
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 */
|
||||
static int is_xmplay() {
|
||||
@ -367,21 +389,88 @@ void winamp_Quit() {
|
||||
|
||||
/* called before extension checks, to allow detection of mms://, etc */
|
||||
int winamp_IsOurFile(const in_char *fn) {
|
||||
VGMSTREAM* infostream;
|
||||
vgmstream_ctx_valid_cfg cfg = {0};
|
||||
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.accept_unknown = settings.exts_unknown_on;
|
||||
cfg.accept_common = settings.exts_common_on;
|
||||
|
||||
/* Winamp seem to have bizarre handling of MP3 without standard names (ex song.mp3a),
|
||||
* 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. */
|
||||
wa_ichar_to_char(filename_utf8, PATH_LIMIT, fn);
|
||||
|
||||
/* returning 0 here means it only accepts the extensions in working_extension_list */
|
||||
return vgmstream_ctx_is_valid(filename_utf8, &cfg);
|
||||
//;vgm_logi("winamp_IsOurFile: %s\n", filename_utf8);
|
||||
|
||||
/* 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];
|
||||
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 */
|
||||
if (vgmstream)
|
||||
return 1;
|
||||
@ -400,7 +491,7 @@ int winamp_Play(const in_char *fn) {
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
/* open the stream */
|
||||
vgmstream = init_vgmstream_winamp(filename,stream_index);
|
||||
vgmstream = init_vgmstream_winamp(filename, stream_index);
|
||||
if (!vgmstream)
|
||||
return 1;
|
||||
|
||||
@ -552,14 +643,8 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
|
||||
else {
|
||||
/* some other file in playlist given by filename */
|
||||
VGMSTREAM* infostream = NULL;
|
||||
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);
|
||||
|
||||
infostream = init_vgmstream_winamp(filename, stream_index);
|
||||
infostream = init_vgmstream_winamp_fileinfo(fn);
|
||||
if (!infostream) return 0;
|
||||
|
||||
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) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (title) {
|
||||
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 {
|
||||
/* some other file in playlist given by filename */
|
||||
VGMSTREAM* infostream = NULL;
|
||||
in_char filename[PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("winamp_GetFileInfo: file %s\n", f8); }
|
||||
|
||||
/* check for info encoded in the filename */
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
/* not changed from last IsOurFile (most common) */
|
||||
if (info_valid && wa_strcmp(fn, info_fn) == 0) {
|
||||
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;
|
||||
|
||||
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 */
|
||||
static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps, int *nch, int *srate) {
|
||||
VGMSTREAM *xvgmstream = NULL;
|
||||
in_char filename[PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
VGMSTREAM* xvgmstream = NULL;
|
||||
|
||||
/* check for info encoded in the filename */
|
||||
parse_fn_string(fn, NULL, filename, PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("Winamp: open common file %s\n", f8); }
|
||||
|
||||
/* open the stream */
|
||||
xvgmstream = init_vgmstream_winamp(filename, stream_index);
|
||||
xvgmstream = init_vgmstream_winamp_fileinfo(fn);
|
||||
if (!xvgmstream) {
|
||||
return NULL;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user