mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-15 02:57:38 +01:00
commit
71329131bd
@ -165,10 +165,10 @@ int msadpcm_check_coefs(STREAMFILE* sf, off_t offset);
|
||||
|
||||
/* yamaha_decoder */
|
||||
void decode_aica(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo);
|
||||
void decode_aska(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_aska(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, size_t frame_size);
|
||||
void decode_nxap(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
size_t yamaha_bytes_to_samples(size_t bytes, int channels);
|
||||
size_t aska_bytes_to_samples(size_t bytes, int channels);
|
||||
size_t aska_bytes_to_samples(size_t bytes, size_t frame_size, int channels);
|
||||
|
||||
|
||||
/* tgcadpcm_decoder */
|
||||
|
@ -19,10 +19,10 @@ static const int scale_delta[16] = {
|
||||
};
|
||||
|
||||
/* Yamaha ADPCM-B (aka DELTA-T) expand used in YM2608/YM2610/etc (cross referenced with various sources and .so) */
|
||||
static void yamaha_adpcmb_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t* hist1, int32_t* step_size, int16_t *out_sample) {
|
||||
static void yamaha_adpcmb_expand_nibble(uint8_t byte, int shift, int32_t* hist1, int32_t* step_size, int16_t *out_sample) {
|
||||
int code, delta, sample;
|
||||
|
||||
code = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift) & 0xf;
|
||||
code = (byte >> shift) & 0xf;
|
||||
delta = ((((code & 0x7) * 2) + 1) * (*step_size)) >> 3; /* like 'mul' IMA */
|
||||
if (code & 8)
|
||||
delta = -delta;
|
||||
@ -109,38 +109,47 @@ void decode_aica(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
|
||||
|
||||
/* tri-Ace Aska ADPCM, Yamaha ADPCM-B with headered frames (reversed from Android SO's .so)
|
||||
* implements table with if-else/switchs too but that's too goofy */
|
||||
void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
int i, sample_count = 0, num_frame;
|
||||
void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, size_t frame_size) {
|
||||
uint8_t frame[0x100] = {0}; /* known max is 0xC0 */
|
||||
off_t frame_offset;
|
||||
int i, sample_count = 0, frames_in;
|
||||
int16_t out_sample;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_size = stream->adpcm_step_index;
|
||||
|
||||
/* external interleave */
|
||||
int block_samples = (0x40 - 0x04*channelspacing) * 2 / channelspacing;
|
||||
num_frame = first_sample / block_samples;
|
||||
int block_samples = (frame_size - 0x04*channelspacing) * 2 / channelspacing;
|
||||
frames_in = first_sample / block_samples;
|
||||
first_sample = first_sample % block_samples;
|
||||
|
||||
if (frame_size > sizeof(frame)) {
|
||||
VGM_LOG_ONCE("ASKA: unknown frame size %x\n", frame_size);
|
||||
return;
|
||||
}
|
||||
|
||||
/* parse frame */
|
||||
frame_offset = stream->offset + frame_size * frames_in;
|
||||
read_streamfile(frame, frame_offset, frame_size, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
/* header (hist+step) */
|
||||
if (first_sample == 0) {
|
||||
off_t header_offset = stream->offset + 0x40*num_frame + 0x04*channel;
|
||||
|
||||
hist1 = read_16bitLE(header_offset+0x00,stream->streamfile);
|
||||
step_size = read_16bitLE(header_offset+0x02,stream->streamfile);
|
||||
/* in most files 1st frame has step 0 but it seems ok and accounted for */
|
||||
hist1 = get_s16le(frame + 0x04*channel + 0x00);
|
||||
step_size = get_s16le(frame + 0x04*channel + 0x02);
|
||||
/* in most files 1st frame has step 0 but it seems ok and needed for correct waveform */
|
||||
//if (step_size < 0x7f) step_size = 0x7f;
|
||||
//else if (step_size > 0x6000) step_size = 0x6000;
|
||||
}
|
||||
|
||||
/* decode nibbles (layout: varies) */
|
||||
/* decode nibbles (layout: one nibble per channel, low-high order, ex 6ch=10325410 32541032 ...) */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
off_t byte_offset = (channelspacing == 2) ?
|
||||
(stream->offset + 0x40*num_frame + 0x04*channelspacing) + i : /* stereo: one nibble per channel */
|
||||
(stream->offset + 0x40*num_frame + 0x04*channelspacing) + i/2; /* mono: consecutive nibbles */
|
||||
int nibble_shift = (channelspacing == 2) ?
|
||||
(!(channel&1) ? 0:4) :
|
||||
(!(i&1) ? 0:4); /* even = low, odd = high */
|
||||
int pos = (channelspacing == 1) ?
|
||||
(0x04*channelspacing) + i/2 :
|
||||
(0x04*channelspacing) + (i * 4 * channelspacing + 4*channel) / 8; /* nibble position to closest byte */
|
||||
int shift = (channelspacing == 1) ? /* low first */
|
||||
(!(i&1) ? 0:4) :
|
||||
(!(channel&1) ? 0:4);
|
||||
|
||||
yamaha_adpcmb_expand_nibble(stream, byte_offset, nibble_shift, &hist1, &step_size, &out_sample);
|
||||
yamaha_adpcmb_expand_nibble(frame[pos], shift, &hist1, &step_size, &out_sample);
|
||||
outbuf[sample_count] = out_sample;
|
||||
sample_count += channelspacing;
|
||||
}
|
||||
@ -150,24 +159,29 @@ void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
|
||||
}
|
||||
|
||||
|
||||
/* NXAP ADPCM, Yamaha ADPCM-B with weird headered frames */
|
||||
/* NXAP ADPCM, Yamaha ADPCM-B with weird headered frames, partially rev'd from the ELF */
|
||||
void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
int i, sample_count = 0, num_frame;
|
||||
uint8_t frame[0x40] = {0}; /* known max is 0xC0 */
|
||||
off_t frame_offset;
|
||||
int i, sample_count = 0, frames_in;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_size = stream->adpcm_step_index;
|
||||
int16_t out_sample;
|
||||
|
||||
/* external interleave, mono */
|
||||
int block_samples = (0x40 - 0x4) * 2;
|
||||
num_frame = first_sample / block_samples;
|
||||
size_t frame_size = 0x40;
|
||||
int block_samples = (frame_size - 0x4) * 2;
|
||||
frames_in = first_sample / block_samples;
|
||||
first_sample = first_sample % block_samples;
|
||||
|
||||
/* parse frame */
|
||||
frame_offset = stream->offset + frame_size * frames_in;
|
||||
read_streamfile(frame, frame_offset, frame_size, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
/* header (hist+step) */
|
||||
if (first_sample == 0) {
|
||||
off_t header_offset = stream->offset + 0x40*num_frame;
|
||||
|
||||
hist1 = read_s16le(header_offset+0x00,stream->streamfile);
|
||||
step_size = read_u16le(header_offset+0x02,stream->streamfile) >> 1; /* remove lower bit, also note unsignedness */
|
||||
hist1 = get_s16le(frame + 0x00);
|
||||
step_size = get_u16le(frame + 0x02) >> 1; /* remove lower bit, also note unsignedness */
|
||||
if (step_size < 0x7f) step_size = 0x7f;
|
||||
else if (step_size > 0x6000) step_size = 0x6000;
|
||||
/* step's lower bit is hist1 sign (useless), and code doesn't seem to do anything useful with it? */
|
||||
@ -175,10 +189,10 @@ void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
|
||||
|
||||
/* decode nibbles (layout: all nibbles from one channel) */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
off_t byte_offset = (stream->offset + 0x40*num_frame + 0x04) + i/2;
|
||||
int nibble_shift = (i&1?0:4);
|
||||
int pos = 0x04 + i/2;
|
||||
int shift = (i&1?0:4);
|
||||
|
||||
yamaha_adpcmb_expand_nibble(stream, byte_offset, nibble_shift, &hist1, &step_size, &out_sample);
|
||||
yamaha_adpcmb_expand_nibble(frame[pos], shift, &hist1, &step_size, &out_sample);
|
||||
outbuf[sample_count] = out_sample;
|
||||
sample_count += channelspacing;
|
||||
}
|
||||
@ -193,8 +207,8 @@ size_t yamaha_bytes_to_samples(size_t bytes, int channels) {
|
||||
return bytes * 2 / channels;
|
||||
}
|
||||
|
||||
size_t aska_bytes_to_samples(size_t bytes, int channels) {
|
||||
int block_align = 0x40;
|
||||
size_t aska_bytes_to_samples(size_t bytes, size_t frame_size, int channels) {
|
||||
int block_align = frame_size;
|
||||
if (channels <= 0) return 0;
|
||||
return (bytes / block_align) * (block_align - 0x04*channels) * 2 / channels
|
||||
+ ((bytes % block_align) ? ((bytes % block_align) - 0x04*channels) * 2 / channels : 0);
|
||||
|
@ -425,7 +425,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) {
|
||||
case coding_AICA_int:
|
||||
return 2;
|
||||
case coding_ASKA:
|
||||
return (0x40-0x04*vgmstream->channels) * 2 / vgmstream->channels;
|
||||
return (vgmstream->frame_size - 0x04*vgmstream->channels) * 2 / vgmstream->channels;
|
||||
case coding_NXAP:
|
||||
return (0x40-0x04) * 2;
|
||||
case coding_NDS_PROCYON:
|
||||
@ -626,6 +626,7 @@ int get_vgmstream_frame_size(VGMSTREAM* vgmstream) {
|
||||
case coding_AICA_int:
|
||||
return 0x01;
|
||||
case coding_ASKA:
|
||||
return vgmstream->frame_size;
|
||||
case coding_NXAP:
|
||||
return 0x40;
|
||||
case coding_NDS_PROCYON:
|
||||
@ -1249,7 +1250,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
|
||||
case coding_ASKA:
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
decode_aska(&vgmstream->ch[ch], buffer+ch,
|
||||
vgmstream->channels, vgmstream->samples_into_block, samples_to_do, ch);
|
||||
vgmstream->channels, vgmstream->samples_into_block, samples_to_do, ch, vgmstream->frame_size);
|
||||
}
|
||||
break;
|
||||
case coding_NXAP:
|
||||
|
@ -37,7 +37,6 @@ static const char* extension_list[] = {
|
||||
"abk",
|
||||
//"ac3", //common, FFmpeg/not parsed (AC3)
|
||||
"acb",
|
||||
"ace", //fake extension for tri-Ace's .aac (renamed, to be removed)
|
||||
"acm",
|
||||
"ad", //txth/reserved [Xenosaga Freaks (PS2)]
|
||||
"adc", //txth/reserved [Tomb Raider The Last Revelation (DC), Tomb Raider Chronicles (DC)]
|
||||
@ -67,6 +66,7 @@ static const char* extension_list[] = {
|
||||
"ams", //txth/reserved [Super Dragon Ball Z (PS2) ELF names]
|
||||
"amts", //fake extension/header id for .stm (renamed? to be removed?)
|
||||
"ao",
|
||||
"ap",
|
||||
"apc",
|
||||
"as4",
|
||||
"asd",
|
||||
@ -166,7 +166,6 @@ static const char* extension_list[] = {
|
||||
"enm",
|
||||
"eno",
|
||||
"ens",
|
||||
"enth",
|
||||
"exa",
|
||||
"ezw",
|
||||
|
||||
@ -261,6 +260,7 @@ static const char* extension_list[] = {
|
||||
"lasf", //fake extension for .asf (various)
|
||||
"lbin", //fake extension for .bin (various)
|
||||
"leg",
|
||||
"lep",
|
||||
"lflac", //fake extension for .flac, FFmpeg/not parsed
|
||||
"lin",
|
||||
"lm0",
|
||||
@ -277,6 +277,7 @@ static const char* extension_list[] = {
|
||||
"lmpc", //fake extension for .mpc, FFmpeg/not parsed
|
||||
"logg", //fake extension for .ogg
|
||||
"lopus", //fake extension for .opus
|
||||
"lp",
|
||||
"lpcm",
|
||||
"lpk",
|
||||
"lps",
|
||||
@ -1177,9 +1178,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_OGL, "Shin'en OGL header"},
|
||||
{meta_MC3, "Paradigm MC3 header"},
|
||||
{meta_GTD, "GTD/GHS header"},
|
||||
{meta_TA_AAC_X360, "tri-Ace AAC (X360) header"},
|
||||
{meta_TA_AAC_PS3, "tri-Ace AAC (PS3) header"},
|
||||
{meta_TA_AAC_MOBILE, "tri-Ace AAC (Mobile) header"},
|
||||
{meta_TA_AAC, "tri-Ace AAC header"},
|
||||
{meta_MTA2, "Konami MTA2 header"},
|
||||
{meta_NGC_ULW, "Criterion ULW raw header"},
|
||||
{meta_XA_XA30, "Reflections XA30 header"},
|
||||
@ -1233,7 +1232,6 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_SPS_N1, "Nippon Ichi .SPS header"},
|
||||
{meta_UBI_BAO, "Ubisoft BAO header"},
|
||||
{meta_DSP_SWITCH_AUDIO, "UE4 Switch Audio header"},
|
||||
{meta_TA_AAC_VITA, "tri-Ace AAC (Vita) header"},
|
||||
{meta_SADF, "Procyon Studio SADF header"},
|
||||
{meta_H4M, "Hudson HVQM4 header"},
|
||||
{meta_ASF, "Argonaut ASF header"},
|
||||
|
@ -348,6 +348,10 @@
|
||||
RelativePath=".\meta\ppst_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ps2_enth_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\vsv_streamfile.h"
|
||||
>
|
||||
|
@ -122,6 +122,7 @@
|
||||
<ClInclude Include="meta\kma9_streamfile.h" />
|
||||
<ClInclude Include="meta\lrmd_streamfile.h" />
|
||||
<ClInclude Include="meta\ppst_streamfile.h" />
|
||||
<ClInclude Include="meta\ps2_enth_streamfile.h" />
|
||||
<ClInclude Include="meta\vsv_streamfile.h" />
|
||||
<ClInclude Include="meta\xavs_streamfile.h" />
|
||||
<ClInclude Include="meta\xnb_streamfile.h" />
|
||||
|
@ -152,6 +152,9 @@
|
||||
<ClInclude Include="meta\ppst_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\ps2_enth_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\vsv_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
@ -601,11 +601,7 @@ VGMSTREAM * init_vgmstream_mc3(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_gtd(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ta_aac_x360(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ta_aac_ps3(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ta_aac_mobile(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ta_aac_mobile_vorbis(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ta_aac_vita(STREAMFILE *streamFile);
|
||||
VGMSTREAM* init_vgmstream_ta_aac(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_va3(STREAMFILE *streamFile);
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "ps2_enth_streamfile.h"
|
||||
|
||||
/* LP/AP/LEP - from Enthusia: Professional Racing */
|
||||
/* LP/AP/LEP - from Konami (KCES)'s Enthusia: Professional Racing (PS2) */
|
||||
VGMSTREAM* init_vgmstream_ps2_enth(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels, sample_rate, interleave;
|
||||
int32_t data_size, loop_start;
|
||||
@ -11,12 +13,12 @@ VGMSTREAM* init_vgmstream_ps2_enth(STREAMFILE* sf) {
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .bin/lbin: assumed (no actual extensino in bigfiles)
|
||||
* .enth: fake */
|
||||
if (!check_extensions(sf, "bin,lbin,enth"))
|
||||
/* .bin/lbin: internal (no names in bigfiles but exes mention "bgm%05d.bin" and "LEP data")
|
||||
* .lp/lep/ap: header ID */
|
||||
if (!check_extensions(sf, "bin,lbin,lp,lep,ap"))
|
||||
goto fail;
|
||||
|
||||
id = read_32bitBE(0x00,sf);
|
||||
id = read_u32be(0x00,sf);
|
||||
switch (id) {
|
||||
case 0x41502020: /* "AP " */
|
||||
case 0x4C502020: /* "LP " */
|
||||
@ -24,7 +26,7 @@ VGMSTREAM* init_vgmstream_ps2_enth(STREAMFILE* sf) {
|
||||
interleave = read_u32le(0x0c,sf);
|
||||
loop_start = read_u32le(0x14,sf);
|
||||
data_size = read_u32le(0x18,sf);
|
||||
start_offset = read_32bitLE(0x1C,sf);
|
||||
start_offset = read_u32le(0x1C,sf);
|
||||
break;
|
||||
|
||||
case 0x4C455020: /* "LEP " */
|
||||
@ -42,6 +44,7 @@ VGMSTREAM* init_vgmstream_ps2_enth(STREAMFILE* sf) {
|
||||
loop_flag = loop_start != 0;
|
||||
channels = 2;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
@ -58,9 +61,10 @@ VGMSTREAM* init_vgmstream_ps2_enth(STREAMFILE* sf) {
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channels, 16);
|
||||
vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channels, 16);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
/* PCM data look different or encrypted
|
||||
* some PCM16 must be xored(?) with 0x8000, not sure when */
|
||||
goto fail;
|
||||
|
||||
temp_sf = setup_lp_streamfile(sf, start_offset); /* encrypted/obfuscated PCM */
|
||||
if (!temp_sf) goto fail;
|
||||
break;
|
||||
|
||||
case 0x41502020: /* "AP " */
|
||||
case 0x4C455020: /* "LEP " */
|
||||
@ -77,10 +81,12 @@ VGMSTREAM* init_vgmstream_ps2_enth(STREAMFILE* sf) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, temp_sf ? temp_sf : sf, start_offset))
|
||||
goto fail;
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
39
src/meta/ps2_enth_streamfile.h
Normal file
39
src/meta/ps2_enth_streamfile.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef _LP_STREAMFILE_H_
|
||||
#define _LP_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t start;
|
||||
} lp_io_data;
|
||||
|
||||
static size_t lp_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, lp_io_data* data) {
|
||||
int i;
|
||||
size_t bytes = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
/* PCM16LE frames are ROL'ed (or lower bit is ignored?) so this only works if reads are aligned
|
||||
* to sizeof(uint16_t); maybe could be a new decoder but seems like a waste */
|
||||
for (i = 0; i < bytes / 2 * 2; i += 2) {
|
||||
if (offset + i >= data->start) {
|
||||
uint16_t v = get_u16le(dest + i);
|
||||
v = (v << 1) | ((v >> 15) & 0x0001);
|
||||
put_u16le(dest + i, v);
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* decrypts Enthusia "LP" PCM streams */
|
||||
static STREAMFILE* setup_lp_streamfile(STREAMFILE* sf, off_t start) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
lp_io_data io_data = {0};
|
||||
|
||||
io_data.start = start;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(lp_io_data), lp_io_read, NULL);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _LP_STREAMFILE_H_ */
|
@ -1,306 +1,168 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* AAC - tri-Ace (Aska engine) Audio Container */
|
||||
typedef struct {
|
||||
int total_subsongs;
|
||||
int codec;
|
||||
int channels;
|
||||
int sample_rate;
|
||||
|
||||
/* Xbox 360 Variants (Star Ocean 4, End of Eternity, Infinite Undiscovery) */
|
||||
VGMSTREAM * init_vgmstream_ta_aac_x360(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count;
|
||||
size_t sampleRate, numSamples, startSample, dataSize, blockSize, blockCount; // A mess
|
||||
int block_count;
|
||||
int block_size;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .aac: expected, .laac/ace: for players to avoid hijacking MP4/AAC */
|
||||
if ( !check_extensions(streamFile,"aac,laac,ace"))
|
||||
int32_t num_samples;
|
||||
int32_t loop_start;
|
||||
int32_t loop_end;
|
||||
int loop_flag;
|
||||
|
||||
off_t stream_offset;
|
||||
off_t stream_size;
|
||||
off_t extra_offset;
|
||||
|
||||
off_t name_offset;
|
||||
} aac_header;
|
||||
|
||||
static int parse_aac(STREAMFILE* sf, aac_header* aac);
|
||||
|
||||
|
||||
/* AAC - tri-Ace (ASKA engine) Audio Container */
|
||||
VGMSTREAM* init_vgmstream_ta_aac(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
aac_header aac = {0};
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .aac: actual extension, .laac: for players to avoid hijacking MP4/AAC */
|
||||
if (!check_extensions(sf, "aac,laac"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00, sf) != 0x41414320 &&
|
||||
read_u32le(0x00, sf) != 0x41414320) /* "AAC " */
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x41414320) /* "AAC " */
|
||||
if (!parse_aac(sf, &aac))
|
||||
goto fail;
|
||||
|
||||
/* Ok, let's check what's behind door number 1 */
|
||||
if (read_32bitBE(0x1000, streamFile) == 0x41534320) /* "ASC " */
|
||||
{
|
||||
loop_flag = read_32bitBE(0x1118, streamFile);
|
||||
|
||||
/*Funky Channel Count Checking */
|
||||
if (read_32bitBE(0x1184, streamFile) == 0x7374726D)
|
||||
channel_count = 6;
|
||||
else if (read_32bitBE(0x1154, streamFile) == 0x7374726D)
|
||||
channel_count = 4;
|
||||
else
|
||||
channel_count = read_8bit(0x1134, streamFile);
|
||||
|
||||
sampleRate = read_32bitBE(0x10F4, streamFile);
|
||||
numSamples = read_32bitBE(0x10FC, streamFile);
|
||||
startSample = read_32bitBE(0x10F8, streamFile);
|
||||
dataSize = read_32bitBE(0x10F0, streamFile);
|
||||
blockSize = read_32bitBE(0x1100, streamFile);
|
||||
blockCount = read_32bitBE(0x110C, streamFile);
|
||||
}
|
||||
else if (read_32bitBE(0x1000, streamFile) == 0x57415645) /* "WAVE" */
|
||||
{
|
||||
loop_flag = read_32bitBE(0x1048, streamFile);
|
||||
|
||||
/*Funky Channel Count Checking */
|
||||
if (read_32bitBE(0x10B0, streamFile) == 0x7374726D)
|
||||
channel_count = 6;
|
||||
else if (read_32bitBE(0x1080, streamFile) == 0x7374726D)
|
||||
channel_count = 4;
|
||||
else
|
||||
channel_count = read_8bit(0x1060, streamFile);
|
||||
|
||||
sampleRate = read_32bitBE(0x1024, streamFile);
|
||||
numSamples = read_32bitBE(0x102C, streamFile);
|
||||
startSample = read_32bitBE(0x1028, streamFile);
|
||||
dataSize = read_32bitBE(0x1020, streamFile);
|
||||
blockSize = read_32bitBE(0x1030, streamFile);
|
||||
blockCount = read_32bitBE(0x103C, streamFile);
|
||||
}
|
||||
else if (read_32bitBE(0x1000, streamFile) == 0x00000000) /* some like to be special */
|
||||
{
|
||||
loop_flag = read_32bitBE(0x6048, streamFile);
|
||||
|
||||
/*Funky Channel Count Checking */
|
||||
if (read_32bitBE(0x60B0, streamFile) == 0x7374726D)
|
||||
channel_count = 6;
|
||||
else if (read_32bitBE(0x6080, streamFile) == 0x7374726D)
|
||||
channel_count = 4;
|
||||
else
|
||||
channel_count = read_8bit(0x6060, streamFile);
|
||||
|
||||
sampleRate = read_32bitBE(0x6024, streamFile);
|
||||
numSamples = read_32bitBE(0x602C, streamFile);
|
||||
startSample = read_32bitBE(0x6028, streamFile);
|
||||
dataSize = read_32bitBE(0x6020, streamFile);
|
||||
blockSize = read_32bitBE(0x6030, streamFile);
|
||||
blockCount = read_32bitBE(0x603C, streamFile);
|
||||
}
|
||||
else
|
||||
goto fail; //cuz I don't know if there are other variants
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
if (read_32bitBE(0x1000, streamFile) == 0x00000000)
|
||||
start_offset = 0x7000;
|
||||
else
|
||||
start_offset = 0x2000;
|
||||
|
||||
vgmstream->sample_rate = sampleRate;
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->num_samples = numSamples;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = startSample;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
vgmstream->meta_type = meta_TA_AAC_X360;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
uint8_t buf[100];
|
||||
size_t bytes, datasize, block_size, block_count;
|
||||
|
||||
block_count = blockCount;
|
||||
block_size = blockSize;
|
||||
datasize = dataSize;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma2(buf,100, vgmstream->num_samples, datasize, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,datasize);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
xma_fix_raw_samples(vgmstream, streamFile, start_offset, datasize, 0, 1,1);
|
||||
if (loop_flag) { /* reapply adjusted samples */
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
/* open the file for reading */
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* PlayStation 3 Variants (Star Ocean International, Resonance of Fate) */
|
||||
VGMSTREAM * init_vgmstream_ta_aac_ps3(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count;
|
||||
uint32_t data_size, loop_start, loop_end, codec_id, asc_chunk;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .aac: expected, .laac/ace: for players to avoid hijacking MP4/AAC */
|
||||
if (!check_extensions(streamFile, "aac,laac,ace"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00, streamFile) != 0x41414320) /* "AAC " */
|
||||
goto fail;
|
||||
|
||||
/* Find the ASC chunk, That's where the goodies are */
|
||||
asc_chunk = read_32bitBE(0x40, streamFile);
|
||||
if (read_32bitBE(asc_chunk, streamFile) != 0x41534320) /* "ASC " */
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(asc_chunk+0x104, streamFile) != 0xFFFFFFFF)
|
||||
loop_flag = 1;
|
||||
else
|
||||
loop_flag = 0;
|
||||
|
||||
channel_count = read_32bitBE(asc_chunk + 0xF4, streamFile);
|
||||
codec_id = read_32bitBE(asc_chunk + 0xF0, streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* ASC header */
|
||||
start_offset = asc_chunk + 0x110;
|
||||
vgmstream->sample_rate = read_32bitBE(asc_chunk + 0xFC, streamFile);
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->meta_type = meta_TA_AAC_PS3;
|
||||
data_size = read_32bitBE(asc_chunk + 0xF8, streamFile);
|
||||
loop_start = read_32bitBE(asc_chunk + 0x104, streamFile);
|
||||
loop_end = read_32bitBE(asc_chunk + 0x108, streamFile);
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
int block_align, encoder_delay;
|
||||
|
||||
block_align = (codec_id == 4 ? 0x60 : (codec_id == 5 ? 0x98 : 0xC0)) * vgmstream->channels;
|
||||
encoder_delay = 1024 + 69; /* approximate, gets good loops */
|
||||
vgmstream->num_samples = atrac3_bytes_to_samples(data_size, block_align) - encoder_delay;
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_atrac3_raw(streamFile, start_offset,data_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* set offset samples (offset 0 jumps to sample 0 > pre-applied delay, and offset end loops after sample end > adjusted delay) */
|
||||
vgmstream->loop_start_sample = atrac3_bytes_to_samples(loop_start, block_align); // - encoder_delay
|
||||
vgmstream->loop_end_sample = atrac3_bytes_to_samples(loop_end, block_align) - encoder_delay;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* open the file for reading */
|
||||
if (!vgmstream_open_stream(vgmstream, streamFile, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Android/iOS Variants (Star Ocean Anamnesis (APK v1.9.2), Heaven x Inferno (iOS)) */
|
||||
VGMSTREAM * init_vgmstream_ta_aac_mobile_vorbis(STREAMFILE *streamFile) {
|
||||
#ifdef VGM_USE_VORBIS
|
||||
off_t start_offset;
|
||||
int8_t codec_id;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .aac: expected, .laac/ace: for players to avoid hijacking MP4/AAC */
|
||||
if (!check_extensions(streamFile, "aac,laac,ace"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitLE(0x00, streamFile) != 0x41414320) /* "AAC " */
|
||||
goto fail;
|
||||
|
||||
if (read_32bitLE(0xf0, streamFile) != 0x57415645) /* "WAVE" */
|
||||
goto fail;
|
||||
|
||||
codec_id = read_8bit(0x104, streamFile);
|
||||
if (codec_id == 0xe) /* Vorbis */
|
||||
{
|
||||
ogg_vorbis_meta_info_t ovmi = {0};
|
||||
VGMSTREAM * result = NULL;
|
||||
|
||||
ovmi.meta_type = meta_TA_AAC_MOBILE;
|
||||
ovmi.loop_start = read_32bitLE(0x140, streamFile);
|
||||
ovmi.loop_end = read_32bitLE(0x144, streamFile);
|
||||
ovmi.loop_flag = ovmi.loop_end > ovmi.loop_start;
|
||||
ovmi.loop_end_found = ovmi.loop_flag;
|
||||
|
||||
start_offset = read_32bitLE(0x120, streamFile);
|
||||
result = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, start_offset, &ovmi);
|
||||
|
||||
if (result != NULL) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
/* clean up anything we may have opened */
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Android/iOS Variants, before they switched to Vorbis (Star Ocean Anamnesis (Android), Heaven x Inferno (iOS)) */
|
||||
VGMSTREAM * init_vgmstream_ta_aac_mobile(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int channel_count, loop_flag, codec;
|
||||
size_t data_size;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .aac: expected, .laac: for players to avoid hijacking MP4/AAC */
|
||||
if (!check_extensions(streamFile, "aac,laac"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitLE(0x00, streamFile) != 0x41414320) /* "AAC " */
|
||||
goto fail;
|
||||
|
||||
if (read_32bitLE(0xf0, streamFile) != 0x57415645) /* "WAVE" */
|
||||
goto fail;
|
||||
|
||||
codec = read_8bit(0x104, streamFile);
|
||||
channel_count = read_8bit(0x105, streamFile);
|
||||
/* 0x106: 0x01?, 0x107: 0x10? */
|
||||
data_size = read_32bitLE(0x10c, streamFile); /* usable data only, cuts last frame */
|
||||
start_offset = read_32bitLE(0x120, streamFile);
|
||||
/* 0x124: full data size */
|
||||
loop_flag = (read_32bitLE(0x134, streamFile) > 0);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
vgmstream = allocate_vgmstream(aac.channels, aac.loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x108, streamFile);
|
||||
vgmstream->meta_type = meta_TA_AAC_MOBILE;
|
||||
vgmstream->meta_type = meta_TA_AAC;
|
||||
vgmstream->sample_rate = aac.sample_rate;
|
||||
vgmstream->num_streams = aac.total_subsongs;
|
||||
vgmstream->stream_size = aac.stream_size;
|
||||
|
||||
switch(codec) {
|
||||
case 0x0d:
|
||||
if (read_32bitLE(0x144, streamFile) != 0x40) goto fail; /* frame size */
|
||||
/* 0x148 or 0x150 (later games): frame samples */
|
||||
if (channel_count > 2) goto fail; /* unknown data layout */
|
||||
switch(aac.codec) {
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x0165: { /* Infinite Undiscovery (X360), Star Ocean 4 (X360), Resonance of Fate (X360) */
|
||||
uint8_t buf[0x100];
|
||||
size_t bytes;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma2(buf, sizeof(buf), aac.num_samples, aac.stream_size, aac.channels, aac.sample_rate, aac.block_count, aac.block_size);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, aac.stream_offset, aac.stream_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = aac.num_samples;
|
||||
vgmstream->loop_start_sample = aac.loop_start;
|
||||
vgmstream->loop_end_sample = aac.loop_end;
|
||||
|
||||
xma_fix_raw_samples(vgmstream, sf, aac.stream_offset, aac.stream_size, 0, 0,1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
case 0x06: { /* Resonance of Fate (PS3), Star Ocean 4 (PS3) */
|
||||
int block_align = (aac.codec == 0x04 ? 0x60 : (aac.codec == 0x05 ? 0x98 : 0xC0)) * aac.channels;
|
||||
int encoder_delay = 1024 + 69; /* AT3 default, gets good loops */
|
||||
|
||||
vgmstream->num_samples = atrac3_bytes_to_samples(aac.stream_size, block_align) - encoder_delay;
|
||||
/* set offset samples (offset 0 jumps to sample 0 > pre-applied delay, and offset end loops after sample end > adjusted delay) */
|
||||
vgmstream->loop_start_sample = atrac3_bytes_to_samples(aac.loop_start, block_align); // - encoder_delay
|
||||
vgmstream->loop_end_sample = atrac3_bytes_to_samples(aac.loop_end, block_align) - encoder_delay;
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_atrac3_raw(sf, aac.stream_offset, aac.stream_size, vgmstream->num_samples, vgmstream->channels, vgmstream->sample_rate, block_align, encoder_delay);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
case 0x08: { /* Judas Code (Vita) */
|
||||
atrac9_config cfg = {0};
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.encoder_delay = read_s32le(aac.extra_offset + 0x04,sf);
|
||||
cfg.config_data = read_u32be(aac.extra_offset + 0x08,sf);
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = atrac9_bytes_to_samples(aac.stream_size, vgmstream->codec_data);
|
||||
vgmstream->num_samples -= cfg.encoder_delay;
|
||||
vgmstream->loop_start_sample = atrac9_bytes_to_samples(aac.loop_start, vgmstream->codec_data);
|
||||
vgmstream->loop_end_sample = atrac9_bytes_to_samples(aac.loop_end, vgmstream->codec_data);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case 0x0a: /* Star Ocean 4 (PC) */
|
||||
if (aac.channels > 2) goto fail; /* unknown data layout */
|
||||
/* 0x00: some value * channels? */
|
||||
/* 0x04: frame size */
|
||||
/* 0x08: frame samples */
|
||||
|
||||
vgmstream->coding_type = coding_MSADPCM;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->frame_size = read_u32le(aac.extra_offset + 0x04,sf);
|
||||
|
||||
vgmstream->num_samples = msadpcm_bytes_to_samples(aac.stream_size, vgmstream->frame_size, aac.channels);
|
||||
vgmstream->loop_start_sample = msadpcm_bytes_to_samples(aac.loop_start, vgmstream->frame_size, aac.channels);
|
||||
vgmstream->loop_end_sample = msadpcm_bytes_to_samples(aac.loop_end, vgmstream->frame_size, aac.channels);
|
||||
break;
|
||||
|
||||
case 0x0d: /* Star Ocean Anamnesis (Android), Heaven x Inferno (iOS), Star Ocean 4 (PC), Resonance of Fate (PC) */
|
||||
/* 0x00: 0x17700 * channels? */
|
||||
/* 0x04: frame size */
|
||||
/* 0x08: frame samples (not always?) */
|
||||
|
||||
vgmstream->coding_type = coding_ASKA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->frame_size = read_u32le(aac.extra_offset + 0x04,sf); /* usually 0x40, rarely 0x20/C0 (ex. some ROF PC) */
|
||||
/* N-channel frames are allowed (ex. 4/6ch in SO4/ROF PC) */
|
||||
|
||||
vgmstream->num_samples = aska_bytes_to_samples(data_size, channel_count);
|
||||
vgmstream->loop_start_sample = aska_bytes_to_samples(read_32bitLE(0x130, streamFile), channel_count);
|
||||
vgmstream->loop_end_sample = aska_bytes_to_samples(read_32bitLE(0x134, streamFile), channel_count);
|
||||
vgmstream->num_samples = aska_bytes_to_samples(aac.stream_size, vgmstream->frame_size, aac.channels);
|
||||
vgmstream->loop_start_sample = aska_bytes_to_samples(aac.loop_start, vgmstream->frame_size, aac.channels);
|
||||
vgmstream->loop_end_sample = aska_bytes_to_samples(aac.loop_end, vgmstream->frame_size, aac.channels);
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x0e: { /* Star Ocean Anamnesis (Android-v1.9.2), Heaven x Inferno (iOS) */
|
||||
vgmstream->codec_data = init_ogg_vorbis(sf, aac.stream_offset, aac.stream_size, NULL);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_OGG_VORBIS;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = aac.num_samples;
|
||||
vgmstream->loop_start_sample = read_s32le(aac.extra_offset + 0x00,sf);
|
||||
vgmstream->loop_end_sample = read_s32le(aac.extra_offset + 0x04,sf);
|
||||
/* seek table after loops */
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (aac.name_offset)
|
||||
read_string(vgmstream->stream_name, STREAM_NAME_SIZE, aac.name_offset, sf);
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, aac.stream_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
@ -309,65 +171,334 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Vita variants [Judas Code (Vita)] */
|
||||
VGMSTREAM * init_vgmstream_ta_aac_vita(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int channel_count, loop_flag;
|
||||
|
||||
/* DIR/dirn + WAVE chunk [Infinite Undiscovery (X360), Star Ocean 4 (X360)] */
|
||||
static int parse_aac_v1(STREAMFILE* sf, aac_header* aac) {
|
||||
off_t offset, test_offset, wave_offset;
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .aac: expected, .laac: for players to avoid hijacking MP4/AAC */
|
||||
if (!check_extensions(streamFile, "aac,laac"))
|
||||
/* base header */
|
||||
/* 0x00: id */
|
||||
/* 0x04: size */
|
||||
/* 0x10: subsongs */
|
||||
/* 0x14: base size */
|
||||
/* 0x14: head size */
|
||||
/* 0x18: data size */
|
||||
/* 0x20: config? (0x00010003) */
|
||||
/* 0x30+ DIR + dirn subsongs */
|
||||
|
||||
if (read_u32be(0x30, sf) != 0x44495220) /* "DIR " */
|
||||
goto fail;
|
||||
aac->total_subsongs = read_u32be(0x40, sf);
|
||||
|
||||
if (read_32bitLE(0x00, streamFile) != 0x41414320) /* "AAC " */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x14, streamFile) != 0x56495441) /* "VITA" */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x10d0, streamFile) != 0x57415645) /* "WAVE" */
|
||||
goto fail;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > aac->total_subsongs || aac->total_subsongs < 1) goto fail;
|
||||
|
||||
/* there is a bunch of chunks but we simplify */
|
||||
|
||||
/* 0x10E4: codec 0x08? */
|
||||
channel_count = read_8bit(0x10E5, streamFile);
|
||||
start_offset = read_32bitLE(0x1100, streamFile);
|
||||
loop_flag = (read_32bitLE(0x1114, streamFile) > 0);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x10e8, streamFile);
|
||||
vgmstream->meta_type = meta_TA_AAC_VITA;
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
{
|
||||
atrac9_config cfg = {0};
|
||||
int i;
|
||||
offset = 0;
|
||||
test_offset = 0x50;
|
||||
for (i = 0; i < aac->total_subsongs; i++) {
|
||||
uint32_t entry_type = read_u32be(test_offset + 0x00, sf);
|
||||
uint32_t entry_size = read_u32be(test_offset + 0x04, sf);
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.encoder_delay = read_32bitLE(0x1124,streamFile);
|
||||
cfg.config_data = read_32bitBE(0x1128,streamFile);
|
||||
switch(entry_type) {
|
||||
case 0x6469726E: /* "dirn" */
|
||||
if (i + 1 == target_subsong) {
|
||||
aac->name_offset = test_offset + 0x10;
|
||||
offset = read_u32be(test_offset + 0x90, sf); /* absolute */
|
||||
}
|
||||
break;
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_samples = atrac9_bytes_to_samples(read_32bitLE(0x10EC, streamFile), vgmstream->codec_data);
|
||||
vgmstream->num_samples -= cfg.encoder_delay;
|
||||
vgmstream->loop_start_sample = atrac9_bytes_to_samples(read_32bitLE(0x1110, streamFile), vgmstream->codec_data);
|
||||
vgmstream->loop_end_sample = atrac9_bytes_to_samples(read_32bitLE(0x1114, streamFile), vgmstream->codec_data);
|
||||
test_offset += entry_size;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (read_u32be(offset + 0x00, sf) != 0x57415645) /* "WAVE" */
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
wave_offset = offset;
|
||||
offset += 0x10;
|
||||
|
||||
{
|
||||
/* X360 */
|
||||
int i, streams;
|
||||
off_t strm_offset;
|
||||
|
||||
/* 0x00: 0x0400 + song ID */
|
||||
streams = read_u16be(offset + 0x04, sf);
|
||||
aac->codec = read_u16be(offset + 0x06, sf);
|
||||
/* 0x08: null */
|
||||
/* 0x0c: null */
|
||||
aac->stream_size = read_u32be(offset + 0x10, sf);
|
||||
aac->sample_rate = read_s32be(offset + 0x14, sf);
|
||||
aac->loop_start = read_u32be(offset + 0x18, sf);
|
||||
aac->loop_end = read_u32be(offset + 0x1C, sf); /* max samples if not set */
|
||||
aac->block_size = read_u32be(offset + 0x20, sf);
|
||||
/* 0x24: max samples */
|
||||
aac->num_samples = read_u32be(offset + 0x28, sf);
|
||||
aac->block_count = read_u32be(offset + 0x2c, sf);
|
||||
|
||||
/* one UI file has a smaller header, early version? */
|
||||
if (read_u32be(offset + 0x30, sf) == 0x7374726D) {
|
||||
aac->loop_flag = 0; /* ? */
|
||||
strm_offset = 0x30;
|
||||
}
|
||||
else {
|
||||
/* 0x30: null */
|
||||
/* 0x34: encoder delay? */
|
||||
aac->loop_flag = read_u32be(offset + 0x38, sf) != 0; /* loop end block */
|
||||
/* 0x3c: size? (loop-related) */
|
||||
strm_offset = 0x40;
|
||||
}
|
||||
|
||||
aac->stream_offset = wave_offset + 0x1000;
|
||||
|
||||
/* channels depends on streams definitions, "strm" chunk (max 2ch per strm) */
|
||||
aac->channels = 0;
|
||||
for (i = 0; i < streams; i++) {
|
||||
/* format: "strm", size, null, null, channels, ?, sample rate, encoder delay, samples, nulls */
|
||||
aac->channels += read_s8(offset + strm_offset + i*0x30 + 0x10, sf);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ASC + WAVE chunks [Resonance of Fate (X360/PS3), Star Ocean 4 (PS3)] */
|
||||
static int parse_aac_v2(STREAMFILE* sf, aac_header* aac) {
|
||||
off_t offset, start, size, test_offset, asc_offset;
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
/* base header */
|
||||
/* 0x00: id */
|
||||
/* 0x04: size */
|
||||
/* 0x10: config? (0x00020100/0x00020002/0x00020301/etc) */
|
||||
/* 0x14: flag (0x80/01) */
|
||||
/* 0x18: align? (PS3=0x30, X360=0xFD0) */
|
||||
/* 0x28: platform (PS3=3, X360=2) */
|
||||
/* 0x30+ offsets+sizes to ASC or GUIDs */
|
||||
|
||||
start = read_u32be(0x2c, sf);
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
aac->total_subsongs = 0;
|
||||
|
||||
if (read_u32be(start + 0x00, sf) == 0x414D4620) { /* "AMF " */
|
||||
/* GUID subsongs */
|
||||
if (read_u32be(start + 0x10, sf) != 0x68656164) /* "head" */
|
||||
goto fail;
|
||||
size = read_u32be(start + 0x10 + 0x10, sf);
|
||||
|
||||
offset = 0;
|
||||
test_offset = start + 0x10;
|
||||
while (test_offset < start + size) {
|
||||
uint32_t entry_type = read_u32be(test_offset + 0x00, sf);
|
||||
uint32_t entry_size = read_u32be(test_offset + 0x04, sf);
|
||||
|
||||
if (entry_type == 0)
|
||||
break;
|
||||
|
||||
switch(entry_type) {
|
||||
case 0x61646472: /* "addr" (GUID + config) */
|
||||
aac->total_subsongs++;
|
||||
if (aac->total_subsongs == target_subsong) {
|
||||
offset = read_u32be(test_offset + 0x2c, sf) + start + size;
|
||||
}
|
||||
break;
|
||||
|
||||
default: /* "head", "buff" */
|
||||
break;
|
||||
}
|
||||
|
||||
test_offset += entry_size;
|
||||
}
|
||||
}
|
||||
else if (read_u32be(start + 0x00, sf) == 0x41534320) { /* "ASC " */
|
||||
/* regular subsongs */
|
||||
offset = 0;
|
||||
for (test_offset = 0x30; test_offset < start; test_offset += 0x10) {
|
||||
uint32_t entry_offset = read_u32be(test_offset + 0x00, sf);
|
||||
/* 0x04: entry size */
|
||||
|
||||
if (entry_offset) { /* often 0 */
|
||||
aac->total_subsongs++;
|
||||
if (aac->total_subsongs == target_subsong) {
|
||||
offset = entry_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (target_subsong < 0 || target_subsong > aac->total_subsongs || aac->total_subsongs < 1) goto fail;
|
||||
|
||||
if (read_u32be(offset + 0x00, sf) != 0x41534320) /* "ASC " */
|
||||
goto fail;
|
||||
asc_offset = offset;
|
||||
|
||||
/* ASC section has offsets to "PLBK" chunk (?) and "WAVE" (header), may be followed by "VRC " (?) */
|
||||
/* 0x50: PLBK offset */
|
||||
offset += read_u32be(offset + 0x54, sf); /* WAVE offset */
|
||||
if (read_u32be(offset + 0x00, sf) != 0x57415645) /* "WAVE" */
|
||||
goto fail;
|
||||
offset += 0x10;
|
||||
|
||||
if (read_u16be(offset + 0x00, sf) == 0x0400) {
|
||||
/* X360 */
|
||||
int i, streams;
|
||||
|
||||
/* 0x00: 0x0400 + song ID? (0) */
|
||||
streams = read_u16be(offset + 0x04, sf);
|
||||
aac->codec = read_u16be(offset + 0x06, sf);
|
||||
/* 0x08: null */
|
||||
/* 0x0c: null */
|
||||
aac->stream_size = read_u32be(offset + 0x10, sf);
|
||||
aac->sample_rate = read_s32be(offset + 0x14, sf);
|
||||
aac->loop_start = read_u32be(offset + 0x18, sf);
|
||||
aac->loop_end = read_u32be(offset + 0x1C, sf); /* max samples if not set */
|
||||
aac->block_size = read_u32be(offset + 0x20, sf);
|
||||
/* 0x24: max samples */
|
||||
aac->num_samples = read_u32be(offset + 0x28, sf);
|
||||
aac->block_count = read_u32be(offset + 0x2c, sf);
|
||||
/* 0x30: null */
|
||||
/* 0x34: encoder delay? */
|
||||
aac->loop_flag = read_u32be(offset + 0x38, sf) != 0; /* loop end block */
|
||||
/* 0x3c: size? (loop-related) */
|
||||
aac->stream_offset = read_u32be(offset + 0x40, sf) + asc_offset;
|
||||
|
||||
/* channels depends on streams definitions, "strm" chunk (max 2ch per strm) */
|
||||
aac->channels = 0;
|
||||
for (i = 0; i < streams; i++) {
|
||||
/* format: "strm", size, null, null, channels, ?, sample rate, encoder delay, samples, nulls */
|
||||
aac->channels += read_s8(offset + 0x44 + i*0x30 + 0x10, sf);
|
||||
}
|
||||
|
||||
/* after streams and aligned to 0x10 is "Seek" table */
|
||||
}
|
||||
else {
|
||||
/* PS3 */
|
||||
aac->codec = read_u32be(offset + 0x00, sf);
|
||||
aac->channels = read_u32be(offset + 0x04, sf);
|
||||
aac->stream_size = read_u32be(offset + 0x08, sf); /* usable size (without padding) */
|
||||
aac->sample_rate = read_s32be(offset + 0x0c, sf);
|
||||
/* 0x10: 0x51? */
|
||||
aac->loop_start = read_u32be(offset + 0x14, sf);
|
||||
aac->loop_end = read_u32be(offset + 0x18, sf);
|
||||
/* 0x1c: null */
|
||||
|
||||
aac->stream_offset = offset + 0x20;
|
||||
}
|
||||
|
||||
aac->loop_flag = (aac->loop_start != -1);
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* AAOB + WAVE + WAVB chunks [Judas Code (Vita), Star Ocean Anamnesis (Android), Star Ocean 4 (PC)] */
|
||||
static int parse_aac_v3(STREAMFILE* sf, aac_header* aac) {
|
||||
off_t offset, size, test_offset;
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
/* base header */
|
||||
/* 0x00: id */
|
||||
/* 0x04: size */
|
||||
/* 0x10: config? (0x00020100/0x00020002/0x00020301/etc) */
|
||||
/* 0x14: platform ("VITA"=Vita, "DRD "=Android, "MSPC"=PC) */
|
||||
|
||||
/* offsets table: offset + flag? + size + align? */
|
||||
offset = read_u32le(0x20, sf); /* "AAOB" table (audio object?) */
|
||||
/* 0x30: "VRCB" table (some cue/config? related to subsongs? may be empty) */
|
||||
/* 0x40: "WAVB" table (wave body, has offset + size per stream then data, not needed since offsets are elsewhere too) */
|
||||
|
||||
if (read_u32le(offset + 0x00, sf) != 0x41414F42) /* "AAOB" */
|
||||
goto fail;
|
||||
size = read_u32le(offset + 0x04, sf);
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
aac->total_subsongs = 0;
|
||||
|
||||
/* AAOB may point to N AAO (headers) in SFX/voice packs, seems signaled with flag 0x80 at AAOB+0x10
|
||||
* but there is no subsong count or even max size (always 0x1000?) */
|
||||
{
|
||||
for (test_offset = offset + 0x20; offset + size; test_offset += 0x10) {
|
||||
uint32_t entry_offset = read_u32le(test_offset + 0x00, sf);
|
||||
/* 0x04: entry size */
|
||||
|
||||
if (entry_offset == 0x41414F20) /* reached first "AAO " */
|
||||
break;
|
||||
|
||||
if (entry_offset) { /* often 0 */
|
||||
aac->total_subsongs++;
|
||||
if (aac->total_subsongs == target_subsong) {
|
||||
offset += entry_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target_subsong < 0 || target_subsong > aac->total_subsongs || aac->total_subsongs < 1) goto fail;
|
||||
|
||||
if (read_u32le(offset + 0x00, sf) != 0x41414F20) /* "AAO " */
|
||||
goto fail;
|
||||
|
||||
|
||||
/* AAO section has offsets to "PLBK" chunk (?) and "WAVE" (header) */
|
||||
/* 0x14: PLBK offset */
|
||||
offset += read_u32le(offset + 0x18, sf); /* WAVE offset */
|
||||
if (read_u32le(offset + 0x00, sf) != 0x57415645) /* "WAVE" */
|
||||
goto fail;
|
||||
offset += 0x10;
|
||||
|
||||
/* 0x00: 0x00/01/01CC0000? */
|
||||
aac->codec = read_u8(offset + 0x04, sf);
|
||||
aac->channels = read_u8(offset + 0x05, sf);
|
||||
/* 0x06: 0x01? */
|
||||
/* 0x07: 0x10? (rarely 0x00) */
|
||||
aac->sample_rate = read_s32le(offset + 0x08, sf);
|
||||
aac->stream_size = read_u32le(offset + 0x0C, sf); /* usable size (without padding) */
|
||||
/* 0x10-1c: null */
|
||||
aac->stream_offset = read_u32le(offset + 0x20, sf); /* absolute */
|
||||
/* 0x24: data size (with padding) */
|
||||
/* 0x28: null */
|
||||
/* 0x2c: null */
|
||||
aac->loop_start = read_u32le(offset + 0x30, sf); /* table positions(?) in OGG */
|
||||
aac->loop_end = read_u32le(offset + 0x34, sf);
|
||||
/* 0x38: ? in OGG */
|
||||
aac->num_samples = read_s32le(offset + 0x3c, sf); /* OGG only */
|
||||
aac->extra_offset = offset + 0x40; /* codec specific */
|
||||
/* may have seek tables or other stuff per codec */
|
||||
|
||||
aac->loop_flag = (aac->loop_end > 0);
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_aac(STREAMFILE* sf, aac_header* aac) {
|
||||
int ok = 0;
|
||||
|
||||
/* try variations as format evolved over time
|
||||
* chunk headers are always: id + size + null + null (ids in machine endianness) */
|
||||
|
||||
ok = parse_aac_v1(sf, aac);
|
||||
if (ok) return 1;
|
||||
|
||||
ok = parse_aac_v2(sf, aac);
|
||||
if (ok) return 1;
|
||||
|
||||
ok = parse_aac_v3(sf, aac);
|
||||
if (ok) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -583,7 +583,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
if (ww.fmt_size == 0x28) {
|
||||
size_t seek_size;
|
||||
|
||||
vgmstream->num_samples += read_32bit(ww.fmt_offset + 0x18, sf);
|
||||
vgmstream->num_samples = read_32bit(ww.fmt_offset + 0x18, sf);
|
||||
/* 0x1c: null? 0x20: data_size without seek_size */
|
||||
seek_size = read_32bit(ww.fmt_offset + 0x24, sf);
|
||||
|
||||
@ -596,6 +596,14 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
|
||||
skip = switch_opus_get_encoder_delay(start_offset, sf); /* should be 120 */
|
||||
|
||||
/* some voices have original sample rate but opus can only do 48000 (ex. Mario Kart Home Circuit 24khz) */
|
||||
if (vgmstream->sample_rate != 48000) {
|
||||
vgmstream->sample_rate = 48000;
|
||||
vgmstream->num_samples = switch_opus_get_samples(start_offset,ww.data_size, sf); /* also original's */
|
||||
vgmstream->num_samples -= skip;
|
||||
}
|
||||
|
||||
|
||||
/* OPUS is VBR so this is very approximate percent, meh */
|
||||
if (ww.truncated) {
|
||||
vgmstream->num_samples = (int32_t)(vgmstream->num_samples *
|
||||
|
@ -318,11 +318,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_ogl,
|
||||
init_vgmstream_mc3,
|
||||
init_vgmstream_gtd,
|
||||
init_vgmstream_ta_aac_x360,
|
||||
init_vgmstream_ta_aac_ps3,
|
||||
init_vgmstream_ta_aac_mobile,
|
||||
init_vgmstream_ta_aac_mobile_vorbis,
|
||||
init_vgmstream_ta_aac_vita,
|
||||
init_vgmstream_ta_aac,
|
||||
init_vgmstream_va3,
|
||||
init_vgmstream_mta2,
|
||||
init_vgmstream_mta2_container,
|
||||
|
@ -605,9 +605,7 @@ typedef enum {
|
||||
meta_OGL, /* Shin'en Wii/WiiU (Jett Rocket (Wii), FAST Racing NEO (WiiU)) */
|
||||
meta_MC3, /* Paradigm games (T3 PS2, MX Rider PS2, MI: Operation Surma PS2) */
|
||||
meta_GTD, /* Knights Contract (X360/PS3), Valhalla Knights 3 (PSV) */
|
||||
meta_TA_AAC_X360, /* tri-Ace AAC (Star Ocean 4, End of Eternity, Infinite Undiscovery) */
|
||||
meta_TA_AAC_PS3, /* tri-Ace AAC (Star Ocean International, Resonance of Fate) */
|
||||
meta_TA_AAC_MOBILE, /* tri-Ace AAC (Star Ocean Anamnesis, Heaven x Inferno) */
|
||||
meta_TA_AAC,
|
||||
meta_MTA2,
|
||||
meta_NGC_ULW, /* Burnout 1 (GC only) */
|
||||
meta_XA_XA30,
|
||||
@ -660,7 +658,6 @@ typedef enum {
|
||||
meta_SPS_N1,
|
||||
meta_UBI_BAO, /* Ubisoft BAO */
|
||||
meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */
|
||||
meta_TA_AAC_VITA, /* tri-Ace AAC (Judas Code) */
|
||||
meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */
|
||||
meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */
|
||||
meta_XMD, /* Konami XMD [Silent Hill 4 (Xbox), Castlevania: Curse of Darkness (Xbox)] */
|
||||
|
Loading…
Reference in New Issue
Block a user