2017-05-13 02:22:15 +02:00
|
|
|
#include "meta.h"
|
|
|
|
#include "../coding/coding.h"
|
|
|
|
|
2019-03-02 21:12:00 +01:00
|
|
|
/* AAC - tri-Ace (Aska engine) Audio Container */
|
2017-05-13 02:22:15 +02:00
|
|
|
|
|
|
|
/* 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;
|
2017-11-10 22:22:04 +01:00
|
|
|
size_t sampleRate, numSamples, startSample, dataSize, blockSize, blockCount; // A mess
|
2017-05-13 02:22:15 +02:00
|
|
|
|
|
|
|
/* check extension, case insensitive */
|
2017-11-10 22:22:04 +01:00
|
|
|
/* .aac: expected, .laac/ace: for players to avoid hijacking MP4/AAC */
|
2017-05-13 02:22:15 +02:00
|
|
|
if ( !check_extensions(streamFile,"aac,laac,ace"))
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (read_32bitBE(0x00,streamFile) != 0x41414320) /* "AAC " */
|
|
|
|
goto fail;
|
|
|
|
|
2017-11-10 22:22:04 +01:00
|
|
|
/* 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
|
2017-05-13 02:22:15 +02:00
|
|
|
|
|
|
|
/* build the VGMSTREAM */
|
|
|
|
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
2017-11-10 22:22:04 +01:00
|
|
|
if (read_32bitBE(0x1000, streamFile) == 0x00000000)
|
|
|
|
start_offset = 0x7000;
|
|
|
|
else
|
|
|
|
start_offset = 0x2000;
|
2017-05-13 02:22:15 +02:00
|
|
|
|
|
|
|
vgmstream->sample_rate = sampleRate;
|
|
|
|
vgmstream->channels = channel_count;
|
|
|
|
vgmstream->num_samples = numSamples;
|
2017-11-10 22:22:04 +01:00
|
|
|
if (loop_flag) {
|
|
|
|
vgmstream->loop_start_sample = startSample;
|
|
|
|
vgmstream->loop_end_sample = vgmstream->num_samples;
|
|
|
|
}
|
2017-05-13 02:22:15 +02:00
|
|
|
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;
|
|
|
|
|
2017-11-10 22:22:04 +01:00
|
|
|
block_count = blockCount;
|
|
|
|
block_size = blockSize;
|
|
|
|
datasize = dataSize;
|
2017-05-13 02:22:15 +02:00
|
|
|
|
|
|
|
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;
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-05-13 02:22:15 +02:00
|
|
|
}
|
|
|
|
#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) {
|
2017-11-10 22:22:04 +01:00
|
|
|
VGMSTREAM * vgmstream = NULL;
|
|
|
|
off_t start_offset;
|
|
|
|
int loop_flag, channel_count;
|
2019-10-09 17:02:53 +02:00
|
|
|
uint32_t data_size, loop_start, loop_end, codec_id, asc_chunk;
|
2017-11-10 22:22:04 +01:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2019-10-09 17:02:53 +02:00
|
|
|
/* Find the ASC chunk, That's where the goodies are */
|
|
|
|
asc_chunk = read_32bitBE(0x40, streamFile);
|
|
|
|
if (read_32bitBE(asc_chunk, streamFile) != 0x41534320) /* "ASC " */
|
2017-11-10 22:22:04 +01:00
|
|
|
goto fail;
|
|
|
|
|
2019-10-09 17:02:53 +02:00
|
|
|
if (read_32bitBE(asc_chunk+0x104, streamFile) != 0xFFFFFFFF)
|
2017-11-10 22:22:04 +01:00
|
|
|
loop_flag = 1;
|
|
|
|
else
|
|
|
|
loop_flag = 0;
|
|
|
|
|
2019-10-09 17:02:53 +02:00
|
|
|
channel_count = read_32bitBE(asc_chunk + 0xF4, streamFile);
|
|
|
|
codec_id = read_32bitBE(asc_chunk + 0xF0, streamFile);
|
2017-11-10 22:22:04 +01:00
|
|
|
|
|
|
|
/* build the VGMSTREAM */
|
|
|
|
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
2019-10-09 17:02:53 +02:00
|
|
|
/* ASC header */
|
|
|
|
start_offset = asc_chunk + 0x110;
|
|
|
|
vgmstream->sample_rate = read_32bitBE(asc_chunk + 0xFC, streamFile);
|
2017-11-10 22:22:04 +01:00
|
|
|
vgmstream->channels = channel_count;
|
|
|
|
vgmstream->meta_type = meta_TA_AAC_PS3;
|
2019-10-09 17:02:53 +02:00
|
|
|
data_size = read_32bitBE(asc_chunk + 0xF8, streamFile);
|
|
|
|
loop_start = read_32bitBE(asc_chunk + 0x104, streamFile);
|
|
|
|
loop_end = read_32bitBE(asc_chunk + 0x108, streamFile);
|
2017-05-13 02:22:15 +02:00
|
|
|
|
|
|
|
#ifdef VGM_USE_FFMPEG
|
2017-11-10 22:22:04 +01:00
|
|
|
{
|
2019-08-26 22:58:43 +02:00
|
|
|
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;
|
2017-11-10 22:22:04 +01:00
|
|
|
vgmstream->coding_type = coding_FFmpeg;
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
|
2019-08-26 22:58:43 +02:00
|
|
|
/* 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;
|
2017-11-10 22:22:04 +01:00
|
|
|
}
|
2017-05-13 02:22:15 +02:00
|
|
|
#endif
|
|
|
|
|
2017-11-10 22:22:04 +01:00
|
|
|
/* open the file for reading */
|
|
|
|
if (!vgmstream_open_stream(vgmstream, streamFile, start_offset))
|
|
|
|
goto fail;
|
|
|
|
return vgmstream;
|
2017-05-13 02:22:15 +02:00
|
|
|
|
|
|
|
fail:
|
2017-11-10 22:22:04 +01:00
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
2017-05-13 02:22:15 +02:00
|
|
|
}
|
2017-12-27 06:14:50 +01:00
|
|
|
|
|
|
|
/* Android/iOS Variants (Star Ocean Anamnesis (APK v1.9.2), Heaven x Inferno (iOS)) */
|
2018-03-24 12:17:04 +01:00
|
|
|
VGMSTREAM * init_vgmstream_ta_aac_mobile_vorbis(STREAMFILE *streamFile) {
|
2017-12-27 06:14:50 +01:00
|
|
|
#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 */
|
|
|
|
{
|
2018-03-23 16:34:48 +01:00
|
|
|
ogg_vorbis_meta_info_t ovmi = {0};
|
2017-12-27 06:14:50 +01:00
|
|
|
VGMSTREAM * result = NULL;
|
|
|
|
|
2018-03-24 12:17:04 +01:00
|
|
|
ovmi.meta_type = meta_TA_AAC_MOBILE;
|
2018-03-23 16:34:48 +01:00
|
|
|
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;
|
2017-12-27 06:14:50 +01:00
|
|
|
|
|
|
|
start_offset = read_32bitLE(0x120, streamFile);
|
2018-03-23 16:34:48 +01:00
|
|
|
result = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, start_offset, &ovmi);
|
2017-12-27 06:14:50 +01:00
|
|
|
|
|
|
|
if (result != NULL) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
/* clean up anything we may have opened */
|
|
|
|
#endif
|
|
|
|
return NULL;
|
|
|
|
}
|
2018-03-24 12:17:04 +01:00
|
|
|
|
|
|
|
/* 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 */
|
2018-05-12 13:53:58 +02:00
|
|
|
/* .aac: expected, .laac: for players to avoid hijacking MP4/AAC */
|
|
|
|
if (!check_extensions(streamFile, "aac,laac"))
|
2018-03-24 12:17:04 +01:00
|
|
|
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);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
|
|
|
vgmstream->sample_rate = read_32bitLE(0x108, streamFile);
|
|
|
|
vgmstream->meta_type = meta_TA_AAC_MOBILE;
|
|
|
|
|
|
|
|
switch(codec) {
|
|
|
|
case 0x0d:
|
|
|
|
if (read_32bitLE(0x144, streamFile) != 0x40) goto fail; /* frame size */
|
|
|
|
if (read_32bitLE(0x148, streamFile) != (0x40-0x04*channel_count)*2 / channel_count) goto fail; /* frame samples */
|
|
|
|
if (channel_count > 2) goto fail; /* unknown data layout */
|
|
|
|
|
2019-03-02 21:12:00 +01:00
|
|
|
vgmstream->coding_type = coding_ASKA;
|
2018-03-24 12:17:04 +01:00
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
|
2019-03-03 02:30:52 +01:00
|
|
|
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);
|
2018-03-24 12:17:04 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
|
|
|
goto fail;
|
|
|
|
return vgmstream;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|
2018-05-12 13:53:58 +02:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
|
|
/* 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(0x14, streamFile) != 0x56495441) /* "VITA" */
|
|
|
|
goto fail;
|
|
|
|
if (read_32bitLE(0x10d0, streamFile) != 0x57415645) /* "WAVE" */
|
|
|
|
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};
|
|
|
|
|
|
|
|
cfg.channels = vgmstream->channels;
|
|
|
|
cfg.encoder_delay = read_32bitLE(0x1124,streamFile);
|
|
|
|
cfg.config_data = read_32bitBE(0x1128,streamFile);
|
|
|
|
|
|
|
|
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(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);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
|
|
|
goto fail;
|
|
|
|
return vgmstream;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|